From 5f781d366e0f4369ec12e36c9978ad63ffa32235 Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 23 Feb 2022 18:19:36 -0800 Subject: [PATCH 001/678] Clean up and simplify some nnue code. Remove some unnecessary code and it's execution during inference. Also the change on line 49 in nnue_architecture.h results in a more efficient SIMD code path through ClippedReLU::propagate(). passed STC: https://tests.stockfishchess.org/tests/view/6217d3bfda649bba32ef25d5 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 12056 W: 3281 L: 3092 D: 5683 Ptnml(0-2): 55, 1213, 3312, 1384, 64 passed STC SMP: https://tests.stockfishchess.org/tests/view/6217f344da649bba32ef295e LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 27376 W: 7295 L: 7137 D: 12944 Ptnml(0-2): 52, 2859, 7715, 3003, 59 closes https://github.com/official-stockfish/Stockfish/pull/3944 No functional change bench: 6820724 --- src/nnue/evaluate_nnue.cpp | 6 +++--- src/nnue/layers/affine_transform.h | 16 ++++++++-------- src/nnue/layers/clipped_relu.h | 8 -------- src/nnue/nnue_architecture.h | 19 ++++++++++--------- src/nnue/nnue_common.h | 4 ++-- src/nnue/nnue_feature_transformer.h | 6 ++++-- 6 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 0fd58462..9254e36f 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -109,7 +109,7 @@ namespace Stockfish::Eval::NNUE { { write_little_endian(stream, Version); write_little_endian(stream, hashValue); - write_little_endian(stream, desc.size()); + write_little_endian(stream, (std::uint32_t)desc.size()); stream.write(&desc[0], desc.size()); return !stream.fail(); } @@ -157,7 +157,7 @@ namespace Stockfish::Eval::NNUE { ASSERT_ALIGNED(transformedFeatures, alignment); - const std::size_t bucket = (pos.count() - 1) / 4; + const int bucket = (pos.count() - 1) / 4; const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); const auto positional = network[bucket]->propagate(transformedFeatures); @@ -197,7 +197,7 @@ namespace Stockfish::Eval::NNUE { NnueEvalTrace t{}; t.correctBucket = (pos.count() - 1) / 4; - for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket); const auto positional = network[bucket]->propagate(transformedFeatures); diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 22451915..9a992608 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -235,10 +235,10 @@ namespace Stockfish::Eval::NNUE::Layers { // Read network parameters bool read_parameters(std::istream& stream) { - for (std::size_t i = 0; i < OutputDimensions; ++i) + for (IndexType i = 0; i < OutputDimensions; ++i) biases[i] = read_little_endian(stream); - for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) weights[get_weight_index(i)] = read_little_endian(stream); return !stream.fail(); @@ -246,10 +246,10 @@ namespace Stockfish::Eval::NNUE::Layers { // Write network parameters bool write_parameters(std::ostream& stream) const { - for (std::size_t i = 0; i < OutputDimensions; ++i) + for (IndexType i = 0; i < OutputDimensions; ++i) write_little_endian(stream, biases[i]); - for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) write_little_endian(stream, weights[get_weight_index(i)]); return !stream.fail(); @@ -422,9 +422,9 @@ namespace Stockfish::Eval::NNUE::Layers { // Read network parameters bool read_parameters(std::istream& stream) { - for (std::size_t i = 0; i < OutputDimensions; ++i) + for (IndexType i = 0; i < OutputDimensions; ++i) biases[i] = read_little_endian(stream); - for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) weights[get_weight_index(i)] = read_little_endian(stream); return !stream.fail(); @@ -432,10 +432,10 @@ namespace Stockfish::Eval::NNUE::Layers { // Write network parameters bool write_parameters(std::ostream& stream) const { - for (std::size_t i = 0; i < OutputDimensions; ++i) + for (IndexType i = 0; i < OutputDimensions; ++i) write_little_endian(stream, biases[i]); - for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) write_little_endian(stream, weights[get_weight_index(i)]); return !stream.fail(); diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index ffd2e3b7..f94d3082 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -171,14 +171,6 @@ namespace Stockfish::Eval::NNUE::Layers { std::max(0, std::min(127, input[i] >> WeightScaleBits))); } - // Affine transform layers expect that there is at least - // ceil_to_multiple(OutputDimensions, 32) initialized values. - // We cannot do this in the affine transform because it requires - // preallocating space here. - for (IndexType i = OutputDimensions; i < PaddedOutputDimensions; ++i) { - output[i] = 0; - } - return output; } }; diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 725b40fb..b4f65364 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -46,7 +46,7 @@ struct Network static constexpr int FC_1_OUTPUTS = 32; Layers::AffineTransform fc_0; - Layers::ClippedReLU ac_0; + Layers::ClippedReLU ac_0; Layers::AffineTransform fc_1; Layers::ClippedReLU ac_1; Layers::AffineTransform fc_2; @@ -97,14 +97,19 @@ struct Network alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out; + + Buffer() + { + std::memset(this, 0, sizeof(*this)); + } }; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - char bufferRaw[sizeof(Buffer) + alignment]; - char* bufferRawAligned = align_ptr_up(&bufferRaw[0]); - Buffer& buffer = *(new (bufferRawAligned) Buffer); + static thread_local char bufferRaw[sizeof(Buffer) + alignment]; + static thread_local char* bufferRawAligned = align_ptr_up(&bufferRaw[0]); + static thread_local Buffer& buffer = *(new (bufferRawAligned) Buffer); #else - alignas(alignment) Buffer buffer; + alignas(alignment) static thread_local Buffer buffer; #endif fc_0.propagate(transformedFeatures, buffer.fc_0_out); @@ -118,10 +123,6 @@ struct Network std::int32_t fwdOut = int(buffer.fc_0_out[FC_0_OUTPUTS]) * (600*OutputScale) / (127*(1<>= 8; } } - u[i] = v; + u[i] = (std::uint8_t)v; stream.write(reinterpret_cast(u), sizeof(IntType)); } diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index fb867421..85598018 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -123,8 +123,10 @@ namespace Stockfish::Eval::NNUE { // We use __m* types as template arguments, which causes GCC to emit warnings // about losing some attribute information. This is irrelevant to us as we // only take their size, so the following pragma are harmless. + #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wignored-attributes" + #endif template (); static constexpr int NumPsqtRegs = BestRegisterCount(); - + #if defined(__GNUC__) #pragma GCC diagnostic pop - + #endif #endif From 174b038bf3b3b8a0d82422a861a050391a33f34a Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Sun, 27 Feb 2022 17:02:13 +0100 Subject: [PATCH 002/678] Use dynamic allocation for evaluation scratch TLS buffer. fixes #3946 an issue related with the toolchain as found in xcode 12 on macOS, related to previous commit 5f781d36. closes https://github.com/official-stockfish/Stockfish/pull/3950 No functional change --- src/nnue/nnue_architecture.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index b4f65364..4f9596ae 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -21,6 +21,8 @@ #ifndef NNUE_ARCHITECTURE_H_INCLUDED #define NNUE_ARCHITECTURE_H_INCLUDED +#include + #include "nnue_common.h" #include "features/half_ka_v2_hm.h" @@ -88,9 +90,7 @@ struct Network std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { - constexpr uint64_t alignment = CacheLineSize; - - struct Buffer + struct alignas(CacheLineSize) Buffer { alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; @@ -104,12 +104,13 @@ struct Network } }; -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - static thread_local char bufferRaw[sizeof(Buffer) + alignment]; - static thread_local char* bufferRawAligned = align_ptr_up(&bufferRaw[0]); - static thread_local Buffer& buffer = *(new (bufferRawAligned) Buffer); +#if defined(__clang__) && (__APPLE__) + // workaround for a bug reported with xcode 12 + static thread_local auto tlsBuffer = std::make_unique(); + // Access TLS only once, cache result. + Buffer& buffer = *tlsBuffer; #else - alignas(alignment) static thread_local Buffer buffer; + alignas(CacheLineSize) static thread_local Buffer buffer; #endif fc_0.propagate(transformedFeatures, buffer.fc_0_out); From 4ac7d726ec5ba38ba593bca2ab5cb7589785a957 Mon Sep 17 00:00:00 2001 From: Giacomo Lorenzetti Date: Tue, 1 Mar 2022 09:34:40 +0100 Subject: [PATCH 003/678] Sort captures This patch (partially) sort captures in analogy to quiet moves. All three movepickers are affected, hence `depth` is added as an argument in probcut's. Passed STC: https://tests.stockfishchess.org/tests/view/621a4576da649bba32ef6fd4 LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 103848 W: 27884 L: 27473 D: 48491 Ptnml(0-2): 587, 11691, 26974, 12068, 604 Passed LTC: https://tests.stockfishchess.org/tests/view/621aaa5bda649bba32ef7c2d LLR: 2.96 (-2.94,2.94) <0.50,3.00> Total: 212032 W: 56420 L: 55739 D: 99873 Ptnml(0-2): 198, 21310, 62348, 21933, 227 closes https://github.com/official-stockfish/Stockfish/pull/3952 Bench: 6833580 --- src/movepick.cpp | 11 ++++++----- src/movepick.h | 2 +- src/search.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 694b9222..dc35113f 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -87,8 +87,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist /// MovePicker constructor for ProbCut: we generate captures with SEE greater /// than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) - : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) +MovePicker::MovePicker(const Position& p, Move ttm, Value th, Depth d, const CapturePieceToHistory* cph) + : pos(p), captureHistory(cph), ttMove(ttm), threshold(th), depth(d) { assert(!pos.checkers()); @@ -169,11 +169,12 @@ top: endMoves = generate(pos, cur); score(); + partial_insertion_sort(cur, endMoves, -3000 * depth); ++stage; goto top; case GOOD_CAPTURE: - if (select([&](){ + if (select([&](){ return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ? // Move losing capture to endBadCaptures to be tried later true : (*endBadCaptures++ = *cur, false); })) @@ -241,10 +242,10 @@ top: return select([](){ return true; }); case PROBCUT: - return select([&](){ return pos.see_ge(*cur, threshold); }); + return select([&](){ return pos.see_ge(*cur, threshold); }); case QCAPTURE: - if (select([&](){ return depth > DEPTH_QS_RECAPTURES + if (select([&](){ return depth > DEPTH_QS_RECAPTURES || to_sq(*cur) == recaptureSquare; })) return *(cur - 1); diff --git a/src/movepick.h b/src/movepick.h index e2cbfcde..9a3c279b 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -126,7 +126,7 @@ public: const CapturePieceToHistory*, const PieceToHistory**, Square); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Value, Depth, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); private: diff --git a/src/search.cpp b/src/search.cpp index 6785ba4c..a6552daf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -863,7 +863,7 @@ namespace { { assert(probCutBeta < VALUE_INFINITE); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, depth - 3, &captureHistory); bool ttPv = ss->ttPv; ss->ttPv = false; From 270a0e737fea1774b409f70f378ca52cbc42dd3d Mon Sep 17 00:00:00 2001 From: Ben Chaney Date: Tue, 1 Mar 2022 17:49:02 -0500 Subject: [PATCH 004/678] Generalize the feature transform to use vec_t macros This commit generalizes the feature transform to use vec_t macros that are architecture defined instead of using a seperate code path for each one. It should make some old architectures (MMX, including improvements by Fanael) faster and make further such improvements easier in the future. Includes some corrections to CI for mingw. closes https://github.com/official-stockfish/Stockfish/pull/3955 closes https://github.com/official-stockfish/Stockfish/pull/3928 No functional change --- .github/workflows/stockfish.yml | 12 +- AUTHORS | 1 + src/nnue/nnue_feature_transformer.h | 165 ++++++++++++---------------- 3 files changed, 78 insertions(+), 100 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index f1741ed8..33560d52 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -82,20 +82,20 @@ jobs: name: "Windows 2022 Mingw-w64 GCC x86_64", os: windows-2022, compiler: g++, - comp: gcc, + comp: mingw, run_64bit_tests: true, msys_sys: 'mingw64', - msys_env: 'x86_64', + msys_env: 'x86_64-gcc', shell: 'msys2 {0}' } - { name: "Windows 2022 Mingw-w64 GCC i686", os: windows-2022, compiler: g++, - comp: gcc, + comp: mingw, run_32bit_tests: true, msys_sys: 'mingw32', - msys_env: 'i686', + msys_env: 'i686-gcc', shell: 'msys2 {0}' } - { @@ -105,7 +105,7 @@ jobs: comp: clang, run_64bit_tests: true, msys_sys: 'clang64', - msys_env: 'clang-x86_64', + msys_env: 'clang-x86_64-clang', shell: 'msys2 {0}' } @@ -129,7 +129,7 @@ jobs: uses: msys2/setup-msys2@v2 with: msystem: ${{matrix.config.msys_sys}} - install: mingw-w64-${{matrix.config.msys_env}}-${{matrix.config.comp}} make git expect + install: mingw-w64-${{matrix.config.msys_env}} make git expect - name: Download the used network from the fishtest framework run: | diff --git a/AUTHORS b/AUTHORS index f49c1db0..65620886 100644 --- a/AUTHORS +++ b/AUTHORS @@ -31,6 +31,7 @@ Arjun Temurnikar Artem Solopiy (EntityFX) Auguste Pop Balint Pfliegel +Ben Chaney (Chaneybenjamini) Ben Koshy (BKSpurgeon) Bill Henry (VoyagerOne) Bojun Guo (noobpwnftw, Nooby) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 85598018..c969ac6c 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -47,12 +47,22 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) _mm512_store_si512(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b) #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) + #define vec_mul_16(a,b) _mm512_mullo_epi16(a,b) + #define vec_zero() _mm512_setzero_epi32() + #define vec_set_16(a) _mm512_set1_epi16(a) + #define vec_max_16(a,b) _mm512_max_epi16(a,b) + #define vec_min_16(a,b) _mm512_min_epi16(a,b) + inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ + vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a,7),_mm512_srli_epi16(b,7)); + return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted); + } #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a,b) _mm256_store_si256(a,b) #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) #define vec_zero_psqt() _mm256_setzero_si256() #define NumRegistersSIMD 32 + #define MaxChunkSize 64 #elif USE_AVX2 typedef __m256i vec_t; @@ -61,12 +71,22 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) _mm256_store_si256(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b) #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) + #define vec_mul_16(a,b) _mm256_mullo_epi16(a,b) + #define vec_zero() _mm256_setzero_si256() + #define vec_set_16(a) _mm256_set1_epi16(a) + #define vec_max_16(a,b) _mm256_max_epi16(a,b) + #define vec_min_16(a,b) _mm256_min_epi16(a,b) + inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ + vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a,7), _mm256_srli_epi16(b,7)); + return _mm256_permute4x64_epi64(compacted, 0b11011000); + } #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a,b) _mm256_store_si256(a,b) #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) #define vec_zero_psqt() _mm256_setzero_si256() #define NumRegistersSIMD 16 + #define MaxChunkSize 32 #elif USE_SSE2 typedef __m128i vec_t; @@ -75,12 +95,19 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_epi16(a,b) #define vec_sub_16(a,b) _mm_sub_epi16(a,b) + #define vec_mul_16(a,b) _mm_mullo_epi16(a,b) + #define vec_zero() _mm_setzero_si128() + #define vec_set_16(a) _mm_set1_epi16(a) + #define vec_max_16(a,b) _mm_max_epi16(a,b) + #define vec_min_16(a,b) _mm_min_epi16(a,b) + #define vec_msb_pack_16(a,b) _mm_packs_epi16(_mm_srli_epi16(a,7),_mm_srli_epi16(b,7)) #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a,b) *(a)=(b) #define vec_add_psqt_32(a,b) _mm_add_epi32(a,b) #define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b) #define vec_zero_psqt() _mm_setzero_si128() #define NumRegistersSIMD (Is64Bit ? 16 : 8) + #define MaxChunkSize 16 #elif USE_MMX typedef __m64 vec_t; @@ -89,12 +116,26 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_pi16(a,b) #define vec_sub_16(a,b) _mm_sub_pi16(a,b) + #define vec_mul_16(a,b) _mm_mullo_pi16(a,b) + #define vec_zero() _mm_setzero_si64() + #define vec_set_16(a) _mm_set1_pi16(a) + inline vec_t vec_max_16(vec_t a,vec_t b){ + vec_t comparison = _mm_cmpgt_pi16(a,b); + return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b)); + } + inline vec_t vec_min_16(vec_t a,vec_t b){ + vec_t comparison = _mm_cmpgt_pi16(a,b); + return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a)); + } + #define vec_msb_pack_16(a,b) _mm_packs_pi16(_mm_srli_pi16(a,7),_mm_srli_pi16(b,7)) #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a,b) *(a)=(b) #define vec_add_psqt_32(a,b) _mm_add_pi32(a,b) #define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b) #define vec_zero_psqt() _mm_setzero_si64() + #define vec_cleanup() _mm_empty() #define NumRegistersSIMD 8 + #define MaxChunkSize 8 #elif USE_NEON typedef int16x8_t vec_t; @@ -103,12 +144,24 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) vaddq_s16(a,b) #define vec_sub_16(a,b) vsubq_s16(a,b) + #define vec_mul_16(a,b) vmulq_s16(a,b) + #define vec_zero() vec_t{0} + #define vec_set_16(a) vdupq_n_s16(a) + #define vec_max_16(a,b) vmaxq_s16(a,b) + #define vec_min_16(a,b) vminq_s16(a,b) + inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ + const int8x8_t shifta = vshrn_n_s16(a, 7); + const int8x8_t shiftb = vshrn_n_s16(b, 7); + const int8x16_t compacted = vcombine_s8(shifta,shiftb); + return *reinterpret_cast (&compacted); + } #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a,b) *(a)=(b) #define vec_add_psqt_32(a,b) vaddq_s32(a,b) #define vec_sub_psqt_32(a,b) vsubq_s32(a,b) #define vec_zero_psqt() psqt_vec_t{0} #define NumRegistersSIMD 16 + #define MaxChunkSize 16 #else #undef VECTOR @@ -235,110 +288,30 @@ namespace Stockfish::Eval::NNUE { { const IndexType offset = (HalfDimensions / 2) * p; -#if defined(USE_AVX512) +#if defined(VECTOR) - constexpr IndexType OutputChunkSize = 512 / 8; + constexpr IndexType OutputChunkSize = MaxChunkSize; static_assert((HalfDimensions / 2) % OutputChunkSize == 0); constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - const __m512i Zero = _mm512_setzero_si512(); - const __m512i One = _mm512_set1_epi16(127); - const __m512i Control = _mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7); + vec_t Zero = vec_zero(); + vec_t One = vec_set_16(127); - const __m512i* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); - const __m512i* in1 = reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); - __m512i* out = reinterpret_cast< __m512i*>(output + offset); + const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); + const vec_t* in1 = reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); + vec_t* out = reinterpret_cast< vec_t*>(output + offset); for (IndexType j = 0; j < NumOutputChunks; j += 1) { - const __m512i sum0a = _mm512_max_epi16(_mm512_min_epi16(in0[j * 2 + 0], One), Zero); - const __m512i sum0b = _mm512_max_epi16(_mm512_min_epi16(in0[j * 2 + 1], One), Zero); - const __m512i sum1a = _mm512_max_epi16(_mm512_min_epi16(in1[j * 2 + 0], One), Zero); - const __m512i sum1b = _mm512_max_epi16(_mm512_min_epi16(in1[j * 2 + 1], One), Zero); + const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); + const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); + const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero); + const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero); - const __m512i pa = _mm512_srli_epi16(_mm512_mullo_epi16(sum0a, sum1a), 7); - const __m512i pb = _mm512_srli_epi16(_mm512_mullo_epi16(sum0b, sum1b), 7); + const vec_t pa = vec_mul_16(sum0a, sum1a); + const vec_t pb = vec_mul_16(sum0b, sum1b); - out[j] = _mm512_permutexvar_epi64(Control, _mm512_packs_epi16(pa, pb)); - } - -#elif defined(USE_AVX2) - - constexpr IndexType OutputChunkSize = 256 / 8; - static_assert((HalfDimensions / 2) % OutputChunkSize == 0); - constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - - const __m256i Zero = _mm256_setzero_si256(); - const __m256i One = _mm256_set1_epi16(127); - constexpr int Control = 0b11011000; - - const __m256i* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); - const __m256i* in1 = reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); - __m256i* out = reinterpret_cast< __m256i*>(output + offset); - - for (IndexType j = 0; j < NumOutputChunks; j += 1) - { - const __m256i sum0a = _mm256_max_epi16(_mm256_min_epi16(in0[j * 2 + 0], One), Zero); - const __m256i sum0b = _mm256_max_epi16(_mm256_min_epi16(in0[j * 2 + 1], One), Zero); - const __m256i sum1a = _mm256_max_epi16(_mm256_min_epi16(in1[j * 2 + 0], One), Zero); - const __m256i sum1b = _mm256_max_epi16(_mm256_min_epi16(in1[j * 2 + 1], One), Zero); - - const __m256i pa = _mm256_srli_epi16(_mm256_mullo_epi16(sum0a, sum1a), 7); - const __m256i pb = _mm256_srli_epi16(_mm256_mullo_epi16(sum0b, sum1b), 7); - - out[j] = _mm256_permute4x64_epi64(_mm256_packs_epi16(pa, pb), Control); - } - -#elif defined(USE_SSE2) - - constexpr IndexType OutputChunkSize = 128 / 8; - static_assert((HalfDimensions / 2) % OutputChunkSize == 0); - constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - - const __m128i Zero = _mm_setzero_si128(); - const __m128i One = _mm_set1_epi16(127); - - const __m128i* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); - const __m128i* in1 = reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); - __m128i* out = reinterpret_cast< __m128i*>(output + offset); - - for (IndexType j = 0; j < NumOutputChunks; j += 1) - { - const __m128i sum0a = _mm_max_epi16(_mm_min_epi16(in0[j * 2 + 0], One), Zero); - const __m128i sum0b = _mm_max_epi16(_mm_min_epi16(in0[j * 2 + 1], One), Zero); - const __m128i sum1a = _mm_max_epi16(_mm_min_epi16(in1[j * 2 + 0], One), Zero); - const __m128i sum1b = _mm_max_epi16(_mm_min_epi16(in1[j * 2 + 1], One), Zero); - - const __m128i pa = _mm_srli_epi16(_mm_mullo_epi16(sum0a, sum1a), 7); - const __m128i pb = _mm_srli_epi16(_mm_mullo_epi16(sum0b, sum1b), 7); - - out[j] = _mm_packs_epi16(pa, pb); - } - -#elif defined(USE_NEON) - - constexpr IndexType OutputChunkSize = 128 / 8; - static_assert((HalfDimensions / 2) % OutputChunkSize == 0); - constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - - const int16x8_t Zero = vdupq_n_s16(0); - const int16x8_t One = vdupq_n_s16(127); - - const int16x8_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); - const int16x8_t* in1 = reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); - int8x16_t* out = reinterpret_cast< int8x16_t*>(output + offset); - - for (IndexType j = 0; j < NumOutputChunks; j += 1) - { - const int16x8_t sum0a = vmaxq_s16(vminq_s16(in0[j * 2 + 0], One), Zero); - const int16x8_t sum0b = vmaxq_s16(vminq_s16(in0[j * 2 + 1], One), Zero); - const int16x8_t sum1a = vmaxq_s16(vminq_s16(in1[j * 2 + 0], One), Zero); - const int16x8_t sum1b = vmaxq_s16(vminq_s16(in1[j * 2 + 1], One), Zero); - - const int8x8_t pa = vshrn_n_s16(vmulq_s16(sum0a, sum1a), 7); - const int8x8_t pb = vshrn_n_s16(vmulq_s16(sum0b, sum1b), 7); - - out[j] = vcombine_s8(pa, pb); + out[j] = vec_msb_pack_16(pa, pb); } #else @@ -354,6 +327,10 @@ namespace Stockfish::Eval::NNUE { #endif } +#if defined(vec_cleanup) + vec_cleanup(); +#endif + return psqt; } // end of function transform() From eae0f8dd066b31102b6663a60c36fffccf4e1269 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 8 Mar 2022 10:56:07 +0300 Subject: [PATCH 005/678] Decrease reductions in Lmr for some Pv nodes This patch makes us reduce less in Lmr at pv nodes in case of static eval being far away from static evaluation of position. Idea is that if it's the case then probably position is pretty complex so we can't be sure about how reliable LMR is so we need to reduce less. Passed STC: https://tests.stockfishchess.org/tests/view/6226276aa9d47c8160e81220 LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 262696 W: 69944 L: 69239 D: 123513 Ptnml(0-2): 1399, 29702, 68436, 30417, 1394 Passed LTC: https://tests.stockfishchess.org/tests/view/6226b002a9d47c8160e82b91 LLR: 2.95 (-2.94,2.94) <0.50,3.00> Total: 64008 W: 17320 L: 16982 D: 29706 Ptnml(0-2): 60, 6378, 18811, 6674, 81 closes https://github.com/official-stockfish/Stockfish/pull/3957 bench 6678390 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index a6552daf..3008079e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1166,6 +1166,11 @@ moves_loop: // When in check, search starts here if (ttCapture) r++; + // Decrease reduction at PvNodes if bestvalue + // is vastly different from static evaluation + if (PvNode && !ss->inCheck && abs(ss->staticEval - bestValue) > 250) + r--; + ss->statScore = thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] From 45f2416db4eaba8fbf023f84609d68a60e94b4ff Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 9 Mar 2022 18:40:54 +0300 Subject: [PATCH 006/678] Improvements in Evaluation adjust parameters in classical evaluation and NNUE scaling. STC: LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 37104 W: 9983 L: 9701 D: 17420 Ptnml(0-2): 154, 4187, 9651, 4343, 217 https://tests.stockfishchess.org/tests/view/6228cb13a9d47c8160e885ba LTC: LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 266792 W: 71101 L: 70295 D: 125396 Ptnml(0-2): 214, 26928, 78353, 27640, 261 https://tests.stockfishchess.org/tests/view/6228d3c4a9d47c8160e887b0 closes https://github.com/official-stockfish/Stockfish/pull/3958 Bench: 6739741 --- src/evaluate.cpp | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 923564cb..e9376aa6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -229,58 +229,58 @@ namespace { // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on // squares of the same color as our bishop. constexpr Score BishopPawns[int(FILE_NB) / 2] = { - S(3, 8), S(3, 9), S(2, 8), S(3, 8) + S(3, 8), S(3, 9), S(2, 7), S(3, 7) }; // KingProtector[knight/bishop] contains penalty for each distance unit to own king - constexpr Score KingProtector[] = { S(8, 9), S(6, 9) }; + constexpr Score KingProtector[] = { S(9, 9), S(7, 9) }; // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. - constexpr Score Outpost[] = { S(57, 38), S(31, 24) }; + constexpr Score Outpost[] = { S(54, 34), S(31, 25) }; // PassedRank[Rank] contains a bonus according to the rank of a passed pawn constexpr Score PassedRank[RANK_NB] = { - S(0, 0), S(7, 27), S(16, 32), S(17, 40), S(64, 71), S(170, 174), S(278, 262) + S(0, 0), S(2, 38), S(15, 36), S(22, 50), S(64, 81), S(166, 184), S(284, 269) }; constexpr Score RookOnClosedFile = S(10, 5); - constexpr Score RookOnOpenFile[] = { S(19, 6), S(47, 26) }; + constexpr Score RookOnOpenFile[] = { S(18, 8), S(49, 26) }; // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to // which piece type attacks which one. Attacks on lesser pieces which are // pawn-defended are not considered. constexpr Score ThreatByMinor[PIECE_TYPE_NB] = { - S(0, 0), S(5, 32), S(55, 41), S(77, 56), S(89, 119), S(79, 162) + S(0, 0), S(6, 37), S(64, 50), S(82, 57), S(103, 130), S(81, 163) }; constexpr Score ThreatByRook[PIECE_TYPE_NB] = { - S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43) + S(0, 0), S(3, 44), S(36, 71), S(44, 59), S(0, 39), S(60, 39) }; constexpr Value CorneredBishop = Value(50); // Assorted bonuses and penalties - constexpr Score UncontestedOutpost = S( 1, 10); + constexpr Score UncontestedOutpost = S( 0, 10); constexpr Score BishopOnKingRing = S( 24, 0); constexpr Score BishopXRayPawns = S( 4, 5); constexpr Score FlankAttacks = S( 8, 0); - constexpr Score Hanging = S( 69, 36); + constexpr Score Hanging = S( 72, 40); constexpr Score KnightOnQueen = S( 16, 11); constexpr Score LongDiagonalBishop = S( 45, 0); constexpr Score MinorBehindPawn = S( 18, 3); - constexpr Score PassedFile = S( 11, 8); - constexpr Score PawnlessFlank = S( 17, 95); - constexpr Score ReachableOutpost = S( 31, 22); - constexpr Score RestrictedPiece = S( 7, 7); + constexpr Score PassedFile = S( 13, 8); + constexpr Score PawnlessFlank = S( 19, 97); + constexpr Score ReachableOutpost = S( 33, 19); + constexpr Score RestrictedPiece = S( 6, 7); constexpr Score RookOnKingRing = S( 16, 0); - constexpr Score SliderOnQueen = S( 60, 18); - constexpr Score ThreatByKing = S( 24, 89); + constexpr Score SliderOnQueen = S( 62, 21); + constexpr Score ThreatByKing = S( 24, 87); constexpr Score ThreatByPawnPush = S( 48, 39); - constexpr Score ThreatBySafePawn = S(173, 94); + constexpr Score ThreatBySafePawn = S(167, 99); constexpr Score TrappedRook = S( 55, 13); constexpr Score WeakQueenProtection = S( 14, 0); - constexpr Score WeakQueen = S( 56, 15); + constexpr Score WeakQueen = S( 57, 19); #undef S @@ -1088,23 +1088,23 @@ Value Eval::evaluate(const Position& pos) { // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, // but we switch to NNUE during long shuffling or with high material on the board. if ( !useNNUE - || abs(eg_value(pos.psq_score())) * 5 > (849 + pos.non_pawn_material() / 64) * (5 + pos.rule50_count())) + || abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (5 + pos.rule50_count())) { v = Evaluation(pos).value(); // classical - useClassical = abs(v) >= 298; + useClassical = abs(v) >= 297; } // If result of a classical evaluation is much lower than threshold fall back to NNUE if (useNNUE && !useClassical) { Value nnue = NNUE::evaluate(pos, true); // NNUE - int scale = 1136 + 20 * pos.non_pawn_material() / 1024; + int scale = 1036 + 20 * pos.non_pawn_material() / 1024; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; Value psq = (stm == WHITE ? 1 : -1) * eg_value(pos.psq_score()); int complexity = 35 * abs(nnue - psq) / 256; - optimism = optimism * (44 + complexity) / 32; + optimism = optimism * (44 + complexity) / 31; v = (nnue + optimism) * scale / 1024 - optimism; if (pos.is_chess960()) @@ -1112,7 +1112,7 @@ Value Eval::evaluate(const Position& pos) { } // Damp down the evaluation linearly when shuffling - v = v * (208 - pos.rule50_count()) / 208; + v = v * (207 - pos.rule50_count()) / 207; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 004ea2c25ebe2b900152d0d837089d03f20c8eca Mon Sep 17 00:00:00 2001 From: Giacomo Lorenzetti Date: Sat, 5 Feb 2022 10:18:50 +0100 Subject: [PATCH 007/678] Small cleanups Delete cast to int in movepick. update AUTHORS. adjust assert in sigmoid. fix spelling mistakes in README closes https://github.com/official-stockfish/Stockfish/pull/3922 closes https://github.com/official-stockfish/Stockfish/pull/3948 closes https://github.com/official-stockfish/Stockfish/pull/3942 No functional change --- AUTHORS | 1 + README.md | 19 +++++++++---------- src/misc.h | 2 +- src/movepick.cpp | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/AUTHORS b/AUTHORS index 65620886..34b95ba5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -155,6 +155,7 @@ Pascal Romaret Pasquale Pigazzini (ppigazzini) Patrick Jansen (mibere) pellanda +Peter Schneider (pschneider1968) Peter Zsifkovits (CoffeeOne) Praveen Kumar Tummala (praveentml) Rahul Dsilva (silversolver1) diff --git a/README.md b/README.md index 330d19ed..37dae511 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,11 @@ Cute Chess, eboard, Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in ord to be used comfortably. Read the documentation for your GUI of choice for information about how to use Stockfish with it. -The Stockfish engine features two evaluation functions for chess, the classical -evaluation based on handcrafted terms, and the NNUE evaluation based on efficiently -updatable neural networks. The classical evaluation runs efficiently on almost all -CPU architectures, while the NNUE evaluation benefits from the vector -intrinsics available on most CPUs (sse2, avx2, neon, or similar). - +The Stockfish engine features two evaluation functions for chess. +The efficiently updatable neural network (NNUE) based evaluation is the default and by far the strongest. +The classical evaluation based on handcrafted terms remains available. +The strongest network is integrated in the binary and downloaded automatically during the build process. +The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2, avx2, neon, or similar). ## Files @@ -37,7 +36,7 @@ This distribution of Stockfish consists of the following files: The Universal Chess Interface (UCI) is a standard protocol used to communicate with a chess engine, and is the recommended way to do so for typical graphical user interfaces -(GUI) or chess tools. Stockfish implements the majority of it options as described +(GUI) or chess tools. Stockfish implements the majority of its options as described in [the UCI protocol](https://www.shredderchess.com/download/div/uci.zip). Developers can see the default values for UCI options available in Stockfish by typing @@ -103,7 +102,7 @@ change them via a chess GUI. This is a list of available UCI options in Stockfis Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6` It is recommended to store .rtbw files on an SSD. There is no loss in storing - the .rtbz files on a regular HD. It is recommended to verify all md5 checksums + the .rtbz files on a regular HDD. It is recommended to verify all md5 checksums of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will lead to engine crashes. @@ -322,10 +321,10 @@ it (either by itself or as part of some bigger software package), or using it as the starting point for a software project of your own. The only real limitation is that whenever you distribute Stockfish in -some way, you MUST always include the full source code, or a pointer +some way, you MUST always include the license, the full source code, or a pointer to where the source code can be found, to generate the exact binary you are distributing. If you make any changes to the source code, -these changes must also be made available under the GPL. +these changes must also be made available under the GPL v3. For full details, read the copy of the GPL v3 found in the file named [*Copying.txt*](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt). diff --git a/src/misc.h b/src/misc.h index b9626733..b666b6be 100644 --- a/src/misc.h +++ b/src/misc.h @@ -163,7 +163,7 @@ inline int64_t sigmoid(int64_t t, int64_t x0, int64_t P, int64_t Q) { - assert(C > 0); + assert(C > 0 && Q != 0); return y0 + P * (t-x0) / (Q * (std::abs(t-x0) + C)) ; } diff --git a/src/movepick.cpp b/src/movepick.cpp index dc35113f..c948620b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -107,8 +107,8 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) - m.value = int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6 - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; + m.value = 6 * PieceValue[MG][pos.piece_on(to_sq(m))] + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; else if constexpr (Type == QUIETS) m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] From f3a2296e591d09dd50323fc3f96e800f5538d8bb Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 12 Mar 2022 07:00:58 -0800 Subject: [PATCH 008/678] Small cleanups (2) - fix a small compile error under MSVC - improve sigmoid comment and assert - fix formatting in README.md closes https://github.com/official-stockfish/Stockfish/pull/3960 No functional change --- README.md | 71 ++++++++++++++++++++++++++---------------------- src/misc.h | 5 ++-- src/movepick.cpp | 4 +-- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 37dae511..6e6e762e 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,28 @@ Cute Chess, eboard, Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in ord to be used comfortably. Read the documentation for your GUI of choice for information about how to use Stockfish with it. -The Stockfish engine features two evaluation functions for chess. -The efficiently updatable neural network (NNUE) based evaluation is the default and by far the strongest. -The classical evaluation based on handcrafted terms remains available. -The strongest network is integrated in the binary and downloaded automatically during the build process. -The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2, avx2, neon, or similar). +The Stockfish engine features two evaluation functions for chess. The efficiently +updatable neural network (NNUE) based evaluation is the default and by far the strongest. +The classical evaluation based on handcrafted terms remains available. The strongest +network is integrated in the binary and downloaded automatically during the build process. +The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2, +avx2, neon, or similar). ## Files This distribution of Stockfish consists of the following files: - * [Readme.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md), the file you are currently reading. + * [Readme.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md), + the file you are currently reading. - * [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), a text file containing the GNU General Public License version 3. + * [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), + a text file containing the GNU General Public License version 3. - * [AUTHORS](https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS), a text file with the list of authors for the project + * [AUTHORS](https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS), + a text file with the list of authors for the project - * [src](https://github.com/official-stockfish/Stockfish/tree/master/src), a subdirectory containing the full source code, including a Makefile + * [src](https://github.com/official-stockfish/Stockfish/tree/master/src), + a subdirectory containing the full source code, including a Makefile that can be used to compile Stockfish on Unix-like systems. * a file with the .nnue extension, storing the neural network for the NNUE @@ -67,9 +72,9 @@ change them via a chess GUI. This is a list of available UCI options in Stockfis * #### EvalFile The name of the file of the NNUE evaluation parameters. Depending on the GUI the - filename might have to include the full path to the folder/directory that contains the file. - Other locations, such as the directory that contains the binary and the working directory, - are also searched. + filename might have to include the full path to the folder/directory that contains + the file. Other locations, such as the directory that contains the binary and the + working directory, are also searched. * #### UCI_AnalyseMode An option handled by your GUI. @@ -137,8 +142,9 @@ change them via a chess GUI. This is a list of available UCI options in Stockfis For developers the following non-standard commands might be of interest, mainly useful for debugging: * #### bench *ttSize threads limit fenFile limitType evalType* - Performs a standard benchmark using various options. The signature of a version (standard node - count) is obtained using all defaults. `bench` is currently `bench 16 1 13 default depth mixed`. + Performs a standard benchmark using various options. The signature of a version + (standard node count) is obtained using all defaults. `bench` is currently + `bench 16 1 13 default depth mixed`. * #### compiler Give information about the compiler and environment used for building a binary. @@ -174,26 +180,27 @@ on the evaluations of millions of positions at moderate search depth. The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward. It can be evaluated efficiently on CPUs, and exploits the fact that only parts of the neural network need to be updated after a typical chess move. -[The nodchip repository](https://github.com/nodchip/Stockfish) provided the first version of -the needed tools to train and develop the NNUE networks. Today, more advanced training tools are available -in [the nnue-pytorch repository](https://github.com/glinscott/nnue-pytorch/), while data generation tools -are available in [a dedicated branch](https://github.com/official-stockfish/Stockfish/tree/tools). +[The nodchip repository](https://github.com/nodchip/Stockfish) provided the first +version of the needed tools to train and develop the NNUE networks. Today, more +advanced training tools are available in +[the nnue-pytorch repository](https://github.com/glinscott/nnue-pytorch/), +while data generation tools are available in +[a dedicated branch](https://github.com/official-stockfish/Stockfish/tree/tools). -On CPUs supporting modern vector instructions -(avx2 and similar), the NNUE evaluation results in much stronger playing strength, even -if the nodes per second computed by the engine is somewhat lower (roughly 80% of nps -is typical). +On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation +results in much stronger playing strength, even if the nodes per second computed by +the engine is somewhat lower (roughly 80% of nps is typical). Notes: -1) the NNUE evaluation depends on the Stockfish binary and the network parameter -file (see the EvalFile UCI option). Not every parameter file is compatible with a given -Stockfish binary, but the default value of the EvalFile UCI option is the name of a network -that is guaranteed to be compatible with that binary. +1) the NNUE evaluation depends on the Stockfish binary and the network parameter file +(see the EvalFile UCI option). Not every parameter file is compatible with a given +Stockfish binary, but the default value of the EvalFile UCI option is the name of a +network that is guaranteed to be compatible with that binary. 2) to use the NNUE evaluation, the additional data file with neural network parameters -needs to be available. Normally, this file is already embedded in the binary or it -can be downloaded. The filename for the default (recommended) net can be found as the default +needs to be available. Normally, this file is already embedded in the binary or it can +be downloaded. The filename for the default (recommended) net can be found as the default value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue` (for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from ``` @@ -321,10 +328,10 @@ it (either by itself or as part of some bigger software package), or using it as the starting point for a software project of your own. The only real limitation is that whenever you distribute Stockfish in -some way, you MUST always include the license, the full source code, or a pointer -to where the source code can be found, to generate the exact binary -you are distributing. If you make any changes to the source code, -these changes must also be made available under the GPL v3. +some way, you MUST always include the license and the full source code +(or a pointer to where the source code can be found) to generate the +exact binary you are distributing. If you make any changes to the +source code, these changes must also be made available under the GPL v3. For full details, read the copy of the GPL v3 found in the file named [*Copying.txt*](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt). diff --git a/src/misc.h b/src/misc.h index b666b6be..dcef22a4 100644 --- a/src/misc.h +++ b/src/misc.h @@ -152,7 +152,7 @@ private: /// - the slope can be adjusted using C > 0, smaller C giving a steeper sigmoid /// - the slope of the sigmoid when t = x0 is P/(Q*C) /// - sigmoid is increasing with t when P > 0 and Q > 0 -/// - to get a decreasing sigmoid, call with -t, or change sign of P +/// - to get a decreasing sigmoid, change sign of P /// - mean value of the sigmoid is y0 /// /// Use to draw the sigmoid @@ -163,7 +163,8 @@ inline int64_t sigmoid(int64_t t, int64_t x0, int64_t P, int64_t Q) { - assert(C > 0 && Q != 0); + assert(C > 0); + assert(Q != 0); return y0 + P * (t-x0) / (Q * (std::abs(t-x0) + C)) ; } diff --git a/src/movepick.cpp b/src/movepick.cpp index c948620b..77453a45 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -107,8 +107,8 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) - m.value = 6 * PieceValue[MG][pos.piece_on(to_sq(m))] - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; + m.value = 6 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; else if constexpr (Type == QUIETS) m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] From e31f97e3baa52042fe60d6f4eeb50fe0d9e61013 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Tue, 15 Mar 2022 16:53:59 +0100 Subject: [PATCH 009/678] Remove ttPv tree shrinking. Via the ttPv flag an implicit tree of current and former PV nodes is maintained. In addition this tree is grown or shrinked at the leafs dependant on the search results. But now the shrinking step has been removed. As the frequency of ttPv nodes decreases with depth the shown scaling behavior (STC barely passed but LTC scales well) of the tests was expected. STC: LLR: 2.93 (-2.94,2.94) <-2.25,0.25> Total: 270408 W: 71593 L: 71785 D: 127030 Ptnml(0-2): 1339, 31024, 70630, 30912, 1299 https://tests.stockfishchess.org/tests/view/622fbf9dc9e950cbfc2376d6 LTC: LLR: 2.96 (-2.94,2.94) <-2.25,0.25> Total: 34368 W: 9135 L: 8992 D: 16241 Ptnml(0-2): 28, 3423, 10135, 3574, 24 https://tests.stockfishchess.org/tests/view/62305257c9e950cbfc238964 closes https://github.com/official-stockfish/Stockfish/pull/3963 Bench: 7044203 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3008079e..672abc05 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1359,10 +1359,6 @@ moves_loop: // When in check, search starts here // 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); - // Otherwise, a counter move has been found and if the position is the last leaf - // in the search tree, remove the position from the search tree. - else if (depth > 3) - ss->ttPv = ss->ttPv && (ss+1)->ttPv; // Write gathered information in transposition table if (!excludedMove && !(rootNode && thisThread->pvIdx)) From 910cf8b21839eb9f1991934a5436eea112021723 Mon Sep 17 00:00:00 2001 From: Giacomo Lorenzetti Date: Wed, 23 Mar 2022 12:04:10 +0100 Subject: [PATCH 010/678] Remove pos.capture_or_promotion() This patch replaces `pos.capture_or_promotion()` with `pos.capture()` and comes after a few attempts with elo-gaining bounds, two of which failed yellow at LTC (https://tests.stockfishchess.org/tests/view/622f8f0cc9e950cbfc237024 and https://tests.stockfishchess.org/tests/view/62319a8bb3b498ba71a6b2dc). Passed non-regression STC: https://tests.stockfishchess.org/tests/view/623aff7eea447151c74828d3 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 246864 W: 65462 L: 65618 D: 115784 Ptnml(0-2): 1201, 28116, 65001, 27866, 1248 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/623c1fdcea447151c7484fb0 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 30120 W: 8125 L: 7978 D: 14017 Ptnml(0-2): 22, 2993, 8881, 3144, 20 closes https://github.com/official-stockfish/Stockfish/pull/3968 Bench: 6847732 --- src/position.h | 5 ----- src/search.cpp | 35 ++++++++++++++++++----------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/position.h b/src/position.h index 8dbf1493..7b6165f3 100644 --- a/src/position.h +++ b/src/position.h @@ -352,11 +352,6 @@ inline bool Position::is_chess960() const { return chess960; } -inline bool Position::capture_or_promotion(Move m) const { - assert(is_ok(m)); - return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m)); -} - inline bool Position::capture(Move m) const { assert(is_ok(m)); // Castling is encoded as "king captures rook" diff --git a/src/search.cpp b/src/search.cpp index 672abc05..2d24c313 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -554,7 +554,7 @@ namespace { Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; bool givesCheck, improving, didLMR, priorCapture; - bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture; + bool capture, doFullDepthSearch, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount, bestMoveCount, improvement, complexity; @@ -624,7 +624,7 @@ namespace { ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() : MOVE_NONE; - ttCapture = ttMove && pos.capture_or_promotion(ttMove); + ttCapture = ttMove && pos.capture(ttMove); if (!excludedMove) ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); @@ -865,12 +865,13 @@ namespace { MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, depth - 3, &captureHistory); bool ttPv = ss->ttPv; + bool captureOrPromotion; ss->ttPv = false; while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) { - assert(pos.capture_or_promotion(move)); + assert(pos.capture(move) || promotion_type(move) == QUEEN); captureOrPromotion = true; @@ -987,7 +988,7 @@ moves_loop: // When in check, search starts here (ss+1)->pv = nullptr; extension = 0; - captureOrPromotion = pos.capture_or_promotion(move); + capture = pos.capture(move); movedPiece = pos.moved_piece(move); givesCheck = pos.gives_check(move); @@ -1007,7 +1008,7 @@ moves_loop: // When in check, search starts here // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount, delta, thisThread->rootDelta), 0); - if ( captureOrPromotion + if ( capture || givesCheck) { // Futility pruning for captures (~0 Elo) @@ -1122,7 +1123,7 @@ moves_loop: // When in check, search starts here // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [captureOrPromotion] + [capture] [movedPiece] [to_sq(move)]; @@ -1138,7 +1139,7 @@ moves_loop: // When in check, search starts here if ( depth >= 2 && moveCount > 1 + (PvNode && ss->ply <= 1) && ( !ss->ttPv - || !captureOrPromotion + || !capture || (cutNode && (ss-1)->moveCount > 1))) { Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); @@ -1215,7 +1216,7 @@ moves_loop: // When in check, search starts here int bonus = value > alpha ? stat_bonus(newDepth) : -stat_bonus(newDepth); - if (captureOrPromotion) + if (capture) bonus /= 6; update_continuation_histories(ss, movedPiece, to_sq(move), bonus); @@ -1306,10 +1307,10 @@ moves_loop: // When in check, search starts here // If the move is worse than some previously searched move, remember it to update its stats later if (move != bestMove) { - if (captureOrPromotion && captureCount < 32) + if (capture && captureCount < 32) capturesSearched[captureCount++] = move; - else if (!captureOrPromotion && quietCount < 64) + else if (!capture && quietCount < 64) quietsSearched[quietCount++] = move; } } @@ -1394,7 +1395,7 @@ moves_loop: // When in check, search starts here Move ttMove, move, bestMove; Depth ttDepth; Value bestValue, value, ttValue, futilityValue, futilityBase; - bool pvHit, givesCheck, captureOrPromotion; + bool pvHit, givesCheck, capture; int moveCount; if (PvNode) @@ -1503,7 +1504,7 @@ moves_loop: // When in check, search starts here continue; givesCheck = pos.gives_check(move); - captureOrPromotion = pos.capture_or_promotion(move); + capture = pos.capture(move); moveCount++; @@ -1543,12 +1544,12 @@ moves_loop: // When in check, search starts here ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [captureOrPromotion] + [capture] [pos.moved_piece(move)] [to_sq(move)]; // Continuation history based pruning (~2 Elo) - if ( !captureOrPromotion + if ( !capture && bestValue > VALUE_TB_LOSS_IN_MAX_PLY && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold) @@ -1557,11 +1558,11 @@ moves_loop: // When in check, search starts here // movecount pruning for quiet check evasions if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY && quietCheckEvasions > 1 - && !captureOrPromotion + && !capture && ss->inCheck) continue; - quietCheckEvasions += !captureOrPromotion && ss->inCheck; + quietCheckEvasions += !capture && ss->inCheck; // Make and search the move pos.do_move(move, st, givesCheck); @@ -1680,7 +1681,7 @@ moves_loop: // When in check, search starts here bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus - if (!pos.capture_or_promotion(bestMove)) + if (!pos.capture(bestMove)) { // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bonus2); From 08e0f52b77edb929989c68c49e954b9bc5d7d67e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 28 Mar 2022 14:15:56 +0300 Subject: [PATCH 011/678] In movepicker increase priority for moves that evade a capture This idea is a mix of koivisto idea of threat history and heuristic that was simplified some time ago in LMR - decreasing reduction for moves that evade a capture. Instead of doing so in LMR this patch does it in movepicker - to do this it calculates squares that are attacked by different piece types and pieces that are located on this squares and boosts up weight of moves that make this pieces land on a square that is not under threat. Boost is greater for pieces with bigger material values. Special thanks to koivisto and seer authors for explaining me ideas behind threat history. Passed STC: https://tests.stockfishchess.org/tests/view/62406e473b32264b9aa1478b LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 19816 W: 5320 L: 5072 D: 9424 Ptnml(0-2): 86, 2165, 5172, 2385, 100 Passed LTC: https://tests.stockfishchess.org/tests/view/62407f2e3b32264b9aa149c8 LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 51200 W: 13805 L: 13500 D: 23895 Ptnml(0-2): 44, 5023, 15164, 5322, 47 closes https://github.com/official-stockfish/Stockfish/pull/3970 bench 7736491 --- src/movepick.cpp | 75 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 77453a45..ce82a59b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -97,6 +97,44 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, Depth d, const Cap && pos.see_ge(ttm, threshold)); } +//squares threatened by pawn attacks +template +Bitboard threatsByPawn (const Position& pos) +{ + return pawn_attacks_bb(pos.pieces(Us, PAWN)); +} + +//squares threatened by minor attacks +template +Bitboard threatsByMinor (const Position& pos) +{ + Bitboard our = pos.pieces(Us, KNIGHT, BISHOP); + Bitboard threats = 0; + while (our) + { + Square s = pop_lsb(our); + if (type_of(pos.piece_on(s)) == KNIGHT) + threats |= attacks_bb(s, pos.pieces()); + else + threats |= attacks_bb(s, pos.pieces()); + } + return threats; +} + +//squares threatened by rook attacks +template +Bitboard threatsByRook (const Position& pos) +{ + Bitboard our = pos.pieces(Us, ROOK); + Bitboard threats = 0; + while (our) + { + Square s = pop_lsb(our); + threats |= attacks_bb(s, pos.pieces()); + } + return threats; +} + /// MovePicker::score() assigns a numerical value to each move in a list, used /// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring /// captures with a good history. Quiets moves are ordered using the histories. @@ -105,6 +143,35 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); + Bitboard threatened, threatenedByPawn, threatenedByMinor, threatenedByRook; + if constexpr (Type == QUIETS) + { + // squares threatened by pawns + threatenedByPawn = pos.side_to_move() == WHITE ? threatsByPawn(pos) : threatsByPawn(pos); + // squares threatened by minors or pawns + threatenedByMinor = pos.side_to_move() == WHITE ? threatsByMinor(pos) : threatsByMinor(pos); + threatenedByMinor |= threatenedByPawn; + // squares threatened by rooks, minors or pawns + threatenedByRook = pos.side_to_move() == WHITE ? threatsByRook(pos) : threatsByRook(pos); + threatenedByRook |= threatenedByMinor; + + // pieces threatened by pieces of lesser material value + threatened = pos.side_to_move() == WHITE ? ((pos.pieces(WHITE, QUEEN) & threatenedByRook) | + (pos.pieces(WHITE, ROOK) & threatenedByMinor) | + (pos.pieces(WHITE, KNIGHT, BISHOP) & threatenedByPawn)) + : ((pos.pieces(BLACK, QUEEN) & threatenedByRook) | + (pos.pieces(BLACK, ROOK) & threatenedByMinor) | + (pos.pieces(BLACK, KNIGHT, BISHOP) & threatenedByPawn)); + } + else + { + // Silence unused variable warning + (void) threatened; + (void) threatenedByPawn; + (void) threatenedByMinor; + (void) threatenedByRook; + } + for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = 6 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) @@ -115,7 +182,13 @@ void MovePicker::score() { + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)]; + + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] + + (threatened & from_sq(m) ? + (type_of(pos.piece_on(from_sq(m))) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000 + : type_of(pos.piece_on(from_sq(m))) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000 + : !(to_sq(m) & threatenedByPawn) ? 15000 + : 0) + : 0); else // Type == EVASIONS { From 471d93063a8fc1803a4a34397fe39e2344a05d76 Mon Sep 17 00:00:00 2001 From: Topologist Date: Mon, 28 Mar 2022 11:50:08 +0200 Subject: [PATCH 012/678] Play more positional in endgames This patch chooses the delta value (which skews the nnue evaluation between positional and materialistic) depending on the material: If the material is low, delta will be higher and the evaluation is shifted to the positional value. If the material is high, the evaluation will be shifted to the psqt value. I don't think slightly negative values of delta should be a concern. Passed STC: https://tests.stockfishchess.org/tests/view/62418513b3b383e86185766f LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 28808 W: 7832 L: 7564 D: 13412 Ptnml(0-2): 147, 3186, 7505, 3384, 182 Passed LTC: https://tests.stockfishchess.org/tests/view/62419137b3b383e861857842 LLR: 2.96 (-2.94,2.94) <0.50,3.00> Total: 58632 W: 15776 L: 15450 D: 27406 Ptnml(0-2): 42, 5889, 17149, 6173, 63 closes https://github.com/official-stockfish/Stockfish/pull/3971 Bench: 7588855 --- src/nnue/evaluate_nnue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 9254e36f..9ee599f4 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -143,7 +143,7 @@ namespace Stockfish::Eval::NNUE { // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; - int delta = 7; + int delta = 10 - pos.non_pawn_material() / 1515; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType transformedFeaturesUnaligned[ From 9f6bcb38c032a18c8b1aec318d5c7255827f9c7b Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 30 Mar 2022 18:14:27 -0700 Subject: [PATCH 013/678] Minor cleanups simplify and relocate to position.cpp some of the recent threat calculations used in the movepicker. passed STC: https://tests.stockfishchess.org/tests/view/62468c301f682ea45ce3b3b9 LLR: 2.96 (-2.94,2.94) <-2.25,0.25> Total: 76544 W: 20247 L: 20152 D: 36145 Ptnml(0-2): 327, 8113, 21317, 8168, 347 closes https://github.com/official-stockfish/Stockfish/pull/3972 No functional change --- src/misc.h | 9 +++---- src/movepick.cpp | 69 ++++++++++-------------------------------------- src/position.h | 18 ++++++++++++- 3 files changed, 34 insertions(+), 62 deletions(-) diff --git a/src/misc.h b/src/misc.h index dcef22a4..2fd2b408 100644 --- a/src/misc.h +++ b/src/misc.h @@ -90,9 +90,6 @@ static inline const bool IsLittleEndian = (Le.c[0] == 4); class RunningAverage { public: - // Constructor - RunningAverage() {} - // Reset the running average to rational value p / q void set(int64_t p, int64_t q) { average = p * PERIOD * RESOLUTION / q; } @@ -102,10 +99,10 @@ class RunningAverage { { average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; } // Test if average is strictly greater than rational a / b - bool is_greater(int64_t a, int64_t b) - { return b * average > a * PERIOD * RESOLUTION ; } + bool is_greater(int64_t a, int64_t b) const + { return b * average > a * (PERIOD * RESOLUTION); } - int64_t value() + int64_t value() const { return average / (PERIOD * RESOLUTION); } private : diff --git a/src/movepick.cpp b/src/movepick.cpp index ce82a59b..b0166c6e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -18,6 +18,7 @@ #include +#include "bitboard.h" #include "movepick.h" namespace Stockfish { @@ -97,44 +98,6 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, Depth d, const Cap && pos.see_ge(ttm, threshold)); } -//squares threatened by pawn attacks -template -Bitboard threatsByPawn (const Position& pos) -{ - return pawn_attacks_bb(pos.pieces(Us, PAWN)); -} - -//squares threatened by minor attacks -template -Bitboard threatsByMinor (const Position& pos) -{ - Bitboard our = pos.pieces(Us, KNIGHT, BISHOP); - Bitboard threats = 0; - while (our) - { - Square s = pop_lsb(our); - if (type_of(pos.piece_on(s)) == KNIGHT) - threats |= attacks_bb(s, pos.pieces()); - else - threats |= attacks_bb(s, pos.pieces()); - } - return threats; -} - -//squares threatened by rook attacks -template -Bitboard threatsByRook (const Position& pos) -{ - Bitboard our = pos.pieces(Us, ROOK); - Bitboard threats = 0; - while (our) - { - Square s = pop_lsb(our); - threats |= attacks_bb(s, pos.pieces()); - } - return threats; -} - /// MovePicker::score() assigns a numerical value to each move in a list, used /// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring /// captures with a good history. Quiets moves are ordered using the histories. @@ -146,26 +109,22 @@ void MovePicker::score() { Bitboard threatened, threatenedByPawn, threatenedByMinor, threatenedByRook; if constexpr (Type == QUIETS) { + Color us = pos.side_to_move(); // squares threatened by pawns - threatenedByPawn = pos.side_to_move() == WHITE ? threatsByPawn(pos) : threatsByPawn(pos); + threatenedByPawn = pos.attacks_by(~us); // squares threatened by minors or pawns - threatenedByMinor = pos.side_to_move() == WHITE ? threatsByMinor(pos) : threatsByMinor(pos); - threatenedByMinor |= threatenedByPawn; + threatenedByMinor = pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; // squares threatened by rooks, minors or pawns - threatenedByRook = pos.side_to_move() == WHITE ? threatsByRook(pos) : threatsByRook(pos); - threatenedByRook |= threatenedByMinor; + threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; // pieces threatened by pieces of lesser material value - threatened = pos.side_to_move() == WHITE ? ((pos.pieces(WHITE, QUEEN) & threatenedByRook) | - (pos.pieces(WHITE, ROOK) & threatenedByMinor) | - (pos.pieces(WHITE, KNIGHT, BISHOP) & threatenedByPawn)) - : ((pos.pieces(BLACK, QUEEN) & threatenedByRook) | - (pos.pieces(BLACK, ROOK) & threatenedByMinor) | - (pos.pieces(BLACK, KNIGHT, BISHOP) & threatenedByPawn)); + threatened = (pos.pieces(us, QUEEN) & threatenedByRook) + | (pos.pieces(us, ROOK) & threatenedByMinor) + | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); } else { - // Silence unused variable warning + // Silence unused variable warnings (void) threatened; (void) threatenedByPawn; (void) threatenedByMinor; @@ -184,11 +143,11 @@ void MovePicker::score() { + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] + (threatened & from_sq(m) ? - (type_of(pos.piece_on(from_sq(m))) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000 - : type_of(pos.piece_on(from_sq(m))) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000 - : !(to_sq(m) & threatenedByPawn) ? 15000 - : 0) - : 0); + (type_of(pos.moved_piece(m)) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000 + : type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000 + : !(to_sq(m) & threatenedByPawn) ? 15000 + : 0) + : 0); else // Type == EVASIONS { diff --git a/src/position.h b/src/position.h index 7b6165f3..e5585818 100644 --- a/src/position.h +++ b/src/position.h @@ -120,12 +120,12 @@ public: Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Bitboard occupied) const; Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; + template Bitboard attacks_by(Color c) const; // Properties of moves bool legal(Move m) const; bool pseudo_legal(const Move m) const; bool capture(Move m) const; - bool capture_or_promotion(Move m) const; bool gives_check(Move m) const; Piece moved_piece(Move m) const; Piece captured_piece() const; @@ -285,6 +285,22 @@ inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); } +template +inline Bitboard Position::attacks_by(Color c) const { + + if constexpr (Pt == PAWN) + return c == WHITE ? pawn_attacks_bb(pieces(WHITE, PAWN)) + : pawn_attacks_bb(pieces(BLACK, PAWN)); + else + { + Bitboard threats = 0; + Bitboard attackers = pieces(c, Pt); + while (attackers) + threats |= attacks_bb(pop_lsb(attackers), pieces()); + return threats; + } +} + inline Bitboard Position::checkers() const { return st->checkersBB; } From 19a90b45bceb69aa62b5c85366343a7d1cfc695f Mon Sep 17 00:00:00 2001 From: Topologist Date: Sat, 9 Apr 2022 08:55:40 +0200 Subject: [PATCH 014/678] Use NNUE in low piece endgames close to the root. This patch enforces that NNUE evaluation is used for endgame positions at shallow depth (depth <= 9). Classic evaluation will still be used for high imbalance positions when the depth is high or there are many pieces. Passed STC: https://tests.stockfishchess.org/tests/view/624c193b3a8a6ac93892dc27 LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 255840 W: 68024 L: 67362 D: 120454 Ptnml(0-2): 1074, 27089, 70926, 27763, 1068 Passed LTC: https://tests.stockfishchess.org/tests/view/624e8675e9e7821808467f77 LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 67088 W: 17784 L: 17454 D: 31850 Ptnml(0-2): 45, 6209, 20715, 6521, 54 closes https://github.com/official-stockfish/Stockfish/pull/3978 bench: 6602222 --- src/evaluate.cpp | 4 +++- src/search.cpp | 1 + src/thread.h | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e9376aa6..d9180f2b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1088,7 +1088,8 @@ Value Eval::evaluate(const Position& pos) { // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, // but we switch to NNUE during long shuffling or with high material on the board. if ( !useNNUE - || abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (5 + pos.rule50_count())) + || ((pos.this_thread()->depth > 9 || pos.count() > 7) && + abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (5 + pos.rule50_count()))) { v = Evaluation(pos).value(); // classical useClassical = abs(v) >= 297; @@ -1138,6 +1139,7 @@ std::string Eval::trace(Position& pos) { std::memset(scores, 0, sizeof(scores)); // Reset any global variable used in eval + pos.this_thread()->depth = 0; pos.this_thread()->trend = SCORE_ZERO; pos.this_thread()->bestValue = VALUE_ZERO; pos.this_thread()->optimism[WHITE] = VALUE_ZERO; diff --git a/src/search.cpp b/src/search.cpp index 2d24c313..fa73dce5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -560,6 +560,7 @@ namespace { // Step 1. Initialize node Thread* thisThread = pos.this_thread(); + thisThread->depth = depth; ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); diff --git a/src/thread.h b/src/thread.h index 594a8ea2..8027855a 100644 --- a/src/thread.h +++ b/src/thread.h @@ -69,7 +69,7 @@ public: Position rootPos; StateInfo rootState; Search::RootMoves rootMoves; - Depth rootDepth, completedDepth; + Depth rootDepth, completedDepth, depth; Value rootDelta; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; From 319af5cf0a77d1057a69cef0cf8885d06475dece Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 14 Apr 2022 23:00:14 +0200 Subject: [PATCH 015/678] Update CPU contributors closes https://github.com/official-stockfish/Stockfish/pull/3979 No functional change --- Top CPU Contributors.txt | 121 ++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 58 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 4bc96cde..76aa01e9 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,59 +1,59 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2022-02-05. +Contributors to Fishtest with >10,000 CPU hours, as of 2022-04-14. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 30730952 2158431735 -mlang 2729669 187335452 -technologov 1696847 74478658 -dew 1635640 97483012 -grandphish2 1062754 64955639 +noobpwnftw 31714850 2267266129 +mlang 2954099 198421098 +technologov 2324150 102449398 +dew 1670874 99276012 +grandphish2 1134273 68070459 +okrout 901194 77738874 +TueRens 821388 50207666 tvijlbrief 795993 51894442 -okrout 773704 63465204 -TueRens 766198 47770388 +pemo 744463 32486677 +JojoM 724378 43660674 mibere 703840 46867607 -JojoM 703005 42689868 -pemo 634102 29868807 linrock 626939 17408017 -gvreuls 517442 33605006 -cw 503905 33850487 -fastgm 482847 29004732 +gvreuls 534079 34352532 +cw 507221 34006775 +fastgm 489749 29344518 crunchy 427035 27344275 -CSU_Dynasty 415864 28116776 -ctoks 403102 26737127 -oz 357710 26490208 -bcross 331095 23165889 +CSU_Dynasty 424643 28525220 +ctoks 415771 27364603 +oz 369200 27017658 +bcross 342642 23671289 Fisherman 327231 21829379 -velislav 321708 20729264 -leszek 303654 19063973 -Dantist 251015 15843226 -mgrabiak 231973 15162494 +velislav 325670 20911076 +leszek 321295 19874113 +Dantist 274747 16910258 +mgrabiak 237604 15418700 +robal 217959 13840386 glinscott 217799 13780820 -robal 213960 13665726 nordlandia 211692 13484886 -drabel 200914 13755384 +drabel 201967 13798360 bking_US 198894 11876016 -mhoram 180229 11610075 +mhoram 194862 12261809 Thanar 179852 12365359 vdv 175544 9904472 spams 157128 10319326 +rpngn 154081 9652139 marrco 150300 9402229 sqrt2 147963 9724586 -vdbergh 137429 8955089 +vdbergh 137430 8955097 CoffeeOne 137100 5024116 malala 136182 8002293 xoto 133759 9159372 -rpngn 131285 8657757 -davar 122661 7996937 +davar 125240 8117121 dsmith 122059 7570238 amicic 119659 7937885 Data 113305 8220352 BrunoBanani 112960 7436849 CypressChess 108321 7759588 +DesolatedDodo 106811 6776980 MaZePallas 102823 6633619 sterni1971 100532 5880772 sunu 100167 7040199 -DesolatedDodo 99038 6414626 ElbertoOne 99028 7023771 skiminki 98123 6478402 brabos 92118 6186135 @@ -61,39 +61,39 @@ cuistot 90358 5351004 psk 89957 5984901 racerschmacer 85712 6119648 Vizvezdenec 83761 5344740 +zeryl 83680 5250995 sschnee 83003 4840890 0x3C33 82614 5271253 BRAVONE 81239 5054681 nssy 76497 5259388 teddybaer 75125 5407666 +jromang 74796 5175825 Pking_cda 73776 5293873 -zeryl 73335 4774257 -jromang 72192 5057715 +Calis007 72477 4088576 solarlight 70517 5028306 dv8silencer 70287 3883992 Bobo1239 68515 4652287 manap 66273 4121774 +yurikvelo 65716 4457300 tinker 64333 4268790 -yurikvelo 63371 4335060 +Wolfgang 62644 3817410 qurashee 61208 3429862 robnjr 57262 4053117 -Wolfgang 57014 3561352 Freja 56938 3733019 ttruscott 56010 3680085 rkl 55132 4164467 renouve 53811 3501516 +megaman7de 52434 3243016 +MaxKlaxxMiner 51977 3153032 finfish 51360 3370515 eva42 51272 3599691 -Calis007 51182 3131552 eastorwest 51058 3451555 rap 49985 3219146 pb00067 49727 3298270 -Spprtr 48260 3141959 +Spprtr 48920 3161711 bigpen0r 47667 3336927 ronaldjerum 47654 3240695 -MaxKlaxxMiner 47584 2972142 biffhero 46564 3111352 -megaman7de 45992 2952006 Fifis 45843 3088497 VoyagerOne 45476 3452465 speedycpu 43842 3003273 @@ -102,25 +102,27 @@ Antihistamine 41788 2761312 mhunt 41735 2691355 homyur 39893 2850481 gri 39871 2515779 +armo9494 39064 2832326 oryx 38867 2976992 SC 37299 2731694 Garf 37213 2986270 +tolkki963 37059 2154330 csnodgrass 36207 2688994 jmdana 36157 2210661 strelock 34716 2074055 +DMBK 34010 2482916 EthanOConnor 33370 2090311 slakovv 32915 2021889 -armo9494 32129 2551682 -tolkki963 32114 1932256 +gopeto 30993 2028106 manapbk 30987 1810399 -DMBK 30675 2383552 Prcuvu 30377 2170122 anst 30301 2190091 jkiiski 30136 1904470 -gopeto 29886 1979118 hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 +ncfish1 29105 1704011 +belzedar94 27935 1789106 OuaisBla 27636 1578800 chriswk 26902 1868317 achambord 26582 1767323 @@ -129,15 +131,14 @@ yorkman 26193 1992080 SFTUser 25182 1675689 nabildanial 24942 1519409 Sharaf_DG 24765 1786697 -ncfish1 24411 1520927 rodneyc 24275 1410450 agg177 23890 1395014 -belzedar94 23707 1593860 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23339 1602636 -Ente 23093 1642458 +Ente 23270 1651432 cisco2015 22897 1762669 +MarcusTullius 22688 1274821 Zirie 22542 1472937 team-oh 22272 1636708 MazeOfGalious 21978 1629593 @@ -146,17 +147,22 @@ ianh2105 21725 1632562 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 +Roady 21323 1433822 sphinx 21211 1384728 +user213718 21196 1397710 +spcc 21065 1311338 jjoshua2 21001 1423089 horst.prack 20878 1465656 -user213718 20783 1379584 0xB00B1ES 20590 1208666 j3corre 20405 941444 +kdave 20364 1389254 Adrian.Schmidt123 20316 1281436 +Ulysses 20217 1351500 +markkulix 19976 1115258 wei 19973 1745989 -Roady 19848 1335928 rstoesser 19569 1293588 eudhan 19274 1283717 +fishtester 18995 1238686 vulcan 18871 1729392 jundery 18445 1115855 iisiraider 18247 1101015 @@ -164,21 +170,19 @@ ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17353 978595 -kdave 17183 1242754 +Wencey 17125 805964 DragonLord 17014 1162790 thirdlife 16996 447356 -spcc 16932 1130940 -fishtester 16644 1123000 -Ulysses 16490 1184400 IgorLeMasson 16064 1147232 ako027ako 15671 1173203 +AndreasKrug 15550 1194497 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 +scuzzi 14928 953313 OssumOpossum 14857 1007129 Karby 14808 867120 -AndreasKrug 14608 1152093 +jsys14 14652 855642 enedene 14476 905279 -jsys14 14340 844792 bpfliegel 14298 884523 mpx86 14019 759568 jpulman 13982 870599 @@ -188,8 +192,8 @@ Nesa92 13786 1114691 mbeier 13650 1044928 Hjax 13535 915487 Dark_wizzie 13422 1007152 +Jopo12321 13367 678852 Rudolphous 13244 883140 -MarcusTullius 13221 843169 Machariel 13010 863104 mabichito 12903 749391 thijsk 12886 722107 @@ -197,34 +201,35 @@ AdrianSA 12860 804972 infinigon 12807 937332 Flopzee 12698 894821 fatmurphy 12547 853210 -scuzzi 12511 845761 SapphireBrand 12416 969604 modolief 12386 896470 Farseer 12249 694108 pgontarz 12151 848794 +pirt 12008 923149 stocky 11954 699440 mschmidt 11941 803401 dbernier 11609 818636 Maxim 11543 836024 -pirt 11516 894513 infinity 11470 727027 aga 11409 695071 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 -markkulix 11331 739098 -FormazChar 11308 847735 +FormazChar 11349 850327 d64 11263 789184 MooTheCow 11237 720174 snicolet 11106 869170 ali-al-zhrani 11098 768494 whelanh 11067 235676 +Jackfish 10978 720078 +deflectooor 10886 520116 basepi 10637 744851 Cubox 10621 826448 michaelrpg 10509 739239 OIVAS7572 10420 995586 dzjp 10343 732529 -Garruk 10332 703905 +Garruk 10334 704065 ols 10259 570669 lbraesch 10252 647825 -Jackfish 10098 682338 +qoo_charly_cai 10212 620407 +Naven94 10069 503192 From c3b67faf983ac918f313d019a7427d99901fcdb0 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 15 Apr 2022 17:23:51 +0200 Subject: [PATCH 016/678] Update WDL model for current SF This updates the WDL model based on the LTC statistics for the last month (8M games). for old results see: https://github.com/official-stockfish/Stockfish/pull/3582 https://github.com/official-stockfish/Stockfish/pull/2778 the model changed a bit from the past, some images to follow in the PR closes https://github.com/official-stockfish/Stockfish/pull/3981 No functional change. --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 741241b3..7b30cc04 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -207,8 +207,8 @@ namespace { // Coefficients of a 3rd order polynomial fit based on fishtest data // for two parameters needed to transform eval to the argument of a // logistic function. - double as[] = {-3.68389304, 30.07065921, -60.52878723, 149.53378557}; - double bs[] = {-2.0181857, 15.85685038, -29.83452023, 47.59078827}; + double as[] = {-1.17202460e-01, 5.94729104e-01, 1.12065546e+01, 1.22606222e+02}; + double bs[] = {-1.79066759, 11.30759193, -17.43677612, 36.47147479}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From c25d4c4887dbc23395afef59e24a520c5d12ab52 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 12 Apr 2022 20:45:25 +0300 Subject: [PATCH 017/678] Tuning classical and NNUE scaling terms changes to parameters in both classical and NNUE scaling, following up from an earlier successful #3958 passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 23936 W: 6490 L: 6234 D: 11212 Ptnml(0-2): 107, 2610, 6306, 2810, 135 https://tests.stockfishchess.org/tests/view/625820aa33c40bb9d964e6ae passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 50376 W: 13629 L: 13327 D: 23420 Ptnml(0-2): 20, 4979, 14920, 5217, 52 https://tests.stockfishchess.org/tests/view/62584592c1d7f5008a33a4d1 closes https://github.com/official-stockfish/Stockfish/pull/3982 Bench: 6964954 --- src/evaluate.cpp | 10 +++++----- src/pawns.cpp | 38 +++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d9180f2b..8bb42ce1 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -198,12 +198,12 @@ namespace { constexpr Value SpaceThreshold = Value(11551); // KingAttackWeights[PieceType] contains king attack weights by piece type - constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 }; + constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 76, 46, 45, 14 }; // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type, // higher if multiple safe checks are possible for that piece type. constexpr int SafeCheck[][2] = { - {}, {}, {803, 1292}, {639, 974}, {1087, 1878}, {759, 1132} + {}, {}, {805, 1292}, {650, 984}, {1071, 1886}, {730, 1128} }; #define S(mg, eg) make_score(mg, eg) @@ -1089,7 +1089,7 @@ Value Eval::evaluate(const Position& pos) { // but we switch to NNUE during long shuffling or with high material on the board. if ( !useNNUE || ((pos.this_thread()->depth > 9 || pos.count() > 7) && - abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (5 + pos.rule50_count()))) + abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count()))) { v = Evaluation(pos).value(); // classical useClassical = abs(v) >= 297; @@ -1099,7 +1099,7 @@ Value Eval::evaluate(const Position& pos) { if (useNNUE && !useClassical) { Value nnue = NNUE::evaluate(pos, true); // NNUE - int scale = 1036 + 20 * pos.non_pawn_material() / 1024; + int scale = 1036 + 22 * pos.non_pawn_material() / 1024; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; Value psq = (stm == WHITE ? 1 : -1) * eg_value(pos.psq_score()); @@ -1113,7 +1113,7 @@ Value Eval::evaluate(const Position& pos) { } // Damp down the evaluation linearly when shuffling - v = v * (207 - pos.rule50_count()) / 207; + v = v * (195 - pos.rule50_count()) / 211; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/pawns.cpp b/src/pawns.cpp index 6e509133..fdcfa022 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -32,30 +32,30 @@ namespace { #define S(mg, eg) make_score(mg, eg) // Pawn penalties - constexpr Score Backward = S( 9, 22); - constexpr Score Doubled = S(13, 51); - constexpr Score DoubledEarly = S(20, 7); - constexpr Score Isolated = S( 3, 15); - constexpr Score WeakLever = S( 4, 58); - constexpr Score WeakUnopposed = S(13, 24); + constexpr Score Backward = S( 6, 19); + constexpr Score Doubled = S(11, 51); + constexpr Score DoubledEarly = S(17, 7); + constexpr Score Isolated = S( 1, 20); + constexpr Score WeakLever = S( 2, 57); + constexpr Score WeakUnopposed = S(15, 18); // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[2] = { S(-17, -6), S(-9, 2) }; + constexpr Score BlockedPawn[2] = { S(-19, -8), S(-7, 3) }; constexpr Score BlockedStorm[RANK_NB] = { - S(0, 0), S(0, 0), S(75, 78), S(-8, 16), S(-6, 10), S(-6, 6), S(0, 2) + S(0, 0), S(0, 0), S(64, 75), S(-3, 14), S(-12, 19), S(-7, 4), S(-10, 5) }; // Connected pawn bonus - constexpr int Connected[RANK_NB] = { 0, 5, 7, 11, 23, 48, 87 }; + constexpr int Connected[RANK_NB] = { 0, 3, 7, 7, 15, 54, 86 }; // Strength of pawn shelter for our king by [distance from edge][rank]. // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = { - { V( -5), V( 82), V( 92), V( 54), V( 36), V( 22), V( 28) }, - { V(-44), V( 63), V( 33), V(-50), V(-30), V(-12), V( -62) }, - { V(-11), V( 77), V( 22), V( -6), V( 31), V( 8), V( -45) }, - { V(-39), V(-12), V(-29), V(-50), V(-43), V(-68), V(-164) } + { V(-2), V(85), V(95), V(53), V(39), V(23), V(25) }, + { V(-55), V(64), V(32), V(-55), V(-30), V(-11), V(-61) }, + { V(-11), V(75), V(19), V(-6), V(26), V(9), V(-47) }, + { V(-41), V(-11), V(-27), V(-58), V(-42), V(-66), V(-163) } }; // Danger of enemy pawns moving toward our king by [distance from edge][rank]. @@ -63,17 +63,17 @@ namespace { // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn // on edge, likely blocked by our king. constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = { - { V( 87), V(-288), V(-168), V( 96), V( 47), V( 44), V( 46) }, - { V( 42), V( -25), V( 120), V( 45), V( 34), V( -9), V( 24) }, - { V( -8), V( 51), V( 167), V( 35), V( -4), V(-16), V(-12) }, - { V(-17), V( -13), V( 100), V( 4), V( 9), V(-16), V(-31) } + { V(94), V(-280), V(-170), V(90), V(59), V(47), V(53) }, + { V(43), V(-17), V(128), V(39), V(26), V(-17), V(15) }, + { V(-9), V(62), V(170), V(34), V(-5), V(-20), V(-11) }, + { V(-27), V(-19), V(106), V(10), V(2), V(-13), V(-24) } }; // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties // for king when the king is on a semi-open or open file. - constexpr Score KingOnFile[2][2] = {{ S(-21,10), S(-7, 1) }, - { S( 0,-3), S( 9,-4) }}; + constexpr Score KingOnFile[2][2] = {{ S(-18,11), S(-6,-3) }, + { S( 0, 0), S( 5,-4) }}; #undef S #undef V From df2f7e75276cc93a8cb8c70057903ab0edbd92bd Mon Sep 17 00:00:00 2001 From: KJE-98 <> Date: Thu, 14 Apr 2022 22:09:50 -0400 Subject: [PATCH 018/678] Decrease LMR at PV nodes with low depth. This patch lessens the Late Move Reduction at PV nodes with low depth. Previously the affect of depth on LMR was independant of nodeType. The idea behind this patch is that at PV nodes, LMR at low depth is will miss out on potential alpha-raising moves. Passed STC: https://tests.stockfishchess.org/tests/view/625aa867d3367522c4b8965c LLR: 2.93 (-2.94,2.94) <0.00,2.50> Total: 19360 W: 5252 L: 5006 D: 9102 Ptnml(0-2): 79, 2113, 5069, 2321, 98 Passed LTC: https://tests.stockfishchess.org/tests/view/625ae844d3367522c4b8a009 LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 39264 W: 10636 L: 10357 D: 18271 Ptnml(0-2): 18, 3928, 11473, 4183, 30 closes https://github.com/official-stockfish/Stockfish/pull/3985 bench: 8129754 --- AUTHORS | 1 + src/search.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/AUTHORS b/AUTHORS index 34b95ba5..edf189d8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -104,6 +104,7 @@ jundery Justin Blanchard (UncombedCoconut) Kelly Wilson Ken Takusagawa +Kian E (KJE-98) kinderchocolate Kiran Panditrao (Krgp) Kojirion diff --git a/src/search.cpp b/src/search.cpp index fa73dce5..49d7c5c9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1173,6 +1173,10 @@ moves_loop: // When in check, search starts here if (PvNode && !ss->inCheck && abs(ss->staticEval - bestValue) > 250) r--; + // Increase depth based reduction if PvNode + if (PvNode) + r -= 15 / ( 3 + depth ); + ss->statScore = thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] From e6e324eb28fd49c1fc44b3b65784f85a773ec61c Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 18 Apr 2022 10:17:57 +0200 Subject: [PATCH 019/678] Stockfish 15 Official release version of Stockfish 15 Bench: 8129754 --- A new major release of Stockfish is now available at https://stockfishchess.org Stockfish 15 continues to push the boundaries of chess, providing unrivalled analysis and playing strength. In our testing, Stockfish 15 is ahead of Stockfish 14 by 36 Elo points and wins nine times more game pairs than it loses[1]. Improvements to the engine have made it possible for Stockfish to end up victorious in tournaments at all sorts of time controls ranging from bullet to classical and even at Fischer random chess[2]. At CCC, Stockfish won all of the latest tournaments: CCC 16 Bullet, Blitz and Rapid, CCC 960 championship, and the CCC 17 Rapid. At TCEC, Stockfish won the Season 21, Cup 9, FRC 4 and in the current Season 22 superfinal, at the time of writing, has won 16 game pairs and not yet lost a single one. This progress is the result of a dedicated team of developers that comes up with new ideas and improvements. For Stockfish 15, we tested nearly 13000 different changes and retained the best 200. These include the fourth generation of our NNUE network architecture, as well as various search improvements. To perform these tests, contributors provide CPU time for testing, and in the last year, they have collectively played roughly a billion chess games. In the last few years, our distributed testing framework, Fishtest, has been operated superbly and has been developed and improved extensively. This work by Pasquale Pigazzini, Tom Vijlbrief, Michel Van den Bergh, and various other developers[3] is an essential part of the success of the Stockfish project. Indeed, the Stockfish project builds on a thriving community of enthusiasts to offer a free and open-source chess engine that is robust, widely available, and very strong. We invite our chess fans to join the Fishtest testing framework and programmers to contribute to the project[4]. The Stockfish team [1] https://tests.stockfishchess.org/tests/view/625d156dff677a888877d1be [2] https://en.wikipedia.org/wiki/Stockfish_(chess)#Competition_results [3] https://github.com/glinscott/fishtest/blob/master/AUTHORS [4] https://stockfishchess.org/get-involved/ --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 41c59b3f..178465c1 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -69,7 +69,7 @@ namespace { /// Version number. If Version is left empty, then compile date in the format /// DD-MM-YY and show in engine_info. -const string Version = ""; +const string Version = "15"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From c4db7fd1f941dbd3875e9faaaeb964755d039633 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 18 Apr 2022 23:05:24 +0200 Subject: [PATCH 020/678] Restore development version No functional change. --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 178465c1..41c59b3f 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -69,7 +69,7 @@ namespace { /// Version number. If Version is left empty, then compile date in the format /// DD-MM-YY and show in engine_info. -const string Version = "15"; +const string Version = ""; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 6e0680efa0d0653686b8eb8753dc49718b95cb2b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 18 Apr 2022 07:34:03 +0200 Subject: [PATCH 021/678] Update default net to nn-d0b74ce1e5eb.nnue train a net using training data with a heavier weight on positions having 16 pieces on the board. More specifically, with a relative weight of `i * (32-i)/(16 * 16)+1` (where i is the number of pieces on the board). This is done with the trainer branch https://github.com/glinscott/nnue-pytorch/pull/173 The command used is: ``` python train.py $datafile $datafile $restarttype $restartfile --gpus 1 --threads 4 --num-workers 12 --random-fen-skipping=3 --batch-size 16384 --progress_bar_refresh_rate 300 --smart-fen-skipping --features=HalfKAv2_hm^ --lambda=1.00 --max_epochs=$epochs --seed $RANDOM --default_root_dir exp/run_$i ``` The datafile is T60T70wIsRightFarseerT60T74T75T76.binpack, the restart is from the master net. passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 22728 W: 6197 L: 5945 D: 10586 Ptnml(0-2): 105, 2453, 6001, 2695, 110 https://tests.stockfishchess.org/tests/view/625cf944ff677a888877cd90 passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 35664 W: 9535 L: 9264 D: 16865 Ptnml(0-2): 30, 3524, 10455, 3791, 32 https://tests.stockfishchess.org/tests/view/625d3c32ff677a888877d7ca closes https://github.com/official-stockfish/Stockfish/pull/3989 Bench: 7269563 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 1934c9bd..e857b799 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-6877cd24400e.nnue" + #define EvalFileDefaultName "nn-d0b74ce1e5eb.nnue" namespace NNUE { From e41f727f0f7041b43997f04c031353be5087856b Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 21 Apr 2022 15:28:23 +0300 Subject: [PATCH 022/678] Simplify away best move count logic the only place where it was used it was true with >99% probability so it seemed to not be doing much any more. Passed STC: https://tests.stockfishchess.org/tests/view/625f4778d00da81c22dd4c93 LLR: 2.95 (-2.94,2.94) <-2.25,0.25> Total: 85152 W: 22487 L: 22406 D: 40259 Ptnml(0-2): 313, 9035, 23818, 9078, 332 Passed LTC: https://tests.stockfishchess.org/tests/view/625ff1f1b03f22647441a215 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 66776 W: 17768 L: 17673 D: 31335 Ptnml(0-2): 46, 6200, 20792, 6313, 37 close https://github.com/official-stockfish/Stockfish/pull/3993 bench 7280798 --- src/search.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 49d7c5c9..167209a3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -556,7 +556,7 @@ namespace { bool givesCheck, improving, didLMR, priorCapture; bool capture, doFullDepthSearch, moveCountPruning, ttCapture; Piece movedPiece; - int moveCount, captureCount, quietCount, bestMoveCount, improvement, complexity; + int moveCount, captureCount, quietCount, improvement, complexity; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); @@ -564,7 +564,7 @@ namespace { ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); - moveCount = bestMoveCount = captureCount = quietCount = ss->moveCount = 0; + moveCount = captureCount = quietCount = ss->moveCount = 0; bestValue = -VALUE_INFINITE; maxValue = VALUE_INFINITE; @@ -1145,11 +1145,6 @@ moves_loop: // When in check, search starts here { Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); - // Decrease reduction at some PvNodes (~2 Elo) - if ( PvNode - && bestMoveCount <= 3) - r--; - // Decrease reduction if position is or has been on the PV // and node is not likely to fail low. (~3 Elo) if ( ss->ttPv @@ -1173,9 +1168,9 @@ moves_loop: // When in check, search starts here if (PvNode && !ss->inCheck && abs(ss->staticEval - bestValue) > 250) r--; - // Increase depth based reduction if PvNode + // Decrease reduction for PvNodes based on depth if (PvNode) - r -= 15 / ( 3 + depth ); + r -= 1 + 15 / ( 3 + depth ); ss->statScore = thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] @@ -1297,10 +1292,7 @@ moves_loop: // When in check, search starts here update_pv(ss->pv, move, (ss+1)->pv); if (PvNode && value < beta) // Update alpha! Always alpha < beta - { alpha = value; - bestMoveCount++; - } else { assert(value >= beta); // Fail high From e1f12aa4e61a8bbb772918c405137acdd85e3eec Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Fri, 22 Apr 2022 08:03:50 +0200 Subject: [PATCH 023/678] Negative extension for ttMove that is less than alpha and value in the context of singular extensions Passed STC: https://tests.stockfishchess.org/tests/view/626047e8b03f22647441ade0 LLR: 2.97 (-2.94,2.94) <0.00,2.50> Total: 50296 W: 13410 L: 13108 D: 23778 Ptnml(0-2): 196, 5548, 13370, 5826, 208 Passed LTC: https://tests.stockfishchess.org/tests/view/6260a513b03f22647441b970 LLR: 2.96 (-2.94,2.94) <0.50,3.00> Total: 83896 W: 22433 L: 22054 D: 39409 Ptnml(0-2): 49, 8273, 24938, 8626, 62 closes https://github.com/official-stockfish/Stockfish/pull/3995 bench: 7729968 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 167209a3..ff6bf335 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1098,6 +1098,10 @@ moves_loop: // When in check, search starts here // If the eval of ttMove is greater than beta, we reduce it (negative extension) else if (ttValue >= beta) extension = -2; + + // If the eval of ttMove is less than alpha and value, we reduce it (negative extension) + else if (ttValue <= alpha && ttValue <= value) + extension = -1; } // Check extensions (~1 Elo) From 285a79eaa0b89b342b5e4f217682e083a6fd33f5 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Wed, 27 Apr 2022 13:09:53 +0200 Subject: [PATCH 024/678] Simplify time management. Replace the best move instability adjustment factor by a simpler version which doesn't have a dependency on the iteration depth. STC: LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 30800 W: 8232 L: 8073 D: 14495 Ptnml(0-2): 101, 3309, 8444, 3422, 124 https://tests.stockfishchess.org/tests/view/6266c77bc5b924ba22908d30 LTC: LLR: 2.95 (-2.94,2.94) <-2.25,0.25> Total: 61664 W: 16375 L: 16272 D: 29017 Ptnml(0-2): 40, 5869, 18897, 6000, 26 https://tests.stockfishchess.org/tests/view/6266fc39b3d1812808915f23 closes https://github.com/official-stockfish/Stockfish/pull/3999 Bench: 7729968 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ff6bf335..cd38e629 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -466,8 +466,7 @@ void Thread::search() { // If the bestMove is stable over several iterations, reduce time accordingly timeReduction = lastBestMoveDepth + 10 < completedDepth ? 1.63 : 0.73; double reduction = (1.56 + mainThread->previousTimeReduction) / (2.20 * timeReduction); - double bestMoveInstability = 1.073 + std::max(1.0, 2.25 - 9.9 / rootDepth) - * totBestMoveChanges / Threads.size(); + double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size(); int complexity = mainThread->complexityAverage.value(); double complexPosition = std::clamp(1.0 + (complexity - 326) / 1618.1, 0.5, 1.5); From a32d2086bc227f1d76bd04808e10c8e7bd230fe9 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Tue, 3 May 2022 12:35:21 +0200 Subject: [PATCH 025/678] Use fail high count for LMR Increase reduction if next ply has a lot of fail high else reset count to 0 Passed STC: https://tests.stockfishchess.org/tests/view/626ea8299116b52aa83b71f6 LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 144288 W: 38377 L: 37902 D: 68009 Ptnml(0-2): 565, 16298, 38054, 16551, 676 Passed LTC: https://tests.stockfishchess.org/tests/view/626fa0fb79f761bab2e382f0 LLR: 2.98 (-2.94,2.94) <0.50,3.00> Total: 74872 W: 20050 L: 19686 D: 35136 Ptnml(0-2): 51, 7541, 21893, 7895, 56 closes https://github.com/official-stockfish/Stockfish/pull/4006 bench: 7084802 --- src/search.cpp | 9 +++++++++ src/search.h | 1 + 2 files changed, 10 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index cd38e629..70b852f3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -603,6 +603,7 @@ namespace { (ss+1)->ttPv = false; (ss+1)->excludedMove = bestMove = MOVE_NONE; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + (ss+2)->cutoffCnt = 0; ss->doubleExtensions = (ss-1)->doubleExtensions; ss->depth = depth; Square prevSq = to_sq((ss-1)->currentMove); @@ -1175,6 +1176,10 @@ moves_loop: // When in check, search starts here if (PvNode) r -= 1 + 15 / ( 3 + depth ); + // Increase reduction if next ply has a lot of fail high else reset count to 0 + if ((ss+1)->cutoffCnt > 3 && !PvNode) + r++; + ss->statScore = thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] @@ -1298,11 +1303,15 @@ moves_loop: // When in check, search starts here alpha = value; else { + ss->cutoffCnt++; assert(value >= beta); // Fail high break; } } } + else + ss->cutoffCnt = 0; + // If the move is worse than some previously searched move, remember it to update its stats later if (move != bestMove) diff --git a/src/search.h b/src/search.h index 806295a1..8bb51832 100644 --- a/src/search.h +++ b/src/search.h @@ -54,6 +54,7 @@ struct Stack { bool ttPv; bool ttHit; int doubleExtensions; + int cutoffCnt; }; From 9eb7b607cf69f6b613c89ae767b7f934f26e59e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Wed, 4 May 2022 07:39:23 +0200 Subject: [PATCH 026/678] Reduce depth after score improvement at PV nodes STC: LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 73760 W: 19590 L: 19244 D: 34926 Ptnml(0-2): 285, 8352, 19292, 8634, 317 https://tests.stockfishchess.org/tests/view/626eb2dc9116b52aa83b73da LTC: LLR: 2.93 (-2.94,2.94) <0.50,3.00> Total: 114400 W: 30561 L: 30111 D: 53728 Ptnml(0-2): 68, 11432, 33785, 11812, 103 https://tests.stockfishchess.org/tests/view/626f730859e9c431e0b10b21 closes https://github.com/official-stockfish/Stockfish/pull/4008 bench: 6174823 --- src/search.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 70b852f3..c12b60e6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1300,7 +1300,18 @@ moves_loop: // When in check, search starts here update_pv(ss->pv, move, (ss+1)->pv); if (PvNode && value < beta) // Update alpha! Always alpha < beta + { alpha = value; + + // Reduce other moves if we have found at least one score improvement + if ( depth > 2 + && depth < 7 + && beta < VALUE_KNOWN_WIN + && alpha > -VALUE_KNOWN_WIN) + depth -= 1; + + assert(depth > 0); + } else { ss->cutoffCnt++; From c079acc26f93acc2eda08c7218c60559854f52f0 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Fri, 13 May 2022 17:26:50 +0200 Subject: [PATCH 027/678] Update NNUE architecture to SFNNv5. Update network to nn-3c0aa92af1da.nnue. Architecture changes: Duplicated activation after the 1024->15 layer with squared crelu (so 15->15*2). As proposed by vondele. Trainer changes: Added bias to L1 factorization, which was previously missing (no measurable improvement but at least neutral in principle) For retraining linearly reduce lambda parameter from 1.0 at epoch 0 to 0.75 at epoch 800. reduce max_skipping_rate from 15 to 10 (compared to vondele's outstanding PR) Note: This network was trained with a ~0.8% error in quantization regarding the newly added activation function. This will be fixed in the released trainer version. Expect a trainer PR tomorrow. Note: The inference implementation cuts a corner to merge results from two activation functions. This could possibly be resolved nicer in the future. AVX2 implementation likely not necessary, but NEON is missing. First training session invocation: python3 train.py \ ../nnue-pytorch-training/data/nodes5000pv2_UHO.binpack \ ../nnue-pytorch-training/data/nodes5000pv2_UHO.binpack \ --gpus "$3," \ --threads 4 \ --num-workers 8 \ --batch-size 16384 \ --progress_bar_refresh_rate 20 \ --random-fen-skipping 3 \ --features=HalfKAv2_hm^ \ --lambda=1.0 \ --max_epochs=400 \ --default_root_dir ../nnue-pytorch-training/experiment_$1/run_$2 Second training session invocation: python3 train.py \ ../nnue-pytorch-training/data/T60T70wIsRightFarseerT60T74T75T76.binpack \ ../nnue-pytorch-training/data/T60T70wIsRightFarseerT60T74T75T76.binpack \ --gpus "$3," \ --threads 4 \ --num-workers 8 \ --batch-size 16384 \ --progress_bar_refresh_rate 20 \ --random-fen-skipping 3 \ --features=HalfKAv2_hm^ \ --start-lambda=1.0 \ --end-lambda=0.75 \ --gamma=0.995 \ --lr=4.375e-4 \ --max_epochs=800 \ --resume-from-model /data/sopel/nnue/nnue-pytorch-training/data/exp367/nn-exp367-run3-epoch399.pt \ --default_root_dir ../nnue-pytorch-training/experiment_$1/run_$2 Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 27288 W: 7445 L: 7178 D: 12665 Ptnml(0-2): 159, 3002, 7054, 3271, 158 https://tests.stockfishchess.org/tests/view/627e8c001919125939623644 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,3.00> Total: 21792 W: 5969 L: 5727 D: 10096 Ptnml(0-2): 25, 2152, 6294, 2406, 19 https://tests.stockfishchess.org/tests/view/627f2a855734b18b2e2ece47 closes https://github.com/official-stockfish/Stockfish/pull/4020 Bench: 6481017 --- src/evaluate.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 120 +++++++++++++++++++++++++++++ src/nnue/nnue_architecture.h | 9 ++- 3 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 src/nnue/layers/sqr_clipped_relu.h diff --git a/src/evaluate.h b/src/evaluate.h index e857b799..f67961a9 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-d0b74ce1e5eb.nnue" + #define EvalFileDefaultName "nn-3c0aa92af1da.nnue" namespace NNUE { diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h new file mode 100644 index 00000000..b603a277 --- /dev/null +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -0,0 +1,120 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer ClippedReLU of NNUE evaluation function + +#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED + +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Layers { + + // Clipped ReLU + template + class SqrClippedReLU { + public: + // Input/output type + using InputType = std::int32_t; + using OutputType = std::uint8_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = InputDimensions; + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, 32); + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream&) { + return true; + } + + // Write network parameters + bool write_parameters(std::ostream&) const { + return true; + } + + // Forward propagation + const OutputType* propagate( + const InputType* input, OutputType* output) const { + + #if defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / 16; + + #ifdef USE_SSE41 + const __m128i Zero = _mm_setzero_si128(); + #else + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + static_assert(WeightScaleBits == 6); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) { + __m128i words0 = _mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 0]), + _mm_load_si128(&in[i * 4 + 1])); + __m128i words1 = _mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 2]), + _mm_load_si128(&in[i * 4 + 3])); + + // Not sure if + words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); + words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); + + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + + _mm_store_si128(&out[i], + + #ifdef USE_SSE41 + _mm_max_epi8(packedbytes, Zero) + #else + _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + #endif + + ); + } + constexpr IndexType Start = NumChunks * 16; + + #else + constexpr IndexType Start = 0; + #endif + + for (IndexType i = Start; i < InputDimensions; ++i) { + output[i] = static_cast( + // realy should be /127 but we need to make it fast + // needs to be accounted for in the trainer + std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128))); + } + + return output; + } + }; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 4f9596ae..cac83730 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -29,6 +29,7 @@ #include "layers/affine_transform.h" #include "layers/clipped_relu.h" +#include "layers/sqr_clipped_relu.h" #include "../misc.h" @@ -48,8 +49,9 @@ struct Network static constexpr int FC_1_OUTPUTS = 32; Layers::AffineTransform fc_0; + Layers::SqrClippedReLU ac_sqr_0; Layers::ClippedReLU ac_0; - Layers::AffineTransform fc_1; + Layers::AffineTransform fc_1; Layers::ClippedReLU ac_1; Layers::AffineTransform fc_2; @@ -93,6 +95,7 @@ struct Network struct alignas(CacheLineSize) Buffer { alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; + alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; @@ -114,8 +117,10 @@ struct Network #endif fc_0.propagate(transformedFeatures, buffer.fc_0_out); + ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); - fc_1.propagate(buffer.ac_0_out, buffer.fc_1_out); + std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType)); + fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); From 5372f81cc81d5be3040db6f2dbfff108c460baf9 Mon Sep 17 00:00:00 2001 From: disservin <45608332+Disservin@users.noreply.github.com> Date: Sat, 14 May 2022 12:10:13 +0200 Subject: [PATCH 028/678] SE depth scaling using the previous depth This patch makes the SE depth condition more robust and allows it to scale with completed depth from a previous search. At long TC this patch is almost equivalent to https://github.com/official-stockfish/Stockfish/pull/4016 which had VLTC: https://tests.stockfishchess.org/tests/view/626abd7e8707aa698c0093a8 Elo: 2.35 +-1.5 (95%) LOS: 99.9% Total: 40000 W: 10991 L: 10720 D: 18289 Ptnml(0-2): 8, 3534, 12648, 3799, 11 nElo: 5.47 +-3.4 (95%) PairsRatio: 1.08 VLTC multicore: https://tests.stockfishchess.org/tests/view/6272a6afc8f14123163c1997 LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 86808 W: 24165 L: 23814 D: 38829 Ptnml(0-2): 11, 7253, 28524, 7606, 10 however, it is now also gaining at LTC: LTC: https://tests.stockfishchess.org/tests/view/627e7cb523c0c72a05b651a9 LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 27064 W: 7285 L: 7046 D: 12733 Ptnml(0-2): 8, 2446, 8390, 2675, 13 and should have nearly no influence at STC as depth 27 is rarely reached. It was noticed that initializing the threshold with MAX_PLY, had an adverse effect, possibly because the first move is sensitive to this. closes https://github.com/official-stockfish/Stockfish/pull/4021 closes https://github.com/official-stockfish/Stockfish/pull/4016 Bench: 6481017 --- AUTHORS | 1 + src/search.cpp | 5 ++++- src/thread.cpp | 3 ++- src/thread.h | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index edf189d8..b435ff8f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -125,6 +125,7 @@ marotear Matt Ginsberg (mattginsberg) Matthew Lai (matthewlai) Matthew Sullivan (Matt14916) +Max A. (Disservin) Maxim Molchanov (Maxim) Michael An (man) Michael Byrne (MichaelB7) diff --git a/src/search.cpp b/src/search.cpp index c12b60e6..4a41a8b0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -238,6 +238,9 @@ void MainThread::search() { bestPreviousScore = bestThread->rootMoves[0].score; bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; + for (Thread* th : Threads) + th->previousDepth = bestThread->completedDepth; + // Send again PV info if we have a new best thread if (bestThread != this) sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; @@ -1061,7 +1064,7 @@ moves_loop: // When in check, search starts here // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. if ( !rootNode - && depth >= 4 + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->previousDepth > 27) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ diff --git a/src/thread.cpp b/src/thread.cpp index 30177a39..08a78db5 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -60,7 +60,8 @@ void Thread::clear() { counterMoves.fill(MOVE_NONE); mainHistory.fill(0); captureHistory.fill(0); - + previousDepth = 0; + for (bool inCheck : { false, true }) for (StatsType c : { NoCaptures, Captures }) { diff --git a/src/thread.h b/src/thread.h index 8027855a..9e9cd488 100644 --- a/src/thread.h +++ b/src/thread.h @@ -69,7 +69,7 @@ public: Position rootPos; StateInfo rootState; Search::RootMoves rootMoves; - Depth rootDepth, completedDepth, depth; + Depth rootDepth, completedDepth, depth, previousDepth; Value rootDelta; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; From 22b7909809c731aea691184dd7c1a2b02c5946af Mon Sep 17 00:00:00 2001 From: xoto10 Date: Sun, 15 May 2022 13:14:28 +0100 Subject: [PATCH 029/678] Tune scale and optimism. Tune scale and optimism in effort to make stockfish play more aggressively. STC @ 10+0.1 th 1: LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 27896 W: 7506 L: 7248 D: 13142 Ptnml(0-2): 103, 3047, 7388, 3309, 101 https://tests.stockfishchess.org/tests/live_elo/627fd0cfab44257388ab1f13 LTC @ 60+0.6 th 1: LLR: 2.93 (-2.94,2.94) <0.50,3.00> Total: 65576 W: 17512 L: 17178 D: 30886 Ptnml(0-2): 37, 6397, 19587, 6729, 38 https://tests.stockfishchess.org/tests/live_elo/627ff666ab44257388ab256d closes https://github.com/official-stockfish/Stockfish/pull/4025 Bench 6407734 --- src/evaluate.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 8bb42ce1..718c7bc0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -192,6 +192,7 @@ using namespace Trace; namespace { + // Threshold for lazy and space evaluation constexpr Value LazyThreshold1 = Value(3631); constexpr Value LazyThreshold2 = Value(2084); @@ -1099,14 +1100,14 @@ Value Eval::evaluate(const Position& pos) { if (useNNUE && !useClassical) { Value nnue = NNUE::evaluate(pos, true); // NNUE - int scale = 1036 + 22 * pos.non_pawn_material() / 1024; + int scale = 1014 + 21 * pos.non_pawn_material() / 1024; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; Value psq = (stm == WHITE ? 1 : -1) * eg_value(pos.psq_score()); int complexity = 35 * abs(nnue - psq) / 256; - optimism = optimism * (44 + complexity) / 31; - v = (nnue + optimism) * scale / 1024 - optimism; + optimism = optimism * (32 + complexity) / 32; + v = (nnue * scale + optimism * (scale - 846)) / 1024; if (pos.is_chess960()) v += fix_FRC(pos); From cc7bcd5303a645223a6cd853817d3172754243aa Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Sat, 21 May 2022 04:20:09 +0200 Subject: [PATCH 030/678] Simplify a condition Principal variation depth late move reduction extension simplification. stc: https://tests.stockfishchess.org/tests/view/6285a1d19d18a78568e7fa24 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 428536 W: 113433 L: 113851 D: 201252 Ptnml(0-2): 1671, 48606, 114090, 48272, 1629 ltc: https://tests.stockfishchess.org/tests/view/62871d20375cdc5de8cf5db3 LLR: 2.95 (-2.94,2.94) <-2.25,0.25> Total: 56792 W: 15123 L: 15011 D: 26658 Ptnml(0-2): 42, 5681, 16825, 5819, 29 closes https://github.com/official-stockfish/Stockfish/pull/4028 bench: 6501437 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4a41a8b0..d09ced9a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1197,7 +1197,7 @@ moves_loop: // When in check, search starts here // deeper than the first move (this may lead to hidden double extensions). int deeper = r >= -1 ? 0 : moveCount <= 4 ? 2 - : PvNode && depth > 4 ? 1 + : PvNode ? 1 : cutNode && moveCount <= 8 ? 1 : 0; From f7d1491b3df28bf10faac81e340cb6a22fc5b57b Mon Sep 17 00:00:00 2001 From: Giacomo Lorenzetti Date: Fri, 1 Apr 2022 18:33:25 +0200 Subject: [PATCH 031/678] Assorted small cleanups closes https://github.com/official-stockfish/Stockfish/pull/3973 No functional change --- README.md | 3 +- src/Makefile | 48 ++++++++++++++--------------- src/evaluate.cpp | 10 +++--- src/nnue/nnue_feature_transformer.h | 18 +++++------ src/search.cpp | 6 +--- src/search.h | 1 - src/syzygy/tbprobe.cpp | 2 +- src/types.h | 2 +- 8 files changed, 42 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 6e6e762e..f84b79d1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ## Overview [![Build Status](https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml/badge.svg)](https://github.com/official-stockfish/Stockfish/actions) -[![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master) [Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine derived from Glaurung 2.1. Stockfish is not a complete chess program and requires a @@ -21,7 +20,7 @@ avx2, neon, or similar). This distribution of Stockfish consists of the following files: - * [Readme.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md), + * [README.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md), the file you are currently reading. * [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), diff --git a/src/Makefile b/src/Makefile index eff8baca..cc9b4201 100644 --- a/src/Makefile +++ b/src/Makefile @@ -542,17 +542,17 @@ ifeq ($(optimize),yes) endif endif - ifeq ($(KERNEL),Darwin) - ifeq ($(comp),$(filter $(comp),clang icc)) - CXXFLAGS += -mdynamic-no-pic - endif + ifeq ($(KERNEL),Darwin) + ifeq ($(comp),$(filter $(comp),clang icc)) + CXXFLAGS += -mdynamic-no-pic + endif - ifeq ($(comp),gcc) - ifneq ($(arch),arm64) - CXXFLAGS += -mdynamic-no-pic - endif - endif - endif + ifeq ($(comp),gcc) + ifneq ($(arch),arm64) + CXXFLAGS += -mdynamic-no-pic + endif + endif + endif ifeq ($(comp),clang) CXXFLAGS += -fexperimental-new-pass-manager @@ -824,22 +824,22 @@ net: $(eval nnuedownloadurl := https://tests.stockfishchess.org/api/nn/$(nnuenet)) $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) @if test -f "$(nnuenet)"; then \ - echo "Already available."; \ - else \ - if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ - else \ - echo "Downloading $(nnuedownloadurl)"; $(curl_or_wget) $(nnuedownloadurl) > $(nnuenet);\ - fi; \ - fi; + echo "Already available."; \ + else \ + if [ "x$(curl_or_wget)" = "x" ]; then \ + echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ + else \ + echo "Downloading $(nnuedownloadurl)"; $(curl_or_wget) $(nnuedownloadurl) > $(nnuenet);\ + fi; \ + fi; $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) @if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Failed download or $(nnuenet) corrupted, please delete!"; exit 1; \ - fi \ - else \ - echo "shasum / sha256sum not found, skipping net validation"; \ - fi + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Failed download or $(nnuenet) corrupted, please delete!"; exit 1; \ + fi \ + else \ + echo "shasum / sha256sum not found, skipping net validation"; \ + fi # clean binaries and objects objclean: diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 718c7bc0..9061a384 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -192,7 +192,6 @@ using namespace Trace; namespace { - // Threshold for lazy and space evaluation constexpr Value LazyThreshold1 = Value(3631); constexpr Value LazyThreshold2 = Value(2084); @@ -1084,13 +1083,14 @@ make_v: Value Eval::evaluate(const Position& pos) { Value v; - bool useClassical = false; + // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, + // but we switch to NNUE during long shuffling or with high material on the board. + bool useClassical = (pos.this_thread()->depth > 9 || pos.count() > 7) && + abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count()); // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, // but we switch to NNUE during long shuffling or with high material on the board. - if ( !useNNUE - || ((pos.this_thread()->depth > 9 || pos.count() > 7) && - abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count()))) + if (!useNNUE || useClassical) { v = Evaluation(pos).value(); // classical useClassical = abs(v) >= 297; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index c969ac6c..34d7292c 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -120,12 +120,12 @@ namespace Stockfish::Eval::NNUE { #define vec_zero() _mm_setzero_si64() #define vec_set_16(a) _mm_set1_pi16(a) inline vec_t vec_max_16(vec_t a,vec_t b){ - vec_t comparison = _mm_cmpgt_pi16(a,b); - return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b)); + vec_t comparison = _mm_cmpgt_pi16(a,b); + return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b)); } inline vec_t vec_min_16(vec_t a,vec_t b){ - vec_t comparison = _mm_cmpgt_pi16(a,b); - return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a)); + vec_t comparison = _mm_cmpgt_pi16(a,b); + return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a)); } #define vec_msb_pack_16(a,b) _mm_packs_pi16(_mm_srli_pi16(a,7),_mm_srli_pi16(b,7)) #define vec_load_psqt(a) (*(a)) @@ -150,10 +150,10 @@ namespace Stockfish::Eval::NNUE { #define vec_max_16(a,b) vmaxq_s16(a,b) #define vec_min_16(a,b) vminq_s16(a,b) inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - const int8x8_t shifta = vshrn_n_s16(a, 7); - const int8x8_t shiftb = vshrn_n_s16(b, 7); - const int8x16_t compacted = vcombine_s8(shifta,shiftb); - return *reinterpret_cast (&compacted); + const int8x8_t shifta = vshrn_n_s16(a, 7); + const int8x8_t shiftb = vshrn_n_s16(b, 7); + const int8x16_t compacted = vcombine_s8(shifta,shiftb); + return *reinterpret_cast (&compacted); } #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a,b) *(a)=(b) @@ -290,7 +290,7 @@ namespace Stockfish::Eval::NNUE { #if defined(VECTOR) - constexpr IndexType OutputChunkSize = MaxChunkSize; + constexpr IndexType OutputChunkSize = MaxChunkSize; static_assert((HalfDimensions / 2) % OutputChunkSize == 0); constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; diff --git a/src/search.cpp b/src/search.cpp index d09ced9a..ff7b996b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -608,7 +608,6 @@ namespace { (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; (ss+2)->cutoffCnt = 0; ss->doubleExtensions = (ss-1)->doubleExtensions; - ss->depth = depth; Square prevSq = to_sq((ss-1)->currentMove); // Initialize statScore to zero for the grandchildren of the current position. @@ -869,7 +868,6 @@ namespace { MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, depth - 3, &captureHistory); bool ttPv = ss->ttPv; - bool captureOrPromotion; ss->ttPv = false; while ((move = mp.next_move()) != MOVE_NONE) @@ -877,11 +875,9 @@ namespace { { assert(pos.capture(move) || promotion_type(move) == QUEEN); - captureOrPromotion = true; - ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [captureOrPromotion] + [true] [pos.moved_piece(move)] [to_sq(move)]; diff --git a/src/search.h b/src/search.h index 8bb51832..4ad5784f 100644 --- a/src/search.h +++ b/src/search.h @@ -47,7 +47,6 @@ struct Stack { Move excludedMove; Move killers[2]; Value staticEval; - Depth depth; int statScore; int moveCount; bool inCheck; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index a1315244..43af89f0 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1290,7 +1290,7 @@ void Tablebases::init(const std::string& paths) { for (auto s : diagonal) MapA1D1D4[s] = code++; - // MapKK[] encodes all the 461 possible legal positions of two kings where + // MapKK[] encodes all the 462 possible legal positions of two kings where // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 // diagonal, the other one shall not to be above the a1-h8 diagonal. std::vector> bothOnDiagonal; diff --git a/src/types.h b/src/types.h index cf42bc9f..c2087c6c 100644 --- a/src/types.h +++ b/src/types.h @@ -450,7 +450,7 @@ constexpr Square to_sq(Move m) { } constexpr int from_to(Move m) { - return m & 0xFFF; + return m & 0xFFF; } constexpr MoveType type_of(Move m) { From 48df0754bc87f099c4b29ff1b2f2629ddacd1c95 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 20 May 2022 07:42:33 +0200 Subject: [PATCH 032/678] Add command line flags to link to information This patch provides command line flags `--help` and `--license` as well as the corresponding `help` and `license` commands. ``` $ ./stockfish --help Stockfish 200522 by the Stockfish developers (see AUTHORS file) Stockfish is a powerful chess engine and free software licensed under the GNU GPLv3. Stockfish is normally used with a separate graphical user interface (GUI). Stockfish implements the universal chess interface (UCI) to exchange information. For further information see https://github.com/official-stockfish/Stockfish#readme or the corresponding README.md and Copying.txt files distributed with this program. ``` The idea is to provide a minimal help that links to the README.md file, not replicating information that is already available elsewhere. We use this opportunity to explicitly report the license as well. closes https://github.com/official-stockfish/Stockfish/pull/4027 No functional change. --- src/uci.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uci.cpp b/src/uci.cpp index 7b30cc04..c28cf6d1 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -285,8 +285,14 @@ void UCI::loop(int argc, char* argv[]) { filename = f; Eval::NNUE::save_eval(filename); } + else if (token == "--help" || token == "help" || token == "--license" || token == "license") + sync_cout << "\nStockfish is a powerful chess engine and free software licensed under the GNU GPLv3." + "\nStockfish is normally used with a separate graphical user interface (GUI)." + "\nStockfish implements the universal chess interface (UCI) to exchange information." + "\nFor further information see https://github.com/official-stockfish/Stockfish#readme" + "\nor the corresponding README.md and Copying.txt files distributed with this program.\n" << sync_endl; else if (!token.empty() && token[0] != '#') - sync_cout << "Unknown command: " << cmd << sync_endl; + sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl; } while (token != "quit" && argc == 1); // Command line args are one-shot } From 1a168201bd8424da4cea384ee4c03973b2ccf0ca Mon Sep 17 00:00:00 2001 From: Giacomo Lorenzetti Date: Sun, 22 May 2022 12:47:30 +0200 Subject: [PATCH 033/678] Small speedup in futility_move_count The speedup is around 0.25% using gcc 11.3.1 (bmi2, nnue bench, depth 16 and 23) while it is neutral using clang (same conditions). According to `perf` that integer division was one of the most time-consuming instructions in search (gcc disassembly). Passed STC: https://tests.stockfishchess.org/tests/view/628a17fe24a074e5cd59b3aa LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 22232 W: 5992 L: 5751 D: 10489 Ptnml(0-2): 88, 2235, 6218, 2498, 77 yellow LTC: https://tests.stockfishchess.org/tests/view/628a35d7ccae0450e35106f7 LLR: -2.95 (-2.94,2.94) <0.50,3.00> Total: 320168 W: 85853 L: 85326 D: 148989 Ptnml(0-2): 185, 29698, 99821, 30165, 215 This patch also suggests that UHO STC is sensible to small speedups (< 0.50%). closes https://github.com/official-stockfish/Stockfish/pull/4032 No functional change --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ff7b996b..0a4b1a18 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -75,7 +75,8 @@ namespace { } constexpr int futility_move_count(bool improving, Depth depth) { - return (3 + depth * depth) / (2 - improving); + return improving ? (3 + depth * depth) + : (3 + depth * depth) / 2; } // History and stats update bonus, based on depth From 6ede1bed89fd9d1c25cc6349722898f084bf5e15 Mon Sep 17 00:00:00 2001 From: proukornew Date: Fri, 17 Dec 2021 00:41:29 +0300 Subject: [PATCH 034/678] Improve handling of variables set in the make environment removes duplication on the commandline for example in a profile-build closes https://github.com/official-stockfish/Stockfish/pull/3859 No functional change --- src/Makefile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Makefile b/src/Makefile index cc9b4201..ff2452d6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -345,9 +345,15 @@ endif ### ========================================================================== ### 3.1 Selecting compiler (default = gcc) -CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) -DEPENDFLAGS += -std=c++17 -LDFLAGS += $(EXTRALDFLAGS) +ifeq ($(MAKELEVEL),0) + export ENV_CXXFLAGS := $(CXXFLAGS) + export ENV_DEPENDFLAGS := $(DEPENDFLAGS) + export ENV_LDFLAGS := $(LDFLAGS) +endif + +CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) +DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17 +LDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS) ifeq ($(COMP),) COMP=gcc From 4c7de9e8abd8d5dc71d0c85dddd75a7b244600d7 Mon Sep 17 00:00:00 2001 From: xoto10 Date: Thu, 19 May 2022 08:51:40 +0100 Subject: [PATCH 035/678] Adjust scale param higher xoto10's scaleopt tune resulted in a yellow LTC, but the main parameter shift looked almost exactly like the tune rate reduction schedule, so further increases of that param were tried. Joint work xoto10 and dubslow. passed LTC: https://tests.stockfishchess.org/tests/view/628c709372775f382300f03e LLR: 2.93 (-2.94,2.94) <0.50,3.00> Total: 70112 W: 18932 L: 18584 D: 32596 Ptnml(0-2): 66, 6904, 20757, 7274, 55 failed STC: https://tests.stockfishchess.org/tests/view/6290e4441e7cd5f29966bdc8 LLR: -2.96 (-2.94,2.94) <0.00,2.50> Total: 59976 W: 15919 L: 16018 D: 28039 Ptnml(0-2): 250, 6791, 15974, 6754, 219 similar LTC's were yellow first yellow LTC: https://tests.stockfishchess.org/tests/view/6288a33f817227d3e5c5b05d double exaggerate yellow: https://tests.stockfishchess.org/tests/live_elo/628e140372775f38230129a6 triple exaggerate yellow: https://tests.stockfishchess.org/tests/live_elo/628e2caf72775f3823012d45 closes https://github.com/official-stockfish/Stockfish/pull/4036 bench 6410652 --- AUTHORS | 1 + src/evaluate.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index b435ff8f..715f83b1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -55,6 +55,7 @@ DiscanX Dominik Schlösser (domschl) double-beep Douglas Matos Gomes (dsmsgms) +Dubslow Eduardo Cáceres (eduherminio) Eelco de Groot (KingDefender) Elvin Liu (solarlight2) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9061a384..9ed9e8e3 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1100,14 +1100,14 @@ Value Eval::evaluate(const Position& pos) { if (useNNUE && !useClassical) { Value nnue = NNUE::evaluate(pos, true); // NNUE - int scale = 1014 + 21 * pos.non_pawn_material() / 1024; + int scale = 1080 + 110 * pos.non_pawn_material() / 5120; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; Value psq = (stm == WHITE ? 1 : -1) * eg_value(pos.psq_score()); - int complexity = 35 * abs(nnue - psq) / 256; + int complexity = (278 * abs(nnue - psq)) / 256; - optimism = optimism * (32 + complexity) / 32; - v = (nnue * scale + optimism * (scale - 846)) / 1024; + optimism = optimism * (251 + complexity) / 256; + v = (nnue * scale + optimism * (scale - 852)) / 1024; if (pos.is_chess960()) v += fix_FRC(pos); From 8fadbcf1b2c3bc281b2d1efd7992fc9f4c3a38be Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 30 May 2022 13:30:59 +0300 Subject: [PATCH 036/678] Add info about elo gained from some heuristics Add info about qsearch and impact of main and continuation histories. Based on these tests: https://tests.stockfishchess.org/tests/view/62946ffcb0d5a7d1b780fc7e https://tests.stockfishchess.org/tests/view/628facb71e7cd5f299669534 https://tests.stockfishchess.org/tests/view/628eade11e7cd5f299666f2e closes https://github.com/official-stockfish/Stockfish/pull/4041 No functional change. --- src/movepick.h | 2 ++ src/search.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/src/movepick.h b/src/movepick.h index 9a3c279b..6b3c08c7 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -86,6 +86,7 @@ enum StatsType { NoCaptures, Captures }; /// unsuccessful during the current search, and is used for reduction and move /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards +/// (~11 elo) typedef Stats ButterflyHistory; /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous @@ -101,6 +102,7 @@ typedef Stats PieceToHistory; /// ContinuationHistory is the combined history of a given pair of moves, usually /// the current one given a previous one. The nested history table is based on /// PieceToHistory instead of ButterflyBoards. +/// (~63 elo) typedef Stats ContinuationHistory; diff --git a/src/search.cpp b/src/search.cpp index 0a4b1a18..407cb701 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1396,6 +1396,7 @@ moves_loop: // When in check, search starts here // qsearch() is the quiescence search function, which is called by the main search // function with zero depth, or recursively with further decreasing depth per call. + // (~155 elo) template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { From 653bd0817ceea2980474d76198fe527f8b84bf04 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Tue, 31 May 2022 21:52:04 +0200 Subject: [PATCH 037/678] cutnode and movecount lmr extension simplification Passed STC https://tests.stockfishchess.org/tests/view/6294133cb0d5a7d1b780ece3 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 41072 W: 11052 L: 10908 D: 19112 Ptnml(0-2): 153, 4324, 11461, 4422, 176 Passed LTC ltc: https://tests.stockfishchess.org/tests/view/62947ae6b0d5a7d1b780fe86 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 102736 W: 27509 L: 27459 D: 47768 Ptnml(0-2): 98, 9734, 31669, 9754, 113 closes https://github.com/official-stockfish/Stockfish/pull/4045 Bench: 6410652 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 407cb701..8ecdbc30 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1194,8 +1194,7 @@ moves_loop: // When in check, search starts here // deeper than the first move (this may lead to hidden double extensions). int deeper = r >= -1 ? 0 : moveCount <= 4 ? 2 - : PvNode ? 1 - : cutNode && moveCount <= 8 ? 1 + : PvNode || cutNode ? 1 : 0; Depth d = std::clamp(newDepth - r, 1, newDepth + deeper); From 7f1333ccf89c715933ead72e31233510c6f4d146 Mon Sep 17 00:00:00 2001 From: xoto10 Date: Wed, 1 Jun 2022 01:20:27 +0100 Subject: [PATCH 038/678] Blend nnue complexity with classical. Following mstembera's test of the complexity value derived from nnue values, this change blends that idea with the old complexity calculation. STC 10+0.1: LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 42320 W: 11436 L: 11148 D: 19736 Ptnml(0-2): 209, 4585, 11263, 4915, 188 https://tests.stockfishchess.org/tests/live_elo/6295c9239c8c2fcb2bad7fd9 LTC 60+0.6: LLR: 2.98 (-2.94,2.94) <0.50,3.00> Total: 34600 W: 9393 L: 9125 D: 16082 Ptnml(0-2): 32, 3323, 10319, 3597, 29 https://tests.stockfishchess.org/tests/view/6295fd5d9c8c2fcb2bad88cf closes https://github.com/official-stockfish/Stockfish/pull/4046 Bench 6078140 --- src/evaluate.cpp | 11 ++++++----- src/evaluate.h | 2 +- src/nnue/evaluate_nnue.cpp | 7 +++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9ed9e8e3..415c18c5 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1099,15 +1099,16 @@ Value Eval::evaluate(const Position& pos) { // If result of a classical evaluation is much lower than threshold fall back to NNUE if (useNNUE && !useClassical) { - Value nnue = NNUE::evaluate(pos, true); // NNUE - int scale = 1080 + 110 * pos.non_pawn_material() / 5120; + int complexity; + int scale = 1048 + 109 * pos.non_pawn_material() / 5120; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; Value psq = (stm == WHITE ? 1 : -1) * eg_value(pos.psq_score()); - int complexity = (278 * abs(nnue - psq)) / 256; + Value nnue = NNUE::evaluate(pos, true, &complexity); // NNUE - optimism = optimism * (251 + complexity) / 256; - v = (nnue * scale + optimism * (scale - 852)) / 1024; + complexity = (137 * complexity + 137 * abs(nnue - psq)) / 256; + optimism = optimism * (255 + complexity) / 256; + v = (nnue * scale + optimism * (scale - 848)) / 1024; if (pos.is_chess960()) v += fix_FRC(pos); diff --git a/src/evaluate.h b/src/evaluate.h index f67961a9..e79eaea3 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -44,7 +44,7 @@ namespace Eval { namespace NNUE { std::string trace(Position& pos); - Value evaluate(const Position& pos, bool adjusted = false); + Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); void init(); void verify(); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 9ee599f4..eb6ad71f 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -137,7 +137,7 @@ namespace Stockfish::Eval::NNUE { } // Evaluation function. Perform differential calculation. - Value evaluate(const Position& pos, bool adjusted) { + Value evaluate(const Position& pos, bool adjusted, int* complexity) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -161,9 +161,12 @@ namespace Stockfish::Eval::NNUE { const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); const auto positional = network[bucket]->propagate(transformedFeatures); + if (complexity) + *complexity = abs(psqt - positional) / OutputScale; + // Give more value to positional evaluation when adjusted flag is set if (adjusted) - return static_cast(((128 - delta) * psqt + (128 + delta) * positional) / 128 / OutputScale); + return static_cast(((128 - delta) * psqt + (128 + delta) * positional) / (128 * OutputScale)); else return static_cast((psqt + positional) / OutputScale); } From 90cf8e7d2bde9e480aac4b119ce130e09dd2be39 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sun, 29 May 2022 19:52:11 -0500 Subject: [PATCH 039/678] Remove LMR condition for complex pos Inspired by Kia's similar test: https://tests.stockfishchess.org/tests/view/6292898c1e7cd5f29966fbe0 Passed STC: https://tests.stockfishchess.org/tests/view/62941588b0d5a7d1b780ed4b LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 266872 W: 70850 L: 71033 D: 124989 Ptnml(0-2): 1180, 30114, 70941, 30111, 1090 Passed LTC: https://tests.stockfishchess.org/tests/view/62964a754628d33daa24f062 LLR: 2.95 (-2.94,2.94) <-2.25,0.25> Total: 70160 W: 18756 L: 18662 D: 32742 Ptnml(0-2): 42, 6976, 20950, 7070, 42 closes https://github.com/official-stockfish/Stockfish/pull/4047 Bench 6237567 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8ecdbc30..ea1e63fe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1167,11 +1167,6 @@ moves_loop: // When in check, search starts here if (ttCapture) r++; - // Decrease reduction at PvNodes if bestvalue - // is vastly different from static evaluation - if (PvNode && !ss->inCheck && abs(ss->staticEval - bestValue) > 250) - r--; - // Decrease reduction for PvNodes based on depth if (PvNode) r -= 1 + 15 / ( 3 + depth ); From 809849fa275512225d19e280809f227990d97eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bo=C5=A1tjan=20Mejak?= Date: Tue, 31 May 2022 20:25:57 +0200 Subject: [PATCH 040/678] Wording of help output and comments. Improved the output text that is diplayed when executing the 'help' command. Also, some comments were fixed along the way. closes https://github.com/official-stockfish/Stockfish/pull/4048 closes https://github.com/official-stockfish/Stockfish/pull/4044 No functional change --- AUTHORS | 1 + src/uci.cpp | 126 ++++++++++++++++++++++++++-------------------------- src/uci.h | 6 +-- 3 files changed, 67 insertions(+), 66 deletions(-) diff --git a/AUTHORS b/AUTHORS index 715f83b1..fc885acb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -35,6 +35,7 @@ Ben Chaney (Chaneybenjamini) Ben Koshy (BKSpurgeon) Bill Henry (VoyagerOne) Bojun Guo (noobpwnftw, Nooby) +Boštjan Mejak (PedanticHacker) braich Brian Sheppard (SapphireBrand, briansheppard-toast) Bruno de Melo Costa (BM123499) diff --git a/src/uci.cpp b/src/uci.cpp index c28cf6d1..c0bacfaf 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -40,14 +40,14 @@ extern vector setup_bench(const Position&, istream&); namespace { - // FEN string of the initial position, normal chess + // FEN string for the initial position in standard chess const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - // position() is called when engine receives the "position" UCI command. - // The function sets up the position described in the given FEN string ("fen") - // or the starting position ("startpos") and then makes the moves given in the - // following move list ("moves"). + // position() is called when the engine receives the "position" UCI command. + // It sets up the position that is described in the given FEN string ("fen") or + // the initial position ("startpos") and then makes the moves given in the following + // move list ("moves"). void position(Position& pos, istringstream& is, StateListPtr& states) { @@ -59,7 +59,7 @@ namespace { if (token == "startpos") { fen = StartFEN; - is >> token; // Consume "moves" token if any + is >> token; // Consume the "moves" token, if any } else if (token == "fen") while (is >> token && token != "moves") @@ -67,10 +67,10 @@ namespace { else return; - states = StateListPtr(new std::deque(1)); // Drop old and create a new one + states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); - // Parse move list (if any) + // Parse the move list, if any while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) { states->emplace_back(); @@ -78,8 +78,8 @@ namespace { } } - // trace_eval() prints the evaluation for the current position, consistent with the UCI - // options set so far. + // trace_eval() prints the evaluation of the current position, consistent with + // the UCI options set so far. void trace_eval(Position& pos) { @@ -93,20 +93,20 @@ namespace { } - // setoption() is called when engine receives the "setoption" UCI command. The - // function updates the UCI option ("name") to the given value ("value"). + // setoption() is called when the engine receives the "setoption" UCI command. + // The function updates the UCI option ("name") to the given value ("value"). void setoption(istringstream& is) { string token, name, value; - is >> token; // Consume "name" token + is >> token; // Consume the "name" token - // Read option name (can contain spaces) + // Read the option name (can contain spaces) while (is >> token && token != "value") name += (name.empty() ? "" : " ") + token; - // Read option value (can contain spaces) + // Read the option value (can contain spaces) while (is >> token) value += (value.empty() ? "" : " ") + token; @@ -117,9 +117,9 @@ namespace { } - // go() is called when engine receives the "go" UCI command. The function sets - // the thinking time and other parameters from the input string, then starts - // the search. + // go() is called when the engine receives the "go" UCI command. The function + // sets the thinking time and other parameters from the input string, then starts + // with a search. void go(Position& pos, istringstream& is, StateListPtr& states) { @@ -127,7 +127,7 @@ namespace { string token; bool ponderMode = false; - limits.startTime = now(); // As early as possible! + limits.startTime = now(); // The search starts as early as possible while (is >> token) if (token == "searchmoves") // Needs to be the last command on the line @@ -151,9 +151,9 @@ namespace { } - // bench() is called when engine receives the "bench" command. Firstly - // a list of UCI commands is setup according to bench parameters, then - // it is run one by one printing a summary at the end. + // bench() is called when the engine receives the "bench" command. + // Firstly, a list of UCI commands is set up according to the bench + // parameters, then it is run one by one, printing a summary at the end. void bench(Position& pos, istream& args, StateListPtr& states) { @@ -184,12 +184,12 @@ namespace { } else if (token == "setoption") setoption(is); else if (token == "position") position(pos, is, states); - else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while + else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take a while } elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' - dbg_print(); // Just before exiting + dbg_print(); cerr << "\n===========================" << "\nTotal time (ms) : " << elapsed @@ -197,36 +197,36 @@ namespace { << "\nNodes/second : " << 1000 * nodes / elapsed << endl; } - // The win rate model returns the probability (per mille) of winning given an eval - // and a game-ply. The model fits rather accurately the LTC fishtest statistics. + // The win rate model returns the probability of winning (in per mille units) given an + // eval and a game ply. It fits the LTC fishtest statistics rather accurately. int win_rate_model(Value v, int ply) { - // The model captures only up to 240 plies, so limit input (and rescale) + // The model only captures up to 240 plies, so limit the input and then rescale double m = std::min(240, ply) / 64.0; - // Coefficients of a 3rd order polynomial fit based on fishtest data - // for two parameters needed to transform eval to the argument of a - // logistic function. + // The coefficients of a third-order polynomial fit is based on the fishtest data + // for two parameters that need to transform eval to the argument of a logistic + // function. double as[] = {-1.17202460e-01, 5.94729104e-01, 1.12065546e+01, 1.22606222e+02}; double bs[] = {-1.79066759, 11.30759193, -17.43677612, 36.47147479}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - // Transform eval to centipawns with limited range + // Transform the eval to centipawns with limited range double x = std::clamp(double(100 * v) / PawnValueEg, -2000.0, 2000.0); - // Return win rate in per mille (rounded to nearest) + // Return the win rate in per mille units rounded to the nearest value return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); } } // namespace -/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate -/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the -/// GUI dies unexpectedly. When called with some command line arguments, e.g. to -/// run 'bench', once the command is executed the function returns immediately. -/// In addition to the UCI ones, also some additional debug commands are supported. +/// UCI::loop() waits for a command from the stdin, parses it and then calls the appropriate +/// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a +/// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, +/// like running 'bench', the function returns immediately after the command is executed. +/// In addition to the UCI ones, some additional debug commands are also supported. void UCI::loop(int argc, char* argv[]) { @@ -240,24 +240,24 @@ void UCI::loop(int argc, char* argv[]) { cmd += std::string(argv[i]) + " "; do { - if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF + if (argc == 1 && !getline(cin, cmd)) // Wait for an input or an end-of-file (EOF) indication cmd = "quit"; istringstream is(cmd); - token.clear(); // Avoid a stale if getline() returns empty or blank line + token.clear(); // Avoid a stale if getline() returns nothing or a blank line is >> skipws >> token; if ( token == "quit" || token == "stop") Threads.stop = true; - // The GUI sends 'ponderhit' to tell us the user has played the expected move. - // So 'ponderhit' will be sent if we were told to ponder on the same move the - // user has played. We should continue searching but switch from pondering to - // normal search. + // The GUI sends 'ponderhit' to tell that the user has played the expected move. + // So, 'ponderhit' is sent if pondering was done on the same move that the user + // has played. The search should continue, but should also switch from pondering + // to the normal search. else if (token == "ponderhit") - Threads.main()->ponder = false; // Switch to normal search + Threads.main()->ponder = false; // Switch to the normal search else if (token == "uci") sync_cout << "id name " << engine_info(true) @@ -270,8 +270,8 @@ void UCI::loop(int argc, char* argv[]) { else if (token == "ucinewgame") Search::clear(); else if (token == "isready") sync_cout << "readyok" << sync_endl; - // Additional custom non-UCI commands, mainly for debugging. - // Do not use these commands during a search! + // Add custom non-UCI commands, mainly for debugging purposes. + // These commands must not be used during a search! else if (token == "flip") pos.flip(); else if (token == "bench") bench(pos, is, states); else if (token == "d") sync_cout << pos << sync_endl; @@ -286,24 +286,24 @@ void UCI::loop(int argc, char* argv[]) { Eval::NNUE::save_eval(filename); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") - sync_cout << "\nStockfish is a powerful chess engine and free software licensed under the GNU GPLv3." - "\nStockfish is normally used with a separate graphical user interface (GUI)." - "\nStockfish implements the universal chess interface (UCI) to exchange information." - "\nFor further information see https://github.com/official-stockfish/Stockfish#readme" - "\nor the corresponding README.md and Copying.txt files distributed with this program.\n" << sync_endl; + sync_cout << "\nStockfish is a powerful chess engine for playing and analyzing." + "\nIt is released as free software licensed under the GNU GPLv3 License." + "\nStockfish is normally used with a graphical user interface (GUI) and implements" + "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." + "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" + "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" << sync_endl; else if (!token.empty() && token[0] != '#') sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl; - } while (token != "quit" && argc == 1); // Command line args are one-shot + } while (token != "quit" && argc == 1); // The command-line arguments are one-shot } -/// UCI::value() converts a Value to a string suitable for use with the UCI -/// protocol specification: +/// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: /// /// cp The score from the engine's point of view in centipawns. -/// mate Mate in y moves, not plies. If the engine is getting mated -/// use negative values for y. +/// mate Mate in 'y' moves (not plies). If the engine is getting mated, +/// uses negative values for 'y'. string UCI::value(Value v) { @@ -320,8 +320,8 @@ string UCI::value(Value v) { } -/// UCI::wdl() report WDL statistics given an evaluation and a game ply, based on -/// data gathered for fishtest LTC games. +/// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation +/// and a game ply based on the data gathered for fishtest LTC games. string UCI::wdl(Value v, int ply) { @@ -344,9 +344,9 @@ std::string UCI::square(Square s) { /// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). -/// The only special case is castling, where we print in the e1g1 notation in -/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all -/// castling moves are always encoded as 'king captures rook'. +/// The only special case is castling where the e1g1 notation is printed in +/// standard chess mode and in e1h1 notation it is printed in Chess960 mode. +/// Internally, all castling moves are always encoded as 'king captures rook'. string UCI::move(Move m, bool chess960) { @@ -376,8 +376,8 @@ string UCI::move(Move m, bool chess960) { Move UCI::to_move(const Position& pos, string& str) { - if (str.length() == 5) // Junior could send promotion piece in uppercase - str[4] = char(tolower(str[4])); + if (str.length() == 5) + str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased for (const auto& m : MoveList(pos)) if (str == UCI::move(m, pos.is_chess960())) diff --git a/src/uci.h b/src/uci.h index 5bb24a4e..76a893f9 100644 --- a/src/uci.h +++ b/src/uci.h @@ -32,15 +32,15 @@ namespace UCI { class Option; -/// Custom comparator because UCI options should be case insensitive +/// Define a custom comparator, because the UCI options should be case-insensitive struct CaseInsensitiveLess { bool operator() (const std::string&, const std::string&) const; }; -/// Our options container is actually a std::map +/// The options container is defined as a std::map typedef std::map OptionsMap; -/// Option class implements an option as defined by UCI protocol +/// The Option class implements each option as specified by the UCI protocol class Option { typedef void (*OnChange)(const Option&); From 00297cfef0ad604a61aaff2747dea029fa6aa733 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Tue, 7 Jun 2022 07:21:43 +0200 Subject: [PATCH 041/678] Use qsearch on step 11 if depth is equal to or below 0 larger reduction of depth if no TT entry is found, and go in qsearch as needed. stc: https://tests.stockfishchess.org/tests/view/629dfacd593a4a9b6482db72 LLR: 2.93 (-2.94,2.94) <0.00,2.50> Total: 31920 W: 8591 L: 8322 D: 15007 Ptnml(0-2): 127, 3551, 8376, 3738, 168 ltc: https://tests.stockfishchess.org/tests/view/629e304e593a4a9b6482e451 LLR: 2.95 (-2.94,2.94) <0.50,3.00> Total: 17488 W: 4842 L: 4614 D: 8032 Ptnml(0-2): 13, 1670, 5151, 1896, 14 closes https://github.com/official-stockfish/Stockfish/pull/4056 Bench: 5870283 --- src/search.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ea1e63fe..17ac05c4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -908,14 +908,17 @@ namespace { ss->ttPv = ttPv; } - // Step 11. If the position is not in TT, decrease depth by 2 or 1 depending on node type (~3 Elo) - if ( PvNode - && depth >= 3 + // Step 11. If the position is not in TT, decrease depth by 3. + // Use qsearch if depth is equal or below zero (~4 Elo) + if ( PvNode && !ttMove) - depth -= 2; + depth -= 3; - if ( cutNode - && depth >= 8 + if (depth <= 0) + return qsearch(pos, ss, alpha, beta); + + if ( cutNode + && depth >= 8 && !ttMove) depth--; From d54b85b4bdcd2354903a31f4f35cca6b3742a7d9 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Mon, 13 Jun 2022 22:08:01 +0200 Subject: [PATCH 042/678] Restore NDKv21 for GitHub Actions GitHub updated the versions of NDK installed on the Actions runners breaking the ARM tests. Restore the NDKv21 using the GitHub suggested mitigation, see: https://github.com/actions/virtual-environments/issues/5595 closes https://github.com/official-stockfish/Stockfish/pull/4077 No functional change --- .github/workflows/stockfish.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 33560d52..782e3f2b 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -5,7 +5,6 @@ on: - master - tools - github_ci - - github_ci_armv7 pull_request: branches: - master @@ -269,6 +268,12 @@ jobs: - name: Test armv8 build if: ${{ matrix.config.run_armv8_tests }} run: | + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle + ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean @@ -280,6 +285,12 @@ jobs: - name: Test armv7 build if: ${{ matrix.config.run_armv7_tests }} run: | + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle + ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean @@ -289,6 +300,12 @@ jobs: - name: Test armv7-neon build if: ${{ matrix.config.run_armv7_tests }} run: | + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle + ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean From 2d5dcf3d18842dc2cfb17e3dd64e0812bf01ab65 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 5 Jun 2022 19:32:05 -0700 Subject: [PATCH 043/678] Minor simplifications and cleanup in search STC: https://tests.stockfishchess.org/tests/view/629d6775593a4a9b6482c1ec LLR: 2.93 (-2.94,2.94) <-2.25,0.25> Total: 77416 W: 20683 L: 20589 D: 36144 Ptnml(0-2): 317, 8690, 20620, 8744, 337 LTC: https://tests.stockfishchess.org/tests/view/629db4be593a4a9b6482ceef LLR: 2.95 (-2.94,2.94) <-2.25,0.25> Total: 106544 W: 28752 L: 28705 D: 49087 Ptnml(0-2): 97, 10692, 31641, 10751, 91 closes https://github.com/official-stockfish/Stockfish/pull/4059 Bench: 5913510 --- src/search.cpp | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 17ac05c4..1ab71a56 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -85,8 +85,8 @@ namespace { } // Add a small random component to draw evaluations to avoid 3-fold blindness - Value value_draw(Thread* thisThread) { - return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); + Value value_draw(const Thread* thisThread) { + return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); } // Skill structure is used to implement strength limit. If we have an uci_elo then @@ -116,7 +116,7 @@ namespace { Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); - void update_pv(Move* pv, Move move, Move* childPv); + void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, @@ -635,10 +635,9 @@ namespace { // At non-PV nodes we check for an early TT cutoff if ( !PvNode && ss->ttHit - && tte->depth() > depth - (thisThread->id() % 2 == 1) + && tte->depth() > depth - ((int)thisThread->id() & 0x1) && ttValue != VALUE_NONE // Possible in case of TT access race - && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) - : (tte->bound() & BOUND_UPPER))) + && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~1 Elo) if (ttMove) @@ -896,12 +895,11 @@ namespace { if (value >= probCutBeta) { // if transposition table doesn't have equal or more deep info write probCut data into it - if ( !(ss->ttHit - && tte->depth() >= depth - 3 - && ttValue != VALUE_NONE)) + if (!( ss->ttHit + && tte->depth() >= depth - 3 + && ttValue != VALUE_NONE)) tte->save(posKey, value_to_tt(value, ss->ply), ttPv, - BOUND_LOWER, - depth - 3, move, ss->staticEval); + BOUND_LOWER, depth - 3, move, ss->staticEval); return value; } } @@ -1172,7 +1170,7 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes based on depth if (PvNode) - r -= 1 + 15 / ( 3 + depth ); + r -= 1 + 15 / (3 + depth); // Increase reduction if next ply has a lot of fail high else reset count to 0 if ((ss+1)->cutoffCnt > 3 && !PvNode) @@ -1450,8 +1448,7 @@ moves_loop: // When in check, search starts here && ss->ttHit && tte->depth() >= ttDepth && ttValue != VALUE_NONE // Only in case of TT access race - && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) - : (tte->bound() & BOUND_UPPER))) + && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; // Evaluate the position statically @@ -1567,14 +1564,14 @@ moves_loop: // When in check, search starts here [to_sq(move)]; // Continuation history based pruning (~2 Elo) - if ( !capture + if ( !capture && bestValue > VALUE_TB_LOSS_IN_MAX_PLY && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold) continue; // movecount pruning for quiet check evasions - if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY + if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY && quietCheckEvasions > 1 && !capture && ss->inCheck) @@ -1675,7 +1672,7 @@ moves_loop: // When in check, search starts here // update_pv() adds current move and appends child pv[] - void update_pv(Move* pv, Move move, Move* childPv) { + void update_pv(Move* pv, Move move, const Move* childPv) { for (*pv++ = move; childPv && *childPv != MOVE_NONE; ) *pv++ = *childPv++; @@ -1688,19 +1685,18 @@ moves_loop: // When in check, search starts here void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) { - int bonus1, bonus2; Color us = pos.side_to_move(); Thread* thisThread = pos.this_thread(); CapturePieceToHistory& captureHistory = thisThread->captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); - - bonus1 = stat_bonus(depth + 1); - bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus - : stat_bonus(depth); // smaller bonus + int bonus1 = stat_bonus(depth + 1); if (!pos.capture(bestMove)) { + int bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus + : stat_bonus(depth); // smaller bonus + // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bonus2); From 6edc29d720b43383a04bd2208e9666a6f3173a64 Mon Sep 17 00:00:00 2001 From: bmc4 Date: Thu, 9 Jun 2022 10:37:24 -0300 Subject: [PATCH 044/678] Simplify away condition in ttSave in probCut Remove condition for tte->save in probCut so it always saves on probCut cutoff. STC: LLR: 2.95 (-2.94,2.94) <-2.25,0.25> Total: 47848 W: 12921 L: 12782 D: 22145 Ptnml(0-2): 207, 5340, 12715, 5431, 231 https://tests.stockfishchess.org/tests/view/62a1f7c87bd8e641e44436f7 LTC: LLR: 2.97 (-2.94,2.94) <-2.25,0.25> Total: 132736 W: 35895 L: 35881 D: 60960 Ptnml(0-2): 109, 13384, 39360, 13414, 101 https://tests.stockfishchess.org/tests/view/62a2421a7bd8e641e444434f closes https://github.com/official-stockfish/Stockfish/pull/4069 bench: 5845802 --- src/search.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1ab71a56..466c9ec1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -894,12 +894,8 @@ namespace { if (value >= probCutBeta) { - // if transposition table doesn't have equal or more deep info write probCut data into it - if (!( ss->ttHit - && tte->depth() >= depth - 3 - && ttValue != VALUE_NONE)) - tte->save(posKey, value_to_tt(value, ss->ply), ttPv, - BOUND_LOWER, depth - 3, move, ss->staticEval); + // Save ProbCut data into transposition table + tte->save(posKey, value_to_tt(value, ss->ply), ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); return value; } } From 4d6a11a04cd8447b56447a9433c0cfb547a00643 Mon Sep 17 00:00:00 2001 From: bmc4 Date: Thu, 16 Jun 2022 07:19:39 -0300 Subject: [PATCH 045/678] Don't change ttPv at probCut STC: LLR: 2.96 (-2.94,2.94) <-2.25,0.25> Total: 35672 W: 9618 L: 9462 D: 16592 Ptnml(0-2): 151, 3890, 9601, 4040, 154 https://tests.stockfishchess.org/tests/view/62ab03f750949cfc241b1965 LTC: LLR: 2.93 (-2.94,2.94) <-2.25,0.25> Total: 54160 W: 14626 L: 14511 D: 25023 Ptnml(0-2): 42, 5414, 16056, 5523, 45 https://tests.stockfishchess.org/tests/view/62ab5e6fd89eb6cf1e071b87 closes https://github.com/official-stockfish/Stockfish/pull/4088 bench: 5798229 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 466c9ec1..6169e060 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -867,8 +867,6 @@ namespace { assert(probCutBeta < VALUE_INFINITE); MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, depth - 3, &captureHistory); - bool ttPv = ss->ttPv; - ss->ttPv = false; while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) @@ -895,11 +893,10 @@ namespace { if (value >= probCutBeta) { // Save ProbCut data into transposition table - tte->save(posKey, value_to_tt(value, ss->ply), ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); + tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); return value; } } - ss->ttPv = ttPv; } // Step 11. If the position is not in TT, decrease depth by 3. From 5304b561ab96ae1c025d98cf4a6d138daa11374d Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 15 Jun 2022 17:30:32 -0500 Subject: [PATCH 046/678] LMR: remove `deeper` ...apparently it wasn't doing much anymore. inspired by rufish's recent attempts to improve this. passed STC: https://tests.stockfishchess.org/tests/view/62abca2cd89eb6cf1e072c04 LLR: 2.95 (-2.94,2.94) <-2.25,0.25> Total: 85576 W: 22766 L: 22683 D: 40127 Ptnml(0-2): 362, 9607, 22741, 9742, 336 passed LTC: https://tests.stockfishchess.org/tests/view/62ac90ffd89eb6cf1e07488f LLR: 2.93 (-2.94,2.94) <-2.25,0.25> Total: 48248 W: 13018 L: 12896 D: 22334 Ptnml(0-2): 32, 4773, 14400, 4879, 40 closes https://github.com/official-stockfish/Stockfish/pull/4088 bench 5578988 --- src/search.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6169e060..37277ec4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1178,15 +1178,10 @@ moves_loop: // When in check, search starts here // Decrease/increase reduction for moves with a good/bad history (~30 Elo) r -= ss->statScore / 15914; - // In general we want to cap the LMR depth search at newDepth. But if reductions - // are really negative and movecount is low, we allow this move to be searched - // deeper than the first move (this may lead to hidden double extensions). - int deeper = r >= -1 ? 0 - : moveCount <= 4 ? 2 - : PvNode || cutNode ? 1 - : 0; - - Depth d = std::clamp(newDepth - r, 1, newDepth + deeper); + // In general we want to cap the LMR depth search at newDepth, but when + // reduction is negative, we allow this move a limited search extension + // beyond the first move depth. This may lead to hidden double extensions. + Depth d = std::clamp(newDepth - r, 1, newDepth + 1); value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); From 442c40b43de8ede1e424efa674c8d45322e3b43c Mon Sep 17 00:00:00 2001 From: Dubslow Date: Fri, 10 Jun 2022 08:11:02 -0500 Subject: [PATCH 047/678] Use NNUE complexity in search, retune related parameters This builds on ideas of xoto10 and mstembera to use more output from NNUE in the search algorithm. passed STC: https://tests.stockfishchess.org/tests/view/62ae454fe7ee5525ef88a957 LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 89208 W: 24127 L: 23753 D: 41328 Ptnml(0-2): 400, 9886, 23642, 10292, 384 passed LTC: https://tests.stockfishchess.org/tests/view/62acc6ddd89eb6cf1e0750a1 LLR: 2.93 (-2.94,2.94) <0.50,3.00> Total: 56352 W: 15430 L: 15115 D: 25807 Ptnml(0-2): 44, 5501, 16782, 5794, 55 closes https://github.com/official-stockfish/Stockfish/pull/4088 bench 5332964 --- src/evaluate.cpp | 32 ++++++++++++++++++++------------ src/evaluate.h | 2 +- src/nnue/evaluate_nnue.cpp | 4 ++-- src/position.h | 5 +++++ src/search.cpp | 20 ++++++++++---------- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 415c18c5..6d2e8da8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1080,35 +1080,39 @@ make_v: /// evaluate() is the evaluator for the outer world. It returns a static /// evaluation of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos) { +Value Eval::evaluate(const Position& pos, int* complexity) { Value v; + Color stm = pos.side_to_move(); + Value psq = pos.psq_eg_stm(); // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, // but we switch to NNUE during long shuffling or with high material on the board. - bool useClassical = (pos.this_thread()->depth > 9 || pos.count() > 7) && - abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count()); + bool useClassical = (pos.this_thread()->depth > 9 || pos.count() > 7) + && abs(psq) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count()); // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, // but we switch to NNUE during long shuffling or with high material on the board. if (!useNNUE || useClassical) { - v = Evaluation(pos).value(); // classical + v = Evaluation(pos).value(); useClassical = abs(v) >= 297; } // If result of a classical evaluation is much lower than threshold fall back to NNUE if (useNNUE && !useClassical) { - int complexity; - int scale = 1048 + 109 * pos.non_pawn_material() / 5120; - Color stm = pos.side_to_move(); + int nnueComplexity; + int scale = 1092 + 106 * pos.non_pawn_material() / 5120; Value optimism = pos.this_thread()->optimism[stm]; - Value psq = (stm == WHITE ? 1 : -1) * eg_value(pos.psq_score()); - Value nnue = NNUE::evaluate(pos, true, &complexity); // NNUE - complexity = (137 * complexity + 137 * abs(nnue - psq)) / 256; - optimism = optimism * (255 + complexity) / 256; - v = (nnue * scale + optimism * (scale - 848)) / 1024; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + // Blend nnue complexity with (semi)classical complexity + nnueComplexity = (104 * nnueComplexity + 131 * abs(nnue - psq)) / 256; + if (complexity) // Return hybrid NNUE complexity to caller + *complexity = nnueComplexity; + + optimism = optimism * (269 + nnueComplexity) / 256; + v = (nnue * scale + optimism * (scale - 754)) / 1024; if (pos.is_chess960()) v += fix_FRC(pos); @@ -1120,6 +1124,10 @@ Value Eval::evaluate(const Position& pos) { // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + // When not using NNUE, return classical complexity to caller + if (complexity && (!useNNUE || useClassical)) + *complexity = abs(v - psq); + return v; } diff --git a/src/evaluate.h b/src/evaluate.h index e79eaea3..e25bd52e 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -31,7 +31,7 @@ class Position; namespace Eval { std::string trace(Position& pos); - Value evaluate(const Position& pos); + Value evaluate(const Position& pos, int* complexity = nullptr); extern bool useNNUE; extern std::string currentEvalFileName; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index eb6ad71f..ba2ed367 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -143,7 +143,7 @@ namespace Stockfish::Eval::NNUE { // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; - int delta = 10 - pos.non_pawn_material() / 1515; + int delta = 24 - pos.non_pawn_material() / 9560; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType transformedFeaturesUnaligned[ @@ -166,7 +166,7 @@ namespace Stockfish::Eval::NNUE { // Give more value to positional evaluation when adjusted flag is set if (adjusted) - return static_cast(((128 - delta) * psqt + (128 + delta) * positional) / (128 * OutputScale)); + return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) / (1024 * OutputScale)); else return static_cast((psqt + positional) / OutputScale); } diff --git a/src/position.h b/src/position.h index e5585818..510875d8 100644 --- a/src/position.h +++ b/src/position.h @@ -161,6 +161,7 @@ public: bool has_repeated() const; int rule50_count() const; Score psq_score() const; + Value psq_eg_stm() const; Value non_pawn_material(Color c) const; Value non_pawn_material() const; @@ -342,6 +343,10 @@ inline Score Position::psq_score() const { return psq; } +inline Value Position::psq_eg_stm() const { + return (sideToMove == WHITE ? 1 : -1) * eg_value(psq); +} + inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } diff --git a/src/search.cpp b/src/search.cpp index 37277ec4..f11d76be 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -307,7 +307,7 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); - complexityAverage.set(202, 1); + complexityAverage.set(174, 1); trend = SCORE_ZERO; optimism[ us] = Value(39); @@ -472,7 +472,7 @@ void Thread::search() { double reduction = (1.56 + mainThread->previousTimeReduction) / (2.20 * timeReduction); double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size(); int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::clamp(1.0 + (complexity - 326) / 1618.1, 0.5, 1.5); + double complexPosition = std::clamp(1.0 + (complexity - 277) / 1819, 0.5, 1.5); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; @@ -736,7 +736,9 @@ namespace { // Never assume anything about values stored in TT ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - ss->staticEval = eval = evaluate(pos); + ss->staticEval = eval = evaluate(pos, &complexity); + else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost + complexity = abs(ss->staticEval - pos.psq_eg_stm()); // Randomize draw evaluation if (eval == VALUE_DRAW) @@ -749,13 +751,15 @@ namespace { } else { - ss->staticEval = eval = evaluate(pos); + ss->staticEval = eval = evaluate(pos, &complexity); // Save static evaluation into transposition table if (!excludedMove) tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } + thisThread->complexityAverage.update(complexity); + // Use static evaluation difference to improve quiet move ordering (~3 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { @@ -770,11 +774,7 @@ namespace { improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval : 175; - improving = improvement > 0; - complexity = abs(ss->staticEval - (us == WHITE ? eg_value(pos.psq_score()) : -eg_value(pos.psq_score()))); - - thisThread->complexityAverage.update(complexity); // Step 7. Razoring. // If eval is really low check with qsearch if it can exceed alpha, if it can't, @@ -803,7 +803,7 @@ namespace { && (ss-1)->statScore < 14695 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 15 * depth - improvement / 15 + 198 + complexity / 28 + && ss->staticEval >= beta - 15 * depth - improvement / 15 + 201 + complexity / 24 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -811,7 +811,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 147, 5) + depth / 3 + 4 - (complexity > 753); + Depth R = std::min(int(eval - beta) / 147, 5) + depth / 3 + 4 - (complexity > 650); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; From 85f8ee6199f8578fbc082fc0f37e1985813e637a Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 1 Jul 2022 15:07:49 +0200 Subject: [PATCH 048/678] Update default net to nn-3c0054ea9860.nnu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First things first... this PR is being made from court. Today, Tord and Stéphane, with broad support of the developer community are defending their complaint, filed in Munich, against ChessBase. With their products Houdini 6 and Fat Fritz 2, both Stockfish derivatives, ChessBase violated repeatedly the Stockfish GPLv3 license. Tord and Stéphane have terminated their license with ChessBase permanently. Today we have the opportunity to present our evidence to the judge and enforce that termination. To read up, have a look at our blog post https://stockfishchess.org/blog/2022/public-court-hearing-soon/ and https://stockfishchess.org/blog/2021/our-lawsuit-against-chessbase/ This PR introduces a net trained with an enhanced data set and a modified loss function in the trainer. A slight adjustment for the scaling was needed to get a pass on standard chess. passed STC: https://tests.stockfishchess.org/tests/view/62c0527a49b62510394bd610 LLR: 2.94 (-2.94,2.94) <0.00,2.50> Total: 135008 W: 36614 L: 36152 D: 62242 Ptnml(0-2): 640, 15184, 35407, 15620, 653 passed LTC: https://tests.stockfishchess.org/tests/view/62c17e459e7d9997a12d458e LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 28864 W: 8007 L: 7749 D: 13108 Ptnml(0-2): 47, 2810, 8466, 3056, 53 Local testing at a fixed 25k nodes resulted in Test run1026/easy_train_data/experiments/experiment_2/training/run_0/nn-epoch799.nnue localElo: 4.2 +- 1.6 The real strength of the net is in FRC and DFRC chess where it gains significantly. Tested at STC with slightly different scaling: FRC: https://tests.stockfishchess.org/tests/view/62c13a4002ba5d0a774d20d4 Elo: 29.78 +-3.4 (95%) LOS: 100.0% Total: 10000 W: 2007 L: 1152 D: 6841 Ptnml(0-2): 31, 686, 2804, 1355, 124 nElo: 59.24 +-6.9 (95%) PairsRatio: 2.06 DFRC: https://tests.stockfishchess.org/tests/view/62c13a5702ba5d0a774d20d9 Elo: 55.25 +-3.9 (95%) LOS: 100.0% Total: 10000 W: 2984 L: 1407 D: 5609 Ptnml(0-2): 51, 636, 2266, 1779, 268 nElo: 96.95 +-7.2 (95%) PairsRatio: 2.98 Tested at LTC with identical scaling: FRC: https://tests.stockfishchess.org/tests/view/62c26a3c9e7d9997a12d6caf Elo: 16.20 +-2.5 (95%) LOS: 100.0% Total: 10000 W: 1192 L: 726 D: 8082 Ptnml(0-2): 10, 403, 3727, 831, 29 nElo: 44.12 +-6.7 (95%) PairsRatio: 2.08 DFRC: https://tests.stockfishchess.org/tests/view/62c26a539e7d9997a12d6cb2 Elo: 40.94 +-3.0 (95%) LOS: 100.0% Total: 10000 W: 2215 L: 1042 D: 6743 Ptnml(0-2): 10, 410, 3053, 1451, 76 nElo: 92.77 +-6.9 (95%) PairsRatio: 3.64 This is due to the mixing in a significant fraction of DFRC training data in the final training round. The net is trained using the easy_train.py script in the following way: ``` python easy_train.py \ --training-dataset=../Leela-dfrc_n5000.binpack \ --experiment-name=2 \ --nnue-pytorch-branch=vondele/nnue-pytorch/lossScan4 \ --additional-training-arg=--param-index=2 \ --start-lambda=1.0 \ --end-lambda=0.75 \ --gamma=0.995 \ --lr=4.375e-4 \ --start-from-engine-test-net True \ --tui=False \ --seed=$RANDOM \ --max_epoch=800 \ --auto-exit-timeout-on-training-finished=900 \ --network-testing-threads 8 \ --num-workers 12 ``` where the data set used (Leela-dfrc_n5000.binpack) is a combination of our previous best data set (mix of Leela and some SF data) and DFRC data, interleaved to form: The data is available in https://drive.google.com/drive/folders/1S9-ZiQa_3ApmjBtl2e8SyHxj4zG4V8gG?usp=sharing Leela mix: https://drive.google.com/file/d/1JUkMhHSfgIYCjfDNKZUMYZt6L5I7Ra6G/view?usp=sharing DFRC: https://drive.google.com/file/d/17vDaff9LAsVo_1OfsgWAIYqJtqR8aHlm/view?usp=sharing The training branch used is https://github.com/vondele/nnue-pytorch/commits/lossScan4 A PR to the main trainer repo will be made later. This contains a revised loss function, now computing the loss from the score based on the win rate model, which is a more accurate representation than what we had before. Scaling constants are tweaked there as well. closes https://github.com/official-stockfish/Stockfish/pull/4100 Bench: 5186781 --- src/evaluate.cpp | 2 +- src/evaluate.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6d2e8da8..53613794 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1102,7 +1102,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { if (useNNUE && !useClassical) { int nnueComplexity; - int scale = 1092 + 106 * pos.non_pawn_material() / 5120; + int scale = 1064 + 106 * pos.non_pawn_material() / 5120; Value optimism = pos.this_thread()->optimism[stm]; Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); diff --git a/src/evaluate.h b/src/evaluate.h index e25bd52e..36d3b2b2 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-3c0aa92af1da.nnue" + #define EvalFileDefaultName "nn-3c0054ea9860.nnue" namespace NNUE { From c2aaaa65f97d4cd5fc06f19ce8d158d85dcd7a7b Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 5 Jul 2022 14:15:34 +0300 Subject: [PATCH 049/678] Simplify away FRC correction term Since new net is trained partially using FRC data this part of adjustment that penalises bishops that are locked in the corner is no longer needed - net should "know" this things itself much better. STC on FRC book : https://tests.stockfishchess.org/tests/view/62c3031b9e7d9997a12d852f LLR: 2.96 (-2.94,2.94) <-2.25,0.25> Total: 22048 W: 3003 L: 2845 D: 16200 Ptnml(0-2): 96, 1778, 7149, 1874, 127 LTC on FRC book : https://tests.stockfishchess.org/tests/view/62c32e939e7d9997a12d8c5e LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 36784 W: 3138 L: 3037 D: 30609 Ptnml(0-2): 36, 1842, 14537, 1939, 38 STC on DFRC book : https://tests.stockfishchess.org/tests/view/62c32efb9e7d9997a12d8c6f LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 20424 W: 3903 L: 3721 D: 12800 Ptnml(0-2): 172, 1984, 5724, 2154, 178 LTC on DFRC book : https://tests.stockfishchess.org/tests/view/62c358c79e7d9997a12d9319 LLR: 2.93 (-2.94,2.94) <-2.25,0.25> Total: 53784 W: 7581 L: 7480 D: 38723 Ptnml(0-2): 87, 3887, 18856, 3962, 100 closes https://github.com/official-stockfish/Stockfish/pull/4101 bench 5182295 --- src/evaluate.cpp | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 53613794..d340d3d5 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1042,38 +1042,6 @@ make_v: return v; } - - /// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE - - Value fix_FRC(const Position& pos) { - - constexpr Bitboard Corners = 1ULL << SQ_A1 | 1ULL << SQ_H1 | 1ULL << SQ_A8 | 1ULL << SQ_H8; - - if (!(pos.pieces(BISHOP) & Corners)) - return VALUE_ZERO; - - int correction = 0; - - if ( pos.piece_on(SQ_A1) == W_BISHOP - && pos.piece_on(SQ_B2) == W_PAWN) - correction -= CorneredBishop; - - if ( pos.piece_on(SQ_H1) == W_BISHOP - && pos.piece_on(SQ_G2) == W_PAWN) - correction -= CorneredBishop; - - if ( pos.piece_on(SQ_A8) == B_BISHOP - && pos.piece_on(SQ_B7) == B_PAWN) - correction += CorneredBishop; - - if ( pos.piece_on(SQ_H8) == B_BISHOP - && pos.piece_on(SQ_G7) == B_PAWN) - correction += CorneredBishop; - - return pos.side_to_move() == WHITE ? Value(3 * correction) - : -Value(3 * correction); - } - } // namespace Eval @@ -1113,9 +1081,6 @@ Value Eval::evaluate(const Position& pos, int* complexity) { optimism = optimism * (269 + nnueComplexity) / 256; v = (nnue * scale + optimism * (scale - 754)) / 1024; - - if (pos.is_chess960()) - v += fix_FRC(pos); } // Damp down the evaluation linearly when shuffling From aa18b68033403ee2d8d49df783cab32e6bccf59c Mon Sep 17 00:00:00 2001 From: Dubslow Date: Fri, 1 Jul 2022 11:53:16 -0500 Subject: [PATCH 050/678] Time mgmt fix division. oversight changed the corresponding float division to integer division in a previous tune https://github.com/official-stockfish/Stockfish/commit/442c40b43de8ede1e424efa674c8d45322e3b43c it is stronger to keep the original float division. green LTC: https://tests.stockfishchess.org/tests/view/62bf34bc0340fb1e0cc934e7 LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 38952 W: 10738 L: 10467 D: 17747 Ptnml(0-2): 46, 3576, 11968, 3833, 53 yellow STC: https://tests.stockfishchess.org/tests/view/62bff6506178ffe6394ba1d1 LLR: -2.95 (-2.94,2.94) <0.00,2.50> Total: 226960 W: 61265 L: 61062 D: 104633 Ptnml(0-2): 938, 24398, 62582, 24647, 915 further slightly tweaked tests confirm this Elo gain. closes https://github.com/official-stockfish/Stockfish/pull/4097 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f11d76be..07f18a34 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -472,7 +472,7 @@ void Thread::search() { double reduction = (1.56 + mainThread->previousTimeReduction) / (2.20 * timeReduction); double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size(); int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::clamp(1.0 + (complexity - 277) / 1819, 0.5, 1.5); + double complexPosition = std::clamp(1.0 + (complexity - 277) / 1819.1, 0.5, 1.5); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; From 2e02dd79366e6f17df5b0599048937289fd5819e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 2 Jul 2022 08:55:05 +0200 Subject: [PATCH 051/678] Limit the researching at same depth. If the elapsed time is close to the available time, the time management thread can signal that the next iterations should be searched at the same depth (Threads.increaseDepth = false). While the rootDepth increases, the adjustedDepth is kept constant with the searchAgainCounter. In exceptional cases, when threading is used and the master thread, which controls the time management, signals to not increaseDepth, but by itself takes a long time to finish the iteration, the helper threads can search repeatedly at the same depth. This search finishes more and more quickly, leading to helper threads that report a rootDepth of MAX_DEPTH (245). The latter is not optimal as it is confusing for the user, stops search on these threads, and leads to an incorrect bias in the thread voting scheme. Probably with only a small impact on strength. This behavior was observed almost two years ago, see https://github.com/official-stockfish/Stockfish/issues/2717 This patch fixes #2717 by ensuring the effective depth increases at once every four iterations, even in increaseDepth is false. Depth 245 searches (for non-trivial positions) were indeed absent with this patch, but frequent with master in the tests below: https://discord.com/channels/435943710472011776/813919248455827515/994872720800088095 Total pgns: 2173 Base: 2867 Patch: 0 it passed non-regression testing in various setups: SMP STC: https://tests.stockfishchess.org/tests/view/62bfecc96178ffe6394ba036 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 37288 W: 10171 L: 10029 D: 17088 Ptnml(0-2): 75, 3777, 10793, 3929, 70 SMP LTC: https://tests.stockfishchess.org/tests/view/62c08f6f49b62510394be066 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 190568 W: 52125 L: 52186 D: 86257 Ptnml(0-2): 70, 17854, 59504, 17779, 77 LTC: https://tests.stockfishchess.org/tests/view/62c08b6049b62510394bdfb6 LLR: 2.96 (-2.94,2.94) <-2.25,0.25> Total: 48120 W: 13204 L: 13083 D: 21833 Ptnml(0-2): 54, 4458, 14919, 4571, 58 Special thanks to miguel-I, Disservin, ruicoelhopedro and others for analysing the problem, the data, and coming up with the key insight, needed to fix this longstanding issue. closes https://github.com/official-stockfish/Stockfish/pull/4104 Bench: 5182295 --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 07f18a34..ca1d2632 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -373,7 +373,9 @@ void Thread::search() { int failedHighCnt = 0; while (true) { - Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter); + // Adjust the effective depth searched, but ensuring at least one effective increment for every + // four searchAgain steps (see issue #2717). + Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting From 95d24b77dfe8a147100d9753c5580563c3e642d2 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 13 Jul 2022 11:59:54 +0300 Subject: [PATCH 052/678] Simplify away some unneeded code in time management The lower bound of the clamp is never used since complexity can't be negative and thus is unneeded. closes https://github.com/official-stockfish/Stockfish/pull/4105 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ca1d2632..c5c7f111 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -474,7 +474,7 @@ void Thread::search() { double reduction = (1.56 + mainThread->previousTimeReduction) / (2.20 * timeReduction); double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size(); int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::clamp(1.0 + (complexity - 277) / 1819.1, 0.5, 1.5); + double complexPosition = std::min(1.0 + (complexity - 277) / 1819.1, 1.5); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; From 4b4b7d1209259811537634c68555d2a8f4af197c Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 11 Jul 2022 17:32:39 +0200 Subject: [PATCH 053/678] Update default net to nn-ad9b42354671.nnue using trainer branch https://github.com/glinscott/nnue-pytorch/pull/208 with a slightly tweaked loss function (power 2.5 instead of 2.6), otherwise same training as in the previous net update https://github.com/official-stockfish/Stockfish/pull/4100 passed STC: LLR: 2.97 (-2.94,2.94) <0.00,2.50> Total: 367536 W: 99465 L: 98573 D: 169498 Ptnml(0-2): 1820, 40994, 97117, 42148, 1689 https://tests.stockfishchess.org/tests/view/62cc43fe50dcbecf5fc1c5b8 passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 25032 W: 6802 L: 6553 D: 11677 Ptnml(0-2): 40, 2424, 7341, 2669, 42 https://tests.stockfishchess.org/tests/view/62ce5f421dacb46e4d5fd277 closes https://github.com/official-stockfish/Stockfish/pull/4107 Bench: 5905619 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 36d3b2b2..f5ac3263 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-3c0054ea9860.nnue" + #define EvalFileDefaultName "nn-ad9b42354671.nnue" namespace NNUE { From c4a644922dc219aade86ea07c85e95899850fac8 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 19 Jul 2022 01:09:30 -0500 Subject: [PATCH 054/678] Simplify reduction condition for cutNodes LMR: for cutNodes, dont exclude killer moves. This was a prelude to reducing allNodes, altho that's failed so far. STC https://tests.stockfishchess.org/tests/view/62d64ad147ae1768b34a27c3 LLR: 2.95 (-2.94,2.94) <-2.25,0.25> Total: 37064 W: 10044 L: 9889 D: 17131 Ptnml(0-2): 162, 4115, 9828, 4260, 167 LTC https://tests.stockfishchess.org/tests/view/62d66cc047ae1768b34a2b14 LLR: 2.94 (-2.94,2.94) <-2.25,0.25> Total: 39832 W: 10796 L: 10659 D: 18377 Ptnml(0-2): 69, 3969, 11706, 4100, 72 closes https://github.com/official-stockfish/Stockfish/pull/4109 bench: 5697891 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c5c7f111..96b1d0f1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1156,7 +1156,7 @@ moves_loop: // When in check, search starts here r--; // Increase reduction for cut nodes (~3 Elo) - if (cutNode && move != ss->killers[0]) + if (cutNode) r += 2; // Increase reduction if ttMove is a capture (~3 Elo) From 18389e269d43af69c96521f3fe9d85e6b7ed073c Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 26 Jul 2022 23:45:19 -0500 Subject: [PATCH 055/678] remove useClassical depth condition passed STC: https://tests.stockfishchess.org/tests/view/62e0c3e98e4fa6ae472695ed LLR: 2.96 (-2.94,2.94) <-2.25,0.25> Total: 293568 W: 78934 L: 79151 D: 135483 Ptnml(0-2): 1344, 31488, 81366, 31213, 1373 passed LTC: https://tests.stockfishchess.org/tests/view/62e190aa8e4fa6ae4726b5b5 LLR: 2.98 (-2.94,2.94) <-2.25,0.25> Total: 187392 W: 50971 L: 51028 D: 85393 Ptnml(0-2): 384, 17801, 57369, 17772, 370 other attempts to otherwise tune this parameter failed, bounds 6,7,10,11 failed STC, 8 passed STC but failed LTC closes https://github.com/official-stockfish/Stockfish/pull/4112 bench 5796377 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d340d3d5..9e3eaba5 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1055,7 +1055,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { Value psq = pos.psq_eg_stm(); // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, // but we switch to NNUE during long shuffling or with high material on the board. - bool useClassical = (pos.this_thread()->depth > 9 || pos.count() > 7) + bool useClassical = (pos.count() > 7) && abs(psq) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count()); // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, From 582c88ee94c24b4352477ffacf8de2afc521bcce Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 31 Jul 2022 02:04:23 +0300 Subject: [PATCH 056/678] Do more TT cutoffs in case of exact bound The idea is that these TT entries are considered move valuable in TT replacement scheme - they are always overwriting other entries. So it makes sence for them to produce more aggressive cutoffs. passed STC https://tests.stockfishchess.org/tests/view/62e4d407b383a712b1385410 LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 96632 W: 26045 L: 25659 D: 44928 Ptnml(0-2): 434, 10635, 25770, 11065, 412 passed LTC https://tests.stockfishchess.org/tests/view/62e523e2b383a712b1386193 LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 77960 W: 21363 L: 20989 D: 35608 Ptnml(0-2): 190, 7591, 23009, 8035, 155 closes https://github.com/official-stockfish/Stockfish/pull/4114 bench 5820568 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 96b1d0f1..004d061b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -637,7 +637,7 @@ namespace { // At non-PV nodes we check for an early TT cutoff if ( !PvNode && ss->ttHit - && tte->depth() > depth - ((int)thisThread->id() & 0x1) + && tte->depth() > depth - ((int)thisThread->id() & 0x1) - (tte->bound() == BOUND_EXACT) && ttValue != VALUE_NONE // Possible in case of TT access race && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { From 675f6a038ba98b6b906a4767f009cf6fe91b0c52 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Tue, 2 Aug 2022 23:14:14 +0200 Subject: [PATCH 057/678] Tweak history updates In general the history update bonus is slightly decreased by 11% which gives a slower saturation speed. In addition only for main history the divisor is halfed (used history values are doubled to maintain same maximum) which have an effect in the opposite direction on saturation speed. STC: LLR: 2.95 (-2.94,2.94) <0.00,2.50> Total: 157088 W: 42673 L: 42168 D: 72247 Ptnml(0-2): 857, 17346, 41642, 17833, 866 https://tests.stockfishchess.org/tests/view/62e5517ab383a712b13867c5 LTC: LLR: 2.94 (-2.94,2.94) <0.50,3.00> Total: 325592 W: 88705 L: 87753 D: 149134 Ptnml(0-2): 594, 32288, 96076, 33248, 590 https://tests.stockfishchess.org/tests/view/62e5e4f4b383a712b1387d53 closes https://github.com/official-stockfish/Stockfish/pull/4119 Bench: 5518728 --- src/movepick.cpp | 4 ++-- src/movepick.h | 2 +- src/search.cpp | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b0166c6e..60d041ab 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -137,7 +137,7 @@ void MovePicker::score() { + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; else if constexpr (Type == QUIETS) - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)] + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] @@ -155,7 +155,7 @@ void MovePicker::score() { m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))); else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)] + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - (1 << 28); } diff --git a/src/movepick.h b/src/movepick.h index 6b3c08c7..979709ae 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -87,7 +87,7 @@ enum StatsType { NoCaptures, Captures }; /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards /// (~11 elo) -typedef Stats ButterflyHistory; +typedef Stats ButterflyHistory; /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous /// move, see www.chessprogramming.org/Countermove_Heuristic diff --git a/src/search.cpp b/src/search.cpp index 004d061b..9b747e78 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min((9 * d + 270) * d - 311 , 2145); + return std::min((8 * d + 240) * d - 276 , 1907); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -1033,7 +1033,7 @@ moves_loop: // When in check, search starts here && history < -3875 * (depth - 1)) continue; - history += thisThread->mainHistory[us][from_to(move)]; + history += 2 * thisThread->mainHistory[us][from_to(move)]; // Futility pruning: parent node (~9 Elo) if ( !ss->inCheck @@ -1171,7 +1171,7 @@ moves_loop: // When in check, search starts here if ((ss+1)->cutoffCnt > 3 && !PvNode) r++; - ss->statScore = thisThread->mainHistory[us][from_to(move)] + ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] From b8f4903fbb8ac9be8d26cef30848b3a9527c8725 Mon Sep 17 00:00:00 2001 From: lonfom169 Date: Wed, 3 Aug 2022 12:43:21 -0300 Subject: [PATCH 058/678] Reintroduce singularQuietLMR STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 88912 W: 23972 L: 23580 D: 41360 Ptnml(0-2): 365, 9820, 23712, 10176, 383 https://tests.stockfishchess.org/tests/view/62e9537a400addce2c13399b LTC: LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 85672 W: 23607 L: 23192 D: 38873 Ptnml(0-2): 219, 8316, 25365, 8703, 233 https://tests.stockfishchess.org/tests/view/62e9a174400addce2c1346e4 closes https://github.com/official-stockfish/Stockfish/pull/4122 Bench: 5921315 --- src/search.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9b747e78..7c0601e6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -558,7 +558,7 @@ namespace { Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, didLMR, priorCapture; + bool givesCheck, improving, didLMR, priorCapture, singularQuietLMR; bool capture, doFullDepthSearch, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount, improvement, complexity; @@ -945,7 +945,7 @@ moves_loop: // When in check, search starts here ss->killers); value = bestValue; - moveCountPruning = false; + moveCountPruning = singularQuietLMR = false; // Indicate PvNodes that will probably fail low if the node was searched // at a depth equal or greater than the current depth, and the result of this search was a fail low. @@ -1075,6 +1075,7 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { extension = 1; + singularQuietLMR = !ttCapture; // Avoid search explosion by limiting the number of double extensions if ( !PvNode @@ -1167,6 +1168,10 @@ moves_loop: // When in check, search starts here if (PvNode) r -= 1 + 15 / (3 + depth); + // Decrease reduction if ttMove has been singularly extended (~1 Elo) + if (singularQuietLMR) + r--; + // Increase reduction if next ply has a lot of fail high else reset count to 0 if ((ss+1)->cutoffCnt > 3 && !PvNode) r++; From 7cc929f437f56396ce151c4d8095e24f3d957bd9 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 31 Jul 2022 15:04:23 +0200 Subject: [PATCH 059/678] Update CPU contributors list Thanks for your contributions! closes https://github.com/official-stockfish/Stockfish/pull/4116 No functional change --- Top CPU Contributors.txt | 148 +++++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 69 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 76aa01e9..23a5d7f9 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,96 +1,102 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2022-04-14. +Contributors to Fishtest with >10,000 CPU hours, as of 2022-07-31. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 31714850 2267266129 -mlang 2954099 198421098 -technologov 2324150 102449398 -dew 1670874 99276012 -grandphish2 1134273 68070459 -okrout 901194 77738874 -TueRens 821388 50207666 +noobpwnftw 33202707 2423743815 +technologov 5064327 270208248 +mlang 2963357 198937430 +dew 1677196 99717674 +grandphish2 1231326 74551309 +okrout 1102747 98977462 +TueRens 925904 57404676 +pemo 911980 35581261 tvijlbrief 795993 51894442 -pemo 744463 32486677 -JojoM 724378 43660674 +JojoM 774270 47311084 mibere 703840 46867607 -linrock 626939 17408017 -gvreuls 534079 34352532 -cw 507221 34006775 -fastgm 489749 29344518 +linrock 697283 18804969 +gvreuls 564284 36392236 +cw 515739 34775505 +fastgm 500949 30101898 +oz 439015 31794460 +CSU_Dynasty 438017 29369136 crunchy 427035 27344275 -CSU_Dynasty 424643 28525220 -ctoks 415771 27364603 -oz 369200 27017658 -bcross 342642 23671289 +ctoks 422671 27812261 +bcross 363335 25108521 +leszek 360149 22674005 +velislav 333325 21444360 Fisherman 327231 21829379 -velislav 325670 20911076 -leszek 321295 19874113 -Dantist 274747 16910258 -mgrabiak 237604 15418700 -robal 217959 13840386 +Dantist 292327 17951982 +mgrabiak 247220 16137378 +nordlandia 226543 14601042 +robal 224740 14314972 glinscott 217799 13780820 -nordlandia 211692 13484886 -drabel 201967 13798360 +ncfish1 207751 13909639 +drabel 203884 13922680 +mhoram 200022 12533963 bking_US 198894 11876016 -mhoram 194862 12261809 +rpngn 191764 12236583 Thanar 179852 12365359 vdv 175544 9904472 spams 157128 10319326 -rpngn 154081 9652139 marrco 150300 9402229 sqrt2 147963 9724586 -vdbergh 137430 8955097 +vdbergh 137480 8958795 CoffeeOne 137100 5024116 malala 136182 8002293 xoto 133759 9159372 -davar 125240 8117121 +davar 128645 8367253 +DesolatedDodo 124877 8056482 dsmith 122059 7570238 -amicic 119659 7937885 +amicic 119661 7938029 Data 113305 8220352 BrunoBanani 112960 7436849 CypressChess 108321 7759588 -DesolatedDodo 106811 6776980 MaZePallas 102823 6633619 +skiminki 102168 6778440 sterni1971 100532 5880772 sunu 100167 7040199 ElbertoOne 99028 7023771 -skiminki 98123 6478402 +zeryl 96984 6162287 brabos 92118 6186135 -cuistot 90358 5351004 +cuistot 91738 5447070 psk 89957 5984901 racerschmacer 85712 6119648 Vizvezdenec 83761 5344740 -zeryl 83680 5250995 sschnee 83003 4840890 0x3C33 82614 5271253 +armo9494 82501 5806056 BRAVONE 81239 5054681 nssy 76497 5259388 +thirdlife 76478 1544524 +Calis007 76457 4281018 +jromang 75885 5230523 teddybaer 75125 5407666 -jromang 74796 5175825 Pking_cda 73776 5293873 -Calis007 72477 4088576 +Wolfgang 72750 4538670 +sebastronomy 70784 1329428 solarlight 70517 5028306 dv8silencer 70287 3883992 Bobo1239 68515 4652287 +yurikvelo 67651 4578970 manap 66273 4121774 -yurikvelo 65716 4457300 tinker 64333 4268790 -Wolfgang 62644 3817410 qurashee 61208 3429862 robnjr 57262 4053117 +megaman7de 57023 3525850 Freja 56938 3733019 +MaxKlaxxMiner 56279 3410158 ttruscott 56010 3680085 rkl 55132 4164467 renouve 53811 3501516 -megaman7de 52434 3243016 -MaxKlaxxMiner 51977 3153032 +tolkki963 53294 3354682 +DMBK 52963 3933332 finfish 51360 3370515 eva42 51272 3599691 +Spprtr 51139 3299983 eastorwest 51058 3451555 rap 49985 3219146 pb00067 49727 3298270 -Spprtr 48920 3161711 bigpen0r 47667 3336927 ronaldjerum 47654 3240695 biffhero 46564 3111352 @@ -102,18 +108,17 @@ Antihistamine 41788 2761312 mhunt 41735 2691355 homyur 39893 2850481 gri 39871 2515779 -armo9494 39064 2832326 -oryx 38867 2976992 +oryx 39602 3024830 SC 37299 2731694 Garf 37213 2986270 -tolkki963 37059 2154330 +Dubslow 36714 2409254 csnodgrass 36207 2688994 jmdana 36157 2210661 +markkulix 35994 2226860 strelock 34716 2074055 -DMBK 34010 2482916 EthanOConnor 33370 2090311 slakovv 32915 2021889 -gopeto 30993 2028106 +gopeto 31078 2033362 manapbk 30987 1810399 Prcuvu 30377 2170122 anst 30301 2190091 @@ -121,7 +126,8 @@ jkiiski 30136 1904470 hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 -ncfish1 29105 1704011 +MarcusTullius 28611 1646671 +spcc 28241 1821198 belzedar94 27935 1789106 OuaisBla 27636 1578800 chriswk 26902 1868317 @@ -131,14 +137,16 @@ yorkman 26193 1992080 SFTUser 25182 1675689 nabildanial 24942 1519409 Sharaf_DG 24765 1786697 -rodneyc 24275 1410450 +rodneyc 24375 1416258 +Ulysses 24017 1626140 agg177 23890 1395014 JanErik 23408 1703875 +Ente 23403 1660988 +kdave 23392 1630462 Isidor 23388 1680691 Norabor 23339 1602636 -Ente 23270 1651432 cisco2015 22897 1762669 -MarcusTullius 22688 1274821 +Wencey 22573 1121406 Zirie 22542 1472937 team-oh 22272 1636708 MazeOfGalious 21978 1629593 @@ -150,19 +158,16 @@ nesoneg 21494 1463031 Roady 21323 1433822 sphinx 21211 1384728 user213718 21196 1397710 -spcc 21065 1311338 jjoshua2 21001 1423089 horst.prack 20878 1465656 0xB00B1ES 20590 1208666 j3corre 20405 941444 -kdave 20364 1389254 Adrian.Schmidt123 20316 1281436 -Ulysses 20217 1351500 -markkulix 19976 1115258 +jcAEie 20221 1504162 wei 19973 1745989 rstoesser 19569 1293588 eudhan 19274 1283717 -fishtester 18995 1238686 +fishtester 19145 1242668 vulcan 18871 1729392 jundery 18445 1115855 iisiraider 18247 1101015 @@ -170,29 +175,31 @@ ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17353 978595 -Wencey 17125 805964 +AndreasKrug 17191 1317997 DragonLord 17014 1162790 -thirdlife 16996 447356 +Jopo12321 16966 944924 +GPUex 16744 1077826 +xwziegtm 16608 1276372 IgorLeMasson 16064 1147232 ako027ako 15671 1173203 -AndreasKrug 15550 1194497 +jsys14 15474 917092 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 -scuzzi 14928 953313 +scuzzi 15112 960373 OssumOpossum 14857 1007129 Karby 14808 867120 -jsys14 14652 855642 enedene 14476 905279 bpfliegel 14298 884523 mpx86 14019 759568 jpulman 13982 870599 +Naven94 13879 811552 +Karpovbot 13808 734276 crocogoat 13803 1117422 joster 13794 950160 Nesa92 13786 1114691 mbeier 13650 1044928 Hjax 13535 915487 Dark_wizzie 13422 1007152 -Jopo12321 13367 678852 Rudolphous 13244 883140 Machariel 13010 863104 mabichito 12903 749391 @@ -200,36 +207,39 @@ thijsk 12886 722107 AdrianSA 12860 804972 infinigon 12807 937332 Flopzee 12698 894821 +pirt 12551 965597 fatmurphy 12547 853210 SapphireBrand 12416 969604 modolief 12386 896470 Farseer 12249 694108 pgontarz 12151 848794 -pirt 12008 923149 stocky 11954 699440 mschmidt 11941 803401 +Oakwen 11925 818865 +MooTheCow 11851 772628 +deflectooor 11642 565132 dbernier 11609 818636 +Skiff84 11604 602786 Maxim 11543 836024 infinity 11470 727027 +FormazChar 11430 856559 aga 11409 695071 +Jackfish 11403 750526 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 -FormazChar 11349 850327 d64 11263 789184 -MooTheCow 11237 720174 +qoo_charly_cai 11127 671959 snicolet 11106 869170 ali-al-zhrani 11098 768494 whelanh 11067 235676 -Jackfish 10978 720078 -deflectooor 10886 520116 basepi 10637 744851 Cubox 10621 826448 +Alb11747 10558 689794 michaelrpg 10509 739239 OIVAS7572 10420 995586 +Garruk 10343 704723 dzjp 10343 732529 -Garruk 10334 704065 ols 10259 570669 lbraesch 10252 647825 -qoo_charly_cai 10212 620407 -Naven94 10069 503192 +Karmatron 10195 661432 From e639c4557786513018f625ff96f04b63157a85c1 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 31 Jul 2022 14:58:13 +0200 Subject: [PATCH 060/678] Update WDL model for current SF This updates the WDL model based on the LTC statistics for the two weeks (3M games). for old results see: https://github.com/official-stockfish/Stockfish/pull/3981 https://github.com/official-stockfish/Stockfish/pull/3582 https://github.com/official-stockfish/Stockfish/pull/2778 closes https://github.com/official-stockfish/Stockfish/pull/4115 No functional change. --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index c0bacfaf..ec106ee9 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -207,8 +207,8 @@ namespace { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - double as[] = {-1.17202460e-01, 5.94729104e-01, 1.12065546e+01, 1.22606222e+02}; - double bs[] = {-1.79066759, 11.30759193, -17.43677612, 36.47147479}; + double as[] = { 0.50379905, -4.12755858, 18.95487051, 152.00733652}; + double bs[] = {-1.71790378, 10.71543602, -17.05515898, 41.15680404}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From 0a01dd044f8a8f291127ebf60462773bf12c90c7 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Tue, 9 Aug 2022 20:56:13 +0200 Subject: [PATCH 061/678] Cleanup code This PR includes following cleanups: - Remove the unused depth variable in the thread class. - cleanup ValueList (added from mstembera) closes https://github.com/official-stockfish/Stockfish/pull/4127 No functional change. --- src/evaluate.cpp | 1 - src/misc.h | 13 ------------- src/position.cpp | 6 ++++-- src/search.cpp | 1 - src/thread.h | 2 +- 5 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9e3eaba5..7d587675 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1114,7 +1114,6 @@ std::string Eval::trace(Position& pos) { std::memset(scores, 0, sizeof(scores)); // Reset any global variable used in eval - pos.this_thread()->depth = 0; pos.this_thread()->trend = SCORE_ZERO; pos.this_thread()->bestValue = VALUE_ZERO; pos.this_thread()->optimism[WHITE] = VALUE_ZERO; diff --git a/src/misc.h b/src/misc.h index 2fd2b408..fe1143de 100644 --- a/src/misc.h +++ b/src/misc.h @@ -116,23 +116,10 @@ class ValueList { public: std::size_t size() const { return size_; } - void resize(std::size_t newSize) { size_ = newSize; } void push_back(const T& value) { values_[size_++] = value; } - T& operator[](std::size_t index) { return values_[index]; } - T* begin() { return values_; } - T* end() { return values_ + size_; } - const T& operator[](std::size_t index) const { return values_[index]; } const T* begin() const { return values_; } const T* end() const { return values_ + size_; } - void swap(ValueList& other) { - const std::size_t maxSize = std::max(size_, other.size_); - for (std::size_t i = 0; i < maxSize; ++i) { - std::swap(values_[i], other.values_[i]); - } - std::swap(size_, other.size_); - } - private: T values_[MaxSize]; std::size_t size_ = 0; diff --git a/src/position.cpp b/src/position.cpp index ec9229ea..08ed1a89 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1099,10 +1099,12 @@ bool Position::see_ge(Move m, Value threshold) const { // Don't allow pinned pieces to attack as long as there are // pinners on their original square. if (pinners(~stm) & occupied) + { stmAttackers &= ~blockers_for_king(stm); - if (!stmAttackers) - break; + if (!stmAttackers) + break; + } res ^= 1; diff --git a/src/search.cpp b/src/search.cpp index 7c0601e6..565fba0f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -565,7 +565,6 @@ namespace { // Step 1. Initialize node Thread* thisThread = pos.this_thread(); - thisThread->depth = depth; ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); diff --git a/src/thread.h b/src/thread.h index 9e9cd488..c430a818 100644 --- a/src/thread.h +++ b/src/thread.h @@ -69,7 +69,7 @@ public: Position rootPos; StateInfo rootState; Search::RootMoves rootMoves; - Depth rootDepth, completedDepth, depth, previousDepth; + Depth rootDepth, completedDepth, previousDepth; Value rootDelta; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; From 1054a483ca0c560d30fb61617c0192cb4cd31528 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 31 Jul 2022 16:55:07 +0200 Subject: [PATCH 062/678] Remove an unneeded randomization of evals. most of the effect comes from the randomization of 3-folds. passed STC: https://tests.stockfishchess.org/tests/view/62e697e97e84186e5d19af6f LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 572976 W: 153168 L: 153539 D: 266269 Ptnml(0-2): 2505, 64783, 152364, 64250, 2586 passed LTC: https://tests.stockfishchess.org/tests/view/62ee5977523c86dcd6957154 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 704808 W: 191212 L: 191680 D: 321916 Ptnml(0-2): 1340, 70579, 208972, 70235, 1278 closes https://github.com/official-stockfish/Stockfish/pull/4128 Bench: 5868987 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 565fba0f..4d56d14d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -741,10 +741,6 @@ namespace { else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost complexity = abs(ss->staticEval - pos.psq_eg_stm()); - // Randomize draw evaluation - if (eval == VALUE_DRAW) - eval = value_draw(thisThread); - // ttValue can be used as a better position evaluation (~4 Elo) if ( ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) From 4568f6369bfb94d8a105a8074e43e8d79c57eaee Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 8 Aug 2022 21:33:59 -0700 Subject: [PATCH 063/678] Report longest PV lines for multithreaded search In case several threads find the same bestmove, report the longest PV line found. closes https://github.com/official-stockfish/Stockfish/pull/4126 No functional change. --- src/thread.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/thread.cpp b/src/thread.cpp index 08a78db5..c834fa9f 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -237,7 +237,9 @@ Thread* ThreadPool::get_best_thread() const { } else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])) + && ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] + || ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] + && th->rootMoves[0].pv.size() > bestThread->rootMoves[0].pv.size())))) bestThread = th; } From 3370f69881564025d933668ea5d407af0c5dcea2 Mon Sep 17 00:00:00 2001 From: mckx00 Date: Sat, 13 Aug 2022 05:32:30 -0700 Subject: [PATCH 064/678] Make LMR code easier to follow Remove flags doFullDepthSearch and didLMR, and reorder instruction. Small measured speedup. Closes https://github.com/official-stockfish/Stockfish/pull/4129 No functional change. --- src/search.cpp | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4d56d14d..7244b8d6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -558,8 +558,8 @@ namespace { Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, didLMR, priorCapture, singularQuietLMR; - bool capture, doFullDepthSearch, moveCountPruning, ttCapture; + bool givesCheck, improving, priorCapture, singularQuietLMR; + bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount, improvement, complexity; @@ -1127,8 +1127,6 @@ moves_loop: // When in check, search starts here // Step 16. Make the move pos.do_move(move, st, givesCheck); - bool doDeeperSearch = false; - // Step 17. Late moves reduction / extension (LMR, ~98 Elo) // We use various heuristics for the sons of a node after the first son has // been searched. In general we would like to reduce them, but there are many @@ -1187,25 +1185,12 @@ moves_loop: // When in check, search starts here value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); - // If the son is reduced and fails high it will be re-searched at full depth - doFullDepthSearch = value > alpha && d < newDepth; - doDeeperSearch = value > (alpha + 78 + 11 * (newDepth - d)); - didLMR = true; - } - else - { - doFullDepthSearch = !PvNode || moveCount > 1; - didLMR = false; - } - - // Step 18. Full depth search when LMR is skipped or fails high - if (doFullDepthSearch) - { - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch, !cutNode); - - // If the move passed LMR update its stats - if (didLMR) + // Do full depth search when reduced LMR search fails high + if (value > alpha && d < newDepth) { + const bool doDeeperSearch = value > (alpha + 78 + 11 * (newDepth - d)); + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch, !cutNode); + int bonus = value > alpha ? stat_bonus(newDepth) : -stat_bonus(newDepth); @@ -1216,6 +1201,12 @@ moves_loop: // When in check, search starts here } } + // Step 18. Full depth search when LMR is skipped + else if (!PvNode || moveCount > 1) + { + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + } + // For PV nodes only, do a full PV search on the first move or after a fail // high (in the latter case search only if value < beta), otherwise let the // parent node fail low with value <= alpha and try another move. From 5f290352cd91beb9374671f6ae489165bd8ac2c0 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 15 Aug 2022 08:52:55 +0300 Subject: [PATCH 065/678] Simplify away smp adjustment in TT use Passed STC https://tests.stockfishchess.org/tests/view/62f7d81f23d42b50a8dab568 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 98160 W: 26307 L: 26165 D: 45688 Ptnml(0-2): 201, 10282, 27960, 10448, 189 Passed LTC https://tests.stockfishchess.org/tests/view/62f8d1a623d42b50a8dad4fb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 81544 W: 22346 L: 22200 D: 36998 Ptnml(0-2): 44, 7542, 25446, 7704, 36 closes https://github.com/official-stockfish/Stockfish/pull/4131 No functional change (single threaded). --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 7244b8d6..6d78403f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -636,7 +636,7 @@ namespace { // At non-PV nodes we check for an early TT cutoff if ( !PvNode && ss->ttHit - && tte->depth() > depth - ((int)thisThread->id() & 0x1) - (tte->bound() == BOUND_EXACT) + && tte->depth() > depth - (tte->bound() == BOUND_EXACT) && ttValue != VALUE_NONE // Possible in case of TT access race && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { From 15ac117ac492e3147b391aa0ee3665fe8876be63 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 12 Aug 2022 12:31:49 +0200 Subject: [PATCH 066/678] Simplify the use of classical eval no benefit of the fallback term (exercised rarely). Cleanup the associated code. passed STC https://tests.stockfishchess.org/tests/view/62f62c2b6f0a08af9f776367 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 67832 W: 18334 L: 18148 D: 31350 Ptnml(0-2): 369, 7171, 18609, 7439, 328 passed LTC https://tests.stockfishchess.org/tests/view/62f68beb6f0a08af9f77710e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 104664 W: 28363 L: 28233 D: 48068 Ptnml(0-2): 169, 10162, 31511, 10350, 140 closes https://github.com/official-stockfish/Stockfish/pull/4132 Bench: 6079565 --- src/evaluate.cpp | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 7d587675..eaad4d55 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1053,34 +1053,29 @@ Value Eval::evaluate(const Position& pos, int* complexity) { Value v; Color stm = pos.side_to_move(); Value psq = pos.psq_eg_stm(); - // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, - // but we switch to NNUE during long shuffling or with high material on the board. - bool useClassical = (pos.count() > 7) - && abs(psq) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count()); - // Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical, + // Deciding between classical and NNUE eval: for high PSQ imbalance we use classical, // but we switch to NNUE during long shuffling or with high material on the board. - if (!useNNUE || useClassical) - { + bool useClassical = !useNNUE || + ((pos.count() > 7) + && abs(psq) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count())); + + if (useClassical) v = Evaluation(pos).value(); - useClassical = abs(v) >= 297; - } - - // If result of a classical evaluation is much lower than threshold fall back to NNUE - if (useNNUE && !useClassical) + else { - int nnueComplexity; - int scale = 1064 + 106 * pos.non_pawn_material() / 5120; - Value optimism = pos.this_thread()->optimism[stm]; + int nnueComplexity; + int scale = 1064 + 106 * pos.non_pawn_material() / 5120; + Value optimism = pos.this_thread()->optimism[stm]; - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - // Blend nnue complexity with (semi)classical complexity - nnueComplexity = (104 * nnueComplexity + 131 * abs(nnue - psq)) / 256; - if (complexity) // Return hybrid NNUE complexity to caller - *complexity = nnueComplexity; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + // Blend nnue complexity with (semi)classical complexity + nnueComplexity = (104 * nnueComplexity + 131 * abs(nnue - psq)) / 256; + if (complexity) // Return hybrid NNUE complexity to caller + *complexity = nnueComplexity; - optimism = optimism * (269 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 754)) / 1024; + optimism = optimism * (269 + nnueComplexity) / 256; + v = (nnue * scale + optimism * (scale - 754)) / 1024; } // Damp down the evaluation linearly when shuffling @@ -1091,7 +1086,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { // When not using NNUE, return classical complexity to caller if (complexity && (!useNNUE || useClassical)) - *complexity = abs(v - psq); + *complexity = abs(v - psq); return v; } From 02ef1f4496965b5ad8c26ac6bc18245eaffae2ea Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 13 Aug 2022 17:01:11 -0700 Subject: [PATCH 067/678] Make key_after() more consistent with key() STC: https://tests.stockfishchess.org/tests/view/62f8547123d42b50a8dac674 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 176640 W: 47699 L: 47189 D: 81752 Ptnml(0-2): 776, 18599, 49129, 18971, 845 A bug fix plus non functional speed optimization. Position::key_after(Move m) is now consistent with Position::key() thus prefetching correct TT entries which speeds things up. Related PR #3759 closes https://github.com/official-stockfish/Stockfish/pull/4130 No functional change --- src/position.cpp | 5 ++++- src/position.h | 12 ++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 08ed1a89..62e6e238 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1054,7 +1054,10 @@ Key Position::key_after(Move m) const { if (captured) k ^= Zobrist::psq[captured][to]; - return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; + + return (captured || type_of(pc) == PAWN) + ? k : adjust_key50(k); } diff --git a/src/position.h b/src/position.h index 510875d8..078ff5b7 100644 --- a/src/position.h +++ b/src/position.h @@ -185,6 +185,8 @@ private: void move_piece(Square from, Square to); template void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + template + Key adjust_key50(Key k) const; // Data members Piece board[SQUARE_NB]; @@ -327,8 +329,14 @@ inline int Position::pawns_on_same_color_squares(Color c, Square s) const { } inline Key Position::key() const { - return st->rule50 < 14 ? st->key - : st->key ^ make_key((st->rule50 - 14) / 8); + return adjust_key50(st->key); +} + +template +inline Key Position::adjust_key50(Key k) const +{ + return st->rule50 < 14 - AfterMove + ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); } inline Key Position::pawn_key() const { From 97860cb575e3e71791b9a99b94f9f7d7a0dbf25e Mon Sep 17 00:00:00 2001 From: dav1312 <63931154+dav1312@users.noreply.github.com> Date: Fri, 26 Aug 2022 11:26:31 +0200 Subject: [PATCH 068/678] Disable ARM CI tests Temporarily disable ARM CI tests until a mitigation is implemented closes https://github.com/official-stockfish/Stockfish/pull/4148 No functional change. --- .github/workflows/stockfish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 782e3f2b..b007ec78 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -50,7 +50,7 @@ jobs: os: ubuntu-20.04, compiler: aarch64-linux-android21-clang++, comp: ndk, - run_armv8_tests: true, + run_armv8_tests: false, shell: 'bash {0}' } - { @@ -58,7 +58,7 @@ jobs: os: ubuntu-20.04, compiler: armv7a-linux-androideabi21-clang++, comp: ndk, - run_armv7_tests: true, + run_armv7_tests: false, shell: 'bash {0}' } - { From dddf8fc2b4867bad8b01fe3a18266c3677f3f726 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 3 Sep 2022 11:15:40 +0200 Subject: [PATCH 069/678] Increase the maximum number of threads to 1024 relatively soon servers with 512 threads will be available 'quite commonly', anticipate even more threads, and increase our current maximum from 512 to 1024. closes https://github.com/official-stockfish/Stockfish/pull/4152 No functional change. --- src/ucioption.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 922fa34f..9fb48345 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -61,7 +61,7 @@ void init(OptionsMap& o) { constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; o["Debug Log File"] << Option("", on_logger); - o["Threads"] << Option(1, 1, 512, on_threads); + o["Threads"] << Option(1, 1, 1024, on_threads); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Clear Hash"] << Option(on_clear_hash); o["Ponder"] << Option(false); From a4d18d23a9f7626234fcfbb6b23b3d0b8d1a9441 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 3 Sep 2022 11:03:09 +0200 Subject: [PATCH 070/678] Provide network download fallback in case the base infrastructure for providing the networks https://tests.stockfishchess.org/nns is down, use an alternate github repo for downloading networks during the build. fixes #4149 fixes #4140 closes https://github.com/official-stockfish/Stockfish/pull/4151 No functional change --- src/Makefile | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/Makefile b/src/Makefile index ff2452d6..8315f33d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -827,24 +827,34 @@ clean: objclean profileclean net: $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) @echo "Default net: $(nnuenet)" - $(eval nnuedownloadurl := https://tests.stockfishchess.org/api/nn/$(nnuenet)) + $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) + $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) - @if test -f "$(nnuenet)"; then \ - echo "Already available."; \ - else \ - if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ - else \ - echo "Downloading $(nnuedownloadurl)"; $(curl_or_wget) $(nnuedownloadurl) > $(nnuenet);\ - fi; \ - fi; + @if [ "x$(curl_or_wget)" = "x" ]; then \ + echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ + fi $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) - @if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Failed download or $(nnuenet) corrupted, please delete!"; exit 1; \ - fi \ - else \ - echo "shasum / sha256sum not found, skipping net validation"; \ + @if [ "x$(shasum_command)" = "x" ]; then \ + echo "shasum / sha256sum not found, skipping net validation"; \ + fi + @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ + if test -f "$(nnuenet)"; then \ + echo "$(nnuenet) available."; \ + else \ + if [ "x$(curl_or_wget)" != "x" ]; then \ + echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ + fi; \ + fi; \ + if [ "x$(shasum_command)" != "x" ]; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing failed download"; rm -f $(nnuenet); \ + else \ + echo "Network validated"; break; \ + fi; \ + fi; \ + done + @if ! test -f "$(nnuenet)"; then \ + echo "Failed to download $(nnuenet)."; \ fi # clean binaries and objects From 5eeb96d0e7d54686376305029c477c42478aa1d5 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 31 Aug 2022 14:45:39 +0300 Subject: [PATCH 071/678] VLTC tuning Tuning some parameters that scale well with longer time control: Failed STC: https://tests.stockfishchess.org/tests/view/6313424d8202a039920e130a LLR: -2.94 (-2.94,2.94) <-1.75,0.25> Total: 42680 W: 11231 L: 11540 D: 19909 Ptnml(0-2): 191, 5008, 11232, 4737, 172 Passed LTC: https://tests.stockfishchess.org/tests/view/6311e2cd874169ca52ae7933 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 53448 W: 14782 L: 14437 D: 24229 Ptnml(0-2): 101, 5214, 15740, 5577, 92 Passed VLTC: https://tests.stockfishchess.org/tests/view/6312530cfa99a92e3002c927 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 123336 W: 33465 L: 33007 D: 56864 Ptnml(0-2): 38, 11466, 38204, 11920, 40 closes https://github.com/official-stockfish/Stockfish/pull/4154 Bench: 5609606 --- src/search.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6d78403f..cd7369c8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -63,7 +63,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(168 * (d - improving)); + return Value(174 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -362,7 +362,7 @@ void Thread::search() { trend = (us == WHITE ? make_score(tr, tr / 2) : -make_score(tr, tr / 2)); - int opt = sigmoid(prev, 8, 17, 144, 13966, 183); + int opt = sigmoid(prev, 8, 17, 144, 15012, 183); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; } @@ -778,7 +778,7 @@ namespace { // return a fail low. if ( !PvNode && depth <= 7 - && eval < alpha - 348 - 258 * depth * depth) + && eval < alpha - 341 - 267 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -797,7 +797,7 @@ namespace { // Step 9. Null move search with verification search (~22 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 14695 + && (ss-1)->statScore < 15344 && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 15 * depth - improvement / 15 + 201 + complexity / 24 @@ -808,7 +808,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 147, 5) + depth / 3 + 4 - (complexity > 650); + Depth R = std::min(int(eval - beta) / 152, 5) + depth / 3 + 4 - (complexity > 650); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -844,7 +844,7 @@ namespace { } } - probCutBeta = beta + 179 - 46 * improving; + probCutBeta = beta + 173 - 46 * improving; // Step 10. ProbCut (~4 Elo) // If we have a good enough capture and a reduced search returns a value @@ -906,9 +906,9 @@ namespace { return qsearch(pos, ss, alpha, beta); if ( cutNode - && depth >= 8 + && depth >= 9 && !ttMove) - depth--; + depth -= 2; moves_loop: // When in check, search starts here @@ -1009,7 +1009,7 @@ moves_loop: // When in check, search starts here && !PvNode && lmrDepth < 6 && !ss->inCheck - && ss->staticEval + 281 + 179 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + && ss->staticEval + 277 + 187 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha) continue; @@ -1173,7 +1173,7 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4334; + - 4560; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) r -= ss->statScore / 15914; @@ -1188,7 +1188,7 @@ moves_loop: // When in check, search starts here // Do full depth search when reduced LMR search fails high if (value > alpha && d < newDepth) { - const bool doDeeperSearch = value > (alpha + 78 + 11 * (newDepth - d)); + const bool doDeeperSearch = value > (alpha + 73 + 12 * (newDepth - d)); value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch, !cutNode); int bonus = value > alpha ? stat_bonus(newDepth) @@ -1337,14 +1337,14 @@ moves_loop: // When in check, search starts here quietsSearched, quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low - else if ( (depth >= 4 || PvNode) + else if ( (depth >= 5 || PvNode) && !priorCapture) { //Assign extra bonus if current node is PvNode or cutNode //or fail low was really bad bool extraBonus = PvNode || cutNode - || bestValue < alpha - 70 * depth; + || bestValue < alpha - 66 * depth; update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus)); } From eaf2c8207fff9699201085fec123c114f454f798 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 11 Sep 2022 01:45:37 +0300 Subject: [PATCH 072/678] Further LTC tuning of search parameters Tuning done by bigpenor with some hand adjustments on top by Viz. Had a good performance at fixed games 180+1.8: https://tests.stockfishchess.org/tests/view/631836b437f41b13973d7da1 Elo: 1.35 +-1.2 (95%) LOS: 98.6% Total: 60000 W: 16422 L: 16189 D: 27389 Ptnml(0-2): 39, 5335, 18992, 5622, 12 nElo: 3.13 +-2.8 (95%) PairsRatio: 1.05 Passed 60+0.6 8 threads SPRT: https://tests.stockfishchess.org/tests/view/631ba0ff74bc4fe483a99db3 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 29712 W: 8301 L: 8039 D: 13372 Ptnml(0-2): 12, 2318, 9925, 2598, 3 closes https://github.com/official-stockfish/Stockfish/pull/4160 bench 3938073 --- src/search.cpp | 92 +++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cd7369c8..162a0994 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -63,7 +63,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(174 * (d - improving)); + return Value(165 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -71,7 +71,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1463 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 1010); + return (r + 1642 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 916); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min((8 * d + 240) * d - 276 , 1907); + return std::min((12 * d + 282) * d - 349 , 1594); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -158,7 +158,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.81 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((20.26 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -307,10 +307,10 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); - complexityAverage.set(174, 1); + complexityAverage.set(155, 1); trend = SCORE_ZERO; - optimism[ us] = Value(39); + optimism[ us] = Value(37); optimism[~us] = -optimism[us]; int searchAgainCounter = 0; @@ -353,16 +353,16 @@ void Thread::search() { if (rootDepth >= 4) { Value prev = rootMoves[pvIdx].averageScore; - delta = Value(16) + int(prev) * prev / 19178; + delta = Value(10) + int(prev) * prev / 15620; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust trend and optimism based on root move's previousScore - int tr = sigmoid(prev, 3, 8, 90, 125, 1); + int tr = sigmoid(prev, 3, 10, 89, 116, 1); trend = (us == WHITE ? make_score(tr, tr / 2) : -make_score(tr, tr / 2)); - int opt = sigmoid(prev, 8, 17, 144, 15012, 183); + int opt = sigmoid(prev, 7, 20, 169, 19350, 164); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; } @@ -465,16 +465,16 @@ void Thread::search() { && !Threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (69 + 12 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 781.4; + double fallingEval = (71 + 12 * (mainThread->bestPreviousAverageScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 656.7; fallingEval = std::clamp(fallingEval, 0.5, 1.5); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 10 < completedDepth ? 1.63 : 0.73; - double reduction = (1.56 + mainThread->previousTimeReduction) / (2.20 * timeReduction); + timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.37 : 0.65; + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.15 * timeReduction); double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size(); int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::min(1.0 + (complexity - 277) / 1819.1, 1.5); + double complexPosition = std::min(1.0 + (complexity - 261) / 1738.7, 1.5); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; @@ -495,7 +495,7 @@ void Thread::search() { } else if ( Threads.increaseDepth && !mainThread->ponder - && Time.elapsed() > totalTime * 0.43) + && Time.elapsed() > totalTime * 0.53) Threads.increaseDepth = false; else Threads.increaseDepth = true; @@ -760,7 +760,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~3 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-16 * int((ss-1)->staticEval + ss->staticEval), -2000, 2000); + int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1914, 1914); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -770,7 +770,7 @@ namespace { // margin and the improving flag are used in various pruning heuristics. improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 175; + : 168; improving = improvement > 0; // Step 7. Razoring. @@ -778,7 +778,7 @@ namespace { // return a fail low. if ( !PvNode && depth <= 7 - && eval < alpha - 341 - 267 * depth * depth) + && eval < alpha - 369 - 254 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -789,18 +789,18 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 8 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 256 >= beta + && eval - futility_margin(depth, improving) - (ss-1)->statScore / 303 >= beta && eval >= beta - && eval < 26305) // larger than VALUE_KNOWN_WIN, but smaller than TB wins. + && eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins. return eval; // Step 9. Null move search with verification search (~22 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 15344 + && (ss-1)->statScore < 17139 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 15 * depth - improvement / 15 + 201 + complexity / 24 + && ss->staticEval >= beta - 20 * depth - improvement / 13 + 233 + complexity / 25 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -808,7 +808,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 152, 5) + depth / 3 + 4 - (complexity > 650); + Depth R = std::min(int(eval - beta) / 168, 7) + depth / 3 + 4 - (complexity > 861); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -844,7 +844,7 @@ namespace { } } - probCutBeta = beta + 173 - 46 * improving; + probCutBeta = beta + 191 - 54 * improving; // Step 10. ProbCut (~4 Elo) // If we have a good enough capture and a reduced search returns a value @@ -913,7 +913,7 @@ namespace { moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~0 Elo) - probCutBeta = beta + 481; + probCutBeta = beta + 417; if ( ss->inCheck && !PvNode && depth >= 2 @@ -1007,14 +1007,14 @@ moves_loop: // When in check, search starts here if ( !pos.empty(to_sq(move)) && !givesCheck && !PvNode - && lmrDepth < 6 + && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 277 + 187 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + && ss->staticEval + 180 + 201 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha) continue; // SEE based pruning (~9 Elo) - if (!pos.see_ge(move, Value(-203) * depth)) + if (!pos.see_ge(move, Value(-222) * depth)) continue; } else @@ -1032,12 +1032,12 @@ moves_loop: // When in check, search starts here // Futility pruning: parent node (~9 Elo) if ( !ss->inCheck - && lmrDepth < 11 - && ss->staticEval + 122 + 138 * lmrDepth + history / 60 <= alpha) + && lmrDepth < 13 + && ss->staticEval + 106 + 145 * lmrDepth + history / 52 <= alpha) continue; // Prune moves with negative SEE (~3 Elo) - if (!pos.see_ge(move, Value(-25 * lmrDepth * lmrDepth - 20 * lmrDepth))) + if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } @@ -1052,7 +1052,7 @@ moves_loop: // When in check, search starts here // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. if ( !rootNode - && depth >= 4 - (thisThread->previousDepth > 27) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->previousDepth > 24) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ @@ -1074,8 +1074,8 @@ moves_loop: // When in check, search starts here // Avoid search explosion by limiting the number of double extensions if ( !PvNode - && value < singularBeta - 26 - && ss->doubleExtensions <= 8) + && value < singularBeta - 25 + && ss->doubleExtensions <= 9) extension = 2; } @@ -1099,14 +1099,14 @@ moves_loop: // When in check, search starts here // Check extensions (~1 Elo) else if ( givesCheck && depth > 9 - && abs(ss->staticEval) > 71) + && abs(ss->staticEval) > 82) extension = 1; // Quiet ttMove extensions (~0 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5491) + && (*contHist[0])[movedPiece][to_sq(move)] >= 5177) extension = 1; } @@ -1159,7 +1159,7 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes based on depth if (PvNode) - r -= 1 + 15 / (3 + depth); + r -= 1 + 11 / (3 + depth); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) @@ -1173,10 +1173,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4560; + - 4433; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / 15914; + r -= ss->statScore / 13628; // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension @@ -1188,7 +1188,7 @@ moves_loop: // When in check, search starts here // Do full depth search when reduced LMR search fails high if (value > alpha && d < newDepth) { - const bool doDeeperSearch = value > (alpha + 73 + 12 * (newDepth - d)); + const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d)); value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch, !cutNode); int bonus = value > alpha ? stat_bonus(newDepth) @@ -1280,8 +1280,8 @@ moves_loop: // When in check, search starts here alpha = value; // Reduce other moves if we have found at least one score improvement - if ( depth > 2 - && depth < 7 + if ( depth > 1 + && depth < 6 && beta < VALUE_KNOWN_WIN && alpha > -VALUE_KNOWN_WIN) depth -= 1; @@ -1344,7 +1344,7 @@ moves_loop: // When in check, search starts here //or fail low was really bad bool extraBonus = PvNode || cutNode - || bestValue < alpha - 66 * depth; + || bestValue < alpha - 62 * depth; update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus)); } @@ -1471,7 +1471,7 @@ moves_loop: // When in check, search starts here if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 118; + futilityBase = bestValue + 153; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1675,8 +1675,8 @@ moves_loop: // When in check, search starts here if (!pos.capture(bestMove)) { - int bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus - : stat_bonus(depth); // smaller bonus + int bonus2 = bestValue > beta + 137 ? bonus1 // larger bonus + : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bonus2); From 9fa258ee1d36d5a432a7a3e857e0723d439528b0 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Fri, 19 Aug 2022 22:49:29 -0500 Subject: [PATCH 073/678] Razor also on PV nodes Simplification introduced by xoto10 blue LTC vs new master: https://tests.stockfishchess.org/tests/view/631ad4ef9cfa5e9b648d1b4e LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 59184 W: 16002 L: 15828 D: 27354 Ptnml(0-2): 65, 5777, 17747, 5925, 78 blue STC vs old master: https://tests.stockfishchess.org/tests/view/6306b87b902a848543334c25 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 213944 W: 57184 L: 57159 D: 99601 Ptnml(0-2): 877, 23448, 58331, 23405, 911 blue LTC vs old master: https://tests.stockfishchess.org/tests/view/63070e6b902a8485433357e7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 192080 W: 52050 L: 52006 D: 88024 Ptnml(0-2): 232, 18981, 57611, 18943, 273 closes https://github.com/official-stockfish/Stockfish/pull/4147 bench 4208975 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 162a0994..12576378 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -776,8 +776,7 @@ namespace { // Step 7. Razoring. // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if ( !PvNode - && depth <= 7 + if ( depth <= 7 && eval < alpha - 369 - 254 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); From 1591e5ac3b24f068f965471f17d7aae33ceaab9f Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 10 Sep 2022 12:30:25 +0300 Subject: [PATCH 074/678] Do less singular extensions for former PVnode Patch is a reintroduction of logic what was simplified a while ago in a slightly different form. Do bigger extension offset in case of non-pv node having a pv. passed STC https://tests.stockfishchess.org/tests/view/631977c048f27688a06e66d5 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 23296 W: 6404 L: 6108 D: 10784 Ptnml(0-2): 88, 2539, 6118, 2795, 108 passed LTC https://tests.stockfishchess.org/tests/view/631989cb48f27688a06e696c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 235592 W: 63890 L: 63188 D: 108514 Ptnml(0-2): 275, 23392, 69804, 24006, 319 closes https://github.com/official-stockfish/Stockfish/pull/4159 Bench: 3993611 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 12576378..c998f20d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1059,7 +1059,7 @@ moves_loop: // When in check, search starts here && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - 3 * depth; + Value singularBeta = ttValue - (3 + (ss->ttPv && !PvNode)) * depth; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; From 82bb21dc7a198609589ef0cc78d185f00f619a90 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 6 Sep 2022 15:02:35 -0700 Subject: [PATCH 075/678] Optimize AVX2 path in NNUE evaluation always selecting AffineTransform specialization for small inputs. A related patch was tested as Initially tested as a simplification STC https://tests.stockfishchess.org/tests/view/6317c3f437f41b13973d6dff LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 58072 W: 15619 L: 15425 D: 27028 Ptnml(0-2): 241, 6191, 15992, 6357, 255 Elo gain speedup test STC https://tests.stockfishchess.org/tests/view/63181c1b37f41b13973d79dc LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 184496 W: 49922 L: 49401 D: 85173 Ptnml(0-2): 851, 19397, 51208, 19964, 828 and this patch gained in testing speedup = +0.0071 P(speedup > 0) = 1.0000 on CPU: 16 x AMD Ryzen 9 3950X closes https://github.com/official-stockfish/Stockfish/pull/4158 No functional change --- src/nnue/layers/affine_transform.h | 16 +++++++++++----- src/{ => nnue/layers}/simd.h | 0 2 files changed, 11 insertions(+), 5 deletions(-) rename src/{ => nnue/layers}/simd.h (100%) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 9a992608..461a7b83 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -25,7 +25,7 @@ #include #include #include "../nnue_common.h" -#include "../../simd.h" +#include "simd.h" /* This file contains the definition for a fully connected layer (aka affine transform). @@ -151,9 +151,15 @@ namespace Stockfish::Eval::NNUE::Layers { template class AffineTransform; +#if defined (USE_AVX512) + constexpr IndexType LargeInputSize = 2 * 64; +#else + constexpr IndexType LargeInputSize = std::numeric_limits::max(); +#endif + // A specialization for large inputs. template - class AffineTransform(InDims, MaxSimdWidth) >= 2*64)>> { + class AffineTransform(InDims, MaxSimdWidth) >= LargeInputSize)>> { public: // Input/output type using InputType = std::uint8_t; @@ -170,7 +176,7 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions >= 128, "Something went wrong. This specialization should not have been chosen."); + static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization should not have been chosen."); #if defined (USE_AVX512) static constexpr const IndexType InputSimdWidth = 64; @@ -369,7 +375,7 @@ namespace Stockfish::Eval::NNUE::Layers { }; template - class AffineTransform(InDims, MaxSimdWidth) < 2*64)>> { + class AffineTransform(InDims, MaxSimdWidth) < LargeInputSize)>> { public: // Input/output type // Input/output type @@ -387,7 +393,7 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions < 128, "Something went wrong. This specialization should not have been chosen."); + static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization should not have been chosen."); #if defined (USE_SSSE3) static constexpr const IndexType OutputSimdWidth = SimdWidth / 4; diff --git a/src/simd.h b/src/nnue/layers/simd.h similarity index 100% rename from src/simd.h rename to src/nnue/layers/simd.h From 5a871e174f22894837c2363b5c215854ee155113 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 11 Sep 2022 21:28:12 +0200 Subject: [PATCH 076/678] Explicitly annotate a few variables as [[maybe_unused]], avoiding the (void)foo trick. closes https://github.com/official-stockfish/Stockfish/pull/4162 No functional change --- src/misc.cpp | 6 ++---- src/movepick.cpp | 10 +--------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 41c59b3f..d19cd840 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -378,10 +378,9 @@ void std_aligned_free(void* ptr) { #if defined(_WIN32) -static void* aligned_large_pages_alloc_windows(size_t allocSize) { +static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { #if !defined(_WIN64) - (void)allocSize; // suppress unused-parameter compiler warning return nullptr; #else @@ -626,8 +625,7 @@ string argv0; // path+name of the executable binary, as given by argv string binaryDirectory; // path of the executable directory string workingDirectory; // path of the working directory -void init(int argc, char* argv[]) { - (void)argc; +void init([[maybe_unused]] int argc, char* argv[]) { string pathSeparator; // extract the path+name of the executable binary diff --git a/src/movepick.cpp b/src/movepick.cpp index 60d041ab..d8d0612a 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -106,7 +106,7 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - Bitboard threatened, threatenedByPawn, threatenedByMinor, threatenedByRook; + [[maybe_unused]] Bitboard threatened, threatenedByPawn, threatenedByMinor, threatenedByRook; if constexpr (Type == QUIETS) { Color us = pos.side_to_move(); @@ -122,14 +122,6 @@ void MovePicker::score() { | (pos.pieces(us, ROOK) & threatenedByMinor) | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); } - else - { - // Silence unused variable warnings - (void) threatened; - (void) threatenedByPawn; - (void) threatenedByMinor; - (void) threatenedByRook; - } for (auto& m : *this) if constexpr (Type == CAPTURES) From 154e7afed0fe9c6f45a2aee8ef6f38d44076cb19 Mon Sep 17 00:00:00 2001 From: atumanian Date: Mon, 12 Sep 2022 20:16:54 +0300 Subject: [PATCH 077/678] Simplify trend and optimism. This patch simplifies the formulas used to compute the trend and optimism values before each search iteration. As a side effect, this removes the parameters which make the relationship between the displayed evaluation value and the expected game result asymmetric. I've also provided links to the results of isotonic regression analysis of the relationship between the evaluation and game result (statistical data and a graph) for both tests, which demonstrate that the new version has a more symmetric relationship: STC: [Data and graph](https://github.com/official-stockfish/Stockfish/discussions/4150#discussioncomment-3548954) LTC: [Data and graph](https://github.com/official-stockfish/Stockfish/discussions/4150#discussioncomment-3626311) See also https://github.com/official-stockfish/Stockfish/issues/4142 passed STC: https://tests.stockfishchess.org/tests/view/6313f44b8202a039920e27e6 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 108016 W: 28903 L: 28760 D: 50353 Ptnml(0-2): 461, 12075, 28850, 12104, 518 passed LTC: https://tests.stockfishchess.org/tests/view/631de45db85daa436625dfe6 LLR: 3.01 (-2.94,2.94) <-1.75,0.25> Total: 34792 W: 9412 L: 9209 D: 16171 Ptnml(0-2): 24, 3374, 10397, 3577, 24 Furthermore, this does not measurably impact Elo strength against weaker engines, as demonstrated in a match of master and patch vs SF13: This patch vs SF 13: https://tests.stockfishchess.org/tests/view/631fa34ae1612778c344c6eb Elo: 141.66 +-1.2 (95%) LOS: 100.0% Total: 100000 W: 48182 L: 9528 D: 42290 Ptnml(0-2): 96, 1426, 13277, 30130, 5071 nElo: 284.13 +-3.3 (95%) PairsRatio: 23.13 Master vs SF 13: https://tests.stockfishchess.org/tests/view/631fa3ece1612778c344c6ff Elo: 143.26 +-1.2 (95%) LOS: 100.0% Total: 100000 W: 48525 L: 9479 D: 41996 Ptnml(0-2): 94, 1537, 13098, 29771, 5500 nElo: 281.70 +-3.3 (95%) PairsRatio: 21.63 closes: https://github.com/official-stockfish/Stockfish/pull/4163 Bench: 4425574 --- src/misc.h | 27 --------------------------- src/search.cpp | 9 ++++----- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/misc.h b/src/misc.h index fe1143de..77b81d50 100644 --- a/src/misc.h +++ b/src/misc.h @@ -126,33 +126,6 @@ private: }; -/// sigmoid(t, x0, y0, C, P, Q) implements a sigmoid-like function using only integers, -/// with the following properties: -/// -/// - sigmoid is centered in (x0, y0) -/// - sigmoid has amplitude [-P/Q , P/Q] instead of [-1 , +1] -/// - limit is (y0 - P/Q) when t tends to -infinity -/// - limit is (y0 + P/Q) when t tends to +infinity -/// - the slope can be adjusted using C > 0, smaller C giving a steeper sigmoid -/// - the slope of the sigmoid when t = x0 is P/(Q*C) -/// - sigmoid is increasing with t when P > 0 and Q > 0 -/// - to get a decreasing sigmoid, change sign of P -/// - mean value of the sigmoid is y0 -/// -/// Use to draw the sigmoid - -inline int64_t sigmoid(int64_t t, int64_t x0, - int64_t y0, - int64_t C, - int64_t P, - int64_t Q) -{ - assert(C > 0); - assert(Q != 0); - return y0 + P * (t-x0) / (Q * (std::abs(t-x0) + C)) ; -} - - /// xorshift64star Pseudo-Random Number Generator /// This class is based on original code written and dedicated /// to the public domain by Sebastiano Vigna (2014). diff --git a/src/search.cpp b/src/search.cpp index c998f20d..0f524093 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -309,9 +309,8 @@ void Thread::search() { complexityAverage.set(155, 1); - trend = SCORE_ZERO; - optimism[ us] = Value(37); - optimism[~us] = -optimism[us]; + trend = SCORE_ZERO; + optimism[us] = optimism[~us] = VALUE_ZERO; int searchAgainCounter = 0; @@ -358,11 +357,11 @@ void Thread::search() { beta = std::min(prev + delta, VALUE_INFINITE); // Adjust trend and optimism based on root move's previousScore - int tr = sigmoid(prev, 3, 10, 89, 116, 1); + int tr = 116 * prev / (std::abs(prev) + 89); trend = (us == WHITE ? make_score(tr, tr / 2) : -make_score(tr, tr / 2)); - int opt = sigmoid(prev, 7, 20, 169, 19350, 164); + int opt = 118 * prev / (std::abs(prev) + 169); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; } From dc0c441b7c5178baa1bb0cc51b2d0294f58759a4 Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 14 Sep 2022 12:11:52 -0700 Subject: [PATCH 078/678] Prioritize checks in movepicker give a little bonus for moving pieces to squares where they give check STC: https://tests.stockfishchess.org/tests/view/631da742162491686d2e40b5 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 80072 W: 21753 L: 21368 D: 36951 Ptnml(0-2): 421, 8876, 21075, 9225, 439 LTC: https://tests.stockfishchess.org/tests/view/631dd9e6b85daa436625de1d LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 263480 W: 70916 L: 70158 D: 122406 Ptnml(0-2): 322, 26156, 78029, 26908, 325 similar ideas have been tested by Viz and Guenther closes https://github.com/official-stockfish/Stockfish/pull/4165 bench: 4326572 --- src/movepick.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index d8d0612a..636f4ba7 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -139,8 +139,8 @@ void MovePicker::score() { : type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000 : !(to_sq(m) & threatenedByPawn) ? 15000 : 0) - : 0); - + : 0) + + bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384; else // Type == EVASIONS { if (pos.capture(m)) From 29295ecfd357b5421eb4f273761dce8d9661f130 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 17 Sep 2022 05:45:54 -0700 Subject: [PATCH 079/678] Simplify EVASIONS scoring remove some multipliers & adjust, doesn't change the move ordering STC https://tests.stockfishchess.org/tests/view/6325c1c9b9c0caa5f4a759ae LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 192760 W: 51528 L: 51482 D: 89750 Ptnml(0-2): 642, 20490, 54148, 20380, 720 Credit to locutus2 closes https://github.com/official-stockfish/Stockfish/pull/4171 No functional change --- src/movepick.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 636f4ba7..e10454b0 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -145,11 +145,11 @@ void MovePicker::score() { { if (pos.capture(m)) m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(type_of(pos.moved_piece(m))); + - Value(type_of(pos.moved_piece(m))) + + (1 << 28); else - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)] - + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - - (1 << 28); + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]; } } From 70e51a5bc81bd15a73d2cb0be5008c7c02b1b0d5 Mon Sep 17 00:00:00 2001 From: Torsten Hellwig Date: Mon, 19 Sep 2022 18:22:56 +0200 Subject: [PATCH 080/678] Always output hashfull This removes the restriction that no hashfull information is printed within the first second of a search. On modern systems, a non-zero value is returned within 6 ms with default settings. passed STC: https://tests.stockfishchess.org/tests/view/63277b08b9c0caa5f4a798e4 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 290096 W: 77505 L: 77561 D: 135030 Ptnml(0-2): 1008, 30713, 81592, 30797, 938 closes https://github.com/official-stockfish/Stockfish/pull/4174 No functional change --- src/search.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0f524093..be0f3451 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1860,12 +1860,9 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); ss << " nodes " << nodesSearched - << " nps " << nodesSearched * 1000 / elapsed; - - if (elapsed > 1000) // Earlier makes little sense - ss << " hashfull " << TT.hashfull(); - - ss << " tbhits " << tbHits + << " nps " << nodesSearched * 1000 / elapsed + << " hashfull " << TT.hashfull() + << " tbhits " << tbHits << " time " << elapsed << " pv"; From 4339a756ac0a97563442ee4fb67694a5dfc66da4 Mon Sep 17 00:00:00 2001 From: Brad Knox <64992190+bknox83@users.noreply.github.com> Date: Tue, 20 Sep 2022 16:15:15 -0500 Subject: [PATCH 081/678] Update README.md Adding some svg icons and additional information, insert links as references closes https://github.com/official-stockfish/Stockfish/pull/4176 No functional change --- README.md | 114 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index f84b79d1..b076ab6b 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,27 @@ +
+ + [![Stockfish][stockfish128-logo]][website-link] + + [![Build][build-badge]][build-link] + [![License][license-badge]][license-link] +
+ [![Release][release-badge]][release-link] + [![Commits][commits-badge]][commits-link] +
+ [![Website][website-badge]][website-link] + [![Fishtest][fishtest-badge]][fishtest-link] + [![Discord][discord-badge]][discord-link] + +
+ ## Overview -[![Build Status](https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml/badge.svg)](https://github.com/official-stockfish/Stockfish/actions) - -[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine -derived from Glaurung 2.1. Stockfish is not a complete chess program and requires a -UCI-compatible graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid, -Cute Chess, eboard, Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order -to be used comfortably. Read the documentation for your GUI of choice for information -about how to use Stockfish with it. +[Stockfish][website-link] is a free, powerful UCI chess engine derived from +Glaurung 2.1. Stockfish is not a complete chess program and requires a UCI-compatible +graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, +Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably. +Read the documentation for your GUI of choice for informationabout how to use +Stockfish with it. The Stockfish engine features two evaluation functions for chess. The efficiently updatable neural network (NNUE) based evaluation is the default and by far the strongest. @@ -20,28 +34,25 @@ avx2, neon, or similar). This distribution of Stockfish consists of the following files: - * [README.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md), - the file you are currently reading. + * [README.md][readme-link], the file you are currently reading. - * [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), - a text file containing the GNU General Public License version 3. + * [Copying.txt][license-link], a text file containing the GNU General Public License + version 3. - * [AUTHORS](https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS), - a text file with the list of authors for the project + * [AUTHORS][authors-link], a text file with the list of authors for the project. - * [src](https://github.com/official-stockfish/Stockfish/tree/master/src), - a subdirectory containing the full source code, including a Makefile + * [src][src-link], a subdirectory containing the full source code, including a Makefile that can be used to compile Stockfish on Unix-like systems. - * a file with the .nnue extension, storing the neural network for the NNUE - evaluation. Binary distributions will have this file embedded. + * a file with the .nnue extension, storing the neural network for the NNUE evaluation. + Binary distributions will have this file embedded. ## The UCI protocol and available options The Universal Chess Interface (UCI) is a standard protocol used to communicate with a chess engine, and is the recommended way to do so for typical graphical user interfaces (GUI) or chess tools. Stockfish implements the majority of its options as described -in [the UCI protocol](https://www.shredderchess.com/download/div/uci.zip). +in [the UCI protocol][uci-link]. Developers can see the default values for UCI options available in Stockfish by typing `./stockfish uci` in a terminal, but the majority of users will typically see them and @@ -179,12 +190,10 @@ on the evaluations of millions of positions at moderate search depth. The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward. It can be evaluated efficiently on CPUs, and exploits the fact that only parts of the neural network need to be updated after a typical chess move. -[The nodchip repository](https://github.com/nodchip/Stockfish) provided the first -version of the needed tools to train and develop the NNUE networks. Today, more -advanced training tools are available in -[the nnue-pytorch repository](https://github.com/glinscott/nnue-pytorch/), -while data generation tools are available in -[a dedicated branch](https://github.com/official-stockfish/Stockfish/tree/tools). +[The nodchip repository][nodchip-link] provided the first version of the needed tools +to train and develop the NNUE networks. Today, more advanced training tools are +available in [the nnue-pytorch repository][pytorch-link], while data generation tools +are available in [a dedicated branch][tools-link]. On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation results in much stronger playing strength, even if the nodes per second computed by @@ -250,8 +259,8 @@ are already enabled, and no configuration is needed. ### Support on Windows The use of large pages requires "Lock Pages in Memory" privilege. See -[Enable the Lock Pages in Memory Option (Windows)](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows) -on how to enable this privilege, then run [RAMMap](https://docs.microsoft.com/en-us/sysinternals/downloads/rammap) +[Enable the Lock Pages in Memory Option (Windows)][lockpages-link] +on how to enable this privilege, then run [RAMMap][rammap-link] to double-check that large pages are used. We suggest that you reboot your computer after you have enabled large pages, because long Windows sessions suffer from memory fragmentation, which may prevent Stockfish @@ -294,26 +303,26 @@ effort. There are a few ways to help contribute to its growth. ### Donating hardware Improving Stockfish requires a massive amount of testing. You can donate -your hardware resources by installing the [Fishtest Worker](https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview) -and view the current tests on [Fishtest](https://tests.stockfishchess.org/tests). +your hardware resources by installing the [Fishtest Worker][worker-link] +and view the current tests on [Fishtest][fishtest-link]. ### Improving the code If you want to help improve the code, there are several valuable resources: -* [In this wiki,](https://www.chessprogramming.org) many techniques used in +* [In this wiki,][programming-link] many techniques used in Stockfish are explained with a lot of background information. -* [The section on Stockfish](https://www.chessprogramming.org/Stockfish) +* [The section on Stockfish][programmingsf-link] describes many features and techniques used by Stockfish. However, it is generic rather than being focused on Stockfish's precise implementation. Nevertheless, a helpful resource. -* The latest source can always be found on [GitHub](https://github.com/official-stockfish/Stockfish). -Discussions about Stockfish take place these days mainly in the [FishCooking](https://groups.google.com/forum/#!forum/fishcooking) -group and on the [Stockfish Discord channel](https://discord.gg/nv8gDtt). -The engine testing is done on [Fishtest](https://tests.stockfishchess.org/tests). -If you want to help improve Stockfish, please read this [guideline](https://github.com/glinscott/fishtest/wiki/Creating-my-first-test) +* The latest source can always be found on [GitHub][github-link]. +Discussions about Stockfish take place these days mainly in the [FishCooking][fishcooking-link] +group and on the [Stockfish Discord channel][discord-link]. +The engine testing is done on [Fishtest][fishtest-link]. +If you want to help improve Stockfish, please read this [guideline][guideline-link] first, where the basics of Stockfish development are explained. @@ -333,4 +342,35 @@ exact binary you are distributing. If you make any changes to the source code, these changes must also be made available under the GPL v3. For full details, read the copy of the GPL v3 found in the file named -[*Copying.txt*](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt). +[*Copying.txt*][license-link]. + +[authors-link]:https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS +[build-badge]:https://img.shields.io/github/workflow/status/official-stockfish/Stockfish/Stockfish?style=for-the-badge&label=stockfish&logo=github +[build-link]:https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml +[commits-badge]:https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge +[commits-link]:https://github.com/official-stockfish/Stockfish/commits/master +[discord-badge]:https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord +[discord-link]:https://discord.com/invite/aefaxmq +[fishcooking-link]:https://groups.google.com/g/fishcooking +[fishtest-badge]:https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests +[fishtest-link]:https://tests.stockfishchess.org/tests +[github-link]:https://github.com/official-stockfish/Stockfish +[guideline-link]:https://github.com/glinscott/fishtest/wiki/Creating-my-first-test +[license-badge]:https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success +[license-link]:https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt +[lockpages-link]:https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows +[nodchip-link]:https://github.com/nodchip/Stockfish +[programming-link]:https://www.chessprogramming.org/Main_Page +[programmingsf-link]:https://www.chessprogramming.org/Stockfish +[pytorch-link]:https://github.com/glinscott/nnue-pytorch +[rammap-link]:https://docs.microsoft.com/en-us/sysinternals/downloads/rammap +[readme-link]:https://github.com/official-stockfish/Stockfish/blob/master/README.md +[release-badge]:https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release +[release-link]:https://github.com/official-stockfish/Stockfish/releases/latest +[src-link]:https://github.com/official-stockfish/Stockfish/tree/master/src +[stockfish128-logo]:https://stockfishchess.org/images/logo/icon_128x128.png +[tools-link]:https://github.com/official-stockfish/Stockfish/tree/tools +[uci-link]:https://www.shredderchess.com/download/div/uci.zip +[website-badge]:https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org +[website-link]:https://stockfishchess.org +[worker-link]:https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview From 232bf19be43117cdecea054c9a825735f0b47842 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Thu, 22 Sep 2022 08:49:21 +0300 Subject: [PATCH 082/678] Simplify both position calls in useClassical Simplify the use of classical evaluation when using default settings to only be dependent on piece count and decisive PSQ passed STC: https://tests.stockfishchess.org/tests/view/632d32a7006ef9eb96d86ce9 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 108048 W: 28904 L: 28763 D: 50381 Ptnml(0-2): 383, 12060, 29006, 12183, 392 passed LTC: https://tests.stockfishchess.org/tests/view/632d705a006ef9eb96d87649 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 76600 W: 20671 L: 20516 D: 35413 Ptnml(0-2): 34, 7533, 23023, 7664, 46 Inspired by sorais, credit to him. closes https://github.com/official-stockfish/Stockfish/pull/4177 bench 4173163 --- src/evaluate.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index eaad4d55..0657088f 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1054,12 +1054,10 @@ Value Eval::evaluate(const Position& pos, int* complexity) { Color stm = pos.side_to_move(); Value psq = pos.psq_eg_stm(); - // Deciding between classical and NNUE eval: for high PSQ imbalance we use classical, - // but we switch to NNUE during long shuffling or with high material on the board. - bool useClassical = !useNNUE || - ((pos.count() > 7) - && abs(psq) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count())); - + // We use the much less accurate but faster Classical eval when the NNUE + // option is set to false. Otherwise we use the NNUE eval unless the + // PSQ advantage is decisive and several pieces remain (~3 Elo) + bool useClassical = !useNNUE || (pos.count() > 7 && abs(psq) > 1760); if (useClassical) v = Evaluation(pos).value(); else From f436bf77ad2eb42228747d9aa58eeb7403e23d49 Mon Sep 17 00:00:00 2001 From: disservin <45608332+Disservin@users.noreply.github.com> Date: Sun, 18 Sep 2022 11:16:54 +0200 Subject: [PATCH 083/678] Use less reduction for escaping moves This patch reuses the threatenedPieces variable (which is calculated in movepicker) to reduce less in the search tree the moves which escape a capture. passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 314352 W: 84042 L: 83328 D: 146982 Ptnml(0-2): 1105, 35084, 84207, 35552, 1228 https://tests.stockfishchess.org/tests/view/63355f37a004bed9a2e4a17f passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 90752 W: 24556 L: 24147 D: 42049 Ptnml(0-2): 59, 8855, 27123, 9296, 43 https://tests.stockfishchess.org/tests/view/63383a7735f43d649ff5fa8b closes https://github.com/official-stockfish/Stockfish/pull/4181 bench: 4114228 --- src/evaluate.cpp | 2 +- src/movepick.cpp | 17 ++++++++--------- src/movepick.h | 2 ++ src/search.cpp | 9 +++++++-- src/thread.cpp | 2 +- src/uci.cpp | 4 ++-- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 0657088f..85700bcc 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1054,7 +1054,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { Color stm = pos.side_to_move(); Value psq = pos.psq_eg_stm(); - // We use the much less accurate but faster Classical eval when the NNUE + // We use the much less accurate but faster Classical eval when the NNUE // option is set to false. Otherwise we use the NNUE eval unless the // PSQ advantage is decisive and several pieces remain (~3 Elo) bool useClassical = !useNNUE || (pos.count() > 7 && abs(psq) > 1760); diff --git a/src/movepick.cpp b/src/movepick.cpp index e10454b0..3428a764 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -69,6 +69,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); + threatenedPieces = 0; } /// MovePicker constructor for quiescence search @@ -106,21 +107,19 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - [[maybe_unused]] Bitboard threatened, threatenedByPawn, threatenedByMinor, threatenedByRook; + [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook; if constexpr (Type == QUIETS) { Color us = pos.side_to_move(); - // squares threatened by pawns + threatenedByPawn = pos.attacks_by(~us); - // squares threatened by minors or pawns threatenedByMinor = pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; - // squares threatened by rooks, minors or pawns threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; - // pieces threatened by pieces of lesser material value - threatened = (pos.pieces(us, QUEEN) & threatenedByRook) - | (pos.pieces(us, ROOK) & threatenedByMinor) - | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); + // Pieces threatened by pieces of lesser material value + threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) + | (pos.pieces(us, ROOK) & threatenedByMinor) + | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); } for (auto& m : *this) @@ -134,7 +133,7 @@ void MovePicker::score() { + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] - + (threatened & from_sq(m) ? + + (threatenedPieces & from_sq(m) ? (type_of(pos.moved_piece(m)) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000 : type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000 : !(to_sq(m) & threatenedByPawn) ? 15000 diff --git a/src/movepick.h b/src/movepick.h index 979709ae..55fcc644 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -131,6 +131,8 @@ public: MovePicker(const Position&, Move, Value, Depth, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); + Bitboard threatenedPieces; + private: template Move select(Pred); template void score(); diff --git a/src/search.cpp b/src/search.cpp index be0f3451..4b6b497a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -789,7 +789,7 @@ namespace { && depth < 8 && eval - futility_margin(depth, improving) - (ss-1)->statScore / 303 >= beta && eval >= beta - && eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins. + && eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~22 Elo) @@ -1163,7 +1163,12 @@ moves_loop: // When in check, search starts here if (singularQuietLMR) r--; - // Increase reduction if next ply has a lot of fail high else reset count to 0 + // Dicrease reduction if we move a threatened piece (~1 Elo) + if ( depth > 9 + && (mp.threatenedPieces & from_sq(move))) + r--; + + // Increase reduction if next ply has a lot of fail high if ((ss+1)->cutoffCnt > 3 && !PvNode) r++; diff --git a/src/thread.cpp b/src/thread.cpp index c834fa9f..288588b0 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -61,7 +61,7 @@ void Thread::clear() { mainHistory.fill(0); captureHistory.fill(0); previousDepth = 0; - + for (bool inCheck : { false, true }) for (StatsType c : { NoCaptures, Captures }) { diff --git a/src/uci.cpp b/src/uci.cpp index ec106ee9..d5e2c2c3 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -224,7 +224,7 @@ namespace { /// UCI::loop() waits for a command from the stdin, parses it and then calls the appropriate /// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a -/// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, +/// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, /// like running 'bench', the function returns immediately after the command is executed. /// In addition to the UCI ones, some additional debug commands are also supported. @@ -240,7 +240,7 @@ void UCI::loop(int argc, char* argv[]) { cmd += std::string(argv[i]) + " "; do { - if (argc == 1 && !getline(cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + if (argc == 1 && !getline(cin, cmd)) // Wait for an input or an end-of-file (EOF) indication cmd = "quit"; istringstream is(cmd); From 8bab09749dd00951bfa9c5f89f6e35bded76c8a9 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 3 Oct 2022 17:45:05 +0300 Subject: [PATCH 084/678] Mix alpha and statScore for reduction Idea by @xoto10, and tuning by @FauziAkram. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 57832 W: 15540 L: 15199 D: 27093 Ptnml(0-2): 207, 6343, 15477, 6680, 209 https://tests.stockfishchess.org/tests/view/6338db6f35f43d649ff60fdc passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 50968 W: 13770 L: 13440 D: 23758 Ptnml(0-2): 25, 4905, 15306, 5211, 37 https://tests.stockfishchess.org/tests/view/6339777035f43d649ff62686 Links to the tuning sessions: https://tests.stockfishchess.org/tests/view/63345725a004bed9a2e47b28 https://tests.stockfishchess.org/tests/view/63345728a004bed9a2e47b2a closes https://github.com/official-stockfish/Stockfish/pull/4183 Bench: 4426602 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4b6b497a..46463b32 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -787,9 +787,9 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 8 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 303 >= beta + && eval - futility_margin(depth, improving) - (ss-1)->statScore / 301 >= beta && eval >= beta - && eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 28692) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~22 Elo) @@ -1179,7 +1179,7 @@ moves_loop: // When in check, search starts here - 4433; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / 13628; + r -= (ss->statScore + 5 * alpha) / 15448; // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension From da937e219ee7981966ac29fc11c43470a505ff18 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 5 Oct 2022 22:59:05 +0200 Subject: [PATCH 085/678] Revert "Mix alpha and statScore for reduction" This reverts commit 8bab09749dd00951bfa9c5f89f6e35bded76c8a9. In this form the patch reduces mate finding effectiveness, as the large alpha value has negative influence on the reductions. see also https://github.com/official-stockfish/Stockfish/pull/4183 Bench: 4114228 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 46463b32..4b6b497a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -787,9 +787,9 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 8 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 301 >= beta + && eval - futility_margin(depth, improving) - (ss-1)->statScore / 303 >= beta && eval >= beta - && eval < 28692) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~22 Elo) @@ -1179,7 +1179,7 @@ moves_loop: // When in check, search starts here - 4433; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= (ss->statScore + 5 * alpha) / 15448; + r -= ss->statScore / 13628; // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension From d5271af0ee28cb52e1033f183f3b34754aa5efa0 Mon Sep 17 00:00:00 2001 From: Giacomo Lorenzetti Date: Wed, 5 Oct 2022 13:08:00 +0200 Subject: [PATCH 086/678] Remove old line in "Futility pruning for captures" The line is no longer needed after https://github.com/official-stockfish/Stockfish/commit/910cf8b21839eb9f1991934a5436eea112021723. This patch incidentally applies "Futility Pruning for Captures" also in case of en-passant, changing the bench signature. Passed STC: https://tests.stockfishchess.org/tests/view/6332c1f1208c26088697b731 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 68760 W: 18440 L: 18256 D: 32064 Ptnml(0-2): 267, 7530, 18595, 7728, 260 Passed LTC: https://tests.stockfishchess.org/tests/view/633312e9208c26088697c59b LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 455552 W: 121910 L: 122123 D: 211519 Ptnml(0-2): 253, 45439, 136600, 45236, 248 closes https://github.com/official-stockfish/Stockfish/pull/4185 Bench: 4374521 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4b6b497a..7019635d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1002,8 +1002,7 @@ moves_loop: // When in check, search starts here || givesCheck) { // Futility pruning for captures (~0 Elo) - if ( !pos.empty(to_sq(move)) - && !givesCheck + if ( !givesCheck && !PvNode && lmrDepth < 7 && !ss->inCheck From e90341f9c9cd24e75aba2485b2ab1e445f4a5b70 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Tue, 4 Oct 2022 18:16:20 +0200 Subject: [PATCH 087/678] Tweak history initialization Simplify initialization of continuation history by using everywhere the same starting value. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 90952 W: 24312 L: 24153 D: 42487 Ptnml(0-2): 356, 10168, 24290, 10285, 377 https://tests.stockfishchess.org/tests/view/633948f235f43d649ff61fd0 LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 162416 W: 43540 L: 43466 D: 75410 Ptnml(0-2): 77, 16289, 48417, 16333, 92 https://tests.stockfishchess.org/tests/view/6339ee8a35f43d649ff63986 closes https://github.com/official-stockfish/Stockfish/pull/4186 Bench: 4156027 --- src/search.cpp | 4 ++-- src/search.h | 3 --- src/thread.cpp | 7 ++----- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7019635d..3c2ae3e5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1549,8 +1549,8 @@ moves_loop: // When in check, search starts here // Continuation history based pruning (~2 Elo) if ( !capture && bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold) + && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 + && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) continue; // movecount pruning for quiet check evasions diff --git a/src/search.h b/src/search.h index 4ad5784f..f264bcae 100644 --- a/src/search.h +++ b/src/search.h @@ -31,9 +31,6 @@ class Position; namespace Search { -/// Threshold used for countermoves based pruning -constexpr int CounterMovePruneThreshold = 0; - /// Stack struct keeps track of the information we need to remember from nodes /// shallower and deeper in the tree during the search. Each search thread has diff --git a/src/thread.cpp b/src/thread.cpp index 288588b0..9ce408e0 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -64,12 +64,9 @@ void Thread::clear() { for (bool inCheck : { false, true }) for (StatsType c : { NoCaptures, Captures }) - { for (auto& to : continuationHistory[inCheck][c]) - for (auto& h : to) - h->fill(-71); - continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1); - } + for (auto& h : to) + h->fill(-71); } From 93f71ecfe1d26e5ccc813318f420b8363cd26003 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 14 Oct 2022 14:41:08 -0700 Subject: [PATCH 088/678] Optimize make_index() using templates and lookup tables. https://tests.stockfishchess.org/tests/view/634517e54bc7650f07542f99 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 642672 W: 171819 L: 170658 D: 300195 Ptnml(0-2): 2278, 68077, 179416, 69336, 2229 this also introduces `-flto-partition=one` as suggested by MinetaS (Syine Mineta) to avoid linking errors due to LTO on 32 bit mingw. This change was tested in isolation as well https://tests.stockfishchess.org/tests/view/634aacf84bc7650f0755188b LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 119352 W: 31986 L: 31862 D: 55504 Ptnml(0-2): 439, 12624, 33400, 12800, 413 closes https://github.com/official-stockfish/Stockfish/pull/4199 No functional change --- src/Makefile | 4 +- src/nnue/features/half_ka_v2_hm.cpp | 33 ++++++++------- src/nnue/features/half_ka_v2_hm.h | 62 +++++++++++++++++++-------- src/nnue/nnue_feature_transformer.h | 65 +++++++++++++++-------------- 4 files changed, 96 insertions(+), 68 deletions(-) diff --git a/src/Makefile b/src/Makefile index 8315f33d..880710fe 100644 --- a/src/Makefile +++ b/src/Makefile @@ -698,11 +698,9 @@ ifeq ($(debug), no) # To use LTO and static linking on Windows, # the tool chain requires gcc version 10.1 or later. else ifeq ($(comp),mingw) - ifneq ($(arch),i386) - CXXFLAGS += -flto + CXXFLAGS += -flto -flto-partition=one LDFLAGS += $(CXXFLAGS) -save-temps endif - endif endif endif diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 07a1d7a1..11e05c94 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -24,50 +24,51 @@ namespace Stockfish::Eval::NNUE::Features { - // Orient a square according to perspective (rotates by 180 for black) - inline Square HalfKAv2_hm::orient(Color perspective, Square s, Square ksq) { - return Square(int(s) ^ (bool(perspective) * SQ_A8) ^ ((file_of(ksq) < FILE_E) * SQ_H1)); - } - // Index of a feature for a given king position and another piece on some square - inline IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) { - Square o_ksq = orient(perspective, ksq, ksq); - return IndexType(orient(perspective, s, ksq) + PieceSquareIndex[perspective][pc] + PS_NB * KingBuckets[o_ksq]); + template + inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { + return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + KingBuckets[Perspective][ksq]); } // Get a list of indices for active features + template void HalfKAv2_hm::append_active_indices( const Position& pos, - Color perspective, IndexList& active ) { - Square ksq = pos.square(perspective); + Square ksq = pos.square(Perspective); Bitboard bb = pos.pieces(); while (bb) { Square s = pop_lsb(bb); - active.push_back(make_index(perspective, s, pos.piece_on(s), ksq)); + active.push_back(make_index(s, pos.piece_on(s), ksq)); } } - + // Explicit template instantiations + template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); + template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); + // append_changed_indices() : get a list of indices for recently changed features - + template void HalfKAv2_hm::append_changed_indices( Square ksq, const DirtyPiece& dp, - Color perspective, IndexList& removed, IndexList& added ) { for (int i = 0; i < dp.dirty_num; ++i) { if (dp.from[i] != SQ_NONE) - removed.push_back(make_index(perspective, dp.from[i], dp.piece[i], ksq)); + removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); if (dp.to[i] != SQ_NONE) - added.push_back(make_index(perspective, dp.to[i], dp.piece[i], ksq)); + added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); } } + // Explicit template instantiations + template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); + template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); + int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; } diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 1e6da0bf..a95d4328 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -49,8 +49,8 @@ namespace Stockfish::Eval::NNUE::Features { PS_B_ROOK = 7 * SQUARE_NB, PS_W_QUEEN = 8 * SQUARE_NB, PS_B_QUEEN = 9 * SQUARE_NB, - PS_KING = 10 * SQUARE_NB, - PS_NB = 11 * SQUARE_NB + PS_KING = 10 * SQUARE_NB, + PS_NB = 11 * SQUARE_NB }; static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { @@ -62,11 +62,9 @@ namespace Stockfish::Eval::NNUE::Features { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE } }; - // Orient a square according to perspective (rotates by 180 for black) - static Square orient(Color perspective, Square s, Square ksq); - // Index of a feature for a given king position and another piece on some square - static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq); + template + static IndexType make_index(Square s, Piece pc, Square ksq); public: // Feature name @@ -79,15 +77,45 @@ namespace Stockfish::Eval::NNUE::Features { static constexpr IndexType Dimensions = static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; - static constexpr int KingBuckets[64] = { - -1, -1, -1, -1, 31, 30, 29, 28, - -1, -1, -1, -1, 27, 26, 25, 24, - -1, -1, -1, -1, 23, 22, 21, 20, - -1, -1, -1, -1, 19, 18, 17, 16, - -1, -1, -1, -1, 15, 14, 13, 12, - -1, -1, -1, -1, 11, 10, 9, 8, - -1, -1, -1, -1, 7, 6, 5, 4, - -1, -1, -1, -1, 3, 2, 1, 0 +#define B(v) (v * PS_NB) + static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = { + { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), + B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), + B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), + B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), + B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), + B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), + B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), + B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) }, + { B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0), + B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), + B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), + B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), + B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), + B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), + B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), + B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) } + }; +#undef B + + // Orient a square according to perspective (rotates by 180 for black) + static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { + { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 }, + { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 } }; // Maximum number of simultaneously active features. @@ -95,16 +123,16 @@ namespace Stockfish::Eval::NNUE::Features { using IndexList = ValueList; // Get a list of indices for active features + template static void append_active_indices( const Position& pos, - Color perspective, IndexList& active); // Get a list of indices for recently changed features + template static void append_changed_indices( Square ksq, const DirtyPiece& dp, - Color perspective, IndexList& removed, IndexList& added ); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 34d7292c..b6dd54d3 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -271,8 +271,8 @@ namespace Stockfish::Eval::NNUE { // Convert input features std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { - update_accumulator(pos, WHITE); - update_accumulator(pos, BLACK); + update_accumulator(pos); + update_accumulator(pos); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& accumulation = pos.state()->accumulator.accumulation; @@ -338,7 +338,8 @@ namespace Stockfish::Eval::NNUE { private: - void update_accumulator(const Position& pos, const Color perspective) const { + template + void update_accumulator(const Position& pos) const { // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -356,18 +357,18 @@ namespace Stockfish::Eval::NNUE { // of the estimated gain in terms of features to be added/subtracted. StateInfo *st = pos.state(), *next = nullptr; int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !st->accumulator.computed[perspective]) + while (st->previous && !st->accumulator.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) + if ( FeatureSet::requires_refresh(st, Perspective) || (gain -= FeatureSet::update_cost(st) + 1) < 0) break; next = st; st = st->previous; } - if (st->accumulator.computed[perspective]) + if (st->accumulator.computed[Perspective]) { if (next == nullptr) return; @@ -376,17 +377,17 @@ namespace Stockfish::Eval::NNUE { // accumulator. Then, we update the current accumulator (pos.state()). // Gather all features to be updated. - const Square ksq = pos.square(perspective); + const Square ksq = pos.square(Perspective); FeatureSet::IndexList removed[2], added[2]; - FeatureSet::append_changed_indices( - ksq, next->dirtyPiece, perspective, removed[0], added[0]); + FeatureSet::append_changed_indices( + ksq, next->dirtyPiece, removed[0], added[0]); for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous) - FeatureSet::append_changed_indices( - ksq, st2->dirtyPiece, perspective, removed[1], added[1]); + FeatureSet::append_changed_indices( + ksq, st2->dirtyPiece, removed[1], added[1]); // Mark the accumulators as computed. - next->accumulator.computed[perspective] = true; - pos.state()->accumulator.computed[perspective] = true; + next->accumulator.computed[Perspective] = true; + pos.state()->accumulator.computed[Perspective] = true; // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. StateInfo *states_to_update[3] = @@ -396,7 +397,7 @@ namespace Stockfish::Eval::NNUE { { // Load accumulator auto accTile = reinterpret_cast( - &st->accumulator.accumulation[perspective][j * TileHeight]); + &st->accumulator.accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_load(&accTile[k]); @@ -422,7 +423,7 @@ namespace Stockfish::Eval::NNUE { // Store accumulator accTile = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[perspective][j * TileHeight]); + &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) vec_store(&accTile[k], acc[k]); } @@ -432,7 +433,7 @@ namespace Stockfish::Eval::NNUE { { // Load accumulator auto accTilePsqt = reinterpret_cast( - &st->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_load_psqt(&accTilePsqt[k]); @@ -458,7 +459,7 @@ namespace Stockfish::Eval::NNUE { // Store accumulator accTilePsqt = reinterpret_cast( - &states_to_update[i]->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqt[k], psqt[k]); } @@ -467,12 +468,12 @@ namespace Stockfish::Eval::NNUE { #else for (IndexType i = 0; states_to_update[i]; ++i) { - std::memcpy(states_to_update[i]->accumulator.accumulation[perspective], - st->accumulator.accumulation[perspective], + std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], + st->accumulator.accumulation[Perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) - states_to_update[i]->accumulator.psqtAccumulation[perspective][k] = st->accumulator.psqtAccumulation[perspective][k]; + states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k]; st = states_to_update[i]; @@ -482,10 +483,10 @@ namespace Stockfish::Eval::NNUE { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[perspective][j] -= weights[offset + j]; + st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; } // Difference calculation for the activated features @@ -494,10 +495,10 @@ namespace Stockfish::Eval::NNUE { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[perspective][j] += weights[offset + j]; + st->accumulator.accumulation[Perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k]; + st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } } #endif @@ -506,9 +507,9 @@ namespace Stockfish::Eval::NNUE { { // Refresh the accumulator auto& accumulator = pos.state()->accumulator; - accumulator.computed[perspective] = true; + accumulator.computed[Perspective] = true; FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, perspective, active); + FeatureSet::append_active_indices(pos, active); #ifdef VECTOR for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) @@ -528,7 +529,7 @@ namespace Stockfish::Eval::NNUE { } auto accTile = reinterpret_cast( - &accumulator.accumulation[perspective][j * TileHeight]); + &accumulator.accumulation[Perspective][j * TileHeight]); for (unsigned k = 0; k < NumRegs; k++) vec_store(&accTile[k], acc[k]); } @@ -548,27 +549,27 @@ namespace Stockfish::Eval::NNUE { } auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqt[k], psqt[k]); } #else - std::memcpy(accumulator.accumulation[perspective], biases, + std::memcpy(accumulator.accumulation[Perspective], biases, HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[perspective][k] = 0; + accumulator.psqtAccumulation[Perspective][k] = 0; for (const auto index : active) { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[perspective][j] += weights[offset + j]; + accumulator.accumulation[Perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k]; + accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } #endif } From f97a86e213fd352a1a7017e5c98608cefc3ef1fa Mon Sep 17 00:00:00 2001 From: Dubslow Date: Fri, 7 Oct 2022 05:44:29 -0500 Subject: [PATCH 089/678] Remove depth condition from razoring The eval condition depends on depth anyways, so this patch is nearly (not quite) non-functional passed STC: https://tests.stockfishchess.org/tests/view/63428169fb7ccb2ea9be2629 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 185992 W: 49612 L: 49558 D: 86822 Ptnml(0-2): 618, 19956, 51842, 19914, 666 passed LTC: https://tests.stockfishchess.org/tests/view/634418b14bc7650f07540760 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 126816 W: 34147 L: 34043 D: 58626 Ptnml(0-2): 74, 11941, 39281, 12031, 81 closes https://github.com/official-stockfish/Stockfish/pull/4196 bench 4148700 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3c2ae3e5..898de875 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -775,8 +775,7 @@ namespace { // Step 7. Razoring. // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if ( depth <= 7 - && eval < alpha - 369 - 254 * depth * depth) + if (eval < alpha - 369 - 254 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) From d6b6360ff5dcdffa141f50d0a81d0be7c6324c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sun, 9 Oct 2022 00:27:26 +0200 Subject: [PATCH 090/678] Tweak the formula for NNUE complexity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Joint work by Ofek Shochat and Stéphane Nicolet. passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 93288 W: 24996 L: 24601 D: 43691 Ptnml(0-2): 371, 10263, 24989, 10642, 379 https://tests.stockfishchess.org/tests/view/63448f4f4bc7650f07541987 passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 84168 W: 22771 L: 22377 D: 39020 Ptnml(0-2): 47, 8181, 25234, 8575, 47 https://tests.stockfishchess.org/tests/view/6345186d4bc7650f07542fbd ================ It seems there are two effects with this patch: effect A : If Stockfish is winning at root, we have optimism > 0 for all leaves in the search tree where Stockfish is to move. There, if (psq - nnue) > 0 (ie if the advantage is more materialistic than positional), then the product D = optimism * (psq - nnue) will be positive, nnueComplexity will increase, and the eval will increase from SF point of view. So the effect A is that if Stockfish is winning at root, she will slightly favor in the search tree (in other words, search more) the positions where she can convert her advantage via materialist means. effect B : If Stockfish is losing at root, we have optimism > 0 for all leaves in the search tree where the opponent is to move. There, if (psq - nnue) < 0 (ie if the opponent advantage is more positional than materialistic), then the product D = optimism * (psq-nnue) will be negative, nnueComplexity will decrease, and the eval will decrease from the opponent point of view. So the effect B is that Stockfish will slightly favor in the search tree (search more) the branches where she can defend by slowly reducing the opponent positional advantage. ================= closes https://github.com/official-stockfish/Stockfish/pull/4195 bench: 4673898 --- src/evaluate.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 85700bcc..d5844593 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1051,25 +1051,33 @@ make_v: Value Eval::evaluate(const Position& pos, int* complexity) { Value v; - Color stm = pos.side_to_move(); Value psq = pos.psq_eg_stm(); // We use the much less accurate but faster Classical eval when the NNUE // option is set to false. Otherwise we use the NNUE eval unless the - // PSQ advantage is decisive and several pieces remain (~3 Elo) + // PSQ advantage is decisive and several pieces remain. (~3 Elo) bool useClassical = !useNNUE || (pos.count() > 7 && abs(psq) > 1760); + if (useClassical) v = Evaluation(pos).value(); else { int nnueComplexity; int scale = 1064 + 106 * pos.non_pawn_material() / 5120; + + Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + // Blend nnue complexity with (semi)classical complexity - nnueComplexity = (104 * nnueComplexity + 131 * abs(nnue - psq)) / 256; - if (complexity) // Return hybrid NNUE complexity to caller + nnueComplexity = ( 416 * nnueComplexity + + 424 * abs(psq - nnue) + + (optimism > 0 ? int(optimism) * int(psq - nnue) : 0) + ) / 1024; + + // Return hybrid NNUE complexity to caller + if (complexity) *complexity = nnueComplexity; optimism = optimism * (269 + nnueComplexity) / 256; From 9be2977da7921aedfd3215f1f4b5f522359effa0 Mon Sep 17 00:00:00 2001 From: xoto10 Date: Sat, 8 Oct 2022 14:53:14 +0100 Subject: [PATCH 091/678] Adjust timeman constants Adjust timeman constants to use more time in early part of game. STC @ 10+0.1 th 1 : LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 93984 W: 25177 L: 24787 D: 44020 Ptnml(0-2): 350, 10096, 25729, 10448, 369 https://tests.stockfishchess.org/tests/live_elo/6339077135f43d649ff6162a LTC @ 60+0.6 th 1 : LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 329368 W: 88953 L: 88093 D: 152322 Ptnml(0-2): 170, 31457, 100594, 32269, 194 https://tests.stockfishchess.org/tests/live_elo/6339baed35f43d649ff63142 Sudden death 10+0 : LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 20400 W: 5908 L: 5588 D: 8904 Ptnml(0-2): 177, 2252, 5128, 2360, 283 https://tests.stockfishchess.org/tests/live_elo/6347c9384bc7650f07549ba7 Sudden death 10+0, no adjudication : LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 17920 W: 4755 L: 4442 D: 8723 Ptnml(0-2): 137, 1985, 4466, 2172, 200 https://tests.stockfishchess.org/tests/live_elo/634806e84bc7650f0754a639 closes https://github.com/official-stockfish/Stockfish/pull/4188 No functional change --- src/timeman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 0400401e..cab0d767 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -80,7 +80,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // game time for the current move, so also cap to 20% of available game time. if (limits.movestogo == 0) { - optScale = std::min(0.0084 + std::pow(ply + 3.0, 0.5) * 0.0042, + optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039, 0.2 * limits.time[us] / double(timeLeft)) * optExtra; maxScale = std::min(7.0, 4.0 + ply / 12.0); From 79c5f3a69247b44ee6362a3d9236cd9bc048c5f5 Mon Sep 17 00:00:00 2001 From: Rodrigo Roim Date: Fri, 7 Oct 2022 16:55:07 -0700 Subject: [PATCH 092/678] Fix tablebase probe for dtz >1000 w/o 50 move rule For qn4N1/6R1/3K4/8/B2k4/8/8/8 w - - 0 1, white loses with DTZ 1034. See https://syzygy-tables.info/?fen=qn4N1/6R1/3K4/8/B2k4/8/8/8_w_-_-_0_1 Prior to this fix, due to a too small hard-coded value, Stockfish interpreted this as winning. The new value picked (1<<18) is large enough to deal with the largest DTZ values that can be stored in the current syzygy format. closes https://github.com/official-stockfish/Stockfish/pull/4187 No functional change. --- AUTHORS | 1 + src/syzygy/tbprobe.cpp | 13 +++++++------ src/syzygy/tbprobe.h | 2 -- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/AUTHORS b/AUTHORS index fc885acb..804232f1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -169,6 +169,7 @@ renouve Reuven Peleg Richard Lloyd Rodrigo Exterckötter Tjäder +Rodrigo Roim (roim) Ron Britvich (Britvich) Ronald de Man (syzygy1, syzygy) rqs diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 43af89f0..f2de036d 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -59,6 +59,7 @@ namespace Stockfish { namespace { constexpr int TBPIECES = 7; // Max number of supported pieces +constexpr int MAX_DTZ = 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit. enum { BigEndian, LittleEndian }; enum TBType { WDL, DTZ }; // Used as template parameter @@ -1522,7 +1523,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // Check whether a position was repeated since the last zeroing move. bool rep = pos.has_repeated(); - int dtz, bound = Options["Syzygy50MoveRule"] ? 900 : 1; + int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1; // Probe and rank each move for (auto& m : rootMoves) @@ -1565,8 +1566,8 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // Better moves are ranked higher. Certain wins are ranked equally. // Losing moves are ranked equally unless a 50-move draw is in sight. - int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? 1000 : 1000 - (dtz + cnt50)) - : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -1000 : -1000 + (-dtz + cnt50)) + int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50)) + : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50)) : 0; m.tbRank = r; @@ -1574,9 +1575,9 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // 1 cp to cursed wins and let it grow to 49 cp as the positions gets // closer to a real win. m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 - : r > 0 ? Value((std::max( 3, r - 800) * int(PawnValueEg)) / 200) + : r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValueEg)) / 200) : r == 0 ? VALUE_DRAW - : r > -bound ? Value((std::min(-3, r + 800) * int(PawnValueEg)) / 200) + : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValueEg)) / 200) : -VALUE_MATE + MAX_PLY + 1; } @@ -1590,7 +1591,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // A return value false indicates that not all probes were successful. bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { - static const int WDL_to_rank[] = { -1000, -899, 0, 899, 1000 }; + static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ }; ProbeState result; StateInfo st; diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index c2917fef..179c7572 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -31,8 +31,6 @@ enum WDLScore { WDLDraw = 0, // Draw WDLCursedWin = 1, // Win, but draw under 50-move rule WDLWin = 2, // Win - - WDLScoreNone = -1000 }; // Possible states after a probing operation From 234d2156fdf011a7bb850e3d6172ed2290ab3ad2 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Mon, 17 Oct 2022 00:03:08 +0900 Subject: [PATCH 093/678] Apply -flto-partition=one / -flto=full This patch fixes a potential bug derived from an incompatibility between LTO and top-level assembly code (INCBIN). Passed non-regression STC (master e90341f): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 119352 W: 31986 L: 31862 D: 55504 Ptnml(0-2): 439, 12624, 33400, 12800, 413 https://tests.stockfishchess.org/tests/view/634aacf84bc7650f0755188b closes https://github.com/official-stockfish/Stockfish/pull/4201 No functional change --- AUTHORS | 1 + src/Makefile | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 804232f1..89e7c9ed 100644 --- a/AUTHORS +++ b/AUTHORS @@ -189,6 +189,7 @@ Stefan Geschwentner (locutus2) Stefano Cardanobile (Stefano80) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) +Syine Mineta (MinetaS) Prokop Randáček (ProkopRandacek) Thanar2 thaspel diff --git a/src/Makefile b/src/Makefile index 880710fe..1d5137d1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -678,7 +678,7 @@ endif ifeq ($(optimize),yes) ifeq ($(debug), no) ifeq ($(comp),clang) - CXXFLAGS += -flto + CXXFLAGS += -flto=full ifeq ($(target_windows),yes) CXXFLAGS += -fuse-ld=lld endif @@ -688,10 +688,10 @@ ifeq ($(debug), no) # GCC on some systems. else ifeq ($(comp),gcc) ifeq ($(gccisclang),) - CXXFLAGS += -flto + CXXFLAGS += -flto -flto-partition=one LDFLAGS += $(CXXFLAGS) -flto=jobserver else - CXXFLAGS += -flto + CXXFLAGS += -flto=full LDFLAGS += $(CXXFLAGS) endif From 804394b939614ca6ac99e31be236c019db365bb7 Mon Sep 17 00:00:00 2001 From: disservin Date: Sun, 16 Oct 2022 14:37:01 +0200 Subject: [PATCH 094/678] enable bit manipulation instruction set 1 bmi1 enables the use of _blsr_u64 for pop_lsb, and is availabe when avx2 is. verified a small speedup (0.2 - 0.6%) closes https://github.com/official-stockfish/Stockfish/pull/4202 No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 1d5137d1..727466f9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -593,7 +593,7 @@ endif ifeq ($(avx2),yes) CXXFLAGS += -DUSE_AVX2 ifeq ($(comp),$(filter $(comp),gcc clang mingw)) - CXXFLAGS += -mavx2 + CXXFLAGS += -mavx2 -mbmi endif endif From 5604b255e6012ed44eb03e2e93949d904fc7279b Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 21 Oct 2022 02:29:57 +0000 Subject: [PATCH 095/678] Add RISC-V 64-bit support adds a riscv64 target architecture to the Makefile to support RISC-V 64-bit. Compiled and tested on VisionFive 2 board. closes https://github.com/official-stockfish/Stockfish/pull/4205 No functional change. --- AUTHORS | 1 + src/Makefile | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 89e7c9ed..d4b37e7a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -43,6 +43,7 @@ Bryan Cross (crossbr) candirufish Chess13234 Chris Cain (ceebo) +clefrks Dale Weiler (graphitemaster) Dan Schmidt (dfannius) Daniel Axtens (daxtens) diff --git a/src/Makefile b/src/Makefile index 727466f9..e481aca5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -116,7 +116,7 @@ ifeq ($(ARCH), $(filter $(ARCH), \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \ - armv7 armv7-neon armv8 apple-silicon general-64 general-32)) + armv7 armv7-neon armv8 apple-silicon general-64 general-32 riscv64)) SUPPORTED_ARCH=true else SUPPORTED_ARCH=false @@ -338,7 +338,11 @@ ifeq ($(findstring e2k,$(ARCH)),e2k) popcnt = yes endif +ifeq ($(ARCH),riscv64) + arch = riscv64 endif +endif + ### ========================================================================== ### Section 3. Low-level Configuration @@ -364,11 +368,14 @@ ifeq ($(COMP),gcc) CXX=g++ CXXFLAGS += -pedantic -Wextra -Wshadow - ifeq ($(arch),$(filter $(arch),armv7 armv8)) + ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) ifeq ($(OS),Android) CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) endif + ifeq ($(ARCH),riscv64) + CXXFLAGS += -latomic + endif else CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) @@ -429,11 +436,14 @@ ifeq ($(COMP),clang) endif endif - ifeq ($(arch),$(filter $(arch),armv7 armv8)) + ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) ifeq ($(OS),Android) CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) endif + ifeq ($(ARCH),riscv64) + CXXFLAGS += -latomic + endif else CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) @@ -757,6 +767,7 @@ help: @echo "apple-silicon > Apple silicon ARM64" @echo "general-64 > unspecified 64-bit" @echo "general-32 > unspecified 32-bit" + @echo "riscv64 > RISC-V 64-bit" @echo "" @echo "Supported compilers:" @echo "" @@ -916,7 +927,7 @@ config-sanity: net @test "$(SUPPORTED_ARCH)" = "true" @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \ - test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" + test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" @test "$(bits)" = "32" || test "$(bits)" = "64" @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" From 4ec8945eafc5b271d1c9d276fab590fa26c24901 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 21 Oct 2022 17:10:45 +0300 Subject: [PATCH 096/678] Use TT moves more often in qsearch During the recapture phase of quiescence search (where we limit the generated moves to recaptures on the last seen capture square), the move picker will now emit the tt move, even if the tt move is not a recapture. Passed STC : https://tests.stockfishchess.org/tests/view/6350df2928d3a71cb1eef838 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 90280 W: 24001 L: 23845 D: 42434 Ptnml(0-2): 273, 9779, 24941, 9813, 334 Passed LTC : https://tests.stockfishchess.org/tests/view/6351308b28d3a71cb1ef06ce LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 104504 W: 27937 L: 27807 D: 48760 Ptnml(0-2): 54, 10378, 31260, 10504, 56 closes https://github.com/official-stockfish/Stockfish/pull/4206 Bench: 4540268 --- src/movepick.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 3428a764..587c6d79 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -83,7 +83,6 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !( ttm - && (pos.checkers() || depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare) && pos.pseudo_legal(ttm)); } From a5500edc555f6d4429b3b697392f5f27215cb1db Mon Sep 17 00:00:00 2001 From: dav1312 <63931154+dav1312@users.noreply.github.com> Date: Tue, 25 Oct 2022 11:15:00 +0200 Subject: [PATCH 097/678] Add issue template Add an issue template using GitHub's form schema https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema closes https://github.com/official-stockfish/Stockfish/pull/4210 No functional change. --- .github/ISSUE_TEMPLATE/BUG-REPORT.yml | 65 +++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++++ README.md | 63 +++++++++++++------------- 3 files changed, 106 insertions(+), 30 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/BUG-REPORT.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 00000000..e46d2bf8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,65 @@ +name: Report issue +description: Create a report to help us fix issues with the engine +body: +- type: textarea + attributes: + label: Describe the issue + description: A clear and concise description of what you're experiencing. + validations: + required: true + +- type: textarea + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + +- type: textarea + attributes: + label: Steps to reproduce + description: | + Steps to reproduce the behavior. + You can also use this section to paste the command line output. + placeholder: | + ``` + position startpos moves g2g4 e7e5 f2f3 + go mate 1 + info string NNUE evaluation using nn-6877cd24400e.nnue enabled + info depth 1 seldepth 1 multipv 1 score mate 1 nodes 33 nps 11000 tbhits 0 time 3 pv d8h4 + bestmove d8h4 + ``` + validations: + required: true + +- type: textarea + attributes: + label: Anything else? + description: | + Anything that will give us more context about the issue you are encountering. + You can also use this section to propose ideas on how to solve the issue. + validations: + required: false + +- type: dropdown + attributes: + label: Operating system + options: + - All + - Windows + - Linux + - MacOS + - Android + - Other or N/A + validations: + required: true + +- type: input + attributes: + label: Stockfish version + description: | + This can be found by running the engine. + You can also use the commit ID. + placeholder: Stockfish 15 / e6e324e + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..1f8694d2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Discord server + url: https://discord.gg/GWDRS3kU6R + about: Feel free to ask for support or have a chat with us in our Discord server! + - name: Discussions, Q&A, ideas, show us something... + url: https://github.com/official-stockfish/Stockfish/discussions/new + about: Do you have an idea for Stockfish? Do you want to show something that you made? Please open a discussion about it! diff --git a/README.md b/README.md index b076ab6b..7066db4b 100644 --- a/README.md +++ b/README.md @@ -344,33 +344,36 @@ source code, these changes must also be made available under the GPL v3. For full details, read the copy of the GPL v3 found in the file named [*Copying.txt*][license-link]. -[authors-link]:https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS -[build-badge]:https://img.shields.io/github/workflow/status/official-stockfish/Stockfish/Stockfish?style=for-the-badge&label=stockfish&logo=github -[build-link]:https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml -[commits-badge]:https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge -[commits-link]:https://github.com/official-stockfish/Stockfish/commits/master -[discord-badge]:https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord -[discord-link]:https://discord.com/invite/aefaxmq -[fishcooking-link]:https://groups.google.com/g/fishcooking -[fishtest-badge]:https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests -[fishtest-link]:https://tests.stockfishchess.org/tests -[github-link]:https://github.com/official-stockfish/Stockfish -[guideline-link]:https://github.com/glinscott/fishtest/wiki/Creating-my-first-test -[license-badge]:https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success -[license-link]:https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt -[lockpages-link]:https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows -[nodchip-link]:https://github.com/nodchip/Stockfish -[programming-link]:https://www.chessprogramming.org/Main_Page -[programmingsf-link]:https://www.chessprogramming.org/Stockfish -[pytorch-link]:https://github.com/glinscott/nnue-pytorch -[rammap-link]:https://docs.microsoft.com/en-us/sysinternals/downloads/rammap -[readme-link]:https://github.com/official-stockfish/Stockfish/blob/master/README.md -[release-badge]:https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release -[release-link]:https://github.com/official-stockfish/Stockfish/releases/latest -[src-link]:https://github.com/official-stockfish/Stockfish/tree/master/src -[stockfish128-logo]:https://stockfishchess.org/images/logo/icon_128x128.png -[tools-link]:https://github.com/official-stockfish/Stockfish/tree/tools -[uci-link]:https://www.shredderchess.com/download/div/uci.zip -[website-badge]:https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org -[website-link]:https://stockfishchess.org -[worker-link]:https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview + +[authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS +[build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml +[commits-link]: https://github.com/official-stockfish/Stockfish/commits/master +[discord-link]: https://discord.gg/GWDRS3kU6R +[fishcooking-link]: https://groups.google.com/g/fishcooking +[fishtest-link]: https://tests.stockfishchess.org/tests +[github-link]: https://github.com/official-stockfish/Stockfish +[guideline-link]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test +[license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt +[lockpages-link]: https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows +[nodchip-link]: https://github.com/nodchip/Stockfish +[programming-link]: https://www.chessprogramming.org/Main_Page +[programmingsf-link]: https://www.chessprogramming.org/Stockfish +[pytorch-link]: https://github.com/glinscott/nnue-pytorch +[rammap-link]: https://docs.microsoft.com/en-us/sysinternals/downloads/rammap +[readme-link]: https://github.com/official-stockfish/Stockfish/blob/master/README.md +[release-link]: https://github.com/official-stockfish/Stockfish/releases/latest +[src-link]: https://github.com/official-stockfish/Stockfish/tree/master/src +[stockfish128-logo]: https://stockfishchess.org/images/logo/icon_128x128.png +[tools-link]: https://github.com/official-stockfish/Stockfish/tree/tools +[uci-link]: https://www.shredderchess.com/download/div/uci.zip +[website-link]: https://stockfishchess.org +[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview + +[build-badge]: https://img.shields.io/github/workflow/status/official-stockfish/Stockfish/Stockfish?style=for-the-badge&label=stockfish&logo=github +[commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge +[discord-badge]: https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord +[fishtest-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests +[license-badge]: https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success +[release-badge]: https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release + +[website-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org From 8333b2a94c7c7e6e13822153f35899b64a690ac2 Mon Sep 17 00:00:00 2001 From: Clausable <97650056+Clausable@users.noreply.github.com> Date: Mon, 24 Oct 2022 16:00:31 -0400 Subject: [PATCH 098/678] Fix README typos, update AUTHORS closes https://github.com/official-stockfish/Stockfish/pull/4208 No functional change --- AUTHORS | 7 ++++--- README.md | 27 +++++++++++++-------------- src/nnue/features/half_ka_v2_hm.cpp | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/AUTHORS b/AUTHORS index d4b37e7a..173669d4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,6 +39,7 @@ Boštjan Mejak (PedanticHacker) braich Brian Sheppard (SapphireBrand, briansheppard-toast) Bruno de Melo Costa (BM123499) +Bruno Pellanda (pellanda) Bryan Cross (crossbr) candirufish Chess13234 @@ -50,6 +51,7 @@ Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) Dariusz Orzechowski (dorzechowski) David Zar +David (dav1312) Daylen Yang (daylen) Deshawn Mohan-Smith (GoldenRare) Dieter Dobbelaere (ddobbelaere) @@ -159,7 +161,6 @@ Panthee Pascal Romaret Pasquale Pigazzini (ppigazzini) Patrick Jansen (mibere) -pellanda Peter Schneider (pschneider1968) Peter Zsifkovits (CoffeeOne) Praveen Kumar Tummala (praveentml) @@ -167,8 +168,8 @@ Rahul Dsilva (silversolver1) Ralph Stößer (Ralph Stoesser) Raminder Singh renouve -Reuven Peleg -Richard Lloyd +Reuven Peleg (R-Peleg) +Richard Lloyd (Richard-Lloyd) Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) Ron Britvich (Britvich) diff --git a/README.md b/README.md index 7066db4b..ac15fbfa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
- + [![Stockfish][stockfish128-logo]][website-link] - + [![Build][build-badge]][build-link] [![License][license-badge]][license-link]
@@ -11,7 +11,7 @@ [![Website][website-badge]][website-link] [![Fishtest][fishtest-badge]][fishtest-link] [![Discord][discord-badge]][discord-link] - +
## Overview @@ -20,14 +20,14 @@ Glaurung 2.1. Stockfish is not a complete chess program and requires a UCI-compatible graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably. -Read the documentation for your GUI of choice for informationabout how to use +Read the documentation for your GUI of choice for information about how to use Stockfish with it. -The Stockfish engine features two evaluation functions for chess. The efficiently +The Stockfish engine features two evaluation functions for chess. The efficiently updatable neural network (NNUE) based evaluation is the default and by far the strongest. -The classical evaluation based on handcrafted terms remains available. The strongest +The classical evaluation based on handcrafted terms remains available. The strongest network is integrated in the binary and downloaded automatically during the build process. -The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2, +The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2, avx2, neon, or similar). ## Files @@ -152,8 +152,8 @@ change them via a chess GUI. This is a list of available UCI options in Stockfis For developers the following non-standard commands might be of interest, mainly useful for debugging: * #### bench *ttSize threads limit fenFile limitType evalType* - Performs a standard benchmark using various options. The signature of a version - (standard node count) is obtained using all defaults. `bench` is currently + Performs a standard benchmark using various options. The signature of a version + (standard node count) is obtained using all defaults. `bench` is currently `bench 16 1 13 default depth mixed`. * #### compiler @@ -201,9 +201,9 @@ the engine is somewhat lower (roughly 80% of nps is typical). Notes: -1) the NNUE evaluation depends on the Stockfish binary and the network parameter file +1) the NNUE evaluation depends on the Stockfish binary and the network parameter file (see the EvalFile UCI option). Not every parameter file is compatible with a given -Stockfish binary, but the default value of the EvalFile UCI option is the name of a +Stockfish binary, but the default value of the EvalFile UCI option is the name of a network that is guaranteed to be compatible with that binary. 2) to use the NNUE evaluation, the additional data file with neural network parameters @@ -337,7 +337,7 @@ using it as the starting point for a software project of your own. The only real limitation is that whenever you distribute Stockfish in some way, you MUST always include the license and the full source code -(or a pointer to where the source code can be found) to generate the +(or a pointer to where the source code can be found) to generate the exact binary you are distributing. If you make any changes to the source code, these changes must also be made available under the GPL v3. @@ -372,8 +372,7 @@ For full details, read the copy of the GPL v3 found in the file named [build-badge]: https://img.shields.io/github/workflow/status/official-stockfish/Stockfish/Stockfish?style=for-the-badge&label=stockfish&logo=github [commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge [discord-badge]: https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord -[fishtest-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests +[fishtest-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests%2Ffinished [license-badge]: https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success [release-badge]: https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release - [website-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 11e05c94..7dbd3415 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -48,7 +48,7 @@ namespace Stockfish::Eval::NNUE::Features { // Explicit template instantiations template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); - + // append_changed_indices() : get a list of indices for recently changed features template void HalfKAv2_hm::append_changed_indices( From f154ed7a2d4176670cc971611b1b32e8d3d18b4b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 29 Oct 2022 08:23:11 +0200 Subject: [PATCH 099/678] Update MacOS CI move to 12 following actions runner update deprecation (see https://github.com/actions/runner-images/issues/5583) closes https://github.com/official-stockfish/Stockfish/pull/4212 No functional change --- .github/workflows/stockfish.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index b007ec78..6ff73522 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -62,17 +62,17 @@ jobs: shell: 'bash {0}' } - { - name: "MacOS 10.15 Apple Clang", - os: macos-10.15, + name: "MacOS 12 Apple Clang", + os: macos-12, compiler: clang++, comp: clang, run_64bit_tests: true, shell: 'bash {0}' } - { - name: "MacOS 10.15 GCC 10", - os: macos-10.15, - compiler: g++-10, + name: "MacOS 12 GCC 11", + os: macos-12, + compiler: g++-11, comp: gcc, run_64bit_tests: true, shell: 'bash {0}' From d09653df0d1bfec0af05ab2e8975e0d8e5cccba8 Mon Sep 17 00:00:00 2001 From: kurt22i Date: Sun, 30 Oct 2022 09:02:48 -0400 Subject: [PATCH 100/678] Adjust reduction less at medium depths This patch dampens the reduction increase/decrease from statScore at mid-range depths. Inspired by patterns noticed in this tune: https://tests.stockfishchess.org/tests/view/635188930e5f47a8d0ffe8f5 Passed STC: https://tests.stockfishchess.org/tests/view/63599dfd6b27ef94d9ec04af LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 87464 W: 23519 L: 23134 D: 40811 Ptnml(0-2): 319, 9599, 23524, 9958, 332 Passed LTC: https://tests.stockfishchess.org/tests/view/635a73046b27ef94d9ec2313 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 154792 W: 41746 L: 41214 D: 71832 Ptnml(0-2): 79, 15181, 46349, 15703, 84 closes https://github.com/official-stockfish/Stockfish/pull/4213 Bench 4271738 --- AUTHORS | 2 +- src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 173669d4..432d9b90 100644 --- a/AUTHORS +++ b/AUTHORS @@ -91,7 +91,7 @@ Hongzhi Cheng Ivan Ivec (IIvec) Jacques B. (Timshel) Jan Ondruš (hxim) -Jared Kish (Kurtbusch) +Jared Kish (Kurtbusch, kurt22i) Jarrod Torriero (DU-jdto) Jean Gauthier (OuaisBla) Jean-Francois Romang (jromang) diff --git a/src/search.cpp b/src/search.cpp index 898de875..422d4b43 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1177,7 +1177,7 @@ moves_loop: // When in check, search starts here - 4433; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / 13628; + r -= ss->statScore / (13628 + 4000 * (depth > 7 && depth < 19)); // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension From 61a2cb84a60b8a48a0e1d34955d8a4d1acdcf497 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 5 Nov 2022 09:14:11 +0100 Subject: [PATCH 101/678] Mark variable as potentially unused fixes CI when compiled with -Werror closes https://github.com/official-stockfish/Stockfish/pull/4221 No functional change --- src/position.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/position.cpp b/src/position.cpp index 62e6e238..5befcaf2 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -129,7 +129,7 @@ void Position::init() { // Prepare the cuckoo tables std::memset(cuckoo, 0, sizeof(cuckoo)); std::memset(cuckooMove, 0, sizeof(cuckooMove)); - int count = 0; + [[maybe_unused]] int count = 0; for (Piece pc : Pieces) for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) From ad2aa8c06f438de8b8bb7b7c8726430e3f2a5685 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 31 Oct 2022 20:36:43 +0100 Subject: [PATCH 102/678] Normalize evaluation Normalizes the internal value as reported by evaluate or search to the UCI centipawn result used in output. This value is derived from the win_rate_model() such that Stockfish outputs an advantage of "100 centipawns" for a position if the engine has a 50% probability to win from this position in selfplay at fishtest LTC time control. The reason to introduce this normalization is that our evaluation is, since NNUE, no longer related to the classical parameter PawnValueEg (=208). This leads to the current evaluation changing quite a bit from release to release, for example, the eval needed to have 50% win probability at fishtest LTC (in cp and internal Value): June 2020 : 113cp (237) June 2021 : 115cp (240) April 2022 : 134cp (279) July 2022 : 167cp (348) With this patch, a 100cp advantage will have a fixed interpretation, i.e. a 50% win chance. To keep this value steady, it will be needed to update the win_rate_model() from time to time, based on fishtest data. This analysis can be performed with a set of scripts currently available at https://github.com/vondele/WLD_model fixes https://github.com/official-stockfish/Stockfish/issues/4155 closes https://github.com/official-stockfish/Stockfish/pull/4216 No functional change --- src/nnue/evaluate_nnue.cpp | 4 ++-- src/uci.cpp | 12 ++++++++---- src/uci.h | 7 +++++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index ba2ed367..4715fed0 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -220,7 +220,7 @@ namespace Stockfish::Eval::NNUE { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - int cp = std::abs(100 * v / PawnValueEg); + int cp = std::abs(100 * v / UCI::NormalizeToPawnValue); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; cp %= 10000; @@ -251,7 +251,7 @@ namespace Stockfish::Eval::NNUE { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - double cp = 1.0 * std::abs(int(v)) / PawnValueEg; + double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue; sprintf(&buffer[1], "%6.2f", cp); } diff --git a/src/uci.cpp b/src/uci.cpp index d5e2c2c3..19e2b0cb 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -207,13 +207,17 @@ namespace { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - double as[] = { 0.50379905, -4.12755858, 18.95487051, 152.00733652}; - double bs[] = {-1.71790378, 10.71543602, -17.05515898, 41.15680404}; + constexpr double as[] = { 1.04790516, -8.58534089, 39.42615625, 316.17524816}; + constexpr double bs[] = { -3.57324784, 22.28816201, -35.47480551, 85.60617701 }; + + // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 + static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; // Transform the eval to centipawns with limited range - double x = std::clamp(double(100 * v) / PawnValueEg, -2000.0, 2000.0); + double x = std::clamp(double(v), -4000.0, 4000.0); // Return the win rate in per mille units rounded to the nearest value return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); @@ -312,7 +316,7 @@ string UCI::value(Value v) { stringstream ss; if (abs(v) < VALUE_MATE_IN_MAX_PLY) - ss << "cp " << v * 100 / PawnValueEg; + ss << "cp " << v * 100 / NormalizeToPawnValue; else ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; diff --git a/src/uci.h b/src/uci.h index 76a893f9..f5f2c385 100644 --- a/src/uci.h +++ b/src/uci.h @@ -30,6 +30,13 @@ class Position; namespace UCI { +// Normalizes the internal value as reported by evaluate or search +// to the UCI centipawn result used in output. This value is derived from +// the win_rate_model() such that Stockfish outputs an advantage of +// "100 centipawns" for a position if the engine has a 50% probability to win +// from this position in selfplay at fishtest LTC time control. +const int NormalizeToPawnValue = 348; + class Option; /// Define a custom comparator, because the UCI options should be case-insensitive From e048d1182562eb7ce1eae45f626681206da446b8 Mon Sep 17 00:00:00 2001 From: disservin Date: Sun, 6 Nov 2022 16:17:17 +0100 Subject: [PATCH 103/678] Change versioning and save binaries as CI artifacts For development versions of Stockfish, the version will now look like dev-20221107-dca9a0533 indicating a development version, the date of the last commit, and the git SHA of that commit. If git is not available, the fallback is the date of compilation. Releases will continue to be versioned as before. Additionally, this PR extends the CI to create binary artifacts, i.e. pushes to master will automatically build Stockfish and upload the binaries to github. closes https://github.com/official-stockfish/Stockfish/pull/4220 No functional change --- .github/workflows/stockfish.yml | 5 +- .github/workflows/stockfish_binaries.yml | 110 +++++++++++++++++++++++ src/Makefile | 18 +++- src/misc.cpp | 47 ++++++---- 4 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/stockfish_binaries.yml diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 6ff73522..28830133 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -10,6 +10,9 @@ on: - master - tools jobs: + Binaries: + if: github.ref == 'refs/heads/master' + uses: ./.github/workflows/stockfish_binaries.yml Stockfish: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} @@ -113,7 +116,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml new file mode 100644 index 00000000..0b205ded --- /dev/null +++ b/.github/workflows/stockfish_binaries.yml @@ -0,0 +1,110 @@ +name: Stockfish +on: + workflow_call: +jobs: + Stockfish: + name: ${{ matrix.config.name }} ${{ matrix.binaries }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + EXT: ${{ matrix.config.ext }} + OS: ${{ matrix.config.os }} + BINARY: ${{ matrix.binaries }} + strategy: + matrix: + config: + - { + name: "Ubuntu 20.04 GCC", + os: ubuntu-20.04, + compiler: g++, + comp: gcc, + shell: 'bash {0}' + } + - { + name: "MacOS 12 Apple Clang", + os: macos-12, + compiler: clang++, + comp: clang, + shell: 'bash {0}' + } + - { + name: "Windows 2022 Mingw-w64 GCC x86_64", + os: windows-2022, + compiler: g++, + comp: mingw, + msys_sys: 'mingw64', + msys_env: 'x86_64-gcc', + shell: 'msys2 {0}', + ext: .exe + } + binaries: + - x86-64 + - x86-64-modern + - x86-64-avx2 + exclude: + - binaries: x86-64-avx2 + config: {os: macos-12} + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download required linux packages + if: runner.os == 'Linux' + run: | + sudo apt update + + - name: Setup msys and install required packages + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{matrix.config.msys_sys}} + install: mingw-w64-${{matrix.config.msys_env}} make git expect + + - name: Download the used network from the fishtest framework + run: | + make net + + - name: Check compiler + run: | + export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin + $COMPILER -v + + - name: Test help target + run: | + make help + + # Compile profile guided builds + + - name: Compile ${{ matrix.binaries }} build + run: | + make clean + make -j2 profile-build ARCH=$BINARY COMP=$COMP + strip ./stockfish$EXT + mv ./stockfish$EXT ../stockfish-$OS-$BINARY$EXT + + - name: Remove non src files + run: rm -f *.o .depend *.nnue + + - name: Create tar archive. + run: | + cd .. + mkdir stockfish + cp -r src stockfish/ + cp stockfish-$OS-$BINARY$EXT stockfish/ + cp "Top CPU Contributors.txt" stockfish/ + cp Copying.txt stockfish/ + cp AUTHORS stockfish/ + tar -cvf stockfish-$OS-$BINARY.tar stockfish + + - name: Upload binaries + uses: actions/upload-artifact@v3 + with: + name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} + path: | + stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}.tar diff --git a/src/Makefile b/src/Makefile index e481aca5..917bd5c0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -682,6 +682,18 @@ ifeq ($(pext),yes) endif endif +### 3.7.1 Try to include git commit sha for versioning +GIT_SHA = $(shell git rev-parse --short HEAD 2>/dev/null) +ifneq ($(GIT_SHA), ) + CXXFLAGS += -DGIT_SHA=\"$(GIT_SHA)\" +endif + +### 3.7.2 Try to include git commit date for versioning +GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) +ifneq ($(GIT_DATE), ) + CXXFLAGS += -DGIT_DATE=\"$(GIT_DATE)\" +endif + ### 3.8 Link Time Optimization ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. @@ -800,7 +812,7 @@ endif .PHONY: help build profile-build strip install clean net objclean profileclean \ config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ - clang-profile-use clang-profile-make + clang-profile-use clang-profile-make FORCE build: net config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all @@ -948,6 +960,10 @@ config-sanity: net $(EXE): $(OBJS) +$(CXX) -o $@ $(OBJS) $(LDFLAGS) +# Force recompilation to ensure version info is up-to-date +misc.o: FORCE +FORCE: + clang-profile-make: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ EXTRACXXFLAGS='-fprofile-instr-generate ' \ diff --git a/src/misc.cpp b/src/misc.cpp index d19cd840..c7fa0e9a 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -67,9 +67,8 @@ namespace Stockfish { namespace { -/// Version number. If Version is left empty, then compile date in the format -/// DD-MM-YY and show in engine_info. -const string Version = ""; +/// Version number or dev. +const string version = "dev"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -138,23 +137,41 @@ public: } // namespace -/// engine_info() returns the full name of the current Stockfish version. This -/// will be either "Stockfish DD-MM-YY" (where DD-MM-YY is the date when -/// the program was compiled) or "Stockfish ", depending on whether -/// Version is empty. +/// engine_info() returns the full name of the current Stockfish version. +/// For local dev compiles we try to append the commit sha and commit date +/// from git if that fails only the local compilation date is set and "nogit" is specified: +/// Stockfish dev-YYYYMMDD-SHA +/// or +/// Stockfish dev-YYYYMMDD-nogit +/// +/// For releases (non dev builds) we only include the version number: +/// Stockfish version string engine_info(bool to_uci) { + stringstream ss; + ss << "Stockfish " << version << setfill('0'); - const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); - string month, day, year; - stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008" - - ss << "Stockfish " << Version << setfill('0'); - - if (Version.empty()) + if (version == "dev") { + ss << "-"; + #ifdef GIT_DATE + ss << GIT_DATE; + #else + const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + string month, day, year; + stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" + date >> month >> day >> year; - ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2); + ss << year << setw(2) << setfill('0') << (1 + months.find(month) / 4) << setw(2) << setfill('0') << day; + #endif + + ss << "-"; + + #ifdef GIT_SHA + ss << GIT_SHA; + #else + ss << "nogit"; + #endif } ss << (to_uci ? "\nid author ": " by ") From a41390079147aea89cb5222089a45e1958ebec8e Mon Sep 17 00:00:00 2001 From: disservin Date: Thu, 3 Nov 2022 21:53:02 +0100 Subject: [PATCH 104/678] Remove trend Simplify trend away. passed Non-regression STC: https://tests.stockfishchess.org/tests/view/63642a63a90afcecbd1cb887 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 130000 W: 34683 L: 34567 D: 60750 Ptnml(0-2): 455, 14424, 35135, 14522, 464 passed Non-regression LTC: https://tests.stockfishchess.org/tests/view/636566fda90afcecbd1cded9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 81592 W: 21938 L: 21787 D: 37867 Ptnml(0-2): 42, 8035, 24490, 8188, 41 closes https://github.com/official-stockfish/Stockfish/pull/4222 Bench: 4239512 --- src/evaluate.cpp | 3 +-- src/search.cpp | 7 +------ src/thread.h | 1 - 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d5844593..d18c2c93 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -981,7 +981,7 @@ namespace { // Initialize score by reading the incrementally updated scores included in // the position object (material + piece square tables) and the material // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->trend; + Score score = pos.psq_score() + me->imbalance(); // Probe the pawn hash table pe = Pawns::probe(pos); @@ -1115,7 +1115,6 @@ std::string Eval::trace(Position& pos) { std::memset(scores, 0, sizeof(scores)); // Reset any global variable used in eval - pos.this_thread()->trend = SCORE_ZERO; pos.this_thread()->bestValue = VALUE_ZERO; pos.this_thread()->optimism[WHITE] = VALUE_ZERO; pos.this_thread()->optimism[BLACK] = VALUE_ZERO; diff --git a/src/search.cpp b/src/search.cpp index 422d4b43..77619345 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -309,7 +309,6 @@ void Thread::search() { complexityAverage.set(155, 1); - trend = SCORE_ZERO; optimism[us] = optimism[~us] = VALUE_ZERO; int searchAgainCounter = 0; @@ -356,11 +355,7 @@ void Thread::search() { alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); - // Adjust trend and optimism based on root move's previousScore - int tr = 116 * prev / (std::abs(prev) + 89); - trend = (us == WHITE ? make_score(tr, tr / 2) - : -make_score(tr, tr / 2)); - + // Adjust optimism based on root move's previousScore int opt = 118 * prev / (std::abs(prev) + 169); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; diff --git a/src/thread.h b/src/thread.h index c430a818..5f0b2c3e 100644 --- a/src/thread.h +++ b/src/thread.h @@ -75,7 +75,6 @@ public: ButterflyHistory mainHistory; CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; - Score trend; }; From 6c1df553fa7a94551de7b07515a29098a2f23c16 Mon Sep 17 00:00:00 2001 From: disservin Date: Mon, 7 Nov 2022 18:15:42 +0100 Subject: [PATCH 105/678] speedup CI Github Actions allows us to use up to 20 workers. This way we can launch multiple different checks at the same time and optimize the overall time the CI takes a bit. closes https://github.com/official-stockfish/Stockfish/pull/4223 No functional change --- .github/workflows/stockfish.yml | 344 +------------------ .github/workflows/stockfish_compile_test.yml | 115 +++++++ .github/workflows/stockfish_sanitizers.yml | 77 +++++ .github/workflows/stockfish_test.yml | 284 +++++++++++++++ 4 files changed, 482 insertions(+), 338 deletions(-) create mode 100644 .github/workflows/stockfish_compile_test.yml create mode 100644 .github/workflows/stockfish_sanitizers.yml create mode 100644 .github/workflows/stockfish_test.yml diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 28830133..07ecfc07 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -10,344 +10,12 @@ on: - master - tools jobs: + Sanitizers: + uses: ./.github/workflows/stockfish_sanitizers.yml + Tests: + uses: ./.github/workflows/stockfish_test.yml + Compiles: + uses: ./.github/workflows/stockfish_compile_test.yml Binaries: if: github.ref == 'refs/heads/master' uses: ./.github/workflows/stockfish_binaries.yml - Stockfish: - name: ${{ matrix.config.name }} - runs-on: ${{ matrix.config.os }} - env: - COMPILER: ${{ matrix.config.compiler }} - COMP: ${{ matrix.config.comp }} - CXXFLAGS: "-Werror" - strategy: - matrix: - config: - # set the variable for the required tests: - # run_expensive_tests: true - # run_32bit_tests: true - # run_64bit_tests: true - # run_armv8_tests: true - # run_armv7_tests: true - - { - name: "Ubuntu 20.04 GCC", - os: ubuntu-20.04, - compiler: g++, - comp: gcc, - run_expensive_tests: true, - run_32bit_tests: true, - run_64bit_tests: true, - shell: 'bash {0}' - } - - { - name: "Ubuntu 20.04 Clang", - os: ubuntu-20.04, - compiler: clang++, - comp: clang, - run_32bit_tests: true, - run_64bit_tests: true, - shell: 'bash {0}' - } - - { - name: "Ubuntu 20.04 NDK armv8", - os: ubuntu-20.04, - compiler: aarch64-linux-android21-clang++, - comp: ndk, - run_armv8_tests: false, - shell: 'bash {0}' - } - - { - name: "Ubuntu 20.04 NDK armv7", - os: ubuntu-20.04, - compiler: armv7a-linux-androideabi21-clang++, - comp: ndk, - run_armv7_tests: false, - shell: 'bash {0}' - } - - { - name: "MacOS 12 Apple Clang", - os: macos-12, - compiler: clang++, - comp: clang, - run_64bit_tests: true, - shell: 'bash {0}' - } - - { - name: "MacOS 12 GCC 11", - os: macos-12, - compiler: g++-11, - comp: gcc, - run_64bit_tests: true, - shell: 'bash {0}' - } - - { - name: "Windows 2022 Mingw-w64 GCC x86_64", - os: windows-2022, - compiler: g++, - comp: mingw, - run_64bit_tests: true, - msys_sys: 'mingw64', - msys_env: 'x86_64-gcc', - shell: 'msys2 {0}' - } - - { - name: "Windows 2022 Mingw-w64 GCC i686", - os: windows-2022, - compiler: g++, - comp: mingw, - run_32bit_tests: true, - msys_sys: 'mingw32', - msys_env: 'i686-gcc', - shell: 'msys2 {0}' - } - - { - name: "Windows 2022 Mingw-w64 Clang x86_64", - os: windows-2022, - compiler: clang++, - comp: clang, - run_64bit_tests: true, - msys_sys: 'clang64', - msys_env: 'clang-x86_64-clang', - shell: 'msys2 {0}' - } - - defaults: - run: - working-directory: src - shell: ${{ matrix.config.shell }} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Download required linux packages - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install expect valgrind g++-multilib qemu-user - - - name: Setup msys and install required packages - if: runner.os == 'Windows' - uses: msys2/setup-msys2@v2 - with: - msystem: ${{matrix.config.msys_sys}} - install: mingw-w64-${{matrix.config.msys_env}} make git expect - - - name: Download the used network from the fishtest framework - run: | - make net - - - name: Extract the bench number from the commit history - run: | - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig - [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found" - - - name: Check compiler - run: | - export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin - $COMPILER -v - - - name: Test help target - run: | - make help - - # x86-32 tests - - - name: Test debug x86-32 build - if: ${{ matrix.config.run_32bit_tests }} - run: | - export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" - make clean - make -j2 ARCH=x86-32 optimize=no debug=yes build - ../tests/signature.sh $benchref - - - name: Test x86-32 build - if: ${{ matrix.config.run_32bit_tests }} - run: | - make clean - make -j2 ARCH=x86-32 build - ../tests/signature.sh $benchref - - - name: Test x86-32-sse41-popcnt build - if: ${{ matrix.config.run_32bit_tests }} - run: | - make clean - make -j2 ARCH=x86-32-sse41-popcnt build - ../tests/signature.sh $benchref - - - name: Test x86-32-sse2 build - if: ${{ matrix.config.run_32bit_tests }} - run: | - make clean - make -j2 ARCH=x86-32-sse2 build - ../tests/signature.sh $benchref - - - name: Test general-32 build - if: ${{ matrix.config.run_32bit_tests }} - run: | - make clean - make -j2 ARCH=general-32 build - ../tests/signature.sh $benchref - - # x86-64 tests - - - name: Test debug x86-64-modern build - if: ${{ matrix.config.run_64bit_tests }} - run: | - export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" - make clean - make -j2 ARCH=x86-64-modern optimize=no debug=yes build - ../tests/signature.sh $benchref - - - name: Test x86-64-modern build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-modern build - ../tests/signature.sh $benchref - - - name: Test x86-64-ssse3 build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-ssse3 build - ../tests/signature.sh $benchref - - - name: Test x86-64-sse3-popcnt build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-sse3-popcnt build - ../tests/signature.sh $benchref - - - name: Test x86-64 build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64 build - ../tests/signature.sh $benchref - - - name: Test general-64 build - if: matrix.config.run_64bit_tests - run: | - make clean - make -j2 ARCH=general-64 build - ../tests/signature.sh $benchref - - # x86-64 with newer extensions tests - - - name: Compile x86-64-avx2 build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-avx2 build - - - name: Compile x86-64-bmi2 build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-bmi2 build - - - name: Compile x86-64-avx512 build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-avx512 build - - - name: Compile x86-64-vnni512 build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-vnni512 build - - - name: Compile x86-64-vnni256 build - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-vnni256 build - - # armv8 tests - - - name: Test armv8 build - if: ${{ matrix.config.run_armv8_tests }} - run: | - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk - SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle - ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT - export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH - export LDFLAGS="-static -Wno-unused-command-line-argument" - make clean - make -j2 ARCH=armv8 build - ../tests/signature.sh $benchref - - # armv7 tests - - - name: Test armv7 build - if: ${{ matrix.config.run_armv7_tests }} - run: | - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk - SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle - ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT - export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH - export LDFLAGS="-static -Wno-unused-command-line-argument" - make clean - make -j2 ARCH=armv7 build - ../tests/signature.sh $benchref - - - name: Test armv7-neon build - if: ${{ matrix.config.run_armv7_tests }} - run: | - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk - SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle - ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT - export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH - export LDFLAGS="-static -Wno-unused-command-line-argument" - make clean - make -j2 ARCH=armv7-neon build - ../tests/signature.sh $benchref - - # Other tests - - - name: Check perft and search reproducibility - if: ${{ matrix.config.run_64bit_tests }} - run: | - make clean - make -j2 ARCH=x86-64-modern build - ../tests/perft.sh - ../tests/reprosearch.sh - - # Sanitizers - - - name: Run under valgrind - if: ${{ matrix.config.run_expensive_tests }} - run: | - export CXXFLAGS="-O1 -fno-inline" - make clean - make -j2 ARCH=x86-64-modern debug=yes optimize=no build > /dev/null - ../tests/instrumented.sh --valgrind - ../tests/instrumented.sh --valgrind-thread - - - name: Run with UB sanitizer - if: ${{ matrix.config.run_expensive_tests }} - run: | - export CXXFLAGS="-O1 -fno-inline" - make clean - make -j2 ARCH=x86-64-modern sanitize=undefined optimize=no debug=yes build > /dev/null - ../tests/instrumented.sh --sanitizer-undefined - - - name: Run with thread sanitizer - if: ${{ matrix.config.run_expensive_tests }} - run: | - export CXXFLAGS="-O1 -fno-inline" - make clean - make -j2 ARCH=x86-64-modern sanitize=thread optimize=no debug=yes build > /dev/null - ../tests/instrumented.sh --sanitizer-thread diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml new file mode 100644 index 00000000..63136737 --- /dev/null +++ b/.github/workflows/stockfish_compile_test.yml @@ -0,0 +1,115 @@ +name: Stockfish +on: + workflow_call: +jobs: + Stockfish: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + strategy: + matrix: + config: + - { + name: "Ubuntu 20.04 GCC", + os: ubuntu-20.04, + compiler: g++, + comp: gcc, + shell: 'bash {0}' + } + - { + name: "Ubuntu 20.04 Clang", + os: ubuntu-20.04, + compiler: clang++, + comp: clang, + shell: 'bash {0}' + } + - { + name: "MacOS 12 Apple Clang", + os: macos-12, + compiler: clang++, + comp: clang, + shell: 'bash {0}' + } + - { + name: "MacOS 12 GCC 11", + os: macos-12, + compiler: g++-11, + comp: gcc, + shell: 'bash {0}' + } + - { + name: "Windows 2022 Mingw-w64 GCC x86_64", + os: windows-2022, + compiler: g++, + comp: mingw, + msys_sys: 'mingw64', + msys_env: 'x86_64-gcc', + shell: 'msys2 {0}' + } + - { + name: "Windows 2022 Mingw-w64 Clang x86_64", + os: windows-2022, + compiler: clang++, + comp: clang, + msys_sys: 'clang64', + msys_env: 'clang-x86_64-clang', + shell: 'msys2 {0}' + } + + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup msys and install required packages + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{matrix.config.msys_sys}} + install: mingw-w64-${{matrix.config.msys_env}} make git expect + + - name: Download the used network from the fishtest framework + run: | + make net + + - name: Check compiler + run: | + export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin + $COMPILER -v + + - name: Test help target + run: | + make help + + # x86-64 with newer extensions tests + + - name: Compile x86-64-avx2 build + run: | + make clean + make -j2 ARCH=x86-64-avx2 build + + - name: Compile x86-64-bmi2 build + run: | + make clean + make -j2 ARCH=x86-64-bmi2 build + + - name: Compile x86-64-avx512 build + run: | + make clean + make -j2 ARCH=x86-64-avx512 build + + - name: Compile x86-64-vnni512 build + run: | + make clean + make -j2 ARCH=x86-64-vnni512 build + + - name: Compile x86-64-vnni256 build + run: | + make clean + make -j2 ARCH=x86-64-vnni256 build \ No newline at end of file diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml new file mode 100644 index 00000000..61eaf0c9 --- /dev/null +++ b/.github/workflows/stockfish_sanitizers.yml @@ -0,0 +1,77 @@ +name: Stockfish +on: + workflow_call: +jobs: + Stockfish: + name: ${{ matrix.sanitizers.name }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + CXXFLAGS: "-Werror" + strategy: + matrix: + config: + - { + name: "Ubuntu 20.04 GCC", + os: ubuntu-20.04, + compiler: g++, + comp: gcc, + shell: 'bash {0}' + } + sanitizers: + - { + name: Run with thread sanitizer, + make_option: sanitize=thread, + instrumented_option: sanitizer-thread + } + - { + name: Run with UB sanitizer, + make_option: sanitize=undefined, + instrumented_option: sanitizer-undefined + } + - { + name: Run under valgrind, + make_option: "", + instrumented_option: valgrind + } + - { + name: Run under valgrind-thread, + make_option: "", + instrumented_option: valgrind-thread + } + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download required linux packages + run: | + sudo apt update + sudo apt install expect valgrind g++-multilib qemu-user + + - name: Download the used network from the fishtest framework + run: | + make net + + - name: Check compiler + run: | + export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin + $COMPILER -v + + - name: Test help target + run: | + make help + + # Sanitizers + + - name: ${{ matrix.sanitizers.name }} + run: | + export CXXFLAGS="-O1 -fno-inline" + make clean + make -j2 ARCH=x86-64-modern ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null + ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }} \ No newline at end of file diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml new file mode 100644 index 00000000..46b4e26f --- /dev/null +++ b/.github/workflows/stockfish_test.yml @@ -0,0 +1,284 @@ +name: Stockfish +on: + workflow_call: +jobs: + Stockfish: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + CXXFLAGS: "-Werror" + strategy: + matrix: + config: + - { + name: "Ubuntu 20.04 GCC", + os: ubuntu-20.04, + compiler: g++, + comp: gcc, + run_32bit_tests: true, + run_64bit_tests: true, + shell: 'bash {0}' + } + - { + name: "Ubuntu 20.04 Clang", + os: ubuntu-20.04, + compiler: clang++, + comp: clang, + run_32bit_tests: true, + run_64bit_tests: true, + shell: 'bash {0}' + } + - { + name: "Ubuntu 20.04 NDK armv8", + os: ubuntu-20.04, + compiler: aarch64-linux-android21-clang++, + comp: ndk, + run_armv8_tests: false, + shell: 'bash {0}' + } + - { + name: "Ubuntu 20.04 NDK armv7", + os: ubuntu-20.04, + compiler: armv7a-linux-androideabi21-clang++, + comp: ndk, + run_armv7_tests: false, + shell: 'bash {0}' + } + - { + name: "MacOS 12 Apple Clang", + os: macos-12, + compiler: clang++, + comp: clang, + run_64bit_tests: true, + shell: 'bash {0}' + } + - { + name: "MacOS 12 GCC 11", + os: macos-12, + compiler: g++-11, + comp: gcc, + run_64bit_tests: true, + shell: 'bash {0}' + } + - { + name: "Windows 2022 Mingw-w64 GCC x86_64", + os: windows-2022, + compiler: g++, + comp: mingw, + run_64bit_tests: true, + msys_sys: 'mingw64', + msys_env: 'x86_64-gcc', + shell: 'msys2 {0}' + } + - { + name: "Windows 2022 Mingw-w64 GCC i686", + os: windows-2022, + compiler: g++, + comp: mingw, + run_32bit_tests: true, + msys_sys: 'mingw32', + msys_env: 'i686-gcc', + shell: 'msys2 {0}' + } + - { + name: "Windows 2022 Mingw-w64 Clang x86_64", + os: windows-2022, + compiler: clang++, + comp: clang, + run_64bit_tests: true, + msys_sys: 'clang64', + msys_env: 'clang-x86_64-clang', + shell: 'msys2 {0}' + } + exclude: + - config: + { + name: "Ubuntu 20.04 NDK armv7" + } + - config: + { + name: "Ubuntu 20.04 NDK armv8" + } + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download required linux packages + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install expect valgrind g++-multilib qemu-user + + - name: Setup msys and install required packages + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{matrix.config.msys_sys}} + install: mingw-w64-${{matrix.config.msys_env}} make git expect + + - name: Download the used network from the fishtest framework + run: | + make net + + - name: Extract the bench number from the commit history + run: | + git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig + [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found" + + - name: Check compiler + run: | + export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin + $COMPILER -v + + - name: Test help target + run: | + make help + + # x86-32 tests + + - name: Test debug x86-32 build + if: ${{ matrix.config.run_32bit_tests }} + run: | + export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" + make clean + make -j2 ARCH=x86-32 optimize=no debug=yes build + ../tests/signature.sh $benchref + + - name: Test x86-32 build + if: ${{ matrix.config.run_32bit_tests }} + run: | + make clean + make -j2 ARCH=x86-32 build + ../tests/signature.sh $benchref + + - name: Test x86-32-sse41-popcnt build + if: ${{ matrix.config.run_32bit_tests }} + run: | + make clean + make -j2 ARCH=x86-32-sse41-popcnt build + ../tests/signature.sh $benchref + + - name: Test x86-32-sse2 build + if: ${{ matrix.config.run_32bit_tests }} + run: | + make clean + make -j2 ARCH=x86-32-sse2 build + ../tests/signature.sh $benchref + + - name: Test general-32 build + if: ${{ matrix.config.run_32bit_tests }} + run: | + make clean + make -j2 ARCH=general-32 build + ../tests/signature.sh $benchref + + # x86-64 tests + + - name: Test debug x86-64-modern build + if: ${{ matrix.config.run_64bit_tests }} + run: | + export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" + make clean + make -j2 ARCH=x86-64-modern optimize=no debug=yes build + ../tests/signature.sh $benchref + + - name: Test x86-64-modern build + if: ${{ matrix.config.run_64bit_tests }} + run: | + make clean + make -j2 ARCH=x86-64-modern build + ../tests/signature.sh $benchref + + - name: Test x86-64-ssse3 build + if: ${{ matrix.config.run_64bit_tests }} + run: | + make clean + make -j2 ARCH=x86-64-ssse3 build + ../tests/signature.sh $benchref + + - name: Test x86-64-sse3-popcnt build + if: ${{ matrix.config.run_64bit_tests }} + run: | + make clean + make -j2 ARCH=x86-64-sse3-popcnt build + ../tests/signature.sh $benchref + + - name: Test x86-64 build + if: ${{ matrix.config.run_64bit_tests }} + run: | + make clean + make -j2 ARCH=x86-64 build + ../tests/signature.sh $benchref + + - name: Test general-64 build + if: matrix.config.run_64bit_tests + run: | + make clean + make -j2 ARCH=general-64 build + ../tests/signature.sh $benchref + + # armv8 tests + + - name: Test armv8 build + if: ${{ matrix.config.run_armv8_tests }} + run: | + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle + ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT + export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + make clean + make -j2 ARCH=armv8 build + ../tests/signature.sh $benchref + + # armv7 tests + + - name: Test armv7 build + if: ${{ matrix.config.run_armv7_tests }} + run: | + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle + ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT + export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + make clean + make -j2 ARCH=armv7 build + ../tests/signature.sh $benchref + + - name: Test armv7-neon build + if: ${{ matrix.config.run_armv7_tests }} + run: | + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle + ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT + export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + make clean + make -j2 ARCH=armv7-neon build + ../tests/signature.sh $benchref + + # Other tests + + - name: Check perft and search reproducibility + if: ${{ matrix.config.run_64bit_tests }} + run: | + make clean + make -j2 ARCH=x86-64-modern build + ../tests/perft.sh + ../tests/reprosearch.sh \ No newline at end of file From 219fa2f0a79381d35d9eb1240781cc598e74f647 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 17 Nov 2022 18:31:59 +0300 Subject: [PATCH 106/678] Do shallower search in case of lmr being not successful enough In case of a move passing LMR but it results being not too far from the current best search result produce a full depth search with reduced depth. Original idea by lonfom169 . Passed STC: https://tests.stockfishchess.org/tests/view/6373409b54d69a2f33913fbd LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 169504 W: 45351 L: 44848 D: 79305 Ptnml(0-2): 598, 18853, 45353, 19344, 604 Passed LTC: https://tests.stockfishchess.org/tests/view/6374c58528e3405283eb8d2d LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 51144 W: 13802 L: 13471 D: 23871 Ptnml(0-2): 19, 4928, 15362, 5229, 34 closes https://github.com/official-stockfish/Stockfish/pull/4230 bench 4277005 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 77619345..5839763d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1184,8 +1184,11 @@ moves_loop: // When in check, search starts here // Do full depth search when reduced LMR search fails high if (value > alpha && d < newDepth) { + // Adjust full depth search based on LMR results - if result + // was good enough search deeper, if it was bad enough search shallower const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d)); - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch, !cutNode); + const bool doShallowerSearch = value < bestValue + newDepth; + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch - doShallowerSearch, !cutNode); int bonus = value > alpha ? stat_bonus(newDepth) : -stat_bonus(newDepth); From 41c6a74d372799c6ed94b842db66f956d080e0b0 Mon Sep 17 00:00:00 2001 From: VoyagerOne Date: Mon, 14 Nov 2022 08:11:27 -0500 Subject: [PATCH 107/678] Simplification away Cutoff Reset STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 150184 W: 39913 L: 39819 D: 70452 Ptnml(0-2): 493, 16796, 40474, 16782, 547 https://tests.stockfishchess.org/tests/view/63723e9e54d69a2f33911d3c LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 58880 W: 15890 L: 15717 D: 27273 Ptnml(0-2): 35, 5765, 17659, 5954, 27 https://tests.stockfishchess.org/tests/view/6373baf49849fa7a36a65427 closes https://github.com/official-stockfish/Stockfish/pull/4231 Bench: 4035152 --- src/search.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5839763d..fa87f1c3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1295,8 +1295,6 @@ moves_loop: // When in check, search starts here } } } - else - ss->cutoffCnt = 0; // If the move is worse than some previously searched move, remember it to update its stats later From d756d97a66cb18de182e018446b9149a5ff8ef18 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 19 Nov 2022 10:57:08 +0100 Subject: [PATCH 108/678] Fix a missing conversion This conversion to cp was overlooked. closes https://github.com/official-stockfish/Stockfish/pull/4235 No functional change --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d18c2c93..87412b81 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -159,7 +159,7 @@ namespace Trace { Score scores[TERM_NB][COLOR_NB]; - double to_cp(Value v) { return double(v) / PawnValueEg; } + double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } void add(int idx, Color c, Score s) { scores[idx][c] = s; From 341163116233682bf150d20cddedcb23e6d09431 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 19 Nov 2022 13:03:14 +0100 Subject: [PATCH 109/678] Update WDL model for current SF This updates the WDL model based on the LTC statistics (2M games). Relatively small change, note that this also adjusts the NormalizeToPawnValue (now 361), to keep win prob at 50% for 100cp. closes https://github.com/official-stockfish/Stockfish/pull/4236 No functional change. --- src/uci.cpp | 4 ++-- src/uci.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 19e2b0cb..5d842d25 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -207,8 +207,8 @@ namespace { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = { 1.04790516, -8.58534089, 39.42615625, 316.17524816}; - constexpr double bs[] = { -3.57324784, 22.28816201, -35.47480551, 85.60617701 }; + constexpr double as[] = { -0.58270499, 2.68512549, 15.24638015, 344.49745382}; + constexpr double bs[] = { -2.65734562, 15.96509799, -20.69040836, 73.61029937 }; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); diff --git a/src/uci.h b/src/uci.h index f5f2c385..7a2ef255 100644 --- a/src/uci.h +++ b/src/uci.h @@ -35,7 +35,7 @@ namespace UCI { // the win_rate_model() such that Stockfish outputs an advantage of // "100 centipawns" for a position if the engine has a 50% probability to win // from this position in selfplay at fishtest LTC time control. -const int NormalizeToPawnValue = 348; +const int NormalizeToPawnValue = 361; class Option; From d8f3209fb4053bec406645e6df0f8a4d71f5a749 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 19 Nov 2022 10:18:04 +0100 Subject: [PATCH 110/678] Update Top CPU Contributors list as of 2022-11-19. Thanks! closes https://github.com/official-stockfish/Stockfish/pull/4234 No functional change --- Top CPU Contributors.txt | 225 +++++++++++++++++++++------------------ 1 file changed, 120 insertions(+), 105 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 23a5d7f9..30c963d7 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,245 +1,260 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2022-07-31. +Contributors to Fishtest with >10,000 CPU hours, as of 2022-11-19. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 33202707 2423743815 -technologov 5064327 270208248 -mlang 2963357 198937430 -dew 1677196 99717674 -grandphish2 1231326 74551309 -okrout 1102747 98977462 -TueRens 925904 57404676 -pemo 911980 35581261 -tvijlbrief 795993 51894442 -JojoM 774270 47311084 +noobpwnftw 36475307 2748033975 +technologov 14570711 760073590 +mlang 3026000 200065824 +dew 1689222 100034318 +grandphish2 1442171 86798057 +okrout 1439985 133471766 +pemo 1405374 44189811 +linrock 1299003 28382783 +TueRens 1163420 71159522 +JojoM 897158 55177114 +tvijlbrief 796125 51897690 mibere 703840 46867607 -linrock 697283 18804969 -gvreuls 564284 36392236 -cw 515739 34775505 -fastgm 500949 30101898 -oz 439015 31794460 -CSU_Dynasty 438017 29369136 +gvreuls 635982 40652394 +oz 590763 41201352 +sebastronomy 581517 23307132 +cw 517915 34865769 +fastgm 504266 30264740 +CSU_Dynasty 479901 31846710 +ctoks 433503 28180725 crunchy 427035 27344275 -ctoks 422671 27812261 -bcross 363335 25108521 -leszek 360149 22674005 -velislav 333325 21444360 +leszek 416883 27493447 +bcross 409982 28062127 +velislav 345954 22232274 Fisherman 327231 21829379 -Dantist 292327 17951982 -mgrabiak 247220 16137378 -nordlandia 226543 14601042 -robal 224740 14314972 -glinscott 217799 13780820 -ncfish1 207751 13909639 -drabel 203884 13922680 -mhoram 200022 12533963 +Dantist 296386 18031762 +mgrabiak 288928 18869896 +rpngn 259965 16281463 +robal 237653 15148350 +ncfish1 231764 15275003 +nordlandia 226923 14624832 +glinscott 208125 13277240 +drabel 204167 13930674 +mhoram 202894 12601997 bking_US 198894 11876016 -rpngn 191764 12236583 +thirdlife 198844 5453268 Thanar 179852 12365359 vdv 175544 9904472 +armo9494 168201 11136452 spams 157128 10319326 -marrco 150300 9402229 +marrco 151599 9551115 sqrt2 147963 9724586 -vdbergh 137480 8958795 +vdbergh 137690 8971569 CoffeeOne 137100 5024116 malala 136182 8002293 +DesolatedDodo 135276 8657464 xoto 133759 9159372 -davar 128645 8367253 -DesolatedDodo 124877 8056482 +davar 129023 8376525 dsmith 122059 7570238 amicic 119661 7938029 Data 113305 8220352 BrunoBanani 112960 7436849 -CypressChess 108321 7759588 +CypressChess 108331 7759788 +skiminki 106518 7062598 MaZePallas 102823 6633619 -skiminki 102168 6778440 sterni1971 100532 5880772 sunu 100167 7040199 +zeryl 99331 6221261 ElbertoOne 99028 7023771 -zeryl 96984 6162287 +DMBK 97572 6950312 +Calis007 96779 5611552 +cuistot 93111 5536500 brabos 92118 6186135 -cuistot 91738 5447070 +Wolfgang 91769 5720158 psk 89957 5984901 -racerschmacer 85712 6119648 +racerschmacer 85805 6122790 +jcAEie 85527 5630616 Vizvezdenec 83761 5344740 -sschnee 83003 4840890 +sschnee 83557 4853690 0x3C33 82614 5271253 -armo9494 82501 5806056 BRAVONE 81239 5054681 +Dubslow 78461 5042980 nssy 76497 5259388 -thirdlife 76478 1544524 -Calis007 76457 4281018 -jromang 75885 5230523 +jromang 76106 5236025 teddybaer 75125 5407666 +yurikvelo 73933 5031096 +tolkki963 73885 4721430 Pking_cda 73776 5293873 -Wolfgang 72750 4538670 -sebastronomy 70784 1329428 +Bobo1239 71675 4860987 solarlight 70517 5028306 dv8silencer 70287 3883992 -Bobo1239 68515 4652287 -yurikvelo 67651 4578970 +Gelma 69304 3980932 manap 66273 4121774 +megaman7de 65419 4120200 +markkulix 65331 4114860 +bigpen0r 64932 4683883 tinker 64333 4268790 qurashee 61208 3429862 +AGI 58325 4258646 robnjr 57262 4053117 -megaman7de 57023 3525850 Freja 56938 3733019 -MaxKlaxxMiner 56279 3410158 +MaxKlaxxMiner 56879 3423958 ttruscott 56010 3680085 rkl 55132 4164467 renouve 53811 3501516 -tolkki963 53294 3354682 -DMBK 52963 3933332 +Spprtr 52736 3410019 finfish 51360 3370515 eva42 51272 3599691 -Spprtr 51139 3299983 -eastorwest 51058 3451555 +eastorwest 51117 3454811 rap 49985 3219146 +unixwizard 49734 2536230 pb00067 49727 3298270 -bigpen0r 47667 3336927 ronaldjerum 47654 3240695 biffhero 46564 3111352 +GPUex 45861 2926502 Fifis 45843 3088497 +oryx 45578 3493978 VoyagerOne 45476 3452465 +Wencey 44943 2654490 speedycpu 43842 3003273 jbwiebe 43305 2805433 Antihistamine 41788 2761312 mhunt 41735 2691355 +olafm 41277 3284344 homyur 39893 2850481 gri 39871 2515779 -oryx 39602 3024830 +MarcusTullius 38303 2251097 +Garf 37741 2999686 +kdave 37424 2557406 SC 37299 2731694 -Garf 37213 2986270 -Dubslow 36714 2409254 csnodgrass 36207 2688994 jmdana 36157 2210661 -markkulix 35994 2226860 strelock 34716 2074055 EthanOConnor 33370 2090311 slakovv 32915 2021889 -gopeto 31078 2033362 +gopeto 31669 2060958 manapbk 30987 1810399 Prcuvu 30377 2170122 anst 30301 2190091 jkiiski 30136 1904470 +spcc 30135 1903728 hyperbolic.tom 29840 2017394 +xwziegtm 29763 2347412 chuckstablers 29659 2093438 Pyafue 29650 1902349 -MarcusTullius 28611 1646671 -spcc 28241 1821198 -belzedar94 27935 1789106 +belzedar94 28846 1811530 OuaisBla 27636 1578800 chriswk 26902 1868317 achambord 26582 1767323 Patrick_G 26276 1801617 yorkman 26193 1992080 +Ulysses 25289 1674274 SFTUser 25182 1675689 nabildanial 24942 1519409 Sharaf_DG 24765 1786697 -rodneyc 24375 1416258 -Ulysses 24017 1626140 +rodneyc 24376 1416402 agg177 23890 1395014 +Ente 23747 1674582 +Karpovbot 23629 1313186 JanErik 23408 1703875 -Ente 23403 1660988 -kdave 23392 1630462 Isidor 23388 1680691 -Norabor 23339 1602636 -cisco2015 22897 1762669 -Wencey 22573 1121406 +Norabor 23371 1603244 +cisco2015 22934 1763773 Zirie 22542 1472937 team-oh 22272 1636708 +Roady 22220 1465606 MazeOfGalious 21978 1629593 -sg4032 21947 1643265 +sg4032 21947 1643353 ianh2105 21725 1632562 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 -Roady 21323 1433822 +user213718 21454 1404128 +AndreasKrug 21227 1577833 sphinx 21211 1384728 -user213718 21196 1397710 jjoshua2 21001 1423089 horst.prack 20878 1465656 +jsys14 20729 1221010 0xB00B1ES 20590 1208666 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 -jcAEie 20221 1504162 +bonsi 20022 1300682 wei 19973 1745989 +dapper 19754 1167758 +Zake9298 19745 1458416 +fishtester 19617 1257388 rstoesser 19569 1293588 eudhan 19274 1283717 -fishtester 19145 1242668 vulcan 18871 1729392 +Jopo12321 18803 1036284 jundery 18445 1115855 -iisiraider 18247 1101015 ville 17883 1384026 +5t0ckf15hTr4in3r 17809 1105858 chris 17698 1487385 +dju 17697 994333 purplefishies 17595 1092533 -dju 17353 978595 -AndreasKrug 17191 1317997 +iisiraider 17275 1049015 DragonLord 17014 1162790 -Jopo12321 16966 944924 -GPUex 16744 1077826 -xwziegtm 16608 1276372 +Karby 16457 1010138 +Goatminola 16278 1145026 IgorLeMasson 16064 1147232 +Gaster319 16056 1109070 +redstone59 15953 1161664 +scuzzi 15757 968735 ako027ako 15671 1173203 -jsys14 15474 917092 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 -scuzzi 15112 960373 +Naven94 15054 834762 OssumOpossum 14857 1007129 -Karby 14808 867120 +qoo_charly_cai 14490 847865 enedene 14476 905279 -bpfliegel 14298 884523 +szupaw 14252 929130 +bpfliegel 14233 882523 mpx86 14019 759568 jpulman 13982 870599 -Naven94 13879 811552 -Karpovbot 13808 734276 crocogoat 13803 1117422 -joster 13794 950160 Nesa92 13786 1114691 +joster 13710 946160 mbeier 13650 1044928 Hjax 13535 915487 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 Machariel 13010 863104 +infinigon 12991 943216 +pirt 12925 985437 +Skiff84 12923 649994 mabichito 12903 749391 thijsk 12886 722107 AdrianSA 12860 804972 -infinigon 12807 937332 Flopzee 12698 894821 -pirt 12551 965597 fatmurphy 12547 853210 +woutboat 12419 836696 SapphireBrand 12416 969604 +Oakwen 12406 840961 +deflectooor 12386 579392 modolief 12386 896470 Farseer 12249 694108 pgontarz 12151 848794 stocky 11954 699440 mschmidt 11941 803401 -Oakwen 11925 818865 -MooTheCow 11851 772628 -deflectooor 11642 565132 -dbernier 11609 818636 -Skiff84 11604 602786 +MooTheCow 11871 773654 +Jackfish 11867 773550 +dbernier 11705 821780 +whelanh 11557 245188 Maxim 11543 836024 +Nullvalue 11534 731410 +icewulf 11528 650470 +FormazChar 11523 861599 infinity 11470 727027 -FormazChar 11430 856559 -aga 11409 695071 -Jackfish 11403 750526 +aga 11412 695127 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 +ali-al-zhrani 11272 781310 d64 11263 789184 -qoo_charly_cai 11127 671959 +Bourbaki 11108 709144 snicolet 11106 869170 -ali-al-zhrani 11098 768494 -whelanh 11067 235676 +Alb11747 10855 696920 basepi 10637 744851 Cubox 10621 826448 -Alb11747 10558 689794 +Karmatron 10616 674818 michaelrpg 10509 739239 OIVAS7572 10420 995586 -Garruk 10343 704723 +Garruk 10348 704905 dzjp 10343 732529 ols 10259 570669 -lbraesch 10252 647825 -Karmatron 10195 661432 From 85ae65db1dd315ea500f30226781c4c471d30c5d Mon Sep 17 00:00:00 2001 From: VoyagerOne Date: Tue, 22 Nov 2022 20:07:33 +0300 Subject: [PATCH 111/678] Skip full depth search in LMR depending on depth dynamically adjust newDepth, and skip full depth search if newDepth doesn't exceed the previous search depth. This affects the used newDepth for future searches, and influences the stat bonus for the move. Passed STC: https://tests.stockfishchess.org/tests/view/63795500aa34433735bc1cfe LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 112776 W: 30082 L: 29663 D: 53031 Ptnml(0-2): 352, 12453, 30423, 12744, 416 Passed LTC: https://tests.stockfishchess.org/tests/view/6379ea39aa34433735bc2f9b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 83576 W: 22559 L: 22169 D: 38848 Ptnml(0-2): 38, 8011, 25303, 8395, 41 closes https://github.com/official-stockfish/Stockfish/pull/4240 Bench: 4390318 --- src/search.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fa87f1c3..5cef5c40 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1188,7 +1188,11 @@ moves_loop: // When in check, search starts here // was good enough search deeper, if it was bad enough search shallower const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d)); const bool doShallowerSearch = value < bestValue + newDepth; - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth + doDeeperSearch - doShallowerSearch, !cutNode); + + newDepth += doDeeperSearch - doShallowerSearch; + + if (newDepth > d) + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); int bonus = value > alpha ? stat_bonus(newDepth) : -stat_bonus(newDepth); From 1370127fcd72b5c6646ff03a4a779b81ad0bcf3d Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Sun, 13 Nov 2022 11:08:17 +0200 Subject: [PATCH 112/678] Simplify both quiet check evasions' conditions passed Non-regression STC: https://tests.stockfishchess.org/tests/view/6370b647f1b748d4819e0b64 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 162904 W: 43249 L: 43171 D: 76484 Ptnml(0-2): 491, 17089, 46220, 17155, 497 closes https://github.com/official-stockfish/Stockfish/pull/4228 No functional change --- src/search.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5cef5c40..e73f68ff 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1552,12 +1552,11 @@ moves_loop: // When in check, search starts here && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) continue; - // movecount pruning for quiet check evasions + // We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter + // and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead. if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && quietCheckEvasions > 1 - && !capture - && ss->inCheck) - continue; + && quietCheckEvasions > 1) + break; quietCheckEvasions += !capture && ss->inCheck; From f5a31b7e576e2e56825fcfdff75c739ed545e852 Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Tue, 22 Nov 2022 11:07:18 +0100 Subject: [PATCH 113/678] Correctly output lowerbound/upperbound in threaded searches fixes the lowerbound/upperbound output by taking the alpha,beta bracket into account also if a bestThread is selected that is different from the master thread. Instead of keeping track which bounds where used in the specific search, in this version we simply store the quality (exact, upperbound, lowerbound) of the score along with the actual score as information on rootMove. closes https://github.com/official-stockfish/Stockfish/pull/4239 No functional change --- src/search.cpp | 14 ++++++++------ src/search.h | 2 ++ src/uci.h | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e73f68ff..b3f60f3d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -244,7 +244,7 @@ void MainThread::search() { // Send again PV info if we have a new best thread if (bestThread != this) - sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; + sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); @@ -392,7 +392,7 @@ void Thread::search() { && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && Time.elapsed() > 3000) - sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; + sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. @@ -423,7 +423,7 @@ void Thread::search() { if ( mainThread && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) - sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; + sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; } if (!Threads.stop) @@ -1246,6 +1246,8 @@ moves_loop: // When in check, search starts here { rm.score = value; rm.selDepth = thisThread->selDepth; + rm.scoreLowerbound = value >= beta; + rm.scoreUpperbound = value <= alpha; rm.pv.resize(1); assert((ss+1)->pv); @@ -1820,7 +1822,7 @@ void MainThread::check_time() { /// UCI::pv() formats PV information according to the UCI protocol. UCI requires /// that all (if any) unsearched PV lines are sent using a previous search score. -string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { +string UCI::pv(const Position& pos, Depth depth) { std::stringstream ss; TimePoint elapsed = Time.elapsed() + 1; @@ -1858,8 +1860,8 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { if (Options["UCI_ShowWDL"]) ss << UCI::wdl(v, pos.game_ply()); - if (!tb && i == pvIdx) - ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); + if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + ss << (rootMoves[i].scoreLowerbound ? " lowerbound" : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / elapsed diff --git a/src/search.h b/src/search.h index f264bcae..60f2762a 100644 --- a/src/search.h +++ b/src/search.h @@ -71,6 +71,8 @@ struct RootMove { Value score = -VALUE_INFINITE; Value previousScore = -VALUE_INFINITE; Value averageScore = -VALUE_INFINITE; + bool scoreLowerbound = false; + bool scoreUpperbound = false; int selDepth = 0; int tbRank = 0; Value tbScore; diff --git a/src/uci.h b/src/uci.h index 7a2ef255..3b5a6764 100644 --- a/src/uci.h +++ b/src/uci.h @@ -79,7 +79,7 @@ void loop(int argc, char* argv[]); std::string value(Value v); std::string square(Square s); std::string move(Move m, bool chess960); -std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); +std::string pv(const Position& pos, Depth depth); std::string wdl(Value v, int ply); Move to_move(const Position& pos, std::string& str); From 6a6faac04db26daae6a77b6df6529e22d549531b Mon Sep 17 00:00:00 2001 From: VoyagerOne Date: Sun, 27 Nov 2022 12:00:16 -0500 Subject: [PATCH 114/678] Remove PvNode Parameter for cutoff LMR STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 198520 W: 52673 L: 52632 D: 93215 Ptnml(0-2): 645, 22241, 53499, 22178, 697 https://tests.stockfishchess.org/tests/view/63746e8f9849fa7a36a6698f LTC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 253568 W: 67487 L: 67501 D: 118580 Ptnml(0-2): 109, 25222, 76141, 25198, 114 https://tests.stockfishchess.org/tests/view/63839859d2b9c924c4c4feb7 closes https://github.com/official-stockfish/Stockfish/pull/4248 Bench: 3733322 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b3f60f3d..abb51190 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1156,13 +1156,13 @@ moves_loop: // When in check, search starts here if (singularQuietLMR) r--; - // Dicrease reduction if we move a threatened piece (~1 Elo) + // Decrease reduction if we move a threatened piece (~1 Elo) if ( depth > 9 && (mp.threatenedPieces & from_sq(move))) r--; // Increase reduction if next ply has a lot of fail high - if ((ss+1)->cutoffCnt > 3 && !PvNode) + if ((ss+1)->cutoffCnt > 3) r++; ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] From c7118fb46dc71d87d3f2c925b24e67184693da4f Mon Sep 17 00:00:00 2001 From: VoyagerOne Date: Thu, 1 Dec 2022 16:31:35 -0500 Subject: [PATCH 115/678] Simply do full sort on captures. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 42712 W: 11413 L: 11203 D: 20096 Ptnml(0-2): 145, 4661, 11544, 4851, 155 https://tests.stockfishchess.org/tests/view/6384df57d2b9c924c4c53900 LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 239072 W: 64065 L: 64067 D: 110940 Ptnml(0-2): 106, 23735, 71859, 23727, 109 https://tests.stockfishchess.org/tests/view/63851120d2b9c924c4c541ee closes https://github.com/official-stockfish/Stockfish/pull/4249 Bench: 3467381 --- src/movepick.cpp | 6 +++--- src/movepick.h | 2 +- src/search.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 587c6d79..188d6bd8 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -88,8 +88,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist /// MovePicker constructor for ProbCut: we generate captures with SEE greater /// than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, Depth d, const CapturePieceToHistory* cph) - : pos(p), captureHistory(cph), ttMove(ttm), threshold(th), depth(d) +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) + : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) { assert(!pos.checkers()); @@ -191,7 +191,7 @@ top: endMoves = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, -3000 * depth); + partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); ++stage; goto top; diff --git a/src/movepick.h b/src/movepick.h index 55fcc644..e4c4a5bf 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -128,7 +128,7 @@ public: const CapturePieceToHistory*, const PieceToHistory**, Square); - MovePicker(const Position&, Move, Value, Depth, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); Bitboard threatenedPieces; diff --git a/src/search.cpp b/src/search.cpp index abb51190..c8163d1f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -855,7 +855,7 @@ namespace { { assert(probCutBeta < VALUE_INFINITE); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, depth - 3, &captureHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) From d60f5de9670cc84ba7940b5815bc3e128da9423f Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 28 Nov 2022 20:59:38 +0100 Subject: [PATCH 116/678] Fix bestThread selection If multiple threads have the same best move, pick the thread with the largest contribution to the confidence vote. This thread will later be used to display PV, so this patch is about user-friendliness and/or least surprises, it non-functional for playing strenght. closes https://github.com/official-stockfish/Stockfish/pull/4246 No functional change --- src/thread.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/thread.cpp b/src/thread.cpp index 9ce408e0..b7471f60 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -221,11 +221,14 @@ Thread* ThreadPool::get_best_thread() const { minScore = std::min(minScore, th->rootMoves[0].score); // Vote according to score and depth, and select the best thread - for (Thread* th : *this) - { - votes[th->rootMoves[0].pv[0]] += - (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); + auto thread_value = [minScore](Thread* th) { + return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); + }; + for (Thread* th : *this) + votes[th->rootMoves[0].pv[0]] += thread_value(th); + + for (Thread* th : *this) if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) { // Make sure we pick the shortest mate / TB conversion or stave off mate the longest @@ -236,9 +239,8 @@ Thread* ThreadPool::get_best_thread() const { || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY && ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] || ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] - && th->rootMoves[0].pv.size() > bestThread->rootMoves[0].pv.size())))) + && thread_value(th) > thread_value(bestThread))))) bestThread = th; - } return bestThread; } From 758f9c9350abee36a5865ec701560db8ea62004d Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 3 Dec 2022 08:50:46 +0100 Subject: [PATCH 117/678] Stockfish 15.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 15.1 Bench: 3467381 --- Today, we have the pleasure to announce Stockfish 15.1. As usual, downloads will be freely available at stockfishchess.org/download *Elo gain and competition results* With this release, version 5 of the NNUE neural net architecture has been introduced, and the training data has been extended to include Fischer random chess (FRC) positions. As a result, Elo gains are largest for FRC, reaching up to 50 Elo for doubly randomized FRC[1] (DFRC). More importantly, also for standard chess this release progressed and will win two times more game pairs than it loses[2] against Stockfish 15. Stockfish continues to win in a dominating way[3] all chess engine tournaments, including the TCEC Superfinal, Cup, FRC, DFRC, and Swiss as well as the CCC Bullet, Blitz, and Rapid events. *New evaluation* This release also introduces a new convention for the evaluation that is reported by search. An evaluation of +1 is now no longer tied to the value of one pawn, but to the likelihood of winning the game. With a +1 evaluation, Stockfish has now a 50% chance of winning the game against an equally strong opponent. This convention scales down evaluations a bit compared to Stockfish 15 and allows for consistent evaluations in the future. *ChessBase settlement* In this release period, the Stockfish team has successfully enforced its GPL license against ChessBase. This has been an intense process that included filing a lawsuit[4], a court hearing[5], and finally negotiating a settlement[6] that established that ChessBase infringed on the license by not distributing the Stockfish derivatives Fat Fritz 2 and Houdini 6 as free software, and that ensures ChessBase will respect the Free Software principles in the future. This settlement has been covered by major chess sites (see e.g. lichess.org[7] and chess.com[8]), and we are proud that it has been hailed as a ‘historic violation settlement[9]’ by the Software Freedom Conservancy. *Thank you* The Stockfish project builds on a thriving community of enthusiasts (thanks everybody!) that contribute their expertise, time, and resources to build a free and open-source chess engine that is robust, widely available, and very strong. We invite our chess fans to join the fishtest testing framework and programmers to contribute to the project[10]. The Stockfish team [1] https://tests.stockfishchess.org/tests/view/638a6170d2b9c924c4c62cb4 [2] https://tests.stockfishchess.org/tests/view/638a4dd7d2b9c924c4c6297b [3] https://en.wikipedia.org/wiki/Stockfish_(chess)#Competition_results [4] https://stockfishchess.org/blog/2021/our-lawsuit-against-chessbase/ [5] https://stockfishchess.org/blog/2022/public-court-hearing-soon/ [6] https://stockfishchess.org/blog/2022/chessbase-stockfish-agreement/ [7] https://lichess.org/blog/Y3u1mRAAACIApBVn/settlement-reached-in-stockfish-v-chessbase [8] https://www.chess.com/news/view/chessbase-stockfish-reach-settlement [9] https://sfconservancy.org/news/2022/nov/28/sfc-named-trusted-party-in-gpl-case/ [10] https://stockfishchess.org/get-involved/ --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index c7fa0e9a..2d86969f 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -68,7 +68,7 @@ namespace Stockfish { namespace { /// Version number or dev. -const string version = "dev"; +const string version = "15.1"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From e8caa6640df15b1823d5d4b94e759d52923769e6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 8 Dec 2022 20:33:32 +0100 Subject: [PATCH 118/678] Restore development version No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 2d86969f..c7fa0e9a 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -68,7 +68,7 @@ namespace Stockfish { namespace { /// Version number or dev. -const string version = "15.1"; +const string version = "dev"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 9fc203a3d05262ac74bfca8b4618155e18d76003 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Thu, 8 Dec 2022 18:32:30 +0100 Subject: [PATCH 119/678] Set the right PATH for ARM compiler and build tests in CI Fix for the GitHub upgrade: https://github.com/actions/runner-images/issues/5879 that broke our ARM workflows because it changed the value of the ANDROID_NDK_HOME variable referenced in our PATH. closes https://github.com/official-stockfish/Stockfish/pull/4267 No functional change --- .github/workflows/stockfish_test.yml | 43 ++++++++++++---------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 46b4e26f..e4e6205f 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -35,7 +35,7 @@ jobs: os: ubuntu-20.04, compiler: aarch64-linux-android21-clang++, comp: ndk, - run_armv8_tests: false, + run_armv8_tests: true, shell: 'bash {0}' } - { @@ -43,7 +43,7 @@ jobs: os: ubuntu-20.04, compiler: armv7a-linux-androideabi21-clang++, comp: ndk, - run_armv7_tests: false, + run_armv7_tests: true, shell: 'bash {0}' } - { @@ -92,15 +92,6 @@ jobs: msys_env: 'clang-x86_64-clang', shell: 'msys2 {0}' } - exclude: - - config: - { - name: "Ubuntu 20.04 NDK armv7" - } - - config: - { - name: "Ubuntu 20.04 NDK armv8" - } defaults: run: working-directory: src @@ -120,8 +111,8 @@ jobs: if: runner.os == 'Windows' uses: msys2/setup-msys2@v2 with: - msystem: ${{matrix.config.msys_sys}} - install: mingw-w64-${{matrix.config.msys_env}} make git expect + msystem: ${{ matrix.config.msys_sys }} + install: mingw-w64-${{ matrix.config.msys_env }} make git expect - name: Download the used network from the fishtest framework run: | @@ -134,7 +125,14 @@ jobs: - name: Check compiler run: | - export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin + if [ $COMP == ndk ]; then + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk + SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 + export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + fi $COMPILER -v - name: Test help target @@ -233,9 +231,8 @@ jobs: ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle - ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT - export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 + export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean make -j2 ARCH=armv8 build @@ -250,9 +247,8 @@ jobs: ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle - ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT - export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 + export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean make -j2 ARCH=armv7 build @@ -265,9 +261,8 @@ jobs: ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk-bundle - ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK_ROOT - export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 + export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean make -j2 ARCH=armv7-neon build @@ -281,4 +276,4 @@ jobs: make clean make -j2 ARCH=x86-64-modern build ../tests/perft.sh - ../tests/reprosearch.sh \ No newline at end of file + ../tests/reprosearch.sh From 98965c139df1483a3d684ee8bc7a60dc4b95efa1 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 2 Dec 2022 18:23:28 +0300 Subject: [PATCH 120/678] doEvenDeeperSearch + tuning Credit for the main idea of doEvenDeeperSearch goes to Vizvezdenec, tuning by FauziAkram: Expansion of existing logic of doDeeperSearch - if value from LMR is really really good do full depth search not 1 ply deeper but rather 2 instead. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 330048 W: 87672 L: 86942 D: 155434 Ptnml(0-2): 1012, 36739, 88912, 37229, 1132 https://tests.stockfishchess.org/tests/view/638a1cadd2b9c924c4c621d2 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 216696 W: 57891 L: 57240 D: 101565 Ptnml(0-2): 72, 21221, 65152, 21790, 113 https://tests.stockfishchess.org/tests/view/638c7d52a971f1f096c68fe2 closes https://github.com/official-stockfish/Stockfish/pull/4256 Bench: 3461830 --- src/evaluate.cpp | 14 +++++++------- src/search.cpp | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 87412b81..93e665df 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { else { int nnueComplexity; - int scale = 1064 + 106 * pos.non_pawn_material() / 5120; + int scale = 1076 + 96 * pos.non_pawn_material() / 5120; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; @@ -1071,21 +1071,21 @@ Value Eval::evaluate(const Position& pos, int* complexity) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 416 * nnueComplexity - + 424 * abs(psq - nnue) + nnueComplexity = ( 412 * nnueComplexity + + 428 * abs(psq - nnue) + (optimism > 0 ? int(optimism) * int(psq - nnue) : 0) - ) / 1024; + ) / 1026; // Return hybrid NNUE complexity to caller if (complexity) *complexity = nnueComplexity; - optimism = optimism * (269 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 754)) / 1024; + optimism = optimism * (278 + nnueComplexity) / 256; + v = (nnue * scale + optimism * (scale - 755)) / 1024; } // Damp down the evaluation linearly when shuffling - v = v * (195 - pos.rule50_count()) / 211; + v = v * (197 - pos.rule50_count()) / 214; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/search.cpp b/src/search.cpp index c8163d1f..343c098a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min((12 * d + 282) * d - 349 , 1594); + return std::min((12 * d + 282) * d - 349 , 1480); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -1172,7 +1172,7 @@ moves_loop: // When in check, search starts here - 4433; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (13628 + 4000 * (depth > 7 && depth < 19)); + r -= ss->statScore / (13000 + 4152 * (depth > 7 && depth < 19)); // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension @@ -1187,9 +1187,10 @@ moves_loop: // When in check, search starts here // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 582; const bool doShallowerSearch = value < bestValue + newDepth; - newDepth += doDeeperSearch - doShallowerSearch; + newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; if (newDepth > d) value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); From cb0c7a98485fbef4e5d6ed5f5b08201113ce0b4e Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Tue, 6 Dec 2022 19:09:33 +0100 Subject: [PATCH 121/678] Correctly output lowerbound/upperbound scores fixes the lowerbound/upperbound output by avoiding scores outside the alpha,beta bracket. Since SF search uses fail-soft we can't simply take the returned value as score. closes https://github.com/official-stockfish/Stockfish/pull/4259 No functional change --- src/search.cpp | 14 ++++++++++---- src/search.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 343c098a..04f73e1c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1245,10 +1245,16 @@ moves_loop: // When in check, search starts here // PV move or new best move? if (moveCount == 1 || value > alpha) { - rm.score = value; + rm.score = rm.uciScore = value; rm.selDepth = thisThread->selDepth; - rm.scoreLowerbound = value >= beta; - rm.scoreUpperbound = value <= alpha; + if (value >= beta) { + rm.scoreLowerbound = true; + rm.uciScore = beta; + } + else if (value <= alpha) { + rm.scoreUpperbound = true; + rm.uciScore = alpha; + } rm.pv.resize(1); assert((ss+1)->pv); @@ -1841,7 +1847,7 @@ string UCI::pv(const Position& pos, Depth depth) { continue; Depth d = updated ? depth : std::max(1, depth - 1); - Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore; + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; if (v == -VALUE_INFINITE) v = VALUE_ZERO; diff --git a/src/search.h b/src/search.h index 60f2762a..b620202d 100644 --- a/src/search.h +++ b/src/search.h @@ -71,6 +71,7 @@ struct RootMove { Value score = -VALUE_INFINITE; Value previousScore = -VALUE_INFINITE; Value averageScore = -VALUE_INFINITE; + Value uciScore = -VALUE_INFINITE; bool scoreLowerbound = false; bool scoreUpperbound = false; int selDepth = 0; From 74fb936dbd49c61bf3352febd1c57a68888100d0 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Tue, 6 Dec 2022 21:32:42 +0900 Subject: [PATCH 122/678] Invoke .depend only on build targets Add a constraint so that the dependency build only occurs when users actually run build tasks. This fixes a bug on some systems where gcc/g++ is not available. closes https://github.com/official-stockfish/Stockfish/pull/4255 No functional change --- src/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 917bd5c0..0c98391b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -512,7 +512,7 @@ endif ### Sometimes gcc is really clang ifeq ($(COMP),gcc) - gccversion = $(shell $(CXX) --version) + gccversion = $(shell $(CXX) --version 2>/dev/null) gccisclang = $(findstring clang,$(gccversion)) ifneq ($(gccisclang),) profile_make = clang-profile-make @@ -1006,4 +1006,6 @@ icc-profile-use: .depend: $(SRCS) -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null +ifneq (, $(filter $(MAKECMDGOALS), build profile-build)) -include .depend +endif From aa603cfeeb6e902bcf996758515170b996ec1fb6 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Thu, 8 Dec 2022 20:50:32 +0100 Subject: [PATCH 123/678] GitHub Action: upload ARM artifacts And some clean up in other files. closes https://github.com/official-stockfish/Stockfish/pull/4269 No functional change --- .github/workflows/stockfish.yml | 3 + .github/workflows/stockfish_arm_binaries.yml | 117 +++++++++++++++++++ .github/workflows/stockfish_binaries.yml | 7 +- .github/workflows/stockfish_compile_test.yml | 5 +- .github/workflows/stockfish_sanitizers.yml | 5 +- .github/workflows/stockfish_test.yml | 20 ++-- 6 files changed, 137 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/stockfish_arm_binaries.yml diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 07ecfc07..6345b27c 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -19,3 +19,6 @@ jobs: Binaries: if: github.ref == 'refs/heads/master' uses: ./.github/workflows/stockfish_binaries.yml + ARM_Binaries: + if: github.ref == 'refs/heads/master' + uses: ./.github/workflows/stockfish_arm_binaries.yml diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml new file mode 100644 index 00000000..cf3ae710 --- /dev/null +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -0,0 +1,117 @@ +name: Stockfish +on: + workflow_call: +jobs: + Stockfish: + name: ${{ matrix.config.name }} ${{ matrix.binaries }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + EMU: ${{ matrix.config.emu }} + EXT: ${{ matrix.config.ext }} + OS: ${{ matrix.config.os }} + BINARY: ${{ matrix.binaries }} + strategy: + matrix: + config: + - { + name: "Android NDK aarch64", + os: ubuntu-20.04, + compiler: aarch64-linux-android21-clang++, + emu: qemu-aarch64, + comp: ndk, + shell: 'bash {0}' + } + - { + name: "Android NDK arm", + os: ubuntu-20.04, + compiler: armv7a-linux-androideabi21-clang++, + emu: qemu-arm, + comp: ndk, + shell: 'bash {0}' + } + binaries: + - armv8 + - armv7 + - armv7-neon + exclude: + - binaries: armv8 + config: {compiler: armv7a-linux-androideabi21-clang++} + - binaries: armv7 + config: {compiler: aarch64-linux-android21-clang++} + - binaries: armv7-neon + config: {compiler: aarch64-linux-android21-clang++} + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download required linux packages + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install qemu-user + + - name: Download the used network from the fishtest framework + run: | + make net + + - name: Check compiler + run: | + if [ $COMP == ndk ]; then + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 + export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + fi + $COMPILER -v + + - name: Test help target + run: | + make help + + # Compile profile guided builds + + - name: Compile ${{ matrix.binaries }} build + run: | + if [ $COMP == ndk ]; then + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 + export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + fi + make clean + make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU + make strip ARCH=$BINARY COMP=$COMP + mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT + + - name: Remove non src files + run: rm -f *.o .depend *.nnue + + - name: Create tar archive. + run: | + cd .. + mkdir stockfish + cp -r src stockfish/ + cp stockfish-android-$BINARY$EXT stockfish/ + cp "Top CPU Contributors.txt" stockfish/ + cp Copying.txt stockfish/ + cp AUTHORS stockfish/ + tar -cvf stockfish-android-$BINARY.tar stockfish + + - name: Upload binaries + uses: actions/upload-artifact@v3 + with: + name: stockfish-android-${{ matrix.binaries }} + path: | + stockfish-android-${{ matrix.binaries }}.tar diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 0b205ded..1fa123fa 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -63,8 +63,8 @@ jobs: if: runner.os == 'Windows' uses: msys2/setup-msys2@v2 with: - msystem: ${{matrix.config.msys_sys}} - install: mingw-w64-${{matrix.config.msys_env}} make git expect + msystem: ${{ matrix.config.msys_sys }} + install: mingw-w64-${{ matrix.config.msys_env }} make - name: Download the used network from the fishtest framework run: | @@ -72,7 +72,6 @@ jobs: - name: Check compiler run: | - export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin $COMPILER -v - name: Test help target @@ -85,7 +84,7 @@ jobs: run: | make clean make -j2 profile-build ARCH=$BINARY COMP=$COMP - strip ./stockfish$EXT + make strip ARCH=$BINARY COMP=$COMP mv ./stockfish$EXT ../stockfish-$OS-$BINARY$EXT - name: Remove non src files diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 63136737..8467f52d 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -72,7 +72,7 @@ jobs: uses: msys2/setup-msys2@v2 with: msystem: ${{matrix.config.msys_sys}} - install: mingw-w64-${{matrix.config.msys_env}} make git expect + install: mingw-w64-${{matrix.config.msys_env}} make - name: Download the used network from the fishtest framework run: | @@ -80,7 +80,6 @@ jobs: - name: Check compiler run: | - export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin $COMPILER -v - name: Test help target @@ -112,4 +111,4 @@ jobs: - name: Compile x86-64-vnni256 build run: | make clean - make -j2 ARCH=x86-64-vnni256 build \ No newline at end of file + make -j2 ARCH=x86-64-vnni256 build diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index 61eaf0c9..b74c2f97 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -52,7 +52,7 @@ jobs: - name: Download required linux packages run: | sudo apt update - sudo apt install expect valgrind g++-multilib qemu-user + sudo apt install expect valgrind g++-multilib - name: Download the used network from the fishtest framework run: | @@ -60,7 +60,6 @@ jobs: - name: Check compiler run: | - export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin $COMPILER -v - name: Test help target @@ -74,4 +73,4 @@ jobs: export CXXFLAGS="-O1 -fno-inline" make clean make -j2 ARCH=x86-64-modern ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null - ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }} \ No newline at end of file + ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }} diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index e4e6205f..3cb89d39 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -31,7 +31,7 @@ jobs: shell: 'bash {0}' } - { - name: "Ubuntu 20.04 NDK armv8", + name: "Android NDK aarch64", os: ubuntu-20.04, compiler: aarch64-linux-android21-clang++, comp: ndk, @@ -39,7 +39,7 @@ jobs: shell: 'bash {0}' } - { - name: "Ubuntu 20.04 NDK armv7", + name: "Android NDK arm", os: ubuntu-20.04, compiler: armv7a-linux-androideabi21-clang++, comp: ndk, @@ -127,8 +127,8 @@ jobs: run: | if [ $COMP == ndk ]; then ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk - SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager echo "y" | $SDKMANAGER "ndk;21.4.7075529" ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH @@ -228,8 +228,8 @@ jobs: if: ${{ matrix.config.run_armv8_tests }} run: | ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk - SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager echo "y" | $SDKMANAGER "ndk;21.4.7075529" ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH @@ -244,8 +244,8 @@ jobs: if: ${{ matrix.config.run_armv7_tests }} run: | ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk - SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager echo "y" | $SDKMANAGER "ndk;21.4.7075529" ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH @@ -258,8 +258,8 @@ jobs: if: ${{ matrix.config.run_armv7_tests }} run: | ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk - SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager echo "y" | $SDKMANAGER "ndk;21.4.7075529" ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH From 9d3fd011f1bc9ef6f3a3091aae10634b31e0032c Mon Sep 17 00:00:00 2001 From: Alfredo Menezes Date: Fri, 9 Dec 2022 12:11:43 -0300 Subject: [PATCH 124/678] Extend all moves at low depth if ttMove is doubly extended If ttMove is doubly extended, we allow a depth growth of the remaining moves. The idea is to get a more realistic score comparison, because of the depth difference. We take some care to avoid this extension for high depths, in order to avoid the cost, since the search result is supposed to be more accurate in this case. This pull request includes some small cleanups. STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 60256 W: 16189 L: 15848 D: 28219 Ptnml(0-2): 182, 6546, 16330, 6889, 181 https://tests.stockfishchess.org/tests/view/639109a1792a529ae8f27777 LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 106232 W: 28487 L: 28053 D: 49692 Ptnml(0-2): 46, 10224, 32145, 10652, 49 https://tests.stockfishchess.org/tests/view/63914cba792a529ae8f282ee closes https://github.com/official-stockfish/Stockfish/pull/4271 Bench: 3622368 --- src/search.cpp | 6 ++++-- src/types.h | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 04f73e1c..ec7cff54 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1067,7 +1067,10 @@ moves_loop: // When in check, search starts here if ( !PvNode && value < singularBeta - 25 && ss->doubleExtensions <= 9) + { extension = 2; + depth += depth < 12; + } } // Multi-cut pruning @@ -1296,7 +1299,7 @@ moves_loop: // When in check, search starts here && depth < 6 && beta < VALUE_KNOWN_WIN && alpha > -VALUE_KNOWN_WIN) - depth -= 1; + depth -= 1; assert(depth > 0); } @@ -1521,7 +1524,6 @@ moves_loop: // When in check, search starts here && futilityBase > -VALUE_KNOWN_WIN && type_of(move) != PROMOTION) { - if (moveCount > 2) continue; diff --git a/src/types.h b/src/types.h index c2087c6c..29c16ce7 100644 --- a/src/types.h +++ b/src/types.h @@ -186,6 +186,9 @@ enum Value : int { VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, + // In the code, we make the assumption that these values + // are such that non_pawn_material() can be used to uniquely + // identify the material on the board. PawnValueMg = 126, PawnValueEg = 208, KnightValueMg = 781, KnightValueEg = 854, BishopValueMg = 825, BishopValueEg = 915, From 44ecadee10111f028e28f47df6dfc9accd908293 Mon Sep 17 00:00:00 2001 From: Douglas Matos Gomes Date: Thu, 8 Dec 2022 21:40:07 -0300 Subject: [PATCH 125/678] Simplify redundant condition. closes https://github.com/official-stockfish/Stockfish/pull/4270 No functional change --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 93e665df..71c4e8d1 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1091,7 +1091,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); // When not using NNUE, return classical complexity to caller - if (complexity && (!useNNUE || useClassical)) + if (complexity && useClassical) *complexity = abs(v - psq); return v; From aedf0251e6170a631b56a24dff31e83b9656933d Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 9 Dec 2022 17:56:55 +0100 Subject: [PATCH 126/678] CI workflows, install git on windows ensures the SF dev version is reported correctly No functional change --- .github/workflows/stockfish_binaries.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 1fa123fa..5ba4784e 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -64,7 +64,7 @@ jobs: uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.config.msys_sys }} - install: mingw-w64-${{ matrix.config.msys_env }} make + install: mingw-w64-${{ matrix.config.msys_env }} make git - name: Download the used network from the fishtest framework run: | From 3a30b478d20c16a357d1e538f1ce9428e7285736 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 9 Dec 2022 18:24:54 +0100 Subject: [PATCH 127/678] CI workflows, install git on windows ensures the SF dev version is reported correctly closes https://github.com/official-stockfish/Stockfish/pull/4272 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 12 ++++++------ .github/workflows/stockfish_binaries.yml | 18 ++++++++---------- .github/workflows/stockfish_compile_test.yml | 14 +++++++------- .github/workflows/stockfish_sanitizers.yml | 12 ++++++------ .github/workflows/stockfish_test.yml | 9 +++++---- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index cf3ae710..ea738cef 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -58,8 +58,7 @@ jobs: sudo apt install qemu-user - name: Download the used network from the fishtest framework - run: | - make net + run: make net - name: Check compiler run: | @@ -74,8 +73,10 @@ jobs: $COMPILER -v - name: Test help target - run: | - make help + run: make help + + - name: Check git + run: git --version # Compile profile guided builds @@ -113,5 +114,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: stockfish-android-${{ matrix.binaries }} - path: | - stockfish-android-${{ matrix.binaries }}.tar + path: stockfish-android-${{ matrix.binaries }}.tar diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 5ba4784e..57535296 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -56,8 +56,7 @@ jobs: - name: Download required linux packages if: runner.os == 'Linux' - run: | - sudo apt update + run: sudo apt update - name: Setup msys and install required packages if: runner.os == 'Windows' @@ -67,16 +66,16 @@ jobs: install: mingw-w64-${{ matrix.config.msys_env }} make git - name: Download the used network from the fishtest framework - run: | - make net + run: make net - name: Check compiler - run: | - $COMPILER -v + run: $COMPILER -v - name: Test help target - run: | - make help + run: make help + + - name: Check git + run: git --version # Compile profile guided builds @@ -105,5 +104,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} - path: | - stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}.tar + path: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}.tar diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 8467f52d..eeb4229c 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -72,19 +72,19 @@ jobs: uses: msys2/setup-msys2@v2 with: msystem: ${{matrix.config.msys_sys}} - install: mingw-w64-${{matrix.config.msys_env}} make + install: mingw-w64-${{matrix.config.msys_env}} make git - name: Download the used network from the fishtest framework - run: | - make net + run: make net - name: Check compiler - run: | - $COMPILER -v + run: $COMPILER -v - name: Test help target - run: | - make help + run: make help + + - name: Check git + run: git --version # x86-64 with newer extensions tests diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index b74c2f97..fa679330 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -55,16 +55,16 @@ jobs: sudo apt install expect valgrind g++-multilib - name: Download the used network from the fishtest framework - run: | - make net + run: make net - name: Check compiler - run: | - $COMPILER -v + run: $COMPILER -v - name: Test help target - run: | - make help + run: make help + + - name: Check git + run: git --version # Sanitizers diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 3cb89d39..953f6820 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -115,8 +115,7 @@ jobs: install: mingw-w64-${{ matrix.config.msys_env }} make git expect - name: Download the used network from the fishtest framework - run: | - make net + run: make net - name: Extract the bench number from the commit history run: | @@ -136,8 +135,10 @@ jobs: $COMPILER -v - name: Test help target - run: | - make help + run: make help + + - name: Check git + run: git --version # x86-32 tests From 8f817ef0824e4d940128f5701573f74819f50da5 Mon Sep 17 00:00:00 2001 From: disservin Date: Fri, 9 Dec 2022 21:48:03 +0100 Subject: [PATCH 128/678] Fix lower/upper bounds output Commit cb0c7a98485fbef4e5d6ed5f5b08201113ce0b4e doesnt reset the lower/upper bounds back to false. fixes #4273 closes https://github.com/official-stockfish/Stockfish/pull/4274 No functional change --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index ec7cff54..b2a5b940 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1250,6 +1250,8 @@ moves_loop: // When in check, search starts here { rm.score = rm.uciScore = value; rm.selDepth = thisThread->selDepth; + rm.scoreLowerbound = rm.scoreUpperbound = false; + if (value >= beta) { rm.scoreLowerbound = true; rm.uciScore = beta; From 955edf1d1d4f5643b450b1ee1e95dc3f094e1884 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 12 Dec 2022 08:12:10 +0100 Subject: [PATCH 129/678] Revert "doEvenDeeperSearch + tuning" This reverts commit 98965c139df1483a3d684ee8bc7a60dc4b95efa1. The increase of depth could lead to search explosions, most visible with TB. fixes https://github.com/official-stockfish/Stockfish/issues/4276 closes https://github.com/official-stockfish/Stockfish/pull/4256 Bench: 3872306 --- src/evaluate.cpp | 14 +++++++------- src/search.cpp | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 71c4e8d1..6a0fae9e 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { else { int nnueComplexity; - int scale = 1076 + 96 * pos.non_pawn_material() / 5120; + int scale = 1064 + 106 * pos.non_pawn_material() / 5120; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; @@ -1071,21 +1071,21 @@ Value Eval::evaluate(const Position& pos, int* complexity) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 412 * nnueComplexity - + 428 * abs(psq - nnue) + nnueComplexity = ( 416 * nnueComplexity + + 424 * abs(psq - nnue) + (optimism > 0 ? int(optimism) * int(psq - nnue) : 0) - ) / 1026; + ) / 1024; // Return hybrid NNUE complexity to caller if (complexity) *complexity = nnueComplexity; - optimism = optimism * (278 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 755)) / 1024; + optimism = optimism * (269 + nnueComplexity) / 256; + v = (nnue * scale + optimism * (scale - 754)) / 1024; } // Damp down the evaluation linearly when shuffling - v = v * (197 - pos.rule50_count()) / 214; + v = v * (195 - pos.rule50_count()) / 211; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/search.cpp b/src/search.cpp index b2a5b940..b58a344a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min((12 * d + 282) * d - 349 , 1480); + return std::min((12 * d + 282) * d - 349 , 1594); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -1175,7 +1175,7 @@ moves_loop: // When in check, search starts here - 4433; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (13000 + 4152 * (depth > 7 && depth < 19)); + r -= ss->statScore / (13628 + 4000 * (depth > 7 && depth < 19)); // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension @@ -1190,10 +1190,9 @@ moves_loop: // When in check, search starts here // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 582; const bool doShallowerSearch = value < bestValue + newDepth; - newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; + newDepth += doDeeperSearch - doShallowerSearch; if (newDepth > d) value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); From 310928e985a6d87bdd73542e2109f93c31e2cc41 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 9 Dec 2022 18:50:06 -0800 Subject: [PATCH 130/678] Avoid truncated PV in the threaded case strongly prefer to pick as bestThread those threads with a longer PV, among those threads that all found the same bestmove. extended discussion in #4244 closes https://github.com/official-stockfish/Stockfish/pull/4278 No functional change --- src/thread.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/thread.cpp b/src/thread.cpp index b7471f60..e8723eb7 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -239,7 +239,8 @@ Thread* ThreadPool::get_best_thread() const { || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY && ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] || ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] - && thread_value(th) > thread_value(bestThread))))) + && thread_value(th) * int(th->rootMoves[0].pv.size() > 2) + > thread_value(bestThread) * int(bestThread->rootMoves[0].pv.size() > 2))))) bestThread = th; return bestThread; From 5fe1fa0210e25cecc9de5520da56173ef94b8e59 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Sun, 11 Dec 2022 12:06:22 +0100 Subject: [PATCH 131/678] GitHub Actions: install NDK once and clean up yaml Use Ubuntu 22.04 as runner for NDK to avoid a qemu bug with `profile-build` closes https://github.com/official-stockfish/Stockfish/pull/4280 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 56 +++--- .github/workflows/stockfish_binaries.yml | 42 ++--- .github/workflows/stockfish_compile_test.yml | 80 ++++---- .github/workflows/stockfish_sanitizers.yml | 44 ++--- .github/workflows/stockfish_test.yml | 184 ++++++++----------- 5 files changed, 177 insertions(+), 229 deletions(-) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index ea738cef..a1b3cdab 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -15,22 +15,18 @@ jobs: strategy: matrix: config: - - { - name: "Android NDK aarch64", - os: ubuntu-20.04, - compiler: aarch64-linux-android21-clang++, - emu: qemu-aarch64, - comp: ndk, - shell: 'bash {0}' - } - - { - name: "Android NDK arm", - os: ubuntu-20.04, - compiler: armv7a-linux-androideabi21-clang++, - emu: qemu-arm, - comp: ndk, - shell: 'bash {0}' - } + - name: Android NDK aarch64 + os: ubuntu-22.04 + compiler: aarch64-linux-android21-clang++ + emu: qemu-aarch64 + comp: ndk + shell: bash {0} + - name: Android NDK arm + os: ubuntu-22.04 + compiler: armv7a-linux-androideabi21-clang++ + emu: qemu-arm + comp: ndk + shell: bash {0} binaries: - armv8 - armv7 @@ -57,18 +53,27 @@ jobs: sudo apt update sudo apt install qemu-user + - name: Install NDK + if: runner.os == 'Linux' + run: | + if [ $COMP == ndk ]; then + NDKV="21.4.7075529" + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;$NDKV" + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV + ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin + echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV + fi + - name: Download the used network from the fishtest framework run: make net - name: Check compiler run: | if [ $COMP == ndk ]; then - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk - SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 - export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH fi $COMPILER -v @@ -83,12 +88,7 @@ jobs: - name: Compile ${{ matrix.binaries }} build run: | if [ $COMP == ndk ]; then - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk - SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 - export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" fi make clean diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 57535296..06b13a9e 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -14,30 +14,24 @@ jobs: strategy: matrix: config: - - { - name: "Ubuntu 20.04 GCC", - os: ubuntu-20.04, - compiler: g++, - comp: gcc, - shell: 'bash {0}' - } - - { - name: "MacOS 12 Apple Clang", - os: macos-12, - compiler: clang++, - comp: clang, - shell: 'bash {0}' - } - - { - name: "Windows 2022 Mingw-w64 GCC x86_64", - os: windows-2022, - compiler: g++, - comp: mingw, - msys_sys: 'mingw64', - msys_env: 'x86_64-gcc', - shell: 'msys2 {0}', - ext: .exe - } + - name: Ubuntu 20.04 GCC + os: ubuntu-20.04 + compiler: g++ + comp: gcc + shell: bash {0} + - name: MacOS 12 Apple Clang + os: macos-12 + compiler: clang++ + comp: clang + shell: bash {0} + - name: Windows 2022 Mingw-w64 GCC x86_64 + os: windows-2022 + compiler: g++ + comp: mingw + msys_sys: mingw64 + msys_env: x86_64-gcc + shell: msys2 {0} + ext: .exe binaries: - x86-64 - x86-64-modern diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index eeb4229c..c7280a85 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -11,52 +11,40 @@ jobs: strategy: matrix: config: - - { - name: "Ubuntu 20.04 GCC", - os: ubuntu-20.04, - compiler: g++, - comp: gcc, - shell: 'bash {0}' - } - - { - name: "Ubuntu 20.04 Clang", - os: ubuntu-20.04, - compiler: clang++, - comp: clang, - shell: 'bash {0}' - } - - { - name: "MacOS 12 Apple Clang", - os: macos-12, - compiler: clang++, - comp: clang, - shell: 'bash {0}' - } - - { - name: "MacOS 12 GCC 11", - os: macos-12, - compiler: g++-11, - comp: gcc, - shell: 'bash {0}' - } - - { - name: "Windows 2022 Mingw-w64 GCC x86_64", - os: windows-2022, - compiler: g++, - comp: mingw, - msys_sys: 'mingw64', - msys_env: 'x86_64-gcc', - shell: 'msys2 {0}' - } - - { - name: "Windows 2022 Mingw-w64 Clang x86_64", - os: windows-2022, - compiler: clang++, - comp: clang, - msys_sys: 'clang64', - msys_env: 'clang-x86_64-clang', - shell: 'msys2 {0}' - } + - name: Ubuntu 20.04 GCC + os: ubuntu-20.04 + compiler: g++ + comp: gcc + shell: bash {0} + - name: Ubuntu 20.04 Clang + os: ubuntu-20.04 + compiler: clang++ + comp: clang + shell: bash {0} + - name: MacOS 12 Apple Clang + os: macos-12 + compiler: clang++ + comp: clang + shell: bash {0} + - name: MacOS 12 GCC 11 + os: macos-12 + compiler: g++-11 + comp: gcc + shell: bash {0} + - name: Windows 2022 Mingw-w64 GCC x86_64 + os: windows-2022 + compiler: g++ + comp: mingw + msys_sys: mingw64 + msys_env: x86_64-gcc + shell: msys2 {0} + - name: Windows 2022 Mingw-w64 Clang x86_64 + os: windows-2022 + compiler: clang++ + comp: clang + msys_sys: clang64 + msys_env: clang-x86_64-clang + shell: msys2 {0} defaults: run: diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index fa679330..708c9227 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -12,34 +12,24 @@ jobs: strategy: matrix: config: - - { - name: "Ubuntu 20.04 GCC", - os: ubuntu-20.04, - compiler: g++, - comp: gcc, - shell: 'bash {0}' - } + - name: Ubuntu 20.04 GCC + os: ubuntu-20.04 + compiler: g++ + comp: gcc + shell: bash {0} sanitizers: - - { - name: Run with thread sanitizer, - make_option: sanitize=thread, - instrumented_option: sanitizer-thread - } - - { - name: Run with UB sanitizer, - make_option: sanitize=undefined, - instrumented_option: sanitizer-undefined - } - - { - name: Run under valgrind, - make_option: "", - instrumented_option: valgrind - } - - { - name: Run under valgrind-thread, - make_option: "", - instrumented_option: valgrind-thread - } + - name: Run with thread sanitizer + make_option: sanitize=thread + instrumented_option: sanitizer-thread + - name: Run with UB sanitizer + make_option: sanitize=undefined + instrumented_option: sanitizer-undefined + - name: Run under valgrind + make_option: "" + instrumented_option: valgrind + - name: Run under valgrind-thread + make_option: "" + instrumented_option: valgrind-thread defaults: run: working-directory: src diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 953f6820..8c383fe7 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -12,86 +12,68 @@ jobs: strategy: matrix: config: - - { - name: "Ubuntu 20.04 GCC", - os: ubuntu-20.04, - compiler: g++, - comp: gcc, - run_32bit_tests: true, - run_64bit_tests: true, - shell: 'bash {0}' - } - - { - name: "Ubuntu 20.04 Clang", - os: ubuntu-20.04, - compiler: clang++, - comp: clang, - run_32bit_tests: true, - run_64bit_tests: true, - shell: 'bash {0}' - } - - { - name: "Android NDK aarch64", - os: ubuntu-20.04, - compiler: aarch64-linux-android21-clang++, - comp: ndk, - run_armv8_tests: true, - shell: 'bash {0}' - } - - { - name: "Android NDK arm", - os: ubuntu-20.04, - compiler: armv7a-linux-androideabi21-clang++, - comp: ndk, - run_armv7_tests: true, - shell: 'bash {0}' - } - - { - name: "MacOS 12 Apple Clang", - os: macos-12, - compiler: clang++, - comp: clang, - run_64bit_tests: true, - shell: 'bash {0}' - } - - { - name: "MacOS 12 GCC 11", - os: macos-12, - compiler: g++-11, - comp: gcc, - run_64bit_tests: true, - shell: 'bash {0}' - } - - { - name: "Windows 2022 Mingw-w64 GCC x86_64", - os: windows-2022, - compiler: g++, - comp: mingw, - run_64bit_tests: true, - msys_sys: 'mingw64', - msys_env: 'x86_64-gcc', - shell: 'msys2 {0}' - } - - { - name: "Windows 2022 Mingw-w64 GCC i686", - os: windows-2022, - compiler: g++, - comp: mingw, - run_32bit_tests: true, - msys_sys: 'mingw32', - msys_env: 'i686-gcc', - shell: 'msys2 {0}' - } - - { - name: "Windows 2022 Mingw-w64 Clang x86_64", - os: windows-2022, - compiler: clang++, - comp: clang, - run_64bit_tests: true, - msys_sys: 'clang64', - msys_env: 'clang-x86_64-clang', - shell: 'msys2 {0}' - } + - name: Ubuntu 20.04 GCC + os: ubuntu-20.04 + compiler: g++ + comp: gcc + run_32bit_tests: true + run_64bit_tests: true + shell: bash {0} + - name: Ubuntu 20.04 Clang + os: ubuntu-20.04 + compiler: clang++ + comp: clang + run_32bit_tests: true + run_64bit_tests: true + shell: bash {0} + - name: Android NDK aarch64 + os: ubuntu-22.04 + compiler: aarch64-linux-android21-clang++ + comp: ndk + run_armv8_tests: true + shell: bash {0} + - name: Android NDK arm + os: ubuntu-22.04 + compiler: armv7a-linux-androideabi21-clang++ + comp: ndk + run_armv7_tests: true + shell: bash {0} + - name: MacOS 12 Apple Clang + os: macos-12 + compiler: clang++ + comp: clang + run_64bit_tests: true + shell: bash {0} + - name: MacOS 12 GCC 11 + os: macos-12 + compiler: g++-11 + comp: gcc + run_64bit_tests: true + shell: bash {0} + - name: Windows 2022 Mingw-w64 GCC x86_64 + os: windows-2022 + compiler: g++ + comp: mingw + run_64bit_tests: true + msys_sys: mingw64 + msys_env: x86_64-gcc + shell: msys2 {0} + - name: Windows 2022 Mingw-w64 GCC i686 + os: windows-2022 + compiler: g++ + comp: mingw + run_32bit_tests: true + msys_sys: mingw32 + msys_env: i686-gcc + shell: msys2 {0} + - name: Windows 2022 Mingw-w64 Clang x86_64 + os: windows-2022 + compiler: clang++ + comp: clang + run_64bit_tests: true + msys_sys: clang64 + msys_env: clang-x86_64-clang + shell: msys2 {0} defaults: run: working-directory: src @@ -107,6 +89,20 @@ jobs: sudo apt update sudo apt install expect valgrind g++-multilib qemu-user + - name: Install NDK + if: runner.os == 'Linux' + run: | + if [ $COMP == ndk ]; then + NDKV="21.4.7075529" + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;$NDKV" + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV + ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin + echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV + fi + - name: Setup msys and install required packages if: runner.os == 'Windows' uses: msys2/setup-msys2@v2 @@ -125,12 +121,7 @@ jobs: - name: Check compiler run: | if [ $COMP == ndk ]; then - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk - SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 - export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH fi $COMPILER -v @@ -228,12 +219,7 @@ jobs: - name: Test armv8 build if: ${{ matrix.config.run_armv8_tests }} run: | - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk - SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 - export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean make -j2 ARCH=armv8 build @@ -244,12 +230,7 @@ jobs: - name: Test armv7 build if: ${{ matrix.config.run_armv7_tests }} run: | - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk - SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 - export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean make -j2 ARCH=armv7 build @@ -258,12 +239,7 @@ jobs: - name: Test armv7-neon build if: ${{ matrix.config.run_armv7_tests }} run: | - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk - SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;21.4.7075529" - ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529 - export PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean make -j2 ARCH=armv7-neon build From 7cf93f8b7101e5cf5df3f7921861f27b3beb9525 Mon Sep 17 00:00:00 2001 From: VoyagerOne Date: Sun, 11 Dec 2022 12:36:13 -0500 Subject: [PATCH 132/678] Simplify Capture Scoring The parameters are now in one place for easier tuning. New formula is very similar to current. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 48176 W: 12819 L: 12616 D: 22741 Ptnml(0-2): 139, 5316, 13001, 5467, 165 LTC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 176752 W: 47364 L: 47304 D: 82084 Ptnml(0-2): 83, 17302, 53536, 17382, 73 https://tests.stockfishchess.org/tests/view/638ec7d068532fcbf79dfa15 closes https://github.com/official-stockfish/Stockfish/pull/4281 Bench: 3410998 --- src/movepick.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 188d6bd8..564adc28 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -100,7 +100,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece /// MovePicker::score() assigns a numerical value to each move in a list, used /// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring -/// captures with a good history. Quiets moves are ordered using the histories. +/// captures with a good history. Quiets moves are ordered using the history tables. template void MovePicker::score() { @@ -123,8 +123,8 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) - m.value = 6 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; + m.value = (7 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; else if constexpr (Type == QUIETS) m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)] @@ -197,7 +197,7 @@ top: case GOOD_CAPTURE: if (select([&](){ - return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ? + return pos.see_ge(*cur, Value(-cur->value)) ? // Move losing capture to endBadCaptures to be tried later true : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); From 3659a9fda0096cac091458df2c259e5636bc9106 Mon Sep 17 00:00:00 2001 From: NguyenPham Date: Thu, 15 Dec 2022 17:29:23 +1100 Subject: [PATCH 133/678] Fixed the help of Makefile make profile-build more prominent, adjust comments closes https://github.com/official-stockfish/Stockfish/pull/4284 No functional change --- src/Makefile | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Makefile b/src/Makefile index 0c98391b..da81ceb4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -747,9 +747,9 @@ help: @echo "Supported targets:" @echo "" @echo "help > Display architecture details" - @echo "build > Standard build" + @echo "profile-build > standard build with profile-guided optimization" + @echo "build > skip profile-guided optimization" @echo "net > Download the default nnue net" - @echo "profile-build > Faster build (with profile-guided optimization)" @echo "strip > Strip executable" @echo "install > Install executable" @echo "clean > Clean up" @@ -789,14 +789,15 @@ help: @echo "icc > Intel compiler" @echo "ndk > Google NDK to cross-compile for Android" @echo "" - @echo "Simple examples. If you don't know what to do, you likely want to run: " + @echo "Simple examples. If you don't know what to do, you likely want to run one of: " @echo "" - @echo "make -j build ARCH=x86-64 (A portable, slow compile for 64-bit systems)" - @echo "make -j build ARCH=x86-32 (A portable, slow compile for 32-bit systems)" + @echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " + @echo "make -j profile-build ARCH=x86-64-modern # A more portable compile for 64-bit systems " + @echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " @echo "" - @echo "Advanced examples, for experienced users looking for performance: " + @echo "Advanced examples, for experienced users: " @echo "" - @echo "make help ARCH=x86-64-bmi2" + @echo "make -j profile-build ARCH=x86-64-bmi2" @echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0" @echo "make -j build ARCH=x86-64-ssse3 COMP=clang" @echo "" From 726e90ccfaea85c5831ad4834a6a08f5bec3f2f2 Mon Sep 17 00:00:00 2001 From: PikaCat <73384062+PikaCat-OuO@users.noreply.github.com> Date: Fri, 16 Dec 2022 10:28:31 +0800 Subject: [PATCH 134/678] Badge link fix Fix the badge link issue mentioned in https://github.com/badges/shields/issues/8671 closes https://github.com/official-stockfish/Stockfish/pull/4285 No functional change --- AUTHORS | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 432d9b90..0c1c5529 100644 --- a/AUTHORS +++ b/AUTHORS @@ -163,6 +163,7 @@ Pasquale Pigazzini (ppigazzini) Patrick Jansen (mibere) Peter Schneider (pschneider1968) Peter Zsifkovits (CoffeeOne) +PikaCat Praveen Kumar Tummala (praveentml) Rahul Dsilva (silversolver1) Ralph Stößer (Ralph Stoesser) diff --git a/README.md b/README.md index ac15fbfa..4cd5968e 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ For full details, read the copy of the GPL v3 found in the file named [website-link]: https://stockfishchess.org [worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview -[build-badge]: https://img.shields.io/github/workflow/status/official-stockfish/Stockfish/Stockfish?style=for-the-badge&label=stockfish&logo=github +[build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github [commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge [discord-badge]: https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord [fishtest-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests%2Ffinished From 39af98c807d236b6511b6e399caf40102398900c Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 17 Dec 2022 12:48:03 +0300 Subject: [PATCH 135/678] Reintroduce doEvenDeeperSearch This patch is basically the same as a reverted patch but now has some guarding against search being stuck - the same way as we do with double extensions. This should help with search explosions - albeit slowly but they eventually should be resolved. passed STC: https://tests.stockfishchess.org/tests/view/639733d0b4e52c95053f3485 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 514048 W: 136423 L: 135435 D: 242190 Ptnml(0-2): 1425, 56945, 139420, 57685, 1549 passed LTC: https://tests.stockfishchess.org/tests/view/639ab79b93ed41c57eded5c3 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 113800 W: 30642 L: 30190 D: 52968 Ptnml(0-2): 53, 11092, 34178, 11504, 73 closes https://github.com/official-stockfish/Stockfish/pull/4287 bench 3611278 --- src/evaluate.cpp | 12 ++++++------ src/search.cpp | 11 +++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6a0fae9e..7619398e 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { else { int nnueComplexity; - int scale = 1064 + 106 * pos.non_pawn_material() / 5120; + int scale = 1076 + 96 * pos.non_pawn_material() / 5120; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; @@ -1071,8 +1071,8 @@ Value Eval::evaluate(const Position& pos, int* complexity) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 416 * nnueComplexity - + 424 * abs(psq - nnue) + nnueComplexity = ( 412 * nnueComplexity + + 428 * abs(psq - nnue) + (optimism > 0 ? int(optimism) * int(psq - nnue) : 0) ) / 1024; @@ -1080,12 +1080,12 @@ Value Eval::evaluate(const Position& pos, int* complexity) { if (complexity) *complexity = nnueComplexity; - optimism = optimism * (269 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 754)) / 1024; + optimism = optimism * (278 + nnueComplexity) / 256; + v = (nnue * scale + optimism * (scale - 755)) / 1024; } // Damp down the evaluation linearly when shuffling - v = v * (195 - pos.rule50_count()) / 211; + v = v * (197 - pos.rule50_count()) / 214; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/search.cpp b/src/search.cpp index b58a344a..4507460a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min((12 * d + 282) * d - 349 , 1594); + return std::min((12 * d + 282) * d - 349 , 1480); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -1066,7 +1066,7 @@ moves_loop: // When in check, search starts here // Avoid search explosion by limiting the number of double extensions if ( !PvNode && value < singularBeta - 25 - && ss->doubleExtensions <= 9) + && ss->doubleExtensions <= 10) { extension = 2; depth += depth < 12; @@ -1175,7 +1175,7 @@ moves_loop: // When in check, search starts here - 4433; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (13628 + 4000 * (depth > 7 && depth < 19)); + r -= ss->statScore / (13000 + 4152 * (depth > 7 && depth < 19)); // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension @@ -1190,9 +1190,12 @@ moves_loop: // When in check, search starts here // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 582 && ss->doubleExtensions <= 5; const bool doShallowerSearch = value < bestValue + newDepth; - newDepth += doDeeperSearch - doShallowerSearch; + ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; + + newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; if (newDepth > d) value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); From 3a32d3e00cca23648f10a40c5b322f2fd56dc0ae Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 12 Dec 2022 23:22:02 -0800 Subject: [PATCH 136/678] Don't reset increaseDepth back to true after it has been set to false Resetting increaseDepth back to true each time on the very next iteration was not intended so this is a bug fix and a simplification. See more discussion here #2482 (comment) Thanks to xoto10 STC: https://tests.stockfishchess.org/tests/view/6398c74693ed41c57ede7bfd LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 51128 W: 13543 L: 13220 D: 24365 Ptnml(0-2): 165, 5363, 14174, 5708, 154 LTC: https://tests.stockfishchess.org/tests/view/6399bcd393ed41c57edea750 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 290864 W: 77282 L: 77334 D: 136248 Ptnml(0-2): 107, 28127, 89029, 28049, 120 closes https://github.com/official-stockfish/Stockfish/pull/4288 bench: 3611278 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4507460a..d39a8ad2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -487,12 +487,11 @@ void Thread::search() { else Threads.stop = true; } - else if ( Threads.increaseDepth - && !mainThread->ponder + else if ( !mainThread->ponder && Time.elapsed() > totalTime * 0.53) - Threads.increaseDepth = false; + Threads.increaseDepth = false; else - Threads.increaseDepth = true; + Threads.increaseDepth = true; } mainThread->iterValue[iterIdx] = bestValue; From 61ea1534ff7026009a3435575c7beee91534db83 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 19 Dec 2022 17:54:36 +0100 Subject: [PATCH 137/678] No error if net available but wget/curl missing do not error out on missing wget/curl if these tools are not needed later on, i.e. if the net is available already. closes https://github.com/official-stockfish/Stockfish/pull/4291 closes https://github.com/official-stockfish/Stockfish/pull/4253 No functional change --- src/Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index da81ceb4..bcf0abdf 100644 --- a/src/Makefile +++ b/src/Makefile @@ -853,7 +853,7 @@ net: $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) @if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ + echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ fi $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) @if [ "x$(shasum_command)" = "x" ]; then \ @@ -864,7 +864,9 @@ net: echo "$(nnuenet) available."; \ else \ if [ "x$(curl_or_wget)" != "x" ]; then \ - echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ + echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ + else \ + echo "No net found and download not possible"; exit 1;\ fi; \ fi; \ if [ "x$(shasum_command)" != "x" ]; then \ From c2d507005c51145211a7963af7c41026bd759d08 Mon Sep 17 00:00:00 2001 From: Alfredo Menezes Date: Mon, 19 Dec 2022 16:07:09 -0300 Subject: [PATCH 138/678] Sometimes do a reduced search if LMR is skipped If the node doesn't go through LMR and r is too big, reduce search depth by one ply. STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 664888 W: 176375 L: 175169 D: 313344 Ptnml(0-2): 1965, 73754, 179851, 74858, 2016 https://tests.stockfishchess.org/tests/view/6399414c93ed41c57ede8fb8 LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 150784 W: 40553 L: 40031 D: 70200 Ptnml(0-2): 76, 14668, 45387, 15180, 81 https://tests.stockfishchess.org/tests/view/639dee6e11c576d919dc2b38 closes https://github.com/official-stockfish/Stockfish/pull/4290 Bench: 3727508 --- src/search.cpp | 96 +++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d39a8ad2..7f2f5284 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1120,6 +1120,52 @@ moves_loop: // When in check, search starts here // Step 16. Make the move pos.do_move(move, st, givesCheck); + Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); + + // Decrease reduction if position is or has been on the PV + // and node is not likely to fail low. (~3 Elo) + if ( ss->ttPv + && !likelyFailLow) + r -= 2; + + // Decrease reduction if opponent's move count is high (~1 Elo) + if ((ss-1)->moveCount > 7) + r--; + + // Increase reduction for cut nodes (~3 Elo) + if (cutNode) + r += 2; + + // Increase reduction if ttMove is a capture (~3 Elo) + if (ttCapture) + r++; + + // Decrease reduction for PvNodes based on depth + if (PvNode) + r -= 1 + 11 / (3 + depth); + + // Decrease reduction if ttMove has been singularly extended (~1 Elo) + if (singularQuietLMR) + r--; + + // Decrease reduction if we move a threatened piece (~1 Elo) + if ( depth > 9 + && (mp.threatenedPieces & from_sq(move))) + r--; + + // Increase reduction if next ply has a lot of fail high + if ((ss+1)->cutoffCnt > 3) + r++; + + ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + + (*contHist[0])[movedPiece][to_sq(move)] + + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] + - 4433; + + // Decrease/increase reduction for moves with a good/bad history (~30 Elo) + r -= ss->statScore / (13000 + 4152 * (depth > 7 && depth < 19)); + // Step 17. Late moves reduction / extension (LMR, ~98 Elo) // We use various heuristics for the sons of a node after the first son has // been searched. In general we would like to reduce them, but there are many @@ -1130,52 +1176,6 @@ moves_loop: // When in check, search starts here || !capture || (cutNode && (ss-1)->moveCount > 1))) { - Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); - - // Decrease reduction if position is or has been on the PV - // and node is not likely to fail low. (~3 Elo) - if ( ss->ttPv - && !likelyFailLow) - r -= 2; - - // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss-1)->moveCount > 7) - r--; - - // Increase reduction for cut nodes (~3 Elo) - if (cutNode) - r += 2; - - // Increase reduction if ttMove is a capture (~3 Elo) - if (ttCapture) - r++; - - // Decrease reduction for PvNodes based on depth - if (PvNode) - r -= 1 + 11 / (3 + depth); - - // Decrease reduction if ttMove has been singularly extended (~1 Elo) - if (singularQuietLMR) - r--; - - // Decrease reduction if we move a threatened piece (~1 Elo) - if ( depth > 9 - && (mp.threatenedPieces & from_sq(move))) - r--; - - // Increase reduction if next ply has a lot of fail high - if ((ss+1)->cutoffCnt > 3) - r++; - - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - - 4433; - - // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (13000 + 4152 * (depth > 7 && depth < 19)); - // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension // beyond the first move depth. This may lead to hidden double extensions. @@ -1209,10 +1209,10 @@ moves_loop: // When in check, search starts here } } - // Step 18. Full depth search when LMR is skipped + // Step 18. Full depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. else if (!PvNode || moveCount > 1) { - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail From 20b02264620397eacf4e43736ec7f7c48a9a7068 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Tue, 20 Dec 2022 16:01:05 +0900 Subject: [PATCH 139/678] Fix a dependency bug Instead of allowing .depend for specific build-related targets, filter non-build-related targets (i.e. help, clean) so that other targets can normally execute .depend target. closes https://github.com/official-stockfish/Stockfish/pull/4293 No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index bcf0abdf..500c1006 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1009,6 +1009,6 @@ icc-profile-use: .depend: $(SRCS) -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null -ifneq (, $(filter $(MAKECMDGOALS), build profile-build)) +ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity)) -include .depend endif From c620886181b5c0fb4a0c067b4173143a81819199 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 19 Dec 2022 16:10:22 -0500 Subject: [PATCH 140/678] Update default net to nn-335a9b2d8a80.nnue Created by retraining the master net with a combination of: the previous best dataset (Leela-dfrc_n5000.binpack), with about half the dataset filtered using depth6 multipv2 search to throw away positions where either of the 2 best moves are captures Leela T80 Oct and Nov training data rescored with best moves, adding ~9.5 billion positions Trained effectively the same way as the previous master net: python3 easy_train.py \ --experiment-name=leela-dfrc-filtered-T80-oct-nov \ --training-dataset=/data/leela-dfrc-filtered-T80-oct-nov.binpack \ --start-from-engine-test-net True \ --gpus="0," \ --start-lambda=1.0 \ --end-lambda=0.75 \ --gamma=0.995 \ --lr=4.375e-4 \ --tui=False \ --seed=$RANDOM \ --max_epoch=800 \ --auto-exit-timeout-on-training-finished=900 \ --network-testing-threads 20 \ --num-workers 6 Local testing at a fixed 25k nodes: experiments/experiment_leela-dfrc-filtered-T80-oct-nov/training/run_0/nn-epoch779.nnue localElo: run_0/nn-epoch779.nnue : 4.7 +/- 3.1 The new Leela T80 part of the dataset was prepared by downloading test80 training data from all of Oct 2022 and Nov 2022, rescoring with syzygy 6-piece tablebases and ~600 GB of 7-piece tablebases, saving best moves to exported .plain files, removing all positions with castling flags, then converting to binpacks and using interleave_binpacks.py to merge them together. Scripts used in this data conversion process are available at: https://github.com/linrock/lc0-data-converter Filtering binpack data using depth6 multipv2 search was done by modifying transform.cpp in the tools branch: https://github.com/linrock/Stockfish/tree/tools-filter-multipv2-no-rescore Links for downloading the training data (total size: 338 GB) are available at: https://robotmoon.com/nnue-training-data/ Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 30544 W: 8244 L: 7947 D: 14353 Ptnml(0-2): 93, 3243, 8302, 3542, 92 https://tests.stockfishchess.org/tests/view/63a0d377264a0cf18f86f82b Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 32464 W: 8866 L: 8573 D: 15025 Ptnml(0-2): 19, 3054, 9794, 3345, 20 https://tests.stockfishchess.org/tests/view/63a10bc9fb452d3c44b1e016 closes https://github.com/official-stockfish/Stockfish/pull/4295 Bench 3554904 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index f5ac3263..d1398dd5 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-ad9b42354671.nnue" + #define EvalFileDefaultName "nn-335a9b2d8a80.nnue" namespace NNUE { From b2bd8699eccc149cdda1f7bf6e1eed42088ec827 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 20 Dec 2022 22:54:12 +0300 Subject: [PATCH 141/678] Update Elo estimates for terms in search based on 25k games per term, using the UHO_XXL_+0.90_+1.19.epd book, at STC. More detailed information in the PR. closes https://github.com/official-stockfish/Stockfish/pull/4294 No functional change --- src/search.cpp | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7f2f5284..77d23ac2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -633,16 +633,16 @@ namespace { && ttValue != VALUE_NONE // Possible in case of TT access race && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { - // If ttMove is quiet, update move sorting heuristics on TT hit (~1 Elo) + // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) if (ttMove) { if (ttValue >= beta) { - // Bonus for a quiet ttMove that fails high (~3 Elo) + // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); - // Extra penalty for early quiet moves of the previous ply (~0 Elo) + // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) if ((ss-1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); } @@ -734,7 +734,7 @@ namespace { else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost complexity = abs(ss->staticEval - pos.psq_eg_stm()); - // ttValue can be used as a better position evaluation (~4 Elo) + // ttValue can be used as a better position evaluation (~7 Elo) if ( ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttValue; @@ -750,7 +750,7 @@ namespace { thisThread->complexityAverage.update(complexity); - // Use static evaluation difference to improve quiet move ordering (~3 Elo) + // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1914, 1914); @@ -766,7 +766,7 @@ namespace { : 168; improving = improvement > 0; - // Step 7. Razoring. + // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. if (eval < alpha - 369 - 254 * depth * depth) @@ -776,7 +776,7 @@ namespace { return value; } - // Step 8. Futility pruning: child node (~25 Elo). + // Step 8. Futility pruning: child node (~40 Elo). // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 8 @@ -785,7 +785,7 @@ namespace { && eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; - // Step 9. Null move search with verification search (~22 Elo) + // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL && (ss-1)->statScore < 17139 @@ -837,7 +837,7 @@ namespace { probCutBeta = beta + 191 - 54 * improving; - // Step 10. ProbCut (~4 Elo) + // Step 10. ProbCut (~10 Elo) // If we have a good enough capture and a reduced search returns a value // much above beta, we can (almost) safely prune the previous move. if ( !PvNode @@ -888,7 +888,7 @@ namespace { } // Step 11. If the position is not in TT, decrease depth by 3. - // Use qsearch if depth is equal or below zero (~4 Elo) + // Use qsearch if depth is equal or below zero (~9 Elo) if ( PvNode && !ttMove) depth -= 3; @@ -903,7 +903,7 @@ namespace { moves_loop: // When in check, search starts here - // Step 12. A small Probcut idea, when we are in check (~0 Elo) + // Step 12. A small Probcut idea, when we are in check (~4 Elo) probCutBeta = beta + 417; if ( ss->inCheck && !PvNode @@ -980,12 +980,12 @@ moves_loop: // When in check, search starts here Value delta = beta - alpha; - // Step 14. Pruning at shallow depth (~98 Elo). Depth conditions are important for mate finding. + // Step 14. Pruning at shallow depth (~120 Elo). Depth conditions are important for mate finding. if ( !rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { - // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~7 Elo) + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) moveCountPruning = moveCount >= futility_move_count(improving, depth); // Reduced depth of the next LMR search @@ -994,7 +994,7 @@ moves_loop: // When in check, search starts here if ( capture || givesCheck) { - // Futility pruning for captures (~0 Elo) + // Futility pruning for captures (~2 Elo) if ( !givesCheck && !PvNode && lmrDepth < 7 @@ -1003,7 +1003,7 @@ moves_loop: // When in check, search starts here + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha) continue; - // SEE based pruning (~9 Elo) + // SEE based pruning (~11 Elo) if (!pos.see_ge(move, Value(-222) * depth)) continue; } @@ -1020,23 +1020,23 @@ moves_loop: // When in check, search starts here history += 2 * thisThread->mainHistory[us][from_to(move)]; - // Futility pruning: parent node (~9 Elo) + // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck && lmrDepth < 13 && ss->staticEval + 106 + 145 * lmrDepth + history / 52 <= alpha) continue; - // Prune moves with negative SEE (~3 Elo) + // Prune moves with negative SEE (~4 Elo) if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } - // Step 15. Extensions (~66 Elo) + // Step 15. Extensions (~100 Elo) // We take care to not overdo to avoid search getting stuck. if (ss->ply < thisThread->rootDepth * 2) { - // Singular extension search (~58 Elo). If all moves but one fail low on a + // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the @@ -1095,7 +1095,7 @@ moves_loop: // When in check, search starts here && abs(ss->staticEval) > 82) extension = 1; - // Quiet ttMove extensions (~0 Elo) + // Quiet ttMove extensions (~1 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] @@ -1166,7 +1166,7 @@ moves_loop: // When in check, search starts here // Decrease/increase reduction for moves with a good/bad history (~30 Elo) r -= ss->statScore / (13000 + 4152 * (depth > 7 && depth < 19)); - // Step 17. Late moves reduction / extension (LMR, ~98 Elo) + // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has // been searched. In general we would like to reduce them, but there are many // cases where we extend a son if it has good chances to be "interesting". @@ -1462,7 +1462,7 @@ moves_loop: // When in check, search starts here if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) ss->staticEval = bestValue = evaluate(pos); - // ttValue can be used as a better position evaluation (~7 Elo) + // ttValue can be used as a better position evaluation (~13 Elo) if ( ttValue != VALUE_NONE && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttValue; @@ -1520,7 +1520,7 @@ moves_loop: // When in check, search starts here moveCount++; - // Futility pruning and moveCount pruning (~5 Elo) + // Futility pruning and moveCount pruning (~10 Elo) if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY && !givesCheck && to_sq(move) != prevSq @@ -1559,7 +1559,7 @@ moves_loop: // When in check, search starts here [pos.moved_piece(move)] [to_sq(move)]; - // Continuation history based pruning (~2 Elo) + // Continuation history based pruning (~3 Elo) if ( !capture && bestValue > VALUE_TB_LOSS_IN_MAX_PLY && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 From 64656f8583dde88d558d5367b4cf8fc136c3214a Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 24 Dec 2022 13:05:24 +0800 Subject: [PATCH 142/678] Add double bonus for prior countermove fail low Add a double extra bonus for particularly bad fail low cases. Original idea by Yoshie2000. STC: https://tests.stockfishchess.org/tests/view/63a2f0d86b5bf07ac7fad543 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 146488 W: 38992 L: 38532 D: 68964 Ptnml(0-2): 385, 16036, 39965, 16450, 408 LTC: https://tests.stockfishchess.org/tests/view/63a3eaeb6b5bf07ac7fafdec LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 103992 W: 27853 L: 27423 D: 48716 Ptnml(0-2): 41, 10029, 31435, 10441, 50 closes https://github.com/official-stockfish/Stockfish/pull/4302 Bench: 3801857 --- AUTHORS | 1 + src/search.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0c1c5529..70b500ea 100644 --- a/AUTHORS +++ b/AUTHORS @@ -145,6 +145,7 @@ Mira Miroslav Fontán (Hexik) Moez Jellouli (MJZ1977) Mohammed Li (tthsqe12) +Muzhen J (XInTheDark) Nathan Rugg (nmrugg) Nick Pelling (nickpelling) Nicklas Persson (NicklasPersson) diff --git a/src/search.cpp b/src/search.cpp index 77d23ac2..0c8377d0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1353,16 +1353,17 @@ moves_loop: // When in check, search starts here quietsSearched, quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low - else if ( (depth >= 5 || PvNode) + else if ( (depth >= 5 || PvNode || bestValue < alpha - 62 * depth) && !priorCapture) { //Assign extra bonus if current node is PvNode or cutNode //or fail low was really bad bool extraBonus = PvNode - || cutNode - || bestValue < alpha - 62 * depth; + || cutNode; - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus)); + bool doubleExtraBonus = extraBonus && bestValue < alpha - 85 * depth; + + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus + doubleExtraBonus)); } if (PvNode) From f09b391ceb5ea282538b721b069db67e3b6e098f Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 27 Dec 2022 13:33:26 +0100 Subject: [PATCH 143/678] Fix comparison with uninitialized variable In both modified methods, the variable 'result' is checked to detect whether the probe operation failed. However, the variable is not initialized on all paths, so the check might test an uninitialized value. A test position (with TB) is given by: position fen 3K1k2/R7/8/8/8/8/8/R6Q w - - 0 1 moves a1b1 f8g8 b1a1 g8f8 a1b1 f8g8 b1a1 This is now fixed by always initializing the variable. closes https://github.com/official-stockfish/Stockfish/pull/4309 No functional change --- src/syzygy/tbprobe.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index f2de036d..4df495a8 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1514,7 +1514,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // A return value false indicates that not all probes were successful. bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { - ProbeState result; + ProbeState result = OK; StateInfo st; // Obtain 50-move counter for the root position @@ -1593,7 +1593,7 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ }; - ProbeState result; + ProbeState result = OK; StateInfo st; WDLScore wdl; From 258c13ba8cc8d054b803a04c433a766d0c5a1e8f Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 27 Dec 2022 10:31:24 +0100 Subject: [PATCH 144/678] Remove redundant extern modifier for function declarations Functions have external linkage by default, so there's no need to declare them extern. closes https://github.com/official-stockfish/Stockfish/pull/4308 No functional change --- src/position.h | 2 +- src/psqt.h | 2 +- src/uci.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.h b/src/position.h index 078ff5b7..90247cb1 100644 --- a/src/position.h +++ b/src/position.h @@ -204,7 +204,7 @@ private: bool chess960; }; -extern std::ostream& operator<<(std::ostream& os, const Position& pos); +std::ostream& operator<<(std::ostream& os, const Position& pos); inline Color Position::side_to_move() const { return sideToMove; diff --git a/src/psqt.h b/src/psqt.h index 4ee0e379..bd78be90 100644 --- a/src/psqt.h +++ b/src/psqt.h @@ -30,7 +30,7 @@ namespace Stockfish::PSQT extern Score psq[PIECE_NB][SQUARE_NB]; // Fill psqt array from a set of internally linked parameters -extern void init(); +void init(); } // namespace Stockfish::PSQT diff --git a/src/uci.cpp b/src/uci.cpp index 5d842d25..dc5f10e1 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -36,7 +36,7 @@ using namespace std; namespace Stockfish { -extern vector setup_bench(const Position&, istream&); +vector setup_bench(const Position&, istream&); namespace { From be9bc420afa11314c886c0aede4c4ae3d76f8a50 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Thu, 29 Dec 2022 10:34:28 -0500 Subject: [PATCH 145/678] Update default net to nn-60fa44e376d9.nnue Created by retraining the master net on the previous best dataset with additional filtering. No new data was added. More of the Leela-dfrc_n5000.binpack part of the dataset was pre-filtered with depth6 multipv2 search to remove bestmove captures. About 93% of the previous Leela/SF data and 99% of the SF dfrc data was filtered. Unfiltered parts of the dataset were left out. The new Leela T80 oct+nov data is the same as before. All early game positions with ply count <= 28 were skipped during training by modifying the training data loader in nnue-pytorch. Trained in a similar way as recent master nets, with a different nnue-pytorch branch for early ply skipping: python3 easy_train.py \ --experiment-name=leela93-dfrc99-filt-only-T80-oct-nov-skip28 \ --training-dataset=/data/leela93-dfrc99-filt-only-T80-oct-nov.binpack \ --start-from-engine-test-net True \ --nnue-pytorch-branch=linrock/nnue-pytorch/misc-fixes-skip-ply-lteq-28 \ --gpus="0," \ --start-lambda=1.0 \ --end-lambda=0.75 \ --gamma=0.995 \ --lr=4.375e-4 \ --tui=False \ --seed=$RANDOM \ --max_epoch=800 \ --network-testing-threads 20 \ --num-workers 6 For the exact training data used: https://robotmoon.com/nnue-training-data/ Details about the previous best dataset: #4295 Local testing at a fixed 25k nodes: experiment_leela93-dfrc99-filt-only-T80-oct-nov-skip28 Local Elo: run_0/nn-epoch779.nnue : 5.1 +/- 1.5 Passed STC https://tests.stockfishchess.org/tests/view/63adb3acae97a464904fd4e8 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 36504 W: 9847 L: 9538 D: 17119 Ptnml(0-2): 108, 3981, 9784, 4252, 127 Passed LTC https://tests.stockfishchess.org/tests/view/63ae0ae25bd1e5f27f13d884 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 36592 W: 10017 L: 9717 D: 16858 Ptnml(0-2): 17, 3461, 11037, 3767, 14 closes https://github.com/official-stockfish/Stockfish/pull/4314 bench 4015511 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index d1398dd5..4001166b 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-335a9b2d8a80.nnue" + #define EvalFileDefaultName "nn-60fa44e376d9.nnue" namespace NNUE { From b60f9cc4515cdfb657a0166abb29a60257cc59e1 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 1 Jan 2023 14:45:08 +0100 Subject: [PATCH 146/678] Update copyright years Happy New Year! closes https://github.com/official-stockfish/Stockfish/pull/4315 No functional change --- src/Makefile | 2 +- src/benchmark.cpp | 2 +- src/bitbase.cpp | 2 +- src/bitboard.cpp | 2 +- src/bitboard.h | 2 +- src/endgame.cpp | 2 +- src/endgame.h | 2 +- src/evaluate.cpp | 2 +- src/evaluate.h | 2 +- src/main.cpp | 2 +- src/material.cpp | 2 +- src/material.h | 2 +- src/misc.cpp | 2 +- src/misc.h | 2 +- src/movegen.cpp | 2 +- src/movegen.h | 2 +- src/movepick.cpp | 2 +- src/movepick.h | 2 +- src/nnue/evaluate_nnue.cpp | 2 +- src/nnue/evaluate_nnue.h | 2 +- src/nnue/features/half_ka_v2_hm.cpp | 2 +- src/nnue/features/half_ka_v2_hm.h | 2 +- src/nnue/layers/affine_transform.h | 2 +- src/nnue/layers/clipped_relu.h | 2 +- src/nnue/layers/simd.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/nnue_accumulator.h | 2 +- src/nnue/nnue_architecture.h | 2 +- src/nnue/nnue_common.h | 2 +- src/nnue/nnue_feature_transformer.h | 2 +- src/pawns.cpp | 2 +- src/pawns.h | 2 +- src/position.cpp | 2 +- src/position.h | 2 +- src/psqt.cpp | 2 +- src/psqt.h | 2 +- src/search.cpp | 2 +- src/search.h | 2 +- src/syzygy/tbprobe.cpp | 2 +- src/syzygy/tbprobe.h | 2 +- src/thread.cpp | 2 +- src/thread.h | 2 +- src/thread_win32_osx.h | 2 +- src/timeman.cpp | 2 +- src/timeman.h | 2 +- src/tt.cpp | 2 +- src/tt.h | 2 +- src/tune.cpp | 2 +- src/tune.h | 2 +- src/types.h | 2 +- src/uci.cpp | 2 +- src/uci.h | 2 +- src/ucioption.cpp | 2 +- 53 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/Makefile b/src/Makefile index 500c1006..d4f089ee 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 -# Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) +# Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.cpp b/src/benchmark.cpp index e1c025ad..2abb9c8f 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitbase.cpp b/src/bitbase.cpp index 84300baf..e21d1fe9 100644 --- a/src/bitbase.cpp +++ b/src/bitbase.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.cpp b/src/bitboard.cpp index fd0ba235..0ed13fd0 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.h b/src/bitboard.h index 2b6e2a69..d4485fcb 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/endgame.cpp b/src/endgame.cpp index e773e7a9..9021f242 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/endgame.h b/src/endgame.h index e79f696f..c184cb3f 100644 --- a/src/endgame.h +++ b/src/endgame.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 7619398e..3b6cbbcd 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.h b/src/evaluate.h index 4001166b..db9e6424 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/main.cpp b/src/main.cpp index fad0ef84..c40e0fa3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/material.cpp b/src/material.cpp index 1567358a..7102f879 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/material.h b/src/material.h index 3ca169ce..f6db85c4 100644 --- a/src/material.h +++ b/src/material.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.cpp b/src/misc.cpp index c7fa0e9a..5bb8da69 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.h b/src/misc.h index 77b81d50..4c1150f8 100644 --- a/src/misc.h +++ b/src/misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.cpp b/src/movegen.cpp index c7a3c29b..a960f863 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.h b/src/movegen.h index bbb35b39..b8df3e65 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.cpp b/src/movepick.cpp index 564adc28..dbe67357 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.h b/src/movepick.h index e4c4a5bf..90f60b8a 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 4715fed0..8d720ccb 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 2e4f1f50..9499f7d9 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 7dbd3415..19ebb15f 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index a95d4328..78063c36 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 461a7b83..710ab8a7 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index f94d3082..51e562da 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 7b9e8fb2..aeab39c4 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index b603a277..df539b39 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 600483b5..8eba4497 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index cac83730..c43a23c3 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 17956189..12309d26 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index b6dd54d3..62f1615d 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/pawns.cpp b/src/pawns.cpp index fdcfa022..0ccafd9e 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/pawns.h b/src/pawns.h index af0370fc..95c9ea3c 100644 --- a/src/pawns.h +++ b/src/pawns.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.cpp b/src/position.cpp index 5befcaf2..e82425af 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.h b/src/position.h index 90247cb1..3de24f22 100644 --- a/src/position.h +++ b/src/position.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/psqt.cpp b/src/psqt.cpp index ca5664c2..d3ebb20d 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/psqt.h b/src/psqt.h index bd78be90..9630f446 100644 --- a/src/psqt.h +++ b/src/psqt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.cpp b/src/search.cpp index 0c8377d0..dd240366 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.h b/src/search.h index b620202d..48a0f7ce 100644 --- a/src/search.h +++ b/src/search.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 4df495a8..b7ba3240 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 179c7572..159c6865 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.cpp b/src/thread.cpp index e8723eb7..7e71edf1 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.h b/src/thread.h index 5f0b2c3e..680da209 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 77d1c3c7..01ff1c77 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.cpp b/src/timeman.cpp index cab0d767..5c826b4f 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.h b/src/timeman.h index a86f0769..3462b823 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.cpp b/src/tt.cpp index c7118aea..39f18d3d 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.h b/src/tt.h index 03fe3e14..3e335b44 100644 --- a/src/tt.h +++ b/src/tt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.cpp b/src/tune.cpp index a885845f..41f6664d 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.h b/src/tune.h index 75ab484a..f5b787af 100644 --- a/src/tune.h +++ b/src/tune.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/types.h b/src/types.h index 29c16ce7..8a021342 100644 --- a/src/types.h +++ b/src/types.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.cpp b/src/uci.cpp index dc5f10e1..c49b9b78 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.h b/src/uci.h index 3b5a6764..bd3df323 100644 --- a/src/uci.h +++ b/src/uci.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 9fb48345..78711c18 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From a6fa683418c6a74491035601e16497851eea6be6 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Thu, 29 Dec 2022 10:34:28 -0500 Subject: [PATCH 147/678] Update default net to nn-a3dc078bafc7.nnue This is a later epoch (epoch 859) from the same experiment run that trained yesterday's master net nn-60fa44e376d9.nnue (epoch 779). The experiment was manually paused around epoch 790 and unpaused with max epoch increased to 900 mainly to get more local elo data without letting the GPU idle. nn-60fa44e376d9.nnue is from #4314 nn-335a9b2d8a80.nnue is from #4295 Local elo vs. nn-335a9b2d8a80.nnue at 25k nodes per move: experiment_leela93-dfrc99-filt-only-T80-oct-nov-skip28 run_0/nn-epoch779.nnue (nn-60fa44e376d9.nnue) : 5.0 +/- 1.2 run_0/nn-epoch859.nnue (nn-a3dc078bafc7.nnue) : 5.6 +/- 1.6 Passed STC vs. nn-335a9b2d8a80.nnue https://tests.stockfishchess.org/tests/view/63ae10495bd1e5f27f13d94f LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 37536 W: 10088 L: 9781 D: 17667 Ptnml(0-2): 110, 4006, 10223, 4325, 104 An LTC test vs. nn-335a9b2d8a80.nnue was paused due to nn-60fa44e376d9.nnue passing LTC first: https://tests.stockfishchess.org/tests/view/63ae5d34331d5fca5113703b Passed LTC vs. nn-60fa44e376d9.nnue https://tests.stockfishchess.org/tests/view/63af1e41465d2b022dbce4e7 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 148704 W: 39672 L: 39155 D: 69877 Ptnml(0-2): 59, 14443, 44843, 14936, 71 closes https://github.com/official-stockfish/Stockfish/pull/4319 bench 3984365 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index db9e6424..0a00faa7 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-60fa44e376d9.nnue" + #define EvalFileDefaultName "nn-a3dc078bafc7.nnue" namespace NNUE { From fc5b59b88bae00b7e74bbad0de7c3c33136937cf Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 2 Jan 2023 17:19:41 +0300 Subject: [PATCH 148/678] Parameter Tweaks This patch is a parameter tweak that passed both STC and LTC tests. STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 80944 W: 21557 L: 21189 D: 38198 Ptnml(0-2): 192, 8883, 22028, 9103, 266 https://tests.stockfishchess.org/tests/view/63b07fe2d421d8f75795a03b LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 30440 W: 8296 L: 8007 D: 14137 Ptnml(0-2): 6, 2893, 9143, 3162, 16 https://tests.stockfishchess.org/tests/view/63b167d02ab1290f961644db closes https://github.com/official-stockfish/Stockfish/pull/4318 Bench: 4182223 --- src/evaluate.cpp | 12 +++++----- src/search.cpp | 60 ++++++++++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3b6cbbcd..d5cda3d8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1056,7 +1056,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { // We use the much less accurate but faster Classical eval when the NNUE // option is set to false. Otherwise we use the NNUE eval unless the // PSQ advantage is decisive and several pieces remain. (~3 Elo) - bool useClassical = !useNNUE || (pos.count() > 7 && abs(psq) > 1760); + bool useClassical = !useNNUE || (pos.count() > 7 && abs(psq) > 1781); if (useClassical) v = Evaluation(pos).value(); @@ -1071,8 +1071,8 @@ Value Eval::evaluate(const Position& pos, int* complexity) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 412 * nnueComplexity - + 428 * abs(psq - nnue) + nnueComplexity = ( 406 * nnueComplexity + + 424 * abs(psq - nnue) + (optimism > 0 ? int(optimism) * int(psq - nnue) : 0) ) / 1024; @@ -1080,12 +1080,12 @@ Value Eval::evaluate(const Position& pos, int* complexity) { if (complexity) *complexity = nnueComplexity; - optimism = optimism * (278 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 755)) / 1024; + optimism = optimism * (272 + nnueComplexity) / 256; + v = (nnue * scale + optimism * (scale - 748)) / 1024; } // Damp down the evaluation linearly when shuffling - v = v * (197 - pos.rule50_count()) / 214; + v = v * (200 - pos.rule50_count()) / 214; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/search.cpp b/src/search.cpp index dd240366..d0ed44fa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -63,7 +63,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(165 * (d - improving)); + return Value(158 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -71,7 +71,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1642 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 916); + return (r + 1460 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 937); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min((12 * d + 282) * d - 349 , 1480); + return std::min((11 * d + 284) * d - 363 , 1650); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -307,7 +307,7 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); - complexityAverage.set(155, 1); + complexityAverage.set(153, 1); optimism[us] = optimism[~us] = VALUE_ZERO; @@ -351,12 +351,12 @@ void Thread::search() { if (rootDepth >= 4) { Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 15620; + delta = Value(10) + int(prev) * prev / 15400; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 118 * prev / (std::abs(prev) + 169); + int opt = 116 * prev / (std::abs(prev) + 170); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; } @@ -753,7 +753,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1914, 1914); + int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1940, 1940); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -763,13 +763,13 @@ namespace { // margin and the improving flag are used in various pruning heuristics. improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 168; + : 172; improving = improvement > 0; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 369 - 254 * depth * depth) + if (eval < alpha - 394 - 255 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -780,18 +780,18 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 8 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 303 >= beta + && eval - futility_margin(depth, improving) - (ss-1)->statScore / 304 >= beta && eval >= beta - && eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 28580) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 17139 + && (ss-1)->statScore < 18200 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 20 * depth - improvement / 13 + 233 + complexity / 25 + && ss->staticEval >= beta - 20 * depth - improvement / 14 + 235 + complexity / 24 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -799,7 +799,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 168, 7) + depth / 3 + 4 - (complexity > 861); + Depth R = std::min(int(eval - beta) / 165, 6) + depth / 3 + 4 - (complexity > 800); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -835,7 +835,7 @@ namespace { } } - probCutBeta = beta + 191 - 54 * improving; + probCutBeta = beta + 180 - 54 * improving; // Step 10. ProbCut (~10 Elo) // If we have a good enough capture and a reduced search returns a value @@ -904,7 +904,7 @@ namespace { moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 417; + probCutBeta = beta + 402; if ( ss->inCheck && !PvNode && depth >= 2 @@ -999,12 +999,12 @@ moves_loop: // When in check, search starts here && !PvNode && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 180 + 201 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + && ss->staticEval + 185 + 203 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha) continue; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-222) * depth)) + if (!pos.see_ge(move, Value(-220) * depth)) continue; } else @@ -1015,7 +1015,7 @@ moves_loop: // When in check, search starts here // Continuation history based pruning (~2 Elo) if ( lmrDepth < 5 - && history < -3875 * (depth - 1)) + && history < -4180 * (depth - 1)) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; @@ -1023,11 +1023,11 @@ moves_loop: // When in check, search starts here // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck && lmrDepth < 13 - && ss->staticEval + 106 + 145 * lmrDepth + history / 52 <= alpha) + && ss->staticEval + 103 + 136 * lmrDepth + history / 53 <= alpha) continue; // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, Value(-25 * lmrDepth * lmrDepth - 16 * lmrDepth))) continue; } } @@ -1092,14 +1092,14 @@ moves_loop: // When in check, search starts here // Check extensions (~1 Elo) else if ( givesCheck && depth > 9 - && abs(ss->staticEval) > 82) + && abs(ss->staticEval) > 78) extension = 1; // Quiet ttMove extensions (~1 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5177) + && (*contHist[0])[movedPiece][to_sq(move)] >= 5600) extension = 1; } @@ -1161,10 +1161,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4433; + - 4467; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (13000 + 4152 * (depth > 7 && depth < 19)); + r -= ss->statScore / (12800 + 4410 * (depth > 7 && depth < 19)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1188,7 +1188,7 @@ moves_loop: // When in check, search starts here { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d)); + const bool doDeeperSearch = value > (alpha + 66 + 11 * (newDepth - d)); const bool doEvenDeeperSearch = value > alpha + 582 && ss->doubleExtensions <= 5; const bool doShallowerSearch = value < bestValue + newDepth; @@ -1353,7 +1353,7 @@ moves_loop: // When in check, search starts here quietsSearched, quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low - else if ( (depth >= 5 || PvNode || bestValue < alpha - 62 * depth) + else if ( (depth >= 5 || PvNode || bestValue < alpha - 65 * depth) && !priorCapture) { //Assign extra bonus if current node is PvNode or cutNode @@ -1361,7 +1361,7 @@ moves_loop: // When in check, search starts here bool extraBonus = PvNode || cutNode; - bool doubleExtraBonus = extraBonus && bestValue < alpha - 85 * depth; + bool doubleExtraBonus = extraBonus && bestValue < alpha - 88 * depth; update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus + doubleExtraBonus)); } @@ -1488,7 +1488,7 @@ moves_loop: // When in check, search starts here if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 153; + futilityBase = bestValue + 158; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1690,7 +1690,7 @@ moves_loop: // When in check, search starts here if (!pos.capture(bestMove)) { - int bonus2 = bestValue > beta + 137 ? bonus1 // larger bonus + int bonus2 = bestValue > beta + 146 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 9fe9ff00823a0d1e989fcfc45410396e94345987 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 27 Dec 2022 16:44:32 -0800 Subject: [PATCH 149/678] Fix stack initialization This fixes a bug where on line 278 the Stack::staticEvals are initialized to 0. However VALUE_NONE is defined to be 32002 so this is a bug in master. It is probably due to the calculation of improvement, where staticEval prior to rootPos can be accessed. https://tests.stockfishchess.org/tests/view/63ab91cf39af998100ce1666 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 53736 W: 14285 L: 13955 D: 25496 Ptnml(0-2): 121, 5921, 14500, 6159, 167 https://tests.stockfishchess.org/tests/view/63b2af5ee28ed36c814bed52 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 33776 W: 9130 L: 8934 D: 15712 Ptnml(0-2): 14, 3240, 10185, 3434, 15 closes https://github.com/official-stockfish/Stockfish/pull/4320 Bench: 4068510 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d0ed44fa..d27b21d2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -276,8 +276,11 @@ void Thread::search() { int iterIdx = 0; std::memset(ss-7, 0, 10 * sizeof(Stack)); - for (int i = 7; i > 0; i--) + for (int i = 7; i > 0; --i) + { (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + (ss-i)->staticEval = VALUE_NONE; + } for (int i = 0; i <= MAX_PLY + 2; ++i) (ss+i)->ply = i; From ea0f34120fbc1ab7aa8047a1319714b87a1f3b87 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sat, 24 Dec 2022 09:43:22 -0600 Subject: [PATCH 150/678] Late countermove bonus: remove "extraBonus &&" passed stc: https://tests.stockfishchess.org/tests/view/63a71e409c0589b83751dc25 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 432480 W: 113846 L: 114055 D: 204579 Ptnml(0-2): 1164, 48205, 117701, 48016, 1154 passed ltc: https://tests.stockfishchess.org/tests/view/63aba66639af998100ce1aa9 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 245344 W: 65309 L: 65317 D: 114718 Ptnml(0-2): 117, 24257, 73903, 24307, 88 closes https://github.com/official-stockfish/Stockfish/pull/4322 bench 4379218 --- src/search.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d27b21d2..55af6aba 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1359,14 +1359,9 @@ moves_loop: // When in check, search starts here else if ( (depth >= 5 || PvNode || bestValue < alpha - 65 * depth) && !priorCapture) { - //Assign extra bonus if current node is PvNode or cutNode - //or fail low was really bad - bool extraBonus = PvNode - || cutNode; - - bool doubleExtraBonus = extraBonus && bestValue < alpha - 88 * depth; - - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus + doubleExtraBonus)); + // Extra bonuses for PV/Cut nodes or bad fail lows + int bonus = 1 + (PvNode || cutNode) + (bestValue < alpha - 88 * depth); + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } if (PvNode) From 4101893a28b4d34262ed1fba10f2365e964b6537 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Mon, 9 Jan 2023 14:26:51 +0100 Subject: [PATCH 151/678] On step 18 increase reduction by 2 if not ttmove and cutnode stc: https://tests.stockfishchess.org/tests/view/63babc9fcd3db0c8d399f723 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 43104 W: 11711 L: 11389 D: 20004 Ptnml(0-2): 211, 4518, 11793, 4798, 232 ltc: https://tests.stockfishchess.org/tests/view/63bb1857cd3db0c8d39a0661 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 127104 W: 33810 L: 33339 D: 59955 Ptnml(0-2): 39, 12155, 38702, 12608, 48 closes https://github.com/official-stockfish/Stockfish/pull/4334 Bench: 4035725 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 55af6aba..f5eeb23b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1215,6 +1215,10 @@ moves_loop: // When in check, search starts here // Step 18. Full depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. else if (!PvNode || moveCount > 1) { + // Increase reduction for cut nodes and not ttMove (~1 Elo) + if (!ttMove && cutNode) + r += 2; + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode); } From fcee83810a5bcf774d3e13cf9e65ccf3b29e3319 Mon Sep 17 00:00:00 2001 From: Jake Senne <26696846+w1wwwwww@users.noreply.github.com> Date: Tue, 3 Jan 2023 15:16:45 -0600 Subject: [PATCH 152/678] Only close file if already open Ensures that the tablebase file is only closed if already open. Fixes #4268 Closes https://github.com/official-stockfish/Stockfish/pull/4321 No functional change --- AUTHORS | 1 + src/syzygy/tbprobe.cpp | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 70b500ea..998399b9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -92,6 +92,7 @@ Ivan Ivec (IIvec) Jacques B. (Timshel) Jan Ondruš (hxim) Jared Kish (Kurtbusch, kurt22i) +Jake Senne (w1wwwwww) Jarrod Torriero (DU-jdto) Jean Gauthier (OuaisBla) Jean-Francois Romang (jromang) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index b7ba3240..cc5e2852 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -199,13 +199,10 @@ public: } } - // Memory map the file and check it. File should be already open and will be - // closed after mapping. + // Memory map the file and check it. uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) { - - assert(is_open()); - - close(); // Need to re-open to get native file descriptor + if (is_open()) + close(); // Need to re-open to get native file descriptor #ifndef _WIN32 struct stat statbuf; From 31acd6bab70f4661316986c2c93163d39736fd61 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 27 Dec 2022 21:06:10 +0100 Subject: [PATCH 153/678] Warn if a global function has no previous declaration If a global function has no previous declaration, either the declaration is missing in the corresponding header file or the function should be declared static. Static functions are local to the translation unit, which allows the compiler to apply some optimizations earlier (when compiling the translation unit rather than during link-time optimization). The commit enables the warning for gcc, clang, and mingw. It also fixes the reported warnings by declaring the functions static or by adding a header file (benchmark.h). closes https://github.com/official-stockfish/Stockfish/pull/4325 No functional change --- src/Makefile | 6 +++--- src/benchmark.cpp | 2 ++ src/benchmark.h | 34 ++++++++++++++++++++++++++++++++++ src/evaluate.cpp | 10 +++++----- src/misc.cpp | 2 +- src/nnue/evaluate_nnue.cpp | 10 +++++----- src/uci.cpp | 3 +-- src/ucioption.cpp | 14 +++++++------- 8 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 src/benchmark.h diff --git a/src/Makefile b/src/Makefile index d4f089ee..30c1be5e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -366,7 +366,7 @@ endif ifeq ($(COMP),gcc) comp=gcc CXX=g++ - CXXFLAGS += -pedantic -Wextra -Wshadow + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) ifeq ($(OS),Android) @@ -410,7 +410,7 @@ ifeq ($(COMP),mingw) CXX=i686-w64-mingw32-c++-posix endif endif - CXXFLAGS += -pedantic -Wextra -Wshadow + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations endif ifeq ($(COMP),icc) @@ -426,7 +426,7 @@ ifeq ($(COMP),clang) CXX=x86_64-w64-mingw32-clang++ endif - CXXFLAGS += -pedantic -Wextra -Wshadow + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),) ifeq ($(target_windows),) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 2abb9c8f..a1ad0550 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -16,6 +16,8 @@ along with this program. If not, see . */ +#include "benchmark.h" + #include #include #include diff --git a/src/benchmark.h b/src/benchmark.h new file mode 100644 index 00000000..64acf833 --- /dev/null +++ b/src/benchmark.h @@ -0,0 +1,34 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef BENCHMARK_H_INCLUDED +#define BENCHMARK_H_INCLUDED + +#include +#include +#include + +namespace Stockfish { + +class Position; + +std::vector setup_bench(const Position&, std::istream&); + +} // namespace Stockfish + +#endif // #ifndef BENCHMARK_H_INCLUDED diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d5cda3d8..3aae9f1c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -159,24 +159,24 @@ namespace Trace { Score scores[TERM_NB][COLOR_NB]; - double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } + static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } - void add(int idx, Color c, Score s) { + static void add(int idx, Color c, Score s) { scores[idx][c] = s; } - void add(int idx, Score w, Score b = SCORE_ZERO) { + static void add(int idx, Score w, Score b = SCORE_ZERO) { scores[idx][WHITE] = w; scores[idx][BLACK] = b; } - std::ostream& operator<<(std::ostream& os, Score s) { + static std::ostream& operator<<(std::ostream& os, Score s) { os << std::setw(5) << to_cp(mg_value(s)) << " " << std::setw(5) << to_cp(eg_value(s)); return os; } - std::ostream& operator<<(std::ostream& os, Term t) { + static std::ostream& operator<<(std::ostream& os, Term t) { if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL) os << " ---- ----" << " | " << " ---- ----"; diff --git a/src/misc.cpp b/src/misc.cpp index 5bb8da69..1cb515c3 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -517,7 +517,7 @@ void bindThisThread(size_t) {} /// API and returns the best node id for the thread with index idx. Original /// code from Texel by Peter Österlund. -int best_node(size_t idx) { +static int best_node(size_t idx) { int threads = 0; int nodes = 0; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 8d720ccb..a06c1978 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -83,7 +83,7 @@ namespace Stockfish::Eval::NNUE { } // namespace Detail // Initialize the evaluation function parameters - void initialize() { + static void initialize() { Detail::initialize(featureTransformer); for (std::size_t i = 0; i < LayerStacks; ++i) @@ -91,7 +91,7 @@ namespace Stockfish::Eval::NNUE { } // Read network header - bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) + static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { std::uint32_t version, size; @@ -105,7 +105,7 @@ namespace Stockfish::Eval::NNUE { } // Write network header - bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) + static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { write_little_endian(stream, Version); write_little_endian(stream, hashValue); @@ -115,7 +115,7 @@ namespace Stockfish::Eval::NNUE { } // Read network parameters - bool read_parameters(std::istream& stream) { + static bool read_parameters(std::istream& stream) { std::uint32_t hashValue; if (!read_header(stream, &hashValue, &netDescription)) return false; @@ -127,7 +127,7 @@ namespace Stockfish::Eval::NNUE { } // Write network parameters - bool write_parameters(std::ostream& stream) { + static bool write_parameters(std::ostream& stream) { if (!write_header(stream, HashValue, netDescription)) return false; if (!Detail::write_parameters(stream, *featureTransformer)) return false; diff --git a/src/uci.cpp b/src/uci.cpp index c49b9b78..eb158e72 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,6 +22,7 @@ #include #include +#include "benchmark.h" #include "evaluate.h" #include "movegen.h" #include "position.h" @@ -36,8 +37,6 @@ using namespace std; namespace Stockfish { -vector setup_bench(const Position&, istream&); - namespace { // FEN string for the initial position in standard chess diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 78711c18..b4ce70b4 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -38,13 +38,13 @@ UCI::OptionsMap Options; // Global object namespace UCI { /// 'On change' actions, triggered by an option's value change -void on_clear_hash(const Option&) { Search::clear(); } -void on_hash_size(const Option& o) { TT.resize(size_t(o)); } -void on_logger(const Option& o) { start_logger(o); } -void on_threads(const Option& o) { Threads.set(size_t(o)); } -void on_tb_path(const Option& o) { Tablebases::init(o); } -void on_use_NNUE(const Option& ) { Eval::NNUE::init(); } -void on_eval_file(const Option& ) { Eval::NNUE::init(); } +static void on_clear_hash(const Option&) { Search::clear(); } +static void on_hash_size(const Option& o) { TT.resize(size_t(o)); } +static void on_logger(const Option& o) { start_logger(o); } +static void on_threads(const Option& o) { Threads.set(size_t(o)); } +static void on_tb_path(const Option& o) { Tablebases::init(o); } +static void on_use_NNUE(const Option&) { Eval::NNUE::init(); } +static void on_eval_file(const Option&) { Eval::NNUE::init(); } /// Our case insensitive less() function as required by UCI protocol bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { From 5a88c5bb9b3e5ee431ac85abb8981b1571b68b2d Mon Sep 17 00:00:00 2001 From: Stefano Di Martino Date: Sat, 7 Jan 2023 01:08:30 +0100 Subject: [PATCH 154/678] Modernize code base a little bit Removed sprintf() which generated a warning, because of security reasons. Replace NULL with nullptr Replace typedef with using Do not inherit from std::vector. Use composition instead. optimize mutex-unlocking closes https://github.com/official-stockfish/Stockfish/pull/4327 No functional change --- AUTHORS | 1 + src/material.h | 2 +- src/misc.cpp | 8 ++++---- src/nnue/evaluate_nnue.cpp | 31 ++++++++++++------------------- src/thread.cpp | 38 +++++++++++++++++++------------------- src/thread.h | 14 +++++++++++--- src/thread_win32_osx.h | 6 +++--- 7 files changed, 51 insertions(+), 49 deletions(-) diff --git a/AUTHORS b/AUTHORS index 998399b9..87fed7b8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -192,6 +192,7 @@ Shawn Varghese (xXH4CKST3RXx) Siad Daboul (Topologist) Stefan Geschwentner (locutus2) Stefano Cardanobile (Stefano80) +Stefano Di Martino (StefanoD) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Syine Mineta (MinetaS) diff --git a/src/material.h b/src/material.h index f6db85c4..73c83100 100644 --- a/src/material.h +++ b/src/material.h @@ -28,7 +28,7 @@ namespace Stockfish::Material { /// Material::Entry contains various information about a material configuration. /// It contains a material imbalance evaluation, a function pointer to a special -/// endgame evaluation function (which in most cases is NULL, meaning that the +/// endgame evaluation function (which in most cases is nullptr, meaning that the /// standard evaluation function will be used), and scale factors. /// /// The scale factors are used to scale the evaluation score up or down. For diff --git a/src/misc.cpp b/src/misc.cpp index 1cb515c3..be7abba5 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -413,7 +413,7 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) return nullptr; - if (LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid)) + if (LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)) { TOKEN_PRIVILEGES tp { }; TOKEN_PRIVILEGES prevTp { }; @@ -432,10 +432,10 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize // Round up size to full pages and allocate allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); mem = VirtualAlloc( - NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); + nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); // Privilege no longer needed, restore previous state - AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL); + AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); } } @@ -453,7 +453,7 @@ void* aligned_large_pages_alloc(size_t allocSize) { // Fall back to regular, page aligned, allocation if necessary if (!mem) - mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); return mem; } diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index a06c1978..06281da0 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -26,7 +26,6 @@ #include "../evaluate.h" #include "../position.h" -#include "../misc.h" #include "../uci.h" #include "../types.h" @@ -245,14 +244,15 @@ namespace Stockfish::Eval::NNUE { } - // format_cp_aligned_dot() converts a Value into (centi)pawns and writes it in a buffer, - // always keeping two decimals. The buffer must have capacity for at least 7 chars. - static void format_cp_aligned_dot(Value v, char* buffer) { + // format_cp_aligned_dot() converts a Value into (centi)pawns, always keeping two decimals. + static void format_cp_aligned_dot(Value v, std::stringstream &stream) { + const double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue; - buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - - double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue; - sprintf(&buffer[1], "%6.2f", cp); + stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') + << std::setiosflags(std::ios::fixed) + << std::setw(6) + << std::setprecision(2) + << cp; } @@ -332,17 +332,10 @@ namespace Stockfish::Eval::NNUE { for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { - char buffer[3][8]; - std::memset(buffer, '\0', sizeof(buffer)); - - format_cp_aligned_dot(t.psqt[bucket], buffer[0]); - format_cp_aligned_dot(t.positional[bucket], buffer[1]); - format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]); - - ss << "| " << bucket << " " - << " | " << buffer[0] << " " - << " | " << buffer[1] << " " - << " | " << buffer[2] << " " + ss << "| " << bucket << " "; + ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss); ss << " " + << " | "; format_cp_aligned_dot(t.positional[bucket], ss); ss << " " + << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); ss << " " << " |"; if (bucket == t.correctBucket) ss << " <-- this bucket is used"; diff --git a/src/thread.cpp b/src/thread.cpp index 7e71edf1..ca1a7c85 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -73,9 +73,9 @@ void Thread::clear() { /// Thread::start_searching() wakes up the thread that will start the search void Thread::start_searching() { - - std::lock_guard lk(mutex); + mutex.lock(); searching = true; + mutex.unlock(); // Unlock before notifying saves a few CPU-cycles cv.notify_one(); // Wake up the thread in idle_loop() } @@ -125,20 +125,20 @@ void Thread::idle_loop() { void ThreadPool::set(size_t requested) { - if (size() > 0) // destroy any existing thread(s) + if (threads.size() > 0) // destroy any existing thread(s) { main()->wait_for_search_finished(); - while (size() > 0) - delete back(), pop_back(); + while (threads.size() > 0) + delete threads.back(), threads.pop_back(); } if (requested > 0) // create new thread(s) { - push_back(new MainThread(0)); + threads.push_back(new MainThread(0)); - while (size() < requested) - push_back(new Thread(size())); + while (threads.size() < requested) + threads.push_back(new Thread(threads.size())); clear(); // Reallocate the hash with the new threadpool size @@ -154,7 +154,7 @@ void ThreadPool::set(size_t requested) { void ThreadPool::clear() { - for (Thread* th : *this) + for (Thread* th : threads) th->clear(); main()->callsCnt = 0; @@ -187,7 +187,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, Tablebases::rank_root_moves(pos, rootMoves); // After ownership transfer 'states' becomes empty, so if we stop the search - // and call 'go' again without setting a new position states.get() == NULL. + // and call 'go' again without setting a new position states.get() == nullptr. assert(states.get() || setupStates.get()); if (states.get()) @@ -198,7 +198,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, // be deduced from a fen string, so set() clears them and they are set from // setupStates->back() later. The rootState is per thread, earlier states are shared // since they are read-only. - for (Thread* th : *this) + for (Thread* th : threads) { th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; th->rootDepth = th->completedDepth = 0; @@ -212,12 +212,12 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = front(); + Thread* bestThread = threads.front(); std::map votes; Value minScore = VALUE_NONE; // Find minimum score of all threads - for (Thread* th: *this) + for (Thread* th: threads) minScore = std::min(minScore, th->rootMoves[0].score); // Vote according to score and depth, and select the best thread @@ -225,10 +225,10 @@ Thread* ThreadPool::get_best_thread() const { return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); }; - for (Thread* th : *this) + for (Thread* th : threads) votes[th->rootMoves[0].pv[0]] += thread_value(th); - for (Thread* th : *this) + for (Thread* th : threads) if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) { // Make sure we pick the shortest mate / TB conversion or stave off mate the longest @@ -251,8 +251,8 @@ Thread* ThreadPool::get_best_thread() const { void ThreadPool::start_searching() { - for (Thread* th : *this) - if (th != front()) + for (Thread* th : threads) + if (th != threads.front()) th->start_searching(); } @@ -261,8 +261,8 @@ void ThreadPool::start_searching() { void ThreadPool::wait_for_search_finished() const { - for (Thread* th : *this) - if (th != front()) + for (Thread* th : threads) + if (th != threads.front()) th->wait_for_search_finished(); } diff --git a/src/thread.h b/src/thread.h index 680da209..7566322c 100644 --- a/src/thread.h +++ b/src/thread.h @@ -101,13 +101,13 @@ struct MainThread : public Thread { /// parking and, most importantly, launching a thread. All the access to threads /// is done through this class. -struct ThreadPool : public std::vector { +struct ThreadPool { void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); void clear(); void set(size_t); - MainThread* main() const { return static_cast(front()); } + MainThread* main() const { return static_cast(threads.front()); } uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } Thread* get_best_thread() const; @@ -116,13 +116,21 @@ struct ThreadPool : public std::vector { std::atomic_bool stop, increaseDepth; + auto cbegin() const noexcept { return threads.cbegin(); } + auto begin() noexcept { return threads.begin(); } + auto end() noexcept { return threads.end(); } + auto cend() const noexcept { return threads.cend(); } + auto size() const noexcept { return threads.size(); } + auto empty() const noexcept { return threads.empty(); } + private: StateListPtr setupStates; + std::vector threads; uint64_t accumulate(std::atomic Thread::* member) const { uint64_t sum = 0; - for (Thread* th : *this) + for (Thread* th : threads) sum += (th->*member).load(std::memory_order_relaxed); return sum; } diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 01ff1c77..330a8341 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -41,7 +41,7 @@ void* start_routine(void* ptr) P* p = reinterpret_cast(ptr); (p->first->*(p->second))(); // Call member function pointer delete p; - return NULL; + return nullptr; } class NativeThread { @@ -56,7 +56,7 @@ public: pthread_attr_setstacksize(attr, TH_STACK_SIZE); pthread_create(&thread, attr, start_routine, new P(obj, fun)); } - void join() { pthread_join(thread, NULL); } + void join() { pthread_join(thread, nullptr); } }; } // namespace Stockfish @@ -65,7 +65,7 @@ public: namespace Stockfish { -typedef std::thread NativeThread; +using NativeThread = std::thread; } // namespace Stockfish From e9e7a7b83f78b5c7d29f69083589b449d9b52390 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sat, 7 Jan 2023 14:53:59 +0100 Subject: [PATCH 155/678] Replace some std::string occurrences with std::string_view std::string_view is more lightweight than std::string. Furthermore, std::string_view variables can be declared constexpr. closes https://github.com/official-stockfish/Stockfish/pull/4328 No functional change --- src/misc.cpp | 9 +++++---- src/nnue/evaluate_nnue.cpp | 7 ++++--- src/position.cpp | 3 ++- src/syzygy/tbprobe.cpp | 7 ++++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index be7abba5..b651972b 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -41,12 +41,13 @@ typedef WORD(*fun5_t)(); } #endif +#include #include #include #include #include +#include #include -#include #if defined(__linux__) && !defined(__ANDROID__) #include @@ -68,7 +69,7 @@ namespace Stockfish { namespace { /// Version number or dev. -const string version = "dev"; +constexpr string_view version = "dev"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -151,13 +152,13 @@ string engine_info(bool to_uci) { stringstream ss; ss << "Stockfish " << version << setfill('0'); - if (version == "dev") + if constexpr (version == "dev") { ss << "-"; #ifdef GIT_DATE ss << GIT_DATE; #else - const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); string month, day, year; stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 06281da0..f132de71 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -18,11 +18,12 @@ // Code for calculating NNUE evaluation function +#include +#include #include #include #include -#include -#include +#include #include "../evaluate.h" #include "../position.h" @@ -210,7 +211,7 @@ namespace Stockfish::Eval::NNUE { return t; } - static const std::string PieceToChar(" PNBRQK pnbrqk"); + constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); // format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. diff --git a/src/position.cpp b/src/position.cpp index e82425af..cfd98f68 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -22,6 +22,7 @@ #include // For std::memset, std::memcmp #include #include +#include #include "bitboard.h" #include "misc.h" @@ -46,7 +47,7 @@ namespace Zobrist { namespace { -const string PieceToChar(" PNBRQK pnbrqk"); +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index cc5e2852..bbfd819d 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -24,9 +24,10 @@ #include #include #include -#include -#include #include +#include +#include +#include #include "../bitboard.h" #include "../movegen.h" @@ -70,7 +71,7 @@ enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, Singl inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } -const std::string PieceToChar = " PNBRQK pnbrqk"; +constexpr std::string_view PieceToChar = " PNBRQK pnbrqk"; int MapPawns[SQUARE_NB]; int MapB1H1H7[SQUARE_NB]; From 4f4e652ecaf8d42a9bd1092f72c3704435ddba12 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Fri, 6 Jan 2023 18:13:21 +0100 Subject: [PATCH 156/678] Avoid unnecessary string copies closes https://github.com/official-stockfish/Stockfish/pull/4326 also fixes typo, closes https://github.com/official-stockfish/Stockfish/pull/4332 No functional change --- src/evaluate.cpp | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/uci.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3aae9f1c..6db977a4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -89,7 +89,7 @@ namespace Eval { vector dirs = { "" , "" , CommandLine::binaryDirectory }; #endif - for (string directory : dirs) + for (const string& directory : dirs) if (currentEvalFileName != eval_file) { if (directory != "") diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index df539b39..3fbb243c 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -106,7 +106,7 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( - // realy should be /127 but we need to make it fast + // really should be /127 but we need to make it fast // needs to be accounted for in the trainer std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128))); } diff --git a/src/uci.cpp b/src/uci.cpp index eb158e72..30c1fa0c 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -160,7 +160,7 @@ namespace { uint64_t num, nodes = 0, cnt = 1; vector list = setup_bench(pos, args); - num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + num = count_if(list.begin(), list.end(), [](const string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); TimePoint elapsed = now(); From 3d2381d76d7bf9686ef0e0671f60c3b885a7058a Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 11 Jan 2023 10:07:56 -0500 Subject: [PATCH 157/678] Update default net to nn-1e7ca356472e.nnue Created by retraining the master net on a dataset composed of: * The Leela-dfrc_n5000.binpack dataset filtered with depth6 multipv2 search to remove positions with only one good move, in addition to removing positions where either of the two best moves are captures * The same Leela T80 oct+nov 2022 training data used in recent best datasets * Additional Leela training data from T60 nov+dec 2021 and T79 apr+may 2022 Trained with end lambda 0.7 and started with max epoch 800. All positions with ply <= 28 were skipped: ``` python easy_train.py \ --experiment-name leela95-dfrc96-mpv-eval-fonly-T80octnov-T79aprmayT60novdec-12tb7p-sk28-lambda7 \ --training-dataset /data/leela95-dfrc96-mpv-eval-fonly-T80octnov-T79aprmayT60novdec-12tb7p.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-skip-ply-lteq-28 \ --start-from-engine-test-net True \ --gpus "0," \ --start-lambda 1.0 \ --end-lambda 0.7 \ --gamma 0.995 \ --lr 4.375e-4 \ --tui False \ --seed $RANDOM \ --max_epoch 800 ``` Around epoch 780, training was manually paused and max epoch increased to 920 before resuming. During depth6 multipv2 data filtering, positions were considered to have only one good move if the score of the best move was significantly better than the 2nd best move in a way that changes the outcome of the game: * the best move leads to a significant advantage while the 2nd best move equalizes or loses * the best move is about equal while the 2nd best move loses The modified stockfish branch and exact score thresholds used for filtering are at: https://github.com/linrock/Stockfish/tree/tools-filter-multipv2-eval-diff/src/filter About 95% of the Leela portion and 96% of the DFRC portion of the Leela-dfrc_n5000.binpack dataset was filtered. Unfiltered parts of the dataset were left out. The additional Leela training data from T60 nov+dec 2021 and T79 apr+may 2022 was WDL-rescored with about 12TB of syzygy 7-piece tablebases where the material difference is less than around 6 pawns. Best moves were exported to .plain data files during data conversion with the lc0 rescorer. The exact training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move experiment_leela95-dfrc96-mpv-eval-fonly-T80octnov-T79aprmayT60novdec-12tb7p-sk28-lambda7 run_0/nn-epoch899.nnue : 3.8 +/- 1.6 Passed STC https://tests.stockfishchess.org/tests/view/63bed1f540aa064159b9c89b LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 103344 W: 27392 L: 26991 D: 48961 Ptnml(0-2): 333, 11223, 28099, 11744, 273 Passed LTC https://tests.stockfishchess.org/tests/view/63c010415705810de2deb3ec LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 21712 W: 5891 L: 5619 D: 10202 Ptnml(0-2): 12, 2022, 6511, 2304, 7 closes https://github.com/official-stockfish/Stockfish/pull/4338 bench 4106793 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 0a00faa7..643e8540 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-a3dc078bafc7.nnue" + #define EvalFileDefaultName "nn-1e7ca356472e.nnue" namespace NNUE { From da5bcec481dc22ad2de2e2c8e5c6e8304a373445 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sat, 14 Jan 2023 16:09:31 +0100 Subject: [PATCH 158/678] Fix asm modifiers in add_dpbusd_epi32x2 implementations The accumulator should be an earlyclobber because it is written before all input operands are read. Otherwise, the asm code computes a wrong result if the accumulator shares a register with one of the other input operands (which happens if we pass in the same expression for the accumulator and the operand). Closes https://github.com/official-stockfish/Stockfish/pull/4339 No functional change --- src/nnue/layers/simd.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index aeab39c4..231f7891 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -153,7 +153,7 @@ namespace Stockfish::Simd { asm( "vpdpbusd %[b0], %[a0], %[acc]\n\t" "vpdpbusd %[b1], %[a1], %[acc]\n\t" - : [acc]"+v"(acc) + : [acc]"+&v"(acc) : [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1) ); # else @@ -249,7 +249,7 @@ namespace Stockfish::Simd { asm( VNNI_PREFIX "vpdpbusd %[b0], %[a0], %[acc]\n\t" VNNI_PREFIX "vpdpbusd %[b1], %[a1], %[acc]\n\t" - : [acc]"+v"(acc) + : [acc]"+&v"(acc) : [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1) ); # else From a08b8d4e9711c20acedbfe17d618c3c384b339ec Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 15 Jan 2023 00:23:24 +0100 Subject: [PATCH 159/678] Update UCI_Elo parameterization The old parameterization (https://github.com/official-stockfish/Stockfish/pull/2225/files) has now become quite inaccurate. This updates the formula based on updated results with master. The formula is based on a fit of the Elo results for games played between master at various skill levels, and various versions of the Stash engine, which have been ranked at CCRL. ``` # PLAYER : RATING ERROR POINTS PLAYED (%) 1 master-skill-19 : 3191.1 40.4 940.0 1707 55 2 master-skill-18 : 3170.3 39.3 1343.0 2519 53 3 master-skill-17 : 3141.3 37.8 2282.0 4422 52 4 master-skill-16 : 3111.2 37.1 2773.0 5423 51 5 master-skill-15 : 3069.5 37.2 2728.5 5386 51 6 master-skill-14 : 3024.8 36.1 2702.0 5339 51 7 master-skill-13 : 2972.9 35.4 2645.5 5263 50 8 master-skill-12 : 2923.1 35.0 2653.5 5165 51 9 master-skill-11 : 2855.5 33.6 2524.0 5081 50 10 master-skill-10 : 2788.3 32.0 2724.5 5511 49 11 stash-bot-v25.0 : 2744.0 31.5 1952.5 3840 51 12 master-skill-9 : 2702.8 30.5 2670.0 5018 53 13 master-skill-8 : 2596.2 28.5 2669.5 4975 54 14 stash-bot-v21.0 : 2561.2 30.0 1338.0 3366 40 15 master-skill-7 : 2499.5 28.5 1934.0 4178 46 16 stash-bot-v20.0 : 2452.6 27.7 1606.5 3378 48 17 stash-bot-v19.0 : 2425.3 26.7 1787.0 3365 53 18 master-skill-6 : 2363.2 26.4 2510.5 4379 57 19 stash-bot-v17.0 : 2280.7 25.4 2209.0 4378 50 20 master-skill-5 : 2203.7 25.3 2859.5 5422 53 21 stash-bot-v15.3 : 2200.0 25.4 1757.0 4383 40 22 stash-bot-v14 : 2145.9 25.5 2890.0 5167 56 23 stash-bot-v13 : 2042.7 25.8 2263.5 4363 52 24 stash-bot-v12 : 1963.4 25.8 1769.5 4210 42 25 master-skill-4 : 1922.9 25.9 2690.0 5399 50 26 stash-bot-v11 : 1873.0 26.3 2203.5 4335 51 27 stash-bot-v10 : 1783.8 27.8 2568.5 4301 60 28 master-skill-3 : 1742.3 27.8 1909.5 4439 43 29 master-skill-2 : 1608.4 29.4 2064.5 4389 47 30 stash-bot-v9 : 1582.6 30.2 2130.0 4230 50 31 master-skill-1 : 1467.6 31.3 2015.5 4244 47 32 stash-bot-v8 : 1452.8 31.5 1953.5 3780 52 33 master-skill-0 : 1320.1 32.9 651.5 2083 31 ``` Skill 0 .. 19, now covers CCRL Blitz Elo from 1320 to 3190, approximately. Indeed, the Elo of stash in this analysis is only to within +- 100 Elo of CCRL, probably because it depends quite a bit on the opponent pool. To obtain a skill level for a given Elo number, the above data is fit as a 3rd degree polynomial Skill(Elo). A quick test confirms the correspondence to the above table: ``` Score of master-elo-2721 vs stash-bot-v21.0: 51 - 16 - 19 [0.703] 86 Elo difference: 150.1 +/- 70.2, LOS: 100.0 %, DrawRatio: 22.1 % ``` closes https://github.com/official-stockfish/Stockfish/pull/4341 No functional change. --- README.md | 2 +- src/search.cpp | 5 ++++- src/ucioption.cpp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4cd5968e..ca90d5d4 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ change them via a chess GUI. This is a list of available UCI options in Stockfis * #### UCI_Elo If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo. - This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4. + This Elo rating has been calibrated at a time control of 120s+1.0s and anchored to +- 100 Elo to CCRL Blitz. * #### Skill Level Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength). diff --git a/src/search.cpp b/src/search.cpp index f5eeb23b..346fd6c3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -96,7 +96,10 @@ namespace { struct Skill { Skill(int skill_level, int uci_elo) { if (uci_elo) - level = std::clamp(std::pow((uci_elo - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0); + { + double e = double(uci_elo - 1320) / (3190 - 1320); + level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0); + } else level = double(skill_level); } diff --git a/src/ucioption.cpp b/src/ucioption.cpp index b4ce70b4..39933ea5 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -73,7 +73,7 @@ void init(OptionsMap& o) { o["UCI_Chess960"] << Option(false); o["UCI_AnalyseMode"] << Option(false); o["UCI_LimitStrength"] << Option(false); - o["UCI_Elo"] << Option(1350, 1350, 2850); + o["UCI_Elo"] << Option(1320, 1320, 3190); o["UCI_ShowWDL"] << Option(false); o["SyzygyPath"] << Option("", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); From 734315ff3099564c80737ad2be8121008870d28b Mon Sep 17 00:00:00 2001 From: Stephen Touset Date: Mon, 16 Jan 2023 14:25:47 -0800 Subject: [PATCH 160/678] Remove precomputed SquareBB Bit-shifting is a single instruction, and should be faster than an array lookup on supported architectures. Besides (ever so slightly) speeding up the conversion of a square into a bitboard, we may see minor general performance improvements due to preserving more of the CPU's existing cache. passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 47280 W: 12469 L: 12271 D: 22540 Ptnml(0-2): 128, 4893, 13402, 5087, 130 https://tests.stockfishchess.org/tests/view/63c5cfe618c20f4929c5fe46 Small speedup locally: ``` Result of 20 runs ================== base (./stockfish.master ) = 1752135 +/- 10943 test (./stockfish.patch ) = 1763939 +/- 10818 diff = +11804 +/- 4731 speedup = +0.0067 P(speedup > 0) = 1.0000 CPU: 16 x AMD Ryzen 9 3950X 16-Core Processor ``` Closes https://github.com/official-stockfish/Stockfish/pull/4343 Bench: 4106793 --- src/bitboard.cpp | 4 ---- src/bitboard.h | 3 +-- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 0ed13fd0..fd5c3c22 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -27,7 +27,6 @@ namespace Stockfish { uint8_t PopCnt16[1 << 16]; uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; -Bitboard SquareBB[SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; @@ -82,9 +81,6 @@ void Bitboards::init() { for (unsigned i = 0; i < (1 << 16); ++i) PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); - for (Square s = SQ_A1; s <= SQ_H8; ++s) - SquareBB[s] = (1ULL << s); - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); diff --git a/src/bitboard.h b/src/bitboard.h index d4485fcb..42fd0e97 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -74,7 +74,6 @@ constexpr Bitboard KingFlank[FILE_NB] = { extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; -extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; @@ -108,7 +107,7 @@ extern Magic BishopMagics[SQUARE_NB]; inline Bitboard square_bb(Square s) { assert(is_ok(s)); - return SquareBB[s]; + return (1ULL << s); } From a2038c1a011552c3d56d3b318780f7f5eadaf05d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 17 Jan 2023 21:30:50 -0700 Subject: [PATCH 161/678] apply if constexpr to additional instances as a form of documentation, and a hint to the compiler. closes https://github.com/official-stockfish/Stockfish/pull/4345 No functional change --- AUTHORS | 1 + src/evaluate.cpp | 10 +++++----- src/movegen.cpp | 16 ++++++++-------- src/movepick.cpp | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/AUTHORS b/AUTHORS index 87fed7b8..42ba5930 100644 --- a/AUTHORS +++ b/AUTHORS @@ -101,6 +101,7 @@ Jerry Donald Watson (jerrydonaldwatson) jjoshua2 Jonathan Calovski (Mysseno) Jonathan Buladas Dumale (SFisGOD) +Jonathan McDermid (jonathanmcdermid) Joost VandeVondele (vondele) Jörg Oster (joergoster) Joseph Ellis (jhellis3) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6db977a4..8683182c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -388,10 +388,10 @@ namespace { template template Score Evaluation::pieces() { - constexpr Color Them = ~Us; - constexpr Direction Down = -pawn_push(Us); - constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB - : Rank5BB | Rank4BB | Rank3BB); + constexpr Color Them = ~Us; + [[maybe_unused]] constexpr Direction Down = -pawn_push(Us); + [[maybe_unused]] constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB + : Rank5BB | Rank4BB | Rank3BB); Bitboard b1 = pos.pieces(Us, Pt); Bitboard b, bb; Score score = SCORE_ZERO; @@ -430,7 +430,7 @@ namespace { int mob = popcount(b & mobilityArea[Us]); mobility[Us] += MobilityBonus[Pt - 2][mob]; - if (Pt == BISHOP || Pt == KNIGHT) + if constexpr (Pt == BISHOP || Pt == KNIGHT) { // Bonus if the piece is on an outpost square or can reach one // Bonus for knights (UncontestedOutpost) if few relevant targets diff --git a/src/movegen.cpp b/src/movegen.cpp index a960f863..47154164 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -26,12 +26,12 @@ namespace Stockfish { namespace { template - ExtMove* make_promotions(ExtMove* moveList, Square to) { + ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { - if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) *moveList++ = make(to - D, to, QUEEN); - if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) + if constexpr (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) { *moveList++ = make(to - D, to, ROOK); *moveList++ = make(to - D, to, BISHOP); @@ -60,18 +60,18 @@ namespace { Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; // Single and double pawn pushes, no promotions - if (Type != CAPTURES) + if constexpr (Type != CAPTURES) { Bitboard b1 = shift(pawnsNotOn7) & emptySquares; Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; - if (Type == EVASIONS) // Consider only blocking squares + if constexpr (Type == EVASIONS) // Consider only blocking squares { b1 &= target; b2 &= target; } - if (Type == QUIET_CHECKS) + if constexpr (Type == QUIET_CHECKS) { // To make a quiet check, you either make a direct check by pushing a pawn // or push a blocker pawn that is not on the same file as the enemy king. @@ -102,7 +102,7 @@ namespace { Bitboard b2 = shift(pawnsOn7) & enemies; Bitboard b3 = shift(pawnsOn7) & emptySquares; - if (Type == EVASIONS) + if constexpr (Type == EVASIONS) b3 &= target; while (b1) @@ -116,7 +116,7 @@ namespace { } // Standard and en passant captures - if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { Bitboard b1 = shift(pawnsNotOn7) & enemies; Bitboard b2 = shift(pawnsNotOn7) & enemies; diff --git a/src/movepick.cpp b/src/movepick.cpp index dbe67357..65155a73 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -158,7 +158,7 @@ Move MovePicker::select(Pred filter) { while (cur < endMoves) { - if (T == Best) + if constexpr (T == Best) std::swap(*cur, *std::max_element(cur, endMoves)); if (*cur != ttMove && filter()) From 596a528c6a9ace6fb1a8407c86d972d96653418d Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 20 Jan 2023 10:41:46 -0500 Subject: [PATCH 162/678] Update default net to nn-bc24c101ada0.nnue Created by retraining the master net with Leela T78 data from Aug+Sep 2022 added to the previous best dataset. Trained with end lambda 0.7 and started with max epoch 800. All positions with ply <= 28 were skipped: ``` python easy_train.py \ --experiment-name leela95-dfrc96-filt-only-T80octnov-T60novdecT78augsepT79aprmay-12tb7p-sk28-lambda7 \ --training-dataset /data/leela95-dfrc96-filt-only-T80octnov-T60novdecT78augsepT79aprmay-12tb7p.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-skip-ply-lteq-28 \ --start-from-engine-test-net True \ --gpus "0," \ --start-lambda 1.0 \ --end-lambda 0.7 \ --gamma 0.995 \ --lr 4.375e-4 \ --tui False \ --seed $RANDOM \ --max_epoch 800 ``` Around epoch 750, training was manually paused and max epoch increased to 950 before resuming. The additional Leela training data from T78 was prepared in the same way as the previous best dataset. The exact training data used can be found at: https://robotmoon.com/nnue-training-data/ While the local elo ratings during this experiment were much lower than in recent master nets, several later epochs had a consistent elo above zero, and this was hypothesized to represent potential strength at slower time controls. Local elo at 25k nodes per move leela95-dfrc96-filt-only-T80octnov-T60novdecT78augsepT79aprmay-12tb7p-sk28-lambda7 nn-epoch819.nnue : 0.4 +/- 1.1 (nn-bc24c101ada0.nnue) nn-epoch799.nnue : 0.3 +/- 1.2 nn-epoch759.nnue : 0.3 +/- 1.1 nn-epoch839.nnue : 0.2 +/- 1.4 Passed STC https://tests.stockfishchess.org/tests/view/63cabf6f0eefe8694a0c6013 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 41608 W: 11161 L: 10848 D: 19599 Ptnml(0-2): 116, 4496, 11281, 4781, 130 Passed LTC https://tests.stockfishchess.org/tests/view/63cb1856344bb01c191af263 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 76760 W: 20517 L: 20137 D: 36106 Ptnml(0-2): 34, 7435, 23070, 7799, 42 closes https://github.com/official-stockfish/Stockfish/pull/4351 bench 3941848 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 643e8540..f7ecaac9 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1e7ca356472e.nnue" + #define EvalFileDefaultName "nn-bc24c101ada0.nnue" namespace NNUE { From 3dd0a7a7cd9daf306cffc795c343459de6d6f54b Mon Sep 17 00:00:00 2001 From: Dubslow Date: Fri, 20 Jan 2023 03:02:25 -0600 Subject: [PATCH 163/678] `stat_bonus`: replace quadratic with nearly identical line passed stc: https://tests.stockfishchess.org/tests/view/63ca58c90eefe8694a0c4eac LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 287960 W: 76146 L: 76201 D: 135613 Ptnml(0-2): 947, 31890, 78307, 31943, 893 passed ltc: https://tests.stockfishchess.org/tests/view/63cc8a51344bb01c191b30f0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 73784 W: 19559 L: 19402 D: 34823 Ptnml(0-2): 33, 7171, 22327, 7328, 33 closes https://github.com/official-stockfish/Stockfish/pull/4352 bench 3990490 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 346fd6c3..b9ba3811 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min((11 * d + 284) * d - 363 , 1650); + return std::min(350 * d - 400, 1650); } // Add a small random component to draw evaluations to avoid 3-fold blindness From d3860f8d5efa4d9df726fa605b24689d6a829c7e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 25 Jan 2023 08:12:40 +0300 Subject: [PATCH 164/678] Rebalance usage of history heuristics in pruning This patch has multiple effects: * history heuristics sum in futility pruning now can't exceed some negative value so futility pruning for moves with negative histories should become slightly less aggressive; * history heuristics are now used in SEE pruning for quiet moves; Passed STC: https://tests.stockfishchess.org/tests/view/63cde339c93e8828d0f02e3a LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 88424 W: 23681 L: 23303 D: 41440 Ptnml(0-2): 258, 9559, 24219, 9899, 277 Passed LTC: https://tests.stockfishchess.org/tests/view/63ce9009c93e8828d0f04e4f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 79536 W: 21223 L: 20843 D: 37470 Ptnml(0-2): 22, 7599, 24146, 7979, 22 closes https://github.com/official-stockfish/Stockfish/pull/4355 Bench: 4208265 --- src/search.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b9ba3811..4b2deadd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1026,12 +1026,17 @@ moves_loop: // When in check, search starts here history += 2 * thisThread->mainHistory[us][from_to(move)]; + lmrDepth += history / 7208; + lmrDepth = std::max(lmrDepth, -2); + // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck && lmrDepth < 13 - && ss->staticEval + 103 + 136 * lmrDepth + history / 53 <= alpha) + && ss->staticEval + 103 + 136 * lmrDepth <= alpha) continue; + lmrDepth = std::max(lmrDepth, 0); + // Prune moves with negative SEE (~4 Elo) if (!pos.see_ge(move, Value(-25 * lmrDepth * lmrDepth - 16 * lmrDepth))) continue; From def296670dbde6cfad446a735f37f25cfe9df6a2 Mon Sep 17 00:00:00 2001 From: disservin Date: Mon, 23 Jan 2023 19:32:26 +0100 Subject: [PATCH 165/678] Fixed UCI TB win values This patch results in search values for a TB win/loss to be reported in a way that does not change with normalization, i.e. will be consistent over time. A value of 200.00 pawns is now reported upon entering a TB won position. Values smaller than 200.00 relate to the distance in plies from the root to the probed position position, with 1 cp being 1 ply distance. closes https://github.com/official-stockfish/Stockfish/pull/4353 No functional change --- src/uci.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/uci.cpp b/src/uci.cpp index 30c1fa0c..2afd2de7 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -314,8 +314,13 @@ string UCI::value(Value v) { stringstream ss; - if (abs(v) < VALUE_MATE_IN_MAX_PLY) + if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) ss << "cp " << v * 100 / NormalizeToPawnValue; + else if (abs(v) < VALUE_MATE_IN_MAX_PLY) + { + const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply + ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); + } else ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; From 2167942b6eab54bafb6aed96d7360c74fec95358 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Wed, 25 Jan 2023 22:25:18 +0100 Subject: [PATCH 166/678] Simplify functions to read/write network parameters closes https://github.com/official-stockfish/Stockfish/pull/4358 No functional change --- src/nnue/nnue_architecture.h | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index c43a23c3..508f3aae 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -72,22 +72,20 @@ struct Network // Read network parameters bool read_parameters(std::istream& stream) { - if (!fc_0.read_parameters(stream)) return false; - if (!ac_0.read_parameters(stream)) return false; - if (!fc_1.read_parameters(stream)) return false; - if (!ac_1.read_parameters(stream)) return false; - if (!fc_2.read_parameters(stream)) return false; - return true; + return fc_0.read_parameters(stream) + && ac_0.read_parameters(stream) + && fc_1.read_parameters(stream) + && ac_1.read_parameters(stream) + && fc_2.read_parameters(stream); } - // Read network parameters + // Write network parameters bool write_parameters(std::ostream& stream) const { - if (!fc_0.write_parameters(stream)) return false; - if (!ac_0.write_parameters(stream)) return false; - if (!fc_1.write_parameters(stream)) return false; - if (!ac_1.write_parameters(stream)) return false; - if (!fc_2.write_parameters(stream)) return false; - return true; + return fc_0.write_parameters(stream) + && ac_0.write_parameters(stream) + && fc_1.write_parameters(stream) + && ac_1.write_parameters(stream) + && fc_2.write_parameters(stream); } std::int32_t propagate(const TransformedFeatureType* transformedFeatures) From 2f67409506e65a47f038055de834462b4a707ccd Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Wed, 25 Jan 2023 22:04:02 +0100 Subject: [PATCH 167/678] Remove redundant const qualifiers The const qualifiers are already implied by the constexpr qualifiers. closes https://github.com/official-stockfish/Stockfish/pull/4359 No functional change --- src/nnue/layers/affine_transform.h | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 710ab8a7..363b4916 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -179,33 +179,33 @@ namespace Stockfish::Eval::NNUE::Layers { static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization should not have been chosen."); #if defined (USE_AVX512) - static constexpr const IndexType InputSimdWidth = 64; - static constexpr const IndexType MaxNumOutputRegs = 16; + static constexpr IndexType InputSimdWidth = 64; + static constexpr IndexType MaxNumOutputRegs = 16; #elif defined (USE_AVX2) - static constexpr const IndexType InputSimdWidth = 32; - static constexpr const IndexType MaxNumOutputRegs = 8; + static constexpr IndexType InputSimdWidth = 32; + static constexpr IndexType MaxNumOutputRegs = 8; #elif defined (USE_SSSE3) - static constexpr const IndexType InputSimdWidth = 16; - static constexpr const IndexType MaxNumOutputRegs = 8; + static constexpr IndexType InputSimdWidth = 16; + static constexpr IndexType MaxNumOutputRegs = 8; #elif defined (USE_NEON) - static constexpr const IndexType InputSimdWidth = 8; - static constexpr const IndexType MaxNumOutputRegs = 8; + static constexpr IndexType InputSimdWidth = 8; + static constexpr IndexType MaxNumOutputRegs = 8; #else // The fallback implementation will not have permuted weights. // We define these to avoid a lot of ifdefs later. - static constexpr const IndexType InputSimdWidth = 1; - static constexpr const IndexType MaxNumOutputRegs = 1; + static constexpr IndexType InputSimdWidth = 1; + static constexpr IndexType MaxNumOutputRegs = 1; #endif // A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs]. // A small block is a region of size [InputSimdWidth, 1] - static constexpr const IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions); - static constexpr const IndexType SmallBlockSize = InputSimdWidth; - static constexpr const IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions; - static constexpr const IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize; - static constexpr const IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize; - static constexpr const IndexType NumBigBlocks = OutputDimensions / NumOutputRegs; + static constexpr IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions); + static constexpr IndexType SmallBlockSize = InputSimdWidth; + static constexpr IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions; + static constexpr IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize; + static constexpr IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize; + static constexpr IndexType NumBigBlocks = OutputDimensions / NumOutputRegs; static_assert(OutputDimensions % NumOutputRegs == 0); @@ -396,8 +396,8 @@ namespace Stockfish::Eval::NNUE::Layers { static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization should not have been chosen."); #if defined (USE_SSSE3) - static constexpr const IndexType OutputSimdWidth = SimdWidth / 4; - static constexpr const IndexType InputSimdWidth = SimdWidth; + static constexpr IndexType OutputSimdWidth = SimdWidth / 4; + static constexpr IndexType InputSimdWidth = SimdWidth; #endif // Hash value embedded in the evaluation file From d4d1cec29631f041adeec98adc5893b5c6a54969 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sun, 15 Jan 2023 04:08:33 -0600 Subject: [PATCH 168/678] Remove `previousDepth` in favor of `completedDepth + 2` Beyond the simplification, this could be considered a bugfix from a certain point of view. However, the effect is very subtle and essentially impossible for users to notice. 5372f81cc8 added about 2 Elo at LTC, but only for second and later `go` commands; now, with this patch, the first `go` command will also benefit from that gain. Games under time controls are unaffected (as per the tests). STC: https://tests.stockfishchess.org/tests/view/63c3d291330c0d3d051d48a8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 473792 W: 124858 L: 125104 D: 223830 Ptnml(0-2): 1338, 49653, 135063, 49601, 1241 LTC: https://tests.stockfishchess.org/tests/view/63c8cd56a83c702aac083bc9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 290728 W: 76926 L: 76978 D: 136824 Ptnml(0-2): 106, 27987, 89221, 27953, 97 closes https://github.com/official-stockfish/Stockfish/pull/4361 bench 4208265 --- src/search.cpp | 5 +---- src/thread.cpp | 1 - src/thread.h | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4b2deadd..c748f1ff 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -242,9 +242,6 @@ void MainThread::search() { bestPreviousScore = bestThread->rootMoves[0].score; bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; - for (Thread* th : Threads) - th->previousDepth = bestThread->completedDepth; - // Send again PV info if we have a new best thread if (bestThread != this) sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; @@ -1053,7 +1050,7 @@ moves_loop: // When in check, search starts here // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. if ( !rootNode - && depth >= 4 - (thisThread->previousDepth > 24) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ diff --git a/src/thread.cpp b/src/thread.cpp index ca1a7c85..c680393e 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -60,7 +60,6 @@ void Thread::clear() { counterMoves.fill(MOVE_NONE); mainHistory.fill(0); captureHistory.fill(0); - previousDepth = 0; for (bool inCheck : { false, true }) for (StatsType c : { NoCaptures, Captures }) diff --git a/src/thread.h b/src/thread.h index 7566322c..46cdb11c 100644 --- a/src/thread.h +++ b/src/thread.h @@ -69,7 +69,7 @@ public: Position rootPos; StateInfo rootState; Search::RootMoves rootMoves; - Depth rootDepth, completedDepth, previousDepth; + Depth rootDepth, completedDepth; Value rootDelta; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; From e4e61cd9cc953e6bc17070da84639d53b7514709 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Thu, 19 Jan 2023 09:49:42 +0900 Subject: [PATCH 169/678] Remove maxNextDepth This patch allows full PV search to have double extensions as well when extension == 1 && doDeeperSearch && doEvenDeeperSearch && !doShallowerSearch is true, which is extremely rare to occur. Passed non-regression STC (master 3d2381d): LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 370824 W: 97835 L: 97974 D: 175015 Ptnml(0-2): 1073, 38814, 105731, 38767, 1027 https://tests.stockfishchess.org/tests/view/63c89416a83c702aac08314c Passed non-regression LTC (master 3d2381d): LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 306048 W: 81173 L: 81237 D: 143638 Ptnml(0-2): 117, 27977, 96901, 27911, 118 https://tests.stockfishchess.org/tests/view/63cc4e84344bb01c191b2658 Bench: 4208265 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c748f1ff..41096c9c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -522,7 +522,6 @@ namespace { constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; - const Depth maxNextDepth = rootNode ? depth : depth + 1; // Check if we have an upcoming move which draws by repetition, or // if the opponent had an alternative move earlier to this position. @@ -1235,8 +1234,7 @@ moves_loop: // When in check, search starts here (ss+1)->pv = pv; (ss+1)->pv[0] = MOVE_NONE; - value = -search(pos, ss+1, -beta, -alpha, - std::min(maxNextDepth, newDepth), false); + value = -search(pos, ss+1, -beta, -alpha, newDepth, false); } // Step 19. Undo move From 0827e00f10709a4475ece44f0588277fc8cdcd9d Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:44:53 +0800 Subject: [PATCH 170/678] Decrease reduction for killer moves with good history If move is a main killer and we have a good history, decrease reduction. STC: https://tests.stockfishchess.org/tests/view/63d38b37721fe2bff693069a LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 46688 W: 12542 L: 12222 D: 21924 Ptnml(0-2): 126, 5013, 12769, 5287, 149 LTC: https://tests.stockfishchess.org/tests/view/63d471e2bde6e5f3cb4be5d3 LLR: 2.93 (-2.94,2.94) <0.50,2.50> Total: 130976 W: 35033 L: 34555 D: 61388 Ptnml(0-2): 38, 12551, 39833, 13027, 39 closes https://github.com/official-stockfish/Stockfish/pull/4369 Bench: 4069938 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 41096c9c..7d618a65 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1164,6 +1164,11 @@ moves_loop: // When in check, search starts here if ((ss+1)->cutoffCnt > 3) r++; + // Decrease reduction if move is a killer and we have a good history + if (move == ss->killers[0] + && (*contHist[0])[movedPiece][to_sq(move)] >= 3600) + r--; + ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] From 7fc0f589d601cb013f995ff44a49b5d2ae6bb253 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Thu, 2 Feb 2023 00:08:23 +0900 Subject: [PATCH 171/678] Add -Wconditional-uninitialized when using Clang Add -Wconditional-uninitialized as it is not controlled by -Wall. closes https://github.com/official-stockfish/Stockfish/pull/4371 No functional change --- src/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 30c1be5e..775c72c3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -426,7 +426,8 @@ ifeq ($(COMP),clang) CXX=x86_64-w64-mingw32-clang++ endif - CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \ + -Wconditional-uninitialized ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),) ifeq ($(target_windows),) From 3589bd008a3470336b3587e3a292a4cd6b02bf6b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 2 Feb 2023 08:41:32 +0100 Subject: [PATCH 172/678] Update WLD model update the WLD model with about 400M positions extracted from recent LTC games after the net updates. This ensures that the 50% win rate is again at 1.0 eval. closes https://github.com/official-stockfish/Stockfish/pull/4373 No functional change. --- src/uci.cpp | 4 ++-- src/uci.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 2afd2de7..3883b3d3 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -206,8 +206,8 @@ namespace { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = { -0.58270499, 2.68512549, 15.24638015, 344.49745382}; - constexpr double bs[] = { -2.65734562, 15.96509799, -20.69040836, 73.61029937 }; + constexpr double as[] = { 0.33677609, -4.30175627, 33.08810557, 365.60223431}; + constexpr double bs[] = { -2.50471102, 14.23235405, -14.33066859, 71.42705250 }; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); diff --git a/src/uci.h b/src/uci.h index bd3df323..5d8ccf1a 100644 --- a/src/uci.h +++ b/src/uci.h @@ -35,7 +35,7 @@ namespace UCI { // the win_rate_model() such that Stockfish outputs an advantage of // "100 centipawns" for a position if the engine has a 50% probability to win // from this position in selfplay at fishtest LTC time control. -const int NormalizeToPawnValue = 361; +const int NormalizeToPawnValue = 394; class Option; From da8513f0eae439f526ab0a905a3433bdb44bcf66 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 2 Feb 2023 13:05:54 +0300 Subject: [PATCH 173/678] Do less SEE pruning in qsearch Current master prunes all moves with negative SEE values in qsearch. This patch sets constant negative threshold thus allowing some moves with negative SEE values to be searched. Value of threshold is completely arbitrary and can be tweaked - also it as function of depth can be tried. Original idea by author of Alexandria engine. Passed STC https://tests.stockfishchess.org/tests/view/63d79a59a67dd929a5564976 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 34864 W: 9392 L: 9086 D: 16386 Ptnml(0-2): 113, 3742, 9429, 4022, 126 Passed LTC https://tests.stockfishchess.org/tests/view/63d8074aa67dd929a5565bc2 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 91616 W: 24532 L: 24126 D: 42958 Ptnml(0-2): 32, 8840, 27662, 9238, 36 closes https://github.com/official-stockfish/Stockfish/pull/4376 Bench: 4010877 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7d618a65..b9ca3961 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1556,9 +1556,9 @@ moves_loop: // When in check, search starts here } } - // Do not search moves with negative SEE values (~5 Elo) + // Do not search moves with bad enough SEE values (~5 Elo) if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && !pos.see_ge(move)) + && !pos.see_ge(move, Value(-108))) continue; // Speculative prefetch as early as possible From 5a30b087c3a3c4435e680e6c61082d5432fa0ace Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 25 Jan 2023 07:32:02 +0900 Subject: [PATCH 174/678] Expand statistics tools for engine development This patch adds more debugging slots up to 32 per type and provide tools to calculate standard deviation and Pearson's correlation coefficient. However, due to slot being 0 at default, dbg_hit_on(c, b) has to be removed. Initial idea from snicolet/Stockfish@d8ab604 closes https://github.com/official-stockfish/Stockfish/pull/4354 No functional change --- src/misc.cpp | 94 ++++++++++++++++++++++++++++++++++++++++++++++------ src/misc.h | 7 ++-- 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index b651972b..7d848d32 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -41,6 +41,7 @@ typedef WORD(*fun5_t)(); } #endif +#include #include #include #include @@ -299,21 +300,94 @@ std::string compiler_info() { /// Debug functions used mainly to collect run-time statistics -static std::atomic hits[2], means[2]; +constexpr int MaxDebugSlots = 32; -void dbg_hit_on(bool b) { ++hits[0]; if (b) ++hits[1]; } -void dbg_hit_on(bool c, bool b) { if (c) dbg_hit_on(b); } -void dbg_mean_of(int v) { ++means[0]; means[1] += v; } +namespace { + +template +struct DebugInfo { + std::atomic data[N] = { 0 }; + + constexpr inline std::atomic& operator[](int index) { return data[index]; } +}; + +DebugInfo<2> hit[MaxDebugSlots]; +DebugInfo<2> mean[MaxDebugSlots]; +DebugInfo<3> stdev[MaxDebugSlots]; +DebugInfo<6> correl[MaxDebugSlots]; + +} // namespace + +void dbg_hit_on(bool cond, int slot) { + + ++hit[slot][0]; + if (cond) + ++hit[slot][1]; +} + +void dbg_mean_of(int64_t value, int slot) { + + ++mean[slot][0]; + mean[slot][1] += value; +} + +void dbg_stdev_of(int64_t value, int slot) { + + ++stdev[slot][0]; + stdev[slot][1] += value; + stdev[slot][2] += value * value; +} + +void dbg_correl_of(int64_t value1, int64_t value2, int slot) { + + ++correl[slot][0]; + correl[slot][1] += value1; + correl[slot][2] += value1 * value1; + correl[slot][3] += value2; + correl[slot][4] += value2 * value2; + correl[slot][5] += value1 * value2; +} void dbg_print() { - if (hits[0]) - cerr << "Total " << hits[0] << " Hits " << hits[1] - << " hit rate (%) " << 100 * hits[1] / hits[0] << endl; + int64_t n; + auto E = [&n](int64_t x) { return double(x) / n; }; + auto sqr = [](double x) { return x * x; }; - if (means[0]) - cerr << "Total " << means[0] << " Mean " - << (double)means[1] / means[0] << endl; + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = hit[i][0])) + std::cerr << "Hit #" << i + << ": Total " << n << " Hits " << hit[i][1] + << " Hit Rate (%) " << 100.0 * E(hit[i][1]) + << std::endl; + + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = mean[i][0])) + { + std::cerr << "Mean #" << i + << ": Total " << n << " Mean " << E(mean[i][1]) + << std::endl; + } + + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = stdev[i][0])) + { + double r = sqrtl(E(stdev[i][2]) - sqr(E(stdev[i][1]))); + std::cerr << "Stdev #" << i + << ": Total " << n << " Stdev " << r + << std::endl; + } + + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = correl[i][0])) + { + double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3])) + / ( sqrtl(E(correl[i][2]) - sqr(E(correl[i][1]))) + * sqrtl(E(correl[i][4]) - sqr(E(correl[i][3])))); + std::cerr << "Correl. #" << i + << ": Total " << n << " Coefficient " << r + << std::endl; + } } diff --git a/src/misc.h b/src/misc.h index 4c1150f8..9761da8a 100644 --- a/src/misc.h +++ b/src/misc.h @@ -39,9 +39,10 @@ void std_aligned_free(void* ptr); void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes void aligned_large_pages_free(void* mem); // nop if mem == nullptr -void dbg_hit_on(bool b); -void dbg_hit_on(bool c, bool b); -void dbg_mean_of(int v); +void dbg_hit_on(bool cond, int slot = 0); +void dbg_mean_of(int64_t value, int slot = 0); +void dbg_stdev_of(int64_t value, int slot = 0); +void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds From 1cdc0f78bd937637128ad10f5168cdb80390f6fb Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 3 Feb 2023 10:52:14 +0300 Subject: [PATCH 175/678] Simplify usage of optimism in complexity This patch removes one condition in optimism usage in complexity, now negative optimism also impacts it. Passed STC: https://tests.stockfishchess.org/tests/view/63d34f43721fe2bff692fb12 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 204920 W: 54343 L: 54309 D: 96268 Ptnml(0-2): 598, 22648, 55897, 22756, 561 Passed LTC: https://tests.stockfishchess.org/tests/view/63d612a2a67dd929a556075c LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 200712 W: 53207 L: 53172 D: 94333 Ptnml(0-2): 58, 19664, 60901, 19651, 82 closes https://github.com/official-stockfish/Stockfish/pull/4377 bench 4204964 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 8683182c..6d5a8a0c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1073,7 +1073,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { // Blend nnue complexity with (semi)classical complexity nnueComplexity = ( 406 * nnueComplexity + 424 * abs(psq - nnue) - + (optimism > 0 ? int(optimism) * int(psq - nnue) : 0) + + int(optimism) * int(psq - nnue) ) / 1024; // Return hybrid NNUE complexity to caller From d2f79ff0e0efc33797120f59355c2a5571b4ab80 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 3 Feb 2023 18:54:30 +0800 Subject: [PATCH 176/678] Remove reduced LMR capture bonus In LMR, simplify away the reduced capture bonus (i.e. if (capture) bonus /= 6). Non-regression STC: https://tests.stockfishchess.org/tests/view/63da1da9bbadd17b3787dced LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 28152 W: 7521 L: 7296 D: 13335 Ptnml(0-2): 76, 3069, 7568, 3280, 83 Non-regression LTC: https://tests.stockfishchess.org/tests/view/63da6ad4bbadd17b3787e98c LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 52472 W: 14120 L: 13941 D: 24411 Ptnml(0-2): 16, 5071, 15887, 5242, 20 closes https://github.com/official-stockfish/Stockfish/pull/4378 Bench: 4034016 --- src/search.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b9ca3961..f6bc0aa9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1214,9 +1214,6 @@ moves_loop: // When in check, search starts here int bonus = value > alpha ? stat_bonus(newDepth) : -stat_bonus(newDepth); - if (capture) - bonus /= 6; - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); } } From 8d3457a9966f8c744ab7f8536be408196ccd8af9 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Fri, 3 Feb 2023 17:57:19 +0100 Subject: [PATCH 177/678] Improve excluded move logic PR consists of 2 improvements on nodes with excludeMove: 1. Remove xoring the posKey with make_key(excludedMove) Since we never call tte->save anymore with excludedMove, the unique left purpose of the xoring was to avoid a TT hit. Nevertheless on a normal bench run this produced ~25 false positives (key collisions) To avoid that we now forbid early TT cutoff's with excludeMove Maybe these accesses to TT with xored key caused useless misses in the CPU caches (L1, L2 ...) Now doing the probe with the same key as the enclosing search does, should hit the CPU cache. 2. Don't probe Tablebases with excludedMove. This can't be tested on fishtest, but it's obvious that tablebases don't deliver any information about suboptimal moves. Side note: Very surprisingly it looks like we cannot use static eval's from TT since they slightly differ over time due to changing optimism. Attempts to use static eval's from TT did loose about 13 ELO. This is something about to investigate. LTC: https://tests.stockfishchess.org/tests/view/63dc0f8de9d4cdfbe672d0c6 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 44736 W: 12046 L: 11733 D: 20957 Ptnml(0-2): 12, 4212, 13617, 4505, 22 An analogue of this passed STC & LTC see PR #4374 (thanks Dubslow for reviewing!) closes https://github.com/official-stockfish/Stockfish/pull/4380 Bench: 4758694 --- src/search.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f6bc0aa9..30a08cb7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -615,22 +615,24 @@ namespace { if (!rootNode) (ss+2)->statScore = 0; - // Step 4. Transposition table lookup. We don't want the score of a partial - // search to overwrite a previous full search TT value, so we use a different - // position key in case of an excluded move. + // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; - posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove); + posKey = pos.key(); tte = TT.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() : MOVE_NONE; ttCapture = ttMove && pos.capture(ttMove); + + // At this point, if excluded, skip straight to step 6, static eval. However, + // to save indentation, we list the condition in all code between here and there. if (!excludedMove) ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); // At non-PV nodes we check for an early TT cutoff if ( !PvNode && ss->ttHit + && !excludedMove && tte->depth() > depth - (tte->bound() == BOUND_EXACT) && ttValue != VALUE_NONE // Possible in case of TT access race && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) @@ -664,7 +666,7 @@ namespace { } // Step 5. Tablebases probe - if (!rootNode && TB::Cardinality) + if (!rootNode && !excludedMove && TB::Cardinality) { int piecesCount = pos.count(); @@ -727,6 +729,12 @@ namespace { complexity = 0; goto moves_loop; } + else if (excludedMove) { + // excludeMove implies that we had a ttHit on the containing non-excluded search with ss->staticEval filled from TT + // However static evals from the TT aren't good enough (-13 elo), presumably due to changing optimism context + // Recalculate value with current optimism (without updating thread avgComplexity) + ss->staticEval = eval = evaluate(pos, &complexity); + } else if (ss->ttHit) { // Never assume anything about values stored in TT @@ -735,6 +743,7 @@ namespace { ss->staticEval = eval = evaluate(pos, &complexity); else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost complexity = abs(ss->staticEval - pos.psq_eg_stm()); + thisThread->complexityAverage.update(complexity); // ttValue can be used as a better position evaluation (~7 Elo) if ( ttValue != VALUE_NONE @@ -744,14 +753,12 @@ namespace { else { ss->staticEval = eval = evaluate(pos, &complexity); + thisThread->complexityAverage.update(complexity); // Save static evaluation into transposition table - if (!excludedMove) - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } - thisThread->complexityAverage.update(complexity); - // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { @@ -1061,6 +1068,7 @@ moves_loop: // When in check, search starts here Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; + // the search with excludedMove will update ss->staticEval value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); ss->excludedMove = MOVE_NONE; From d5817a5896a8d93c4560b405da576a02aaa0c08a Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 4 Feb 2023 14:16:58 +0100 Subject: [PATCH 178/678] remove unnecessary variable pinned already has to be true for the bitwise & closes https://github.com/official-stockfish/Stockfish/pull/4381 No functional change --- src/movegen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 47154164..255dce04 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -264,7 +264,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) - if ( ((pinned && pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) + if ( ((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) && !pos.legal(*cur)) *cur = (--moveList)->move; else From 8f843633db3faaf447cc191cbaed9f5ddfd374bd Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 4 Feb 2023 23:46:44 +0300 Subject: [PATCH 179/678] Cleanup and reorder in qsearch This patch is a simplification / code normalisation in qsearch. Adds steps in comments the same way we have in search; Makes a separate "pruning" stage instead of heuristics randomly being spread over qsearch code; Reorders pruning heuristics from least taxing ones to more taxing ones; Removes repeated check for best value not being mated, instead uses 1 check - thus removes some lines of code. Moves prefetch and move setup after pruning - makes no sense to do them if move will actually get pruned. Passed non-regression test: https://tests.stockfishchess.org/tests/view/63dd2c5ff9a50a69252c1413 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 113504 W: 29898 L: 29770 D: 53836 Ptnml(0-2): 287, 11861, 32327, 11991, 286 https://github.com/official-stockfish/Stockfish/pull/4382 Non-functional change. --- src/search.cpp | 54 ++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 30a08cb7..aa87948b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1427,6 +1427,7 @@ moves_loop: // When in check, search starts here bool pvHit, givesCheck, capture; int moveCount; + // Step 1. Initialize node if (PvNode) { (ss+1)->pv = pv; @@ -1438,7 +1439,7 @@ moves_loop: // When in check, search starts here ss->inCheck = pos.checkers(); moveCount = 0; - // Check for an immediate draw or maximum ply reached + // Step 2. Check for an immediate draw or maximum ply reached if ( pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; @@ -1450,13 +1451,14 @@ moves_loop: // When in check, search starts here // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; - // Transposition table lookup + // Step 3. Transposition table lookup posKey = pos.key(); tte = TT.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = ss->ttHit ? tte->move() : MOVE_NONE; pvHit = ss->ttHit && tte->is_pv(); + // At non-PV nodes we check for an early TT cutoff if ( !PvNode && ss->ttHit && tte->depth() >= ttDepth @@ -1464,7 +1466,7 @@ moves_loop: // When in check, search starts here && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; - // Evaluate the position statically + // Step 4. Static evaluation of the position if (ss->inCheck) { ss->staticEval = VALUE_NONE; @@ -1522,7 +1524,8 @@ moves_loop: // When in check, search starts here int quietCheckEvasions = 0; - // Loop through the moves until no moves remain or a beta cutoff occurs + // Step 5. Loop through all pseudo-legal moves until no moves remain + // or a beta cutoff occurs. while ((move = mp.next_move()) != MOVE_NONE) { assert(is_ok(move)); @@ -1536,9 +1539,11 @@ moves_loop: // When in check, search starts here moveCount++; + // Step 6. Pruning. + if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + { // Futility pruning and moveCount pruning (~10 Elo) - if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && !givesCheck + if ( !givesCheck && to_sq(move) != prevSq && futilityBase > -VALUE_KNOWN_WIN && type_of(move) != PROMOTION) @@ -1561,43 +1566,43 @@ moves_loop: // When in check, search starts here } } - // Do not search moves with bad enough SEE values (~5 Elo) - if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && !pos.see_ge(move, Value(-108))) + // We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter + // and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead. + if (quietCheckEvasions > 1) + break; + + // Continuation history based pruning (~3 Elo) + if ( !capture + && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 + && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) continue; + // Do not search moves with bad enough SEE values (~5 Elo) + if (!pos.see_ge(move, Value(-108))) + continue; + + } + // Speculative prefetch as early as possible prefetch(TT.first_entry(pos.key_after(move))); + // Update the current move ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] [capture] [pos.moved_piece(move)] [to_sq(move)]; - // Continuation history based pruning (~3 Elo) - if ( !capture - && bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) - continue; - - // We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter - // and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead. - if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && quietCheckEvasions > 1) - break; - quietCheckEvasions += !capture && ss->inCheck; - // Make and search the move + // Step 7. Make and search the move pos.do_move(move, st, givesCheck); value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // Check for a new best move + // Step 8. Check for a new best move if (value > bestValue) { bestValue = value; @@ -1617,6 +1622,7 @@ moves_loop: // When in check, search starts here } } + // Step 9. Check for mate // All legal moves have been searched. A special case: if we're in check // and no legal moves were found, it is checkmate. if (ss->inCheck && bestValue == -VALUE_INFINITE) From e25bcaed3cb69406cec4cd3d212cdf5232234949 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Fri, 3 Feb 2023 18:18:55 -0600 Subject: [PATCH 180/678] Update `complexityAverage` in all branches of static eval STC: https://tests.stockfishchess.org/tests/view/63dda49573223e7f52ad0f8c LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 117416 W: 31173 L: 31049 D: 55194 Ptnml(0-2): 290, 12246, 33533, 12328, 311 LTC: https://tests.stockfishchess.org/tests/view/63dfa90873223e7f52ad69b8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 44416 W: 11924 L: 11744 D: 20748 Ptnml(0-2): 5, 4036, 13968, 4172, 27 closes https://github.com/official-stockfish/Stockfish/pull/4385 bench 4758694 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index aa87948b..2eed74b8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -743,7 +743,6 @@ namespace { ss->staticEval = eval = evaluate(pos, &complexity); else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost complexity = abs(ss->staticEval - pos.psq_eg_stm()); - thisThread->complexityAverage.update(complexity); // ttValue can be used as a better position evaluation (~7 Elo) if ( ttValue != VALUE_NONE @@ -753,11 +752,10 @@ namespace { else { ss->staticEval = eval = evaluate(pos, &complexity); - thisThread->complexityAverage.update(complexity); - // Save static evaluation into transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } + thisThread->complexityAverage.update(complexity); // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) From 05dea2ca4657dec10637bb53c4ad583f680e0677 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 5 Feb 2023 13:00:30 -0500 Subject: [PATCH 181/678] Update default net to nn-1337b1adec5b.nnue Created by retraining the master net on a dataset composed of: * Most of the previous best dataset filtered to remove positions likely having only one good move * Adding training data from Leela T77 dec2021 rescored with 16tb of 7-piece tablebases Trained with end lambda 0.7 and max epoch 900. Positions with ply <= 28 were removed from most of the previous best dataset before training began. A new nnue-pytorch trainer param for skipping early plies was used to skip plies <= 24 in the unfiltered and additional Leela T77 parts of the dataset. ``` python easy_train.py \ --experiment-name leela96-dfrc99-T80octnovT79aprmayT60novdec-eval-filt-v2-T78augsep-12tb-T77dec-16tb-lambda7-sk24 \ --training-dataset /data/leela96-dfrc99-T80octnovT79aprmayT60novdec-eval-filt-v2-T78augsep-12tb-T77dec-16tb.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/easy-train-early-fen-skipping \ --early-fen-skipping 24 \ --gpus "0," \ --start-from-engine-test-net True \ --start-lambda 1.0 \ --end-lambda 0.7 \ --gamma 0.995 \ --lr 4.375e-4 \ --tui False \ --seed $RANDOM \ --max_epoch 900 ``` The depth6 multipv2 search filtering method is the same as the one used for filtering recent best datasets, with a lower eval difference threshold to remove slightly more positions than before. These parts of the dataset were filtered: * 96% of T60T70wIsRightFarseerT60T74T75T76.binpack * 99% of dfrc_n5000.binpack * T80 oct + nov 2022 data, no positions with castling flags, rescored with ~600gb 7p tablebases * T79 apr + may 2022 data, rescored with 12tb 7p tablebases * T60 nov + dec 2021 data, rescored with 12tb 7p tablebases These parts of the dataset were not filtered. Positions with ply <= 24 were skipped during training: * T78 aug + sep 2022 data, rescored with 12tb 7p tablebases * 84% of T77 dec 2021 data, rescored with 16tb 7p tablebases The code and exact evaluation thresholds used for data filtering can be found at: https://github.com/linrock/Stockfish/tree/tools-filter-multipv2-eval-diff-t2/src/filter The exact training data used can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch859.nnue : 3.5 +/ 1.2 Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> https://tests.stockfishchess.org/tests/view/63dfeefc73223e7f52ad769f Total: 219744 W: 58572 L: 58002 D: 103170 Ptnml(0-2): 609, 24446, 59284, 24832, 701 Passed LTC: https://tests.stockfishchess.org/tests/view/63e268fc73223e7f52ade7b6 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 91256 W: 24528 L: 24121 D: 42607 Ptnml(0-2): 48, 8863, 27390, 9288, 39 closes https://github.com/official-stockfish/Stockfish/pull/4387 bench 3841998 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index f7ecaac9..cdea2ab2 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-bc24c101ada0.nnue" + #define EvalFileDefaultName "nn-1337b1adec5b.nnue" namespace NNUE { From e5f6d71b96b5149e5e1df30721e1870abdb218ce Mon Sep 17 00:00:00 2001 From: borg323 Date: Thu, 9 Feb 2023 21:14:59 +0200 Subject: [PATCH 182/678] Fix build on arm windows avoids the use of _mm_malloc on arm windows. fixes #4379 closes https://github.com/official-stockfish/Stockfish/pull/4388 No functional change --- AUTHORS | 1 + src/misc.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 42ba5930..634de4a3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -35,6 +35,7 @@ Ben Chaney (Chaneybenjamini) Ben Koshy (BKSpurgeon) Bill Henry (VoyagerOne) Bojun Guo (noobpwnftw, Nooby) +borg323 Boštjan Mejak (PedanticHacker) braich Brian Sheppard (SapphireBrand, briansheppard-toast) diff --git a/src/misc.cpp b/src/misc.cpp index 7d848d32..e65faab9 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -448,8 +448,10 @@ void* std_aligned_alloc(size_t alignment, size_t size) { #if defined(POSIXALIGNEDALLOC) void *mem; return posix_memalign(&mem, alignment, size) ? nullptr : mem; -#elif defined(_WIN32) +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) return _mm_malloc(size, alignment); +#elif defined(_WIN32) + return _aligned_malloc(size, alignment); #else return std::aligned_alloc(alignment, size); #endif @@ -459,8 +461,10 @@ void std_aligned_free(void* ptr) { #if defined(POSIXALIGNEDALLOC) free(ptr); -#elif defined(_WIN32) +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) _mm_free(ptr); +#elif defined(_WIN32) + _aligned_free(ptr); #else free(ptr); #endif From 852330ee5060e42a42c1cddd85b82e28d09f4229 Mon Sep 17 00:00:00 2001 From: disservin Date: Sat, 11 Feb 2023 17:14:16 +0100 Subject: [PATCH 183/678] update cuckoo link use webarchive to link to the cycle detection paper by Kervinck. closes https://github.com/official-stockfish/Stockfish/pull/4389 No functional change --- src/position.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/position.cpp b/src/position.cpp index cfd98f68..37aa2e9e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -97,7 +97,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { // Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition" // situations. Description of the algorithm in the following paper: -// https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf +// http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf // First and second hash functions for indexing the cuckoo tables inline int H1(Key h) { return h & 0x1fff; } From 2c36d1e7e7374b8babb3cc503c0bc07ceb83dbf8 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Mon, 13 Feb 2023 11:54:59 +0900 Subject: [PATCH 184/678] Fix overflow in add_dpbusd_epi32x2 This patch fixes 16bit overflow in *_add_dpbusd_epi32x2 functions, that can be triggered in rare cases depending on the NNUE weights. While the code leads to some slowdown on affected architectures (most notably avx2), the fix is simpler than some of the other options discussed in https://github.com/official-stockfish/Stockfish/pull/4394 Code suggested by Sopel97. Result of "bench 4096 1 30 default depth nnue": | Architecture | master | patch (gcc) | patch (clang) | |---------------------|-----------|-------------|---------------| | x86-64-vnni512 | 762122798 | 762122798 | 762122798 | | x86-64-avx512 | 769723503 | 762122798 | 762122798 | | x86-64-bmi2 | 769723503 | 762122798 | 762122798 | | x86-64-ssse3 | 769723503 | 762122798 | 762122798 | | x86-64 | 762122798 | 762122798 | 762122798 | Following architectures will experience ~4% slowdown due to an additional instruction in the middle of hot path: * x86-64-avx512 * x86-64-bmi2 * x86-64-avx2 * x86-64-sse41-popcnt (x86-64-modern) * x86-64-ssse3 * x86-32-sse41-popcnt This patch clearly loses Elo against master with both STC and LTC. Failed non-regression STC (256bit fix only): LLR: -2.95 (-2.94,2.94) <-1.75,0.25> Total: 33528 W: 8769 L: 9049 D: 15710 Ptnml(0-2): 96, 3616, 9600, 3376, 76 https://tests.stockfishchess.org/tests/view/63e6a5b44299542b1e26a485 60+0.6 @ 30000 games: Elo: -1.67 +-1.7 (95%) LOS: 2.8% Total: 30000 W: 7848 L: 7992 D: 14160 Ptnml(0-2): 12, 2847, 9436, 2683, 22 nElo: -3.84 +-3.9 (95%) PairsRatio: 0.95 https://tests.stockfishchess.org/tests/view/63e7ac716d0e1db55f35a660 However, a test against nn-a3dc078bafc7.nnue, which is the latest "safe" network not causing the bug, passed with regular bounds. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 160456 W: 42658 L: 42175 D: 75623 Ptnml(0-2): 487, 17638, 43469, 18173, 461 https://tests.stockfishchess.org/tests/view/63e89836d62a5d02b0fa82c8 closes https://github.com/official-stockfish/Stockfish/pull/4391 closes https://github.com/official-stockfish/Stockfish/pull/4394 No functional change --- src/nnue/layers/simd.h | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 231f7891..381e7a68 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -165,18 +165,19 @@ namespace Stockfish::Simd { __m512i tmp0 = _mm512_maddubs_epi16(a0, b0); __m512i tmp1 = _mm512_maddubs_epi16(a1, b1); asm( - "vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t" "vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t" + "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t" + "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t" "vpaddd %[acc], %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0) - : [tmp1]"v"(tmp1), [ones]"v"(_mm512_set1_epi16(1)) + : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) + : [ones]"v"(_mm512_set1_epi16(1)) ); # else __m512i product0 = _mm512_maddubs_epi16(a0, b0); __m512i product1 = _mm512_maddubs_epi16(a1, b1); - product0 = _mm512_adds_epi16(product0, product1); product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, product0); + product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); # endif # endif } @@ -261,18 +262,19 @@ namespace Stockfish::Simd { __m256i tmp0 = _mm256_maddubs_epi16(a0, b0); __m256i tmp1 = _mm256_maddubs_epi16(a1, b1); asm( - "vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t" "vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t" + "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t" + "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t" "vpaddd %[acc], %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0) - : [tmp1]"v"(tmp1), [ones]"v"(_mm256_set1_epi16(1)) + : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) + : [ones]"v"(_mm256_set1_epi16(1)) ); # else __m256i product0 = _mm256_maddubs_epi16(a0, b0); __m256i product1 = _mm256_maddubs_epi16(a1, b1); - product0 = _mm256_adds_epi16(product0, product1); product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, product0); + product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); # endif # endif } @@ -326,18 +328,19 @@ namespace Stockfish::Simd { __m128i tmp0 = _mm_maddubs_epi16(a0, b0); __m128i tmp1 = _mm_maddubs_epi16(a1, b1); asm( - "paddsw %[tmp1], %[tmp0]\n\t" "pmaddwd %[ones], %[tmp0]\n\t" + "pmaddwd %[ones], %[tmp1]\n\t" + "paddd %[tmp1], %[tmp0]\n\t" "paddd %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0) - : [tmp1]"v"(tmp1), [ones]"v"(_mm_set1_epi16(1)) + : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) + : [ones]"v"(_mm_set1_epi16(1)) ); # else __m128i product0 = _mm_maddubs_epi16(a0, b0); __m128i product1 = _mm_maddubs_epi16(a1, b1); - product0 = _mm_adds_epi16(product0, product1); product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, product0); + product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); # endif } From 29c1e072b669c2257e4b48094391e7dc39fb31a5 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 12 Feb 2023 17:33:19 -0800 Subject: [PATCH 185/678] Simplify nnueComplexity calculation. further simplification after https://github.com/official-stockfish/Stockfish/pull/4377 STC https://tests.stockfishchess.org/tests/view/63e02a3773223e7f52ad8190 LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 359072 W: 94605 L: 94733 D: 169734 Ptnml(0-2): 994, 39874, 97958, 39686, 1024 LTC https://tests.stockfishchess.org/tests/view/63e3fd12b5f425d71f77002a LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 248424 W: 66020 L: 66030 D: 116374 Ptnml(0-2): 113, 24653, 74689, 24645, 112 closes https://github.com/official-stockfish/Stockfish/pull/4390 bench: 4098325 --- src/evaluate.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6d5a8a0c..080d412b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1072,8 +1072,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { // Blend nnue complexity with (semi)classical complexity nnueComplexity = ( 406 * nnueComplexity - + 424 * abs(psq - nnue) - + int(optimism) * int(psq - nnue) + + (424 + optimism) * abs(psq - nnue) ) / 1024; // Return hybrid NNUE complexity to caller From 085cace4574bf561472d8d3d3afe50c2c536b4e3 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sun, 22 Jan 2023 19:45:46 -0600 Subject: [PATCH 186/678] Simplify late countermove bonus condition STC: https://tests.stockfishchess.org/tests/view/63d53ac6a67dd929a555e1e2 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 216096 W: 56862 L: 56839 D: 102395 Ptnml(0-2): 648, 24033, 58650, 24082, 635 LTC: https://tests.stockfishchess.org/tests/view/63d7f9a6a67dd929a5565991 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 808512 W: 214060 L: 214610 D: 379842 Ptnml(0-2): 301, 79448, 245293, 78928, 286 closes https://github.com/official-stockfish/Stockfish/pull/4392 Bench: 4283297 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 2eed74b8..eccb97fd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1371,11 +1371,10 @@ moves_loop: // When in check, search starts here quietsSearched, quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low - else if ( (depth >= 5 || PvNode || bestValue < alpha - 65 * depth) - && !priorCapture) + else if (!priorCapture) { // Extra bonuses for PV/Cut nodes or bad fail lows - int bonus = 1 + (PvNode || cutNode) + (bestValue < alpha - 88 * depth); + int bonus = (depth > 4) + (PvNode || cutNode) + (bestValue < alpha - 88 * depth); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } From 037ef3e18dc7f5455cc671995ae38d5b4d1fce4a Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 14 Feb 2023 18:04:17 -0600 Subject: [PATCH 187/678] Remove one `reduction` call even though bench is unchanged to depth 28, due to adjusting depth in singular extensions this might be functional. STC: https://tests.stockfishchess.org/tests/view/63ec21affe833123fef34153 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 195712 W: 51625 L: 51581 D: 92506 Ptnml(0-2): 504, 20527, 55779, 20513, 533 LTC: https://tests.stockfishchess.org/tests/view/63ed3487fe833123fef375ed LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 32176 W: 8631 L: 8442 D: 15103 Ptnml(0-2): 5, 2794, 10309, 2967, 13 closes https://github.com/official-stockfish/Stockfish/pull/4395 Bench 4283297 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index eccb97fd..6ca2cfa5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -987,6 +987,8 @@ moves_loop: // When in check, search starts here Value delta = beta - alpha; + Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); + // Step 14. Pruning at shallow depth (~120 Elo). Depth conditions are important for mate finding. if ( !rootNode && pos.non_pawn_material(us) @@ -996,7 +998,7 @@ moves_loop: // When in check, search starts here moveCountPruning = moveCount >= futility_move_count(improving, depth); // Reduced depth of the next LMR search - int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount, delta, thisThread->rootDelta), 0); + int lmrDepth = std::max(newDepth - r, 0); if ( capture || givesCheck) @@ -1133,8 +1135,6 @@ moves_loop: // When in check, search starts here // Step 16. Make the move pos.do_move(move, st, givesCheck); - Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); - // Decrease reduction if position is or has been on the PV // and node is not likely to fail low. (~3 Elo) if ( ss->ttPv From b4ad3a3c4b68f9c8736f444aeb3364f833247fdc Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 21 Feb 2023 22:18:17 +0100 Subject: [PATCH 188/678] Add support for ARM dot product instructions The sdot instruction computes (and accumulates) a signed dot product, which is quite handy for Stockfish's NNUE code. The instruction is optional for Armv8.2 and Armv8.3, and mandatory for Armv8.4 and above. The commit adds a new 'arm-dotprod' architecture with enabled dot product support. It also enables dot product support for the existing 'apple-silicon' architecture, which is at least Armv8.5. The following local speed test was performed on an Apple M1 with ARCH=apple-silicon. I had to remove CPU pinning from the benchmark script. However, the results were still consistent: Checking both binaries against themselves reported a speedup of +0.0000 and +0.0005, respectively. ``` Result of 100 runs ================== base (...ish.037ef3e1) = 1917997 +/- 7152 test (...fish.dotprod) = 2159682 +/- 9066 diff = +241684 +/- 2923 speedup = +0.1260 P(speedup > 0) = 1.0000 CPU: 10 x arm Hyperthreading: off ``` Fixes #4193 closes https://github.com/official-stockfish/Stockfish/pull/4400 No functional change --- src/Makefile | 65 +++++++++++++++++++----------- src/nnue/layers/affine_transform.h | 24 +++++++++++ src/nnue/layers/simd.h | 13 ++++++ 3 files changed, 78 insertions(+), 24 deletions(-) diff --git a/src/Makefile b/src/Makefile index 775c72c3..3d6432fd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -69,32 +69,33 @@ VPATH = syzygy:nnue:nnue/features ### Section 2. High-level Configuration ### ========================================================================== # -# flag --- Comp switch --- Description +# flag --- Comp switch --- Description # ---------------------------------------------------------------------------- # -# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode +# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode # sanitize = none/ ... (-fsanitize ) -# --- ( undefined ) --- enable undefined behavior checks -# --- ( thread ) --- enable threading error checks -# --- ( address ) --- enable memory access checks -# --- ...etc... --- see compiler documentation for supported sanitizers -# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations -# arch = (name) --- (-arch) --- Target architecture -# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system -# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction -# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction -# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction -# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions -# mmx = yes/no --- -mmmx --- Use Intel MMX instructions -# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2 -# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3 -# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1 -# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 -# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX -# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 -# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256 -# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 -# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture +# --- ( undefined ) --- enable undefined behavior checks +# --- ( thread ) --- enable threading error checks +# --- ( address ) --- enable memory access checks +# --- ...etc... --- see compiler documentation for supported sanitizers +# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations +# arch = (name) --- (-arch) --- Target architecture +# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system +# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction +# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction +# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction +# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions +# mmx = yes/no --- -mmmx --- Use Intel MMX instructions +# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2 +# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3 +# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1 +# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 +# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX +# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 +# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256 +# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 +# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture +# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions # # Note that Makefile is space sensitive, so when adding new architectures # or modifying existing flags, you have to make sure there are no extra spaces @@ -116,7 +117,7 @@ ifeq ($(ARCH), $(filter $(ARCH), \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \ - armv7 armv7-neon armv8 apple-silicon general-64 general-32 riscv64)) + armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64)) SUPPORTED_ARCH=true else SUPPORTED_ARCH=false @@ -140,6 +141,7 @@ avx512 = no vnni256 = no vnni512 = no neon = no +dotprod = no arm_version = 0 STRIP = strip @@ -308,11 +310,21 @@ ifeq ($(ARCH),armv8) arm_version = 8 endif +ifeq ($(ARCH),armv8-dotprod) + arch = armv8 + prefetch = yes + popcnt = yes + neon = yes + dotprod = yes + arm_version = 8 +endif + ifeq ($(ARCH),apple-silicon) arch = arm64 prefetch = yes popcnt = yes neon = yes + dotprod = yes arm_version = 8 endif @@ -675,6 +687,10 @@ ifeq ($(neon),yes) endif endif +ifeq ($(dotprod),yes) + CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD +endif + ### 3.7 pext ifeq ($(pext),yes) CXXFLAGS += -DUSE_PEXT @@ -776,6 +792,7 @@ help: @echo "armv7 > ARMv7 32-bit" @echo "armv7-neon > ARMv7 32-bit with popcnt and neon" @echo "armv8 > ARMv8 64-bit with popcnt and neon" + @echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" @echo "e2k > Elbrus 2000" @echo "apple-silicon > Apple silicon ARM64" @echo "general-64 > unspecified 64-bit" diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 363b4916..63b58af3 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -72,6 +72,10 @@ namespace Stockfish::Eval::NNUE::Layers { const __m64 Zeros = _mm_setzero_si64(); const auto inputVector = reinterpret_cast(input); +# elif defined(USE_NEON_DOTPROD) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); + # elif defined(USE_NEON) constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; const auto inputVector = reinterpret_cast(input); @@ -123,6 +127,14 @@ namespace Stockfish::Eval::NNUE::Layers { sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum)); output[i] = _mm_cvtsi64_si32(sum); +# elif defined(USE_NEON_DOTPROD) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) { + sum = vdotq_s32(sum, inputVector[j], row[j]); + } + output[i] = vaddvq_s32(sum); + # elif defined(USE_NEON) int32x4_t sum = {biases[i]}; const auto row = reinterpret_cast(&weights[offset]); @@ -187,6 +199,9 @@ namespace Stockfish::Eval::NNUE::Layers { #elif defined (USE_SSSE3) static constexpr IndexType InputSimdWidth = 16; static constexpr IndexType MaxNumOutputRegs = 8; +#elif defined (USE_NEON_DOTPROD) + static constexpr IndexType InputSimdWidth = 16; + static constexpr IndexType MaxNumOutputRegs = 8; #elif defined (USE_NEON) static constexpr IndexType InputSimdWidth = 8; static constexpr IndexType MaxNumOutputRegs = 8; @@ -292,6 +307,15 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 #define vec_hadd Simd::m128_hadd #define vec_haddx4 Simd::m128_haddx4 +#elif defined (USE_NEON_DOTPROD) + using acc_vec_t = int32x4_t; + using bias_vec_t = int32x4_t; + using weight_vec_t = int8x16_t; + using in_vec_t = int8x16_t; + #define vec_zero {0} + #define vec_add_dpbusd_32x2 Simd::dotprod_m128_add_dpbusd_epi32x2 + #define vec_hadd Simd::neon_m128_hadd + #define vec_haddx4 Simd::neon_m128_haddx4 #elif defined (USE_NEON) using acc_vec_t = int32x4_t; using bias_vec_t = int32x4_t; diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 381e7a68..22c51980 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -346,6 +346,19 @@ namespace Stockfish::Simd { #endif +#if defined (USE_NEON_DOTPROD) + + [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( + int32x4_t& acc, + int8x16_t a0, int8x16_t b0, + int8x16_t a1, int8x16_t b1) { + + acc = vdotq_s32(acc, a0, b0); + acc = vdotq_s32(acc, a1, b1); + } + +#endif + #if defined (USE_NEON) [[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { From 77dfcbedce2861b2c6c5056d49e7a8731fea4256 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 19 Feb 2023 11:25:10 +0100 Subject: [PATCH 189/678] Remove unused macros closes https://github.com/official-stockfish/Stockfish/pull/4397 No functional change --- src/nnue/layers/affine_transform.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 63b58af3..313b1568 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -480,18 +480,14 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_add_dpbusd_32x4 Simd::m256_add_dpbusd_epi32x4 #define vec_hadd Simd::m256_hadd - #define vec_haddx4 Simd::m256_haddx4 #elif defined (USE_SSSE3) using vec_t = __m128i; #define vec_setzero _mm_setzero_si128 #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_add_dpbusd_32x4 Simd::m128_add_dpbusd_epi32x4 #define vec_hadd Simd::m128_hadd - #define vec_haddx4 Simd::m128_haddx4 #endif #if defined (USE_SSSE3) @@ -542,9 +538,7 @@ namespace Stockfish::Eval::NNUE::Layers { # undef vec_set_32 # undef vec_add_dpbusd_32 # undef vec_add_dpbusd_32x2 -# undef vec_add_dpbusd_32x4 # undef vec_hadd -# undef vec_haddx4 #else // Use old implementation for the other architectures. affine_transform_non_ssse3< From 08385527dd470ece814ac85013802995a0e7f6ca Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 20 Feb 2023 20:02:55 +0100 Subject: [PATCH 190/678] Introduce a function to compute NNUE accumulator This patch introduces `hint_common_parent_position()` to signal that potentially several child nodes will require an NNUE eval. By populating explicitly the accumulator, these subsequent evaluations can be performed more efficiently. This was based on the observation that calculating the evaluation in an excluded move position yielded a significant Elo gain, even though the evaluation itself was already available (work by pb00067). Sopel wrote the code to perform just the accumulator update. This PR is based on cleaned up code that passed STC: https://tests.stockfishchess.org/tests/view/63f62f9be74a12625bcd4aa0 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 110368 W: 29607 L: 29167 D: 51594 Ptnml(0-2): 41, 10551, 33572, 10967, 53 and in an the earlier (equivalent) version passed STC: https://tests.stockfishchess.org/tests/view/63f3c3fee74a12625bcce2a6 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 47552 W: 12786 L: 12467 D: 22299 Ptnml(0-2): 120, 5107, 12997, 5438, 114 passed LTC: https://tests.stockfishchess.org/tests/view/63f45cc2e74a12625bccfa63 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 110368 W: 29607 L: 29167 D: 51594 Ptnml(0-2): 41, 10551, 33572, 10967, 53 closes https://github.com/official-stockfish/Stockfish/pull/4402 Bench: 3726250 --- src/evaluate.h | 1 + src/nnue/evaluate_nnue.cpp | 5 + src/nnue/evaluate_nnue.h | 1 + src/nnue/nnue_feature_transformer.h | 425 +++++++++++++++++----------- src/search.cpp | 8 +- 5 files changed, 266 insertions(+), 174 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index cdea2ab2..46f20259 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -45,6 +45,7 @@ namespace Eval { std::string trace(Position& pos); Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); + void hint_common_parent_position(const Position& pos); void init(); void verify(); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index f132de71..f33aa3b8 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -136,6 +136,11 @@ namespace Stockfish::Eval::NNUE { return (bool)stream; } + void hint_common_parent_position(const Position& pos) { + if (Eval::useNNUE) + featureTransformer->hint_common_access(pos); + } + // Evaluation function. Perform differential calculation. Value evaluate(const Position& pos, bool adjusted, int* complexity) { diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 9499f7d9..15638cae 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -31,6 +31,7 @@ namespace Stockfish::Eval::NNUE { constexpr std::uint32_t HashValue = FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); + // Deleter for automating release of memory area template struct AlignedDeleter { diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 62f1615d..13f1604f 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -25,6 +25,7 @@ #include "nnue_architecture.h" #include // std::memset() +#include // std::pair namespace Stockfish::Eval::NNUE { @@ -332,27 +333,16 @@ namespace Stockfish::Eval::NNUE { #endif return psqt; + } // end of function transform() - } // end of function transform() - - + void hint_common_access(const Position& pos) const { + hint_common_access_for_perspective(pos); + hint_common_access_for_perspective(pos); + } private: template - void update_accumulator(const Position& pos) const { - - // 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. - - #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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; - #endif - + [[nodiscard]] std::pair 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(), *next = nullptr; @@ -367,218 +357,313 @@ namespace Stockfish::Eval::NNUE { next = st; st = st->previous; } + return { st, next }; + } - if (st->accumulator.computed[Perspective]) - { - if (next == nullptr) - return; + // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. + // All states must be sequential, that is states_to_update[i] must either be reachable + // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. + // computed_st must be reachable by repeatadly applying ->previous on states_to_update[0], if not nullptr. + template + void update_accumulator_incremetal(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { + static_assert(N > 0); + assert(states_to_update[N-1] == nullptr); - // Update incrementally in two steps. First, we update the "next" - // accumulator. Then, we update the current accumulator (pos.state()). - - // Gather all features to be updated. - const Square ksq = pos.square(Perspective); - FeatureSet::IndexList removed[2], added[2]; - FeatureSet::append_changed_indices( - ksq, next->dirtyPiece, removed[0], added[0]); - for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous) - FeatureSet::append_changed_indices( - ksq, st2->dirtyPiece, removed[1], added[1]); - - // Mark the accumulators as computed. - next->accumulator.computed[Perspective] = true; - pos.state()->accumulator.computed[Perspective] = true; - - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. - StateInfo *states_to_update[3] = - { next, next == pos.state() ? nullptr : pos.state(), nullptr }; #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + // Gcc-10.2 unnecessarily spills AVX2 registers if this array + // is defined in the VECTOR code below, once in each branch + vec_t acc[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; + #endif + + if (states_to_update[0] == nullptr) + return; + + // Update incrementally going back through states_to_update. + + // Gather all features to be updated. + const Square ksq = pos.square(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. + FeatureSet::IndexList removed[N-1], added[N-1]; + + { + int i = N-2; // last potential state to update. Skip last element because it must be nullptr. + while (states_to_update[i] == nullptr) + --i; + + StateInfo *st2 = states_to_update[i]; + + for (; i >= 0; --i) { - // Load accumulator - auto accTile = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTile[k]); + states_to_update[i]->accumulator.computed[Perspective] = true; - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } + StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - accTile = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTile[k], acc[k]); - } + for (; st2 != end_state; st2 = st2->previous) + FeatureSet::append_changed_indices( + ksq, st2->dirtyPiece, removed[i], added[i]); } + } - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - // Load accumulator - auto accTilePsqt = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_load_psqt(&accTilePsqt[k]); + StateInfo* st = computed_st; - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. +#ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator + auto accTile = reinterpret_cast( + &st->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTile[k]); - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - // Store accumulator - accTilePsqt = reinterpret_cast( - &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - } - - #else for (IndexType i = 0; states_to_update[i]; ++i) { - std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], - st->accumulator.accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k]; - - st = states_to_update[i]; - // Difference calculation for the deactivated features for (const auto index : removed[i]) { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); } // Difference calculation for the activated features for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } - } - #endif - } - else - { - // Refresh the accumulator - auto& accumulator = pos.state()->accumulator; - accumulator.computed[Perspective] = true; - FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, active); - - #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast( - &biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - for (const auto index : active) { const IndexType offset = HalfDimensions * index + j * TileHeight; auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_add_16(acc[k], column[k]); } - auto accTile = reinterpret_cast( - &accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) + // Store accumulator + accTile = reinterpret_cast( + &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) vec_store(&accTile[k], acc[k]); } + } - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + // Load accumulator + auto accTilePsqt = reinterpret_cast( + &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_load_psqt(&accTilePsqt[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) { - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_zero_psqt(); - - for (const auto index : active) + // Difference calculation for the deactivated features + for (const auto index : removed[i]) { const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + // Store accumulator + accTilePsqt = reinterpret_cast( + &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqt[k], psqt[k]); } + } - #else - std::memcpy(accumulator.accumulation[Perspective], biases, +#else + for (IndexType i = 0; states_to_update[i]; ++i) + { + std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], + st->accumulator.accumulation[Perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; + states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k]; - for (const auto index : active) + st = states_to_update[i]; + + // Difference calculation for the deactivated features + for (const auto index : removed[i]) { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; + st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } - #endif } +#endif #if defined(USE_MMX) _mm_empty(); #endif } + template + void update_accumulator_refresh(const Position& pos) const { + #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[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; + #endif + + // Refresh the accumulator + // Could be extracted to a separate function because it's done in 2 places, + // but it's unclear if compilers would correctly handle register allocation. + auto& accumulator = pos.state()->accumulator; + accumulator.computed[Perspective] = true; + FeatureSet::IndexList active; + FeatureSet::append_active_indices(pos, active); + +#ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto biasesTile = reinterpret_cast( + &biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + auto accTile = reinterpret_cast( + &accumulator.accumulation[Perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_zero_psqt(); + + for (const auto index : active) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + auto accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + +#else + std::memcpy(accumulator.accumulation[Perspective], biases, + HalfDimensions * sizeof(BiasType)); + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] = 0; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + } +#endif + + #if defined(USE_MMX) + _mm_empty(); + #endif + } + + template + void hint_common_access_for_perspective(const Position& pos) const { + + // Works like update_accumulator, but performs less work. + // Updates ONLY the accumulator for pos. + + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + // Fast early exit. + if (pos.state()->accumulator.computed[Perspective]) + return; + + auto [oldest_st, _] = try_find_computed_accumulator(pos); + + if (oldest_st->accumulator.computed[Perspective]) + { + // Only update current position accumulator to minimize work. + StateInfo* states_to_update[2] = { pos.state(), nullptr }; + update_accumulator_incremetal(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } + } + + template + void update_accumulator(const Position& pos) const { + + auto [oldest_st, next] = try_find_computed_accumulator(pos); + + if (oldest_st->accumulator.computed[Perspective]) + { + if (next == nullptr) + return; + + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + // Currently we update 2 accumulators. + // 1. for the current position + // 2. the next accumulator after the computed one + // The heuristic may change in the future. + StateInfo *states_to_update[3] = + { next, next == pos.state() ? nullptr : pos.state(), nullptr }; + + update_accumulator_incremetal(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } + } + alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; diff --git a/src/search.cpp b/src/search.cpp index 6ca2cfa5..5cb9750c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -730,10 +730,10 @@ namespace { goto moves_loop; } else if (excludedMove) { - // excludeMove implies that we had a ttHit on the containing non-excluded search with ss->staticEval filled from TT - // However static evals from the TT aren't good enough (-13 elo), presumably due to changing optimism context - // Recalculate value with current optimism (without updating thread avgComplexity) - ss->staticEval = eval = evaluate(pos, &complexity); + // 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); + eval = ss->staticEval; + complexity = abs(ss->staticEval - pos.psq_eg_stm()); } else if (ss->ttHit) { From 69639d764bde566e524b8c2566119bf677cb2622 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 21 Feb 2023 11:17:59 -0500 Subject: [PATCH 191/678] Reintroduce nnue pawn scaling with lower lazy thresholds Params found with the nevergrad TBPSA optimizer via nevergrad4sf modified to: * use SPRT LLR with fishtest STC elo gainer bounds [0, 2] as the objective function * increase the game batch size after each new optimal point is found The params were the optimal point after TBPSA iteration 7 and 160 nevergrad evaluations with: * initial batch size of 96 games per evaluation * batch size increase of 64 games after each iteration * a budget of 512 evaluations * TC: fixed 1.5 million nodes per move, no time limit nevergrad4sf enables optimizing stockfish params with TBPSA: https://github.com/vondele/nevergrad4sf Using pentanomial game results with smaller game batch sizes was inspired by: Use of SPRT LLR calculated from pentanomial game results as the objective function was an experiment at maximizing the information from game batches to reduce the computational cost for TBPSA to converge on good parameters. For the exact code used to find the params: https://github.com/linrock/tuning-fork Passed STC: https://tests.stockfishchess.org/tests/view/63f4ef5ee74a12625bcd114a LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 66552 W: 17736 L: 17390 D: 31426 Ptnml(0-2): 164, 7229, 18166, 7531, 186 Passed LTC: https://tests.stockfishchess.org/tests/view/63f56028e74a12625bcd2550 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 71264 W: 19150 L: 18787 D: 33327 Ptnml(0-2): 23, 6728, 21771, 7083, 27 closes https://github.com/official-stockfish/Stockfish/pull/4401 bench 3687580 --- src/evaluate.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 080d412b..cf6f23ea 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -193,8 +193,8 @@ using namespace Trace; namespace { // Threshold for lazy and space evaluation - constexpr Value LazyThreshold1 = Value(3631); - constexpr Value LazyThreshold2 = Value(2084); + constexpr Value LazyThreshold1 = Value(3622); + constexpr Value LazyThreshold2 = Value(1962); constexpr Value SpaceThreshold = Value(11551); // KingAttackWeights[PieceType] contains king attack weights by piece type @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { else { int nnueComplexity; - int scale = 1076 + 96 * pos.non_pawn_material() / 5120; + int scale = 1001 + 5 * pos.count() + 61 * pos.non_pawn_material() / 4096; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; From 29b5ad5deaf323f43019443b322090caec13f847 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Thu, 23 Feb 2023 19:01:51 +0100 Subject: [PATCH 192/678] Fix typo in method name closes https://github.com/official-stockfish/Stockfish/pull/4404 No functional change --- src/nnue/nnue_feature_transformer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 13f1604f..b0d5743e 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -365,7 +365,7 @@ namespace Stockfish::Eval::NNUE { // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. // computed_st must be reachable by repeatadly applying ->previous on states_to_update[0], if not nullptr. template - void update_accumulator_incremetal(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { + void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { static_assert(N > 0); assert(states_to_update[N-1] == nullptr); @@ -630,7 +630,7 @@ namespace Stockfish::Eval::NNUE { { // Only update current position accumulator to minimize work. StateInfo* states_to_update[2] = { pos.state(), nullptr }; - update_accumulator_incremetal(pos, oldest_st, states_to_update); + update_accumulator_incremental(pos, oldest_st, states_to_update); } else { @@ -656,7 +656,7 @@ namespace Stockfish::Eval::NNUE { StateInfo *states_to_update[3] = { next, next == pos.state() ? nullptr : pos.state(), nullptr }; - update_accumulator_incremetal(pos, oldest_st, states_to_update); + update_accumulator_incremental(pos, oldest_st, states_to_update); } else { From 472e726bff0d0e496dc8359cc071726a76317a72 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 24 Feb 2023 18:25:24 +0300 Subject: [PATCH 193/678] Search tuning at very long time control This patch is a result of tuning session of approximately 100k games at 120+1.2. Biggest changes are in extensions, stat bonus and depth reduction for nodes without a tt move. Failed STC: https://tests.stockfishchess.org/tests/view/63f72c72e74a12625bcd7938 LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 13872 W: 3535 L: 3769 D: 6568 Ptnml(0-2): 56, 1621, 3800, 1419, 40 Close to neutral at LTC: https://tests.stockfishchess.org/tests/view/63f738f5e74a12625bcd7b8a Elo: 0.80 +-1.2 (95%) LOS: 90.0% Total: 60000 W: 16213 L: 16074 D: 27713 Ptnml(0-2): 24, 5718, 18379, 5853, 26 nElo: 1.82 +-2.8 (95%) PairsRatio: 1.02 Passed 180+1.8 VLTC: https://tests.stockfishchess.org/tests/view/63f868f3e74a12625bcdb33e LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 15864 W: 4449 L: 4202 D: 7213 Ptnml(0-2): 1, 1301, 5083, 1544, 3 Passed 60+0.6 8 threads SMP VLTC: https://tests.stockfishchess.org/tests/view/63f8a5d6e74a12625bcdbdb3 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 6288 W: 1821 L: 1604 D: 2863 Ptnml(0-2): 0, 402, 2123, 619, 0 closes https://github.com/official-stockfish/Stockfish/pull/4406 bench 4705194 --- src/search.cpp | 102 ++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5cb9750c..a41ea4fd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -63,7 +63,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(158 * (d - improving)); + return Value(154 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -71,7 +71,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1460 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 937); + return (r + 1449 - int(delta) * 1032 / int(rootDelta)) / 1024 + (!i && r > 941); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(350 * d - 400, 1650); + return std::min(340 * d - 470, 1855); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -161,7 +161,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.26 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((19.47 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -354,12 +354,12 @@ void Thread::search() { if (rootDepth >= 4) { Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 15400; + delta = Value(10) + int(prev) * prev / 16502; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 116 * prev / (std::abs(prev) + 170); + int opt = 120 * prev / (std::abs(prev) + 161); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; } @@ -462,16 +462,16 @@ void Thread::search() { && !Threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (71 + 12 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 656.7; + double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 619.6; fallingEval = std::clamp(fallingEval, 0.5, 1.5); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.37 : 0.65; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.15 * timeReduction); - double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size(); + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); + double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::min(1.0 + (complexity - 261) / 1738.7, 1.5); + double complexPosition = std::min(1.03 + (complexity - 241) / 1552.0, 1.45); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; @@ -491,7 +491,7 @@ void Thread::search() { Threads.stop = true; } else if ( !mainThread->ponder - && Time.elapsed() > totalTime * 0.53) + && Time.elapsed() > totalTime * 0.50) Threads.increaseDepth = false; else Threads.increaseDepth = true; @@ -760,7 +760,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1940, 1940); + int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1920, 1920); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -770,13 +770,13 @@ namespace { // margin and the improving flag are used in various pruning heuristics. improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 172; + : 156; improving = improvement > 0; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 394 - 255 * depth * depth) + if (eval < alpha - 426 - 252 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -786,19 +786,19 @@ namespace { // Step 8. Futility pruning: child node (~40 Elo). // The depth condition is important for mate finding. if ( !ss->ttPv - && depth < 8 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 304 >= beta + && depth < 9 + && eval - futility_margin(depth, improving) - (ss-1)->statScore / 280 >= beta && eval >= beta - && eval < 28580) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 25128) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 18200 + && (ss-1)->statScore < 18755 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 20 * depth - improvement / 14 + 235 + complexity / 24 + && ss->staticEval >= beta - 19 * depth - improvement / 13 + 253 + complexity / 25 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -806,7 +806,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 165, 6) + depth / 3 + 4 - (complexity > 800); + Depth R = std::min(int(eval - beta) / 168, 6) + depth / 3 + 4 - (complexity > 825); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -842,7 +842,7 @@ namespace { } } - probCutBeta = beta + 180 - 54 * improving; + probCutBeta = beta + 186 - 54 * improving; // Step 10. ProbCut (~10 Elo) // If we have a good enough capture and a reduced search returns a value @@ -904,14 +904,14 @@ namespace { return qsearch(pos, ss, alpha, beta); if ( cutNode - && depth >= 9 + && depth >= 7 && !ttMove) depth -= 2; moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 402; + probCutBeta = beta + 391; if ( ss->inCheck && !PvNode && depth >= 2 @@ -1006,14 +1006,14 @@ moves_loop: // When in check, search starts here // Futility pruning for captures (~2 Elo) if ( !givesCheck && !PvNode - && lmrDepth < 7 + && lmrDepth < 6 && !ss->inCheck - && ss->staticEval + 185 + 203 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha) + && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-220) * depth)) + if (!pos.see_ge(move, Value(-206) * depth)) continue; } else @@ -1024,24 +1024,24 @@ moves_loop: // When in check, search starts here // Continuation history based pruning (~2 Elo) if ( lmrDepth < 5 - && history < -4180 * (depth - 1)) + && history < -4405 * (depth - 1)) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7208; + lmrDepth += history / 7278; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck && lmrDepth < 13 - && ss->staticEval + 103 + 136 * lmrDepth <= alpha) + && ss->staticEval + 103 + 138 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-25 * lmrDepth * lmrDepth - 16 * lmrDepth))) + if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } @@ -1056,7 +1056,7 @@ moves_loop: // When in check, search starts here // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 21) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ @@ -1064,7 +1064,7 @@ moves_loop: // When in check, search starts here && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (3 + (ss->ttPv && !PvNode)) * depth; + Value singularBeta = ttValue - (2 + (ss->ttPv && !PvNode)) * depth; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; @@ -1083,7 +1083,7 @@ moves_loop: // When in check, search starts here && ss->doubleExtensions <= 10) { extension = 2; - depth += depth < 12; + depth += depth < 13; } } @@ -1106,15 +1106,15 @@ moves_loop: // When in check, search starts here // Check extensions (~1 Elo) else if ( givesCheck - && depth > 9 - && abs(ss->staticEval) > 78) + && depth > 10 + && abs(ss->staticEval) > 88) extension = 1; // Quiet ttMove extensions (~1 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5600) + && (*contHist[0])[movedPiece][to_sq(move)] >= 5705) extension = 1; } @@ -1155,7 +1155,7 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes based on depth if (PvNode) - r -= 1 + 11 / (3 + depth); + r -= 1 + 12 / (3 + depth); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) @@ -1172,17 +1172,17 @@ moves_loop: // When in check, search starts here // Decrease reduction if move is a killer and we have a good history if (move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 3600) + && (*contHist[0])[movedPiece][to_sq(move)] >= 3722) r--; ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4467; + - 4182; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (12800 + 4410 * (depth > 7 && depth < 19)); + r -= ss->statScore / (11791 + 3992 * (depth > 6 && depth < 19)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1206,8 +1206,8 @@ moves_loop: // When in check, search starts here { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (alpha + 66 + 11 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 582 && ss->doubleExtensions <= 5; + const bool doDeeperSearch = value > (alpha + 58 + 12 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 588 && ss->doubleExtensions <= 5; const bool doShallowerSearch = value < bestValue + newDepth; ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; @@ -1318,8 +1318,8 @@ moves_loop: // When in check, search starts here // Reduce other moves if we have found at least one score improvement if ( depth > 1 && depth < 6 - && beta < VALUE_KNOWN_WIN - && alpha > -VALUE_KNOWN_WIN) + && beta < 10534 + && alpha > -10534) depth -= 1; assert(depth > 0); @@ -1374,7 +1374,7 @@ moves_loop: // When in check, search starts here else if (!priorCapture) { // Extra bonuses for PV/Cut nodes or bad fail lows - int bonus = (depth > 4) + (PvNode || cutNode) + (bestValue < alpha - 88 * depth); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } @@ -1502,7 +1502,7 @@ moves_loop: // When in check, search starts here if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 158; + futilityBase = bestValue + 168; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1575,7 +1575,7 @@ moves_loop: // When in check, search starts here continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-108))) + if (!pos.see_ge(move, Value(-110))) continue; } @@ -1708,7 +1708,7 @@ moves_loop: // When in check, search starts here if (!pos.capture(bestMove)) { - int bonus2 = bestValue > beta + 146 ? bonus1 // larger bonus + int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 98dafda6c8d432924821d085fd958c89bc8834c3 Mon Sep 17 00:00:00 2001 From: Alfredo Menezes Date: Mon, 27 Feb 2023 00:39:26 -0300 Subject: [PATCH 194/678] Simplify condition in step 15 Remove 'ttValue <= alpha' check for negative extension in singular search. Also apply some small code style changes. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 127888 W: 33766 L: 33651 D: 60471 Ptnml(0-2): 303, 14082, 35089, 14137, 333 https://tests.stockfishchess.org/tests/view/63f79528e74a12625bcd8c05 LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 89048 W: 23924 L: 23782 D: 41342 Ptnml(0-2): 27, 8635, 27065, 8763, 34 https://tests.stockfishchess.org/tests/view/63f82177e74a12625bcda6f4 LTC (retest): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 196360 W: 52514 L: 52475 D: 91371 Ptnml(0-2): 103, 19066, 59780, 19151, 80 https://tests.stockfishchess.org/tests/view/63f934bfe74a12625bcdd929 closes https://github.com/official-stockfish/Stockfish/pull/4407 Bench: 5310866 --- src/search.cpp | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a41ea4fd..6ccc70cc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -334,7 +334,7 @@ void Thread::search() { pvLast = 0; if (!Threads.increaseDepth) - searchAgainCounter++; + searchAgainCounter++; // MultiPV loop. We perform a full root search for each PV line for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) @@ -432,9 +432,10 @@ void Thread::search() { if (!Threads.stop) completedDepth = rootDepth; - if (rootMoves[0].pv[0] != lastBestMove) { - lastBestMove = rootMoves[0].pv[0]; - lastBestMoveDepth = rootDepth; + if (rootMoves[0].pv[0] != lastBestMove) + { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; } // Have we found a "mate in x"? @@ -729,7 +730,8 @@ namespace { complexity = 0; goto moves_loop; } - else if (excludedMove) { + 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); eval = ss->staticEval; @@ -755,6 +757,7 @@ namespace { // Save static evaluation into transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } + thisThread->complexityAverage.update(complexity); // Use static evaluation difference to improve quiet move ordering (~4 Elo) @@ -920,11 +923,9 @@ moves_loop: // When in check, search starts here && tte->depth() >= depth - 3 && ttValue >= probCutBeta && abs(ttValue) <= VALUE_KNOWN_WIN - && abs(beta) <= VALUE_KNOWN_WIN - ) + && abs(beta) <= VALUE_KNOWN_WIN) return probCutBeta; - const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, nullptr , (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; @@ -1099,8 +1100,8 @@ moves_loop: // When in check, search starts here else if (ttValue >= beta) extension = -2; - // If the eval of ttMove is less than alpha and value, we reduce it (negative extension) - else if (ttValue <= alpha && ttValue <= value) + // If the eval of ttMove is less than value, we reduce it (negative extension) + else if (ttValue <= value) extension = -1; } @@ -1227,11 +1228,11 @@ moves_loop: // When in check, search starts here // Step 18. Full depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. else if (!PvNode || moveCount > 1) { - // Increase reduction for cut nodes and not ttMove (~1 Elo) - if (!ttMove && cutNode) - r += 2; + // Increase reduction for cut nodes and not ttMove (~1 Elo) + if (!ttMove && cutNode) + r += 2; - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode); + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail @@ -1271,14 +1272,17 @@ moves_loop: // When in check, search starts here rm.selDepth = thisThread->selDepth; rm.scoreLowerbound = rm.scoreUpperbound = false; - if (value >= beta) { - rm.scoreLowerbound = true; - rm.uciScore = beta; + if (value >= beta) + { + rm.scoreLowerbound = true; + rm.uciScore = beta; } - else if (value <= alpha) { - rm.scoreUpperbound = true; - rm.uciScore = alpha; + else if (value <= alpha) + { + rm.scoreUpperbound = true; + rm.uciScore = alpha; } + rm.pv.resize(1); assert((ss+1)->pv); @@ -1447,7 +1451,8 @@ moves_loop: // When in check, search starts here // TT entry depth that we are going to use. Note that in qsearch we use // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS - : DEPTH_QS_NO_CHECKS; + : DEPTH_QS_NO_CHECKS; + // Step 3. Transposition table lookup posKey = pos.key(); tte = TT.probe(posKey, ss->ttHit); @@ -1577,7 +1582,6 @@ moves_loop: // When in check, search starts here // Do not search moves with bad enough SEE values (~5 Elo) if (!pos.see_ge(move, Value(-110))) continue; - } // Speculative prefetch as early as possible From 728b963614a765f5cb64c44a078169cca977750f Mon Sep 17 00:00:00 2001 From: pb00067 Date: Sun, 26 Feb 2023 09:59:35 +0100 Subject: [PATCH 195/678] Use common_parent_position hint also at PVNodes TT hits. Credits to Stefan Geschwentner (locutus2) showing that the hint is useful on PvNodes. In contrast to his test, this version avoids to use the hint when in check. I believe checking positions aren't good candidates for the hint because: - evasion moves are rather few, so a checking pos. has much less childs than a normal position - if the king has to move the NNUE eval can't use incremental updates, so the child nodes have to do a full refresh anyway. Passed STC: https://tests.stockfishchess.org/tests/view/63f9c5b1e74a12625bcdf585 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 124472 W: 33268 L: 32846 D: 58358 Ptnml(0-2): 350, 12986, 35170, 13352, 378 closes https://github.com/official-stockfish/Stockfish/pull/4410 no functional change --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 6ccc70cc..206779ed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -744,7 +744,11 @@ namespace { if (eval == VALUE_NONE) ss->staticEval = eval = evaluate(pos, &complexity); else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost + { complexity = abs(ss->staticEval - pos.psq_eg_stm()); + if (PvNode) + Eval::NNUE::hint_common_parent_position(pos); + } // ttValue can be used as a better position evaluation (~7 Elo) if ( ttValue != VALUE_NONE From ff5a6f8df196d61a0d9b1ebe54d84eeb9af20079 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 26 Feb 2023 09:56:54 +0100 Subject: [PATCH 196/678] NNUE accumulator update in probcut. Call the recently added hint function for NNUE accumulator update after a failed probcut search. In this case we already searched at least some captures and tt move which, however, is not sufficient for a cutoff. So it seems we have a greater chance that the full search will also have no cutoff and hence all moves have to be searched. STC: https://tests.stockfishchess.org/tests/view/63fa74a4e74a12625bce1823 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 70096 W: 18770 L: 18423 D: 32903 Ptnml(0-2): 191, 7342, 19654, 7651, 210 To be sure that we have no heavy interaction retest on top of #4410. Rebased STC: https://tests.stockfishchess.org/tests/view/63fb2f62e74a12625bce3b03 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 137688 W: 36790 L: 36349 D: 64549 Ptnml(0-2): 397, 14373, 38919, 14702, 453 closes https://github.com/official-stockfish/Stockfish/pull/4411 No functional change --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 206779ed..cfb569b9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -899,6 +899,8 @@ namespace { return value; } } + + Eval::NNUE::hint_common_parent_position(pos); } // Step 11. If the position is not in TT, decrease depth by 3. From 564456a6a824bfca26d6d9af5b35a055eb9fc6c2 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 26 Feb 2023 19:42:31 +0100 Subject: [PATCH 197/678] Unify type alias declarations The commit unifies the declaration of type aliases by replacing all typedefs with corresponding using statements. closing https://github.com/official-stockfish/Stockfish/pull/4412 No functional change --- src/material.h | 2 +- src/misc.cpp | 12 ++++++------ src/misc.h | 4 ++-- src/movepick.h | 14 +++++++------- src/nnue/nnue_feature_transformer.h | 20 ++++++++++---------- src/pawns.h | 2 +- src/position.h | 2 +- src/search.h | 2 +- src/syzygy/tbprobe.cpp | 4 ++-- src/tune.h | 6 +++--- src/types.h | 6 +++--- src/uci.h | 4 ++-- 12 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/material.h b/src/material.h index 73c83100..9acf78f5 100644 --- a/src/material.h +++ b/src/material.h @@ -62,7 +62,7 @@ struct Entry { uint8_t factor[COLOR_NB]; }; -typedef HashTable Table; +using Table = HashTable; Entry* probe(const Position& pos); diff --git a/src/misc.cpp b/src/misc.cpp index e65faab9..c22126af 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -32,12 +32,12 @@ // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. extern "C" { -typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP, - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); -typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY); -typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); -typedef bool(*fun4_t)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); -typedef WORD(*fun5_t)(); +using fun1_t = bool(*)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); +using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY); +using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); +using fun5_t = WORD(*)(); } #endif diff --git a/src/misc.h b/src/misc.h index 9761da8a..c20a816e 100644 --- a/src/misc.h +++ b/src/misc.h @@ -45,7 +45,7 @@ void dbg_stdev_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); -typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds +using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); inline TimePoint now() { return std::chrono::duration_cast @@ -165,7 +165,7 @@ public: inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #if defined(__GNUC__) && defined(IS_64BIT) - __extension__ typedef unsigned __int128 uint128; + __extension__ using uint128 = unsigned __int128; return ((uint128)a * (uint128)b) >> 64; #else uint64_t aL = (uint32_t)a, aH = a >> 32; diff --git a/src/movepick.h b/src/movepick.h index 90f60b8a..b6c07378 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -62,14 +62,14 @@ public: template struct Stats : public std::array, Size> { - typedef Stats stats; + using stats = Stats; void fill(const T& v) { // For standard-layout 'this' points to first struct member assert(std::is_standard_layout::value); - typedef StatsEntry entry; + using entry = StatsEntry; entry* p = reinterpret_cast(this); std::fill(p, p + sizeof(*this) / sizeof(entry), v); } @@ -87,23 +87,23 @@ enum StatsType { NoCaptures, Captures }; /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards /// (~11 elo) -typedef Stats ButterflyHistory; +using ButterflyHistory = Stats; /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous /// move, see www.chessprogramming.org/Countermove_Heuristic -typedef Stats CounterMoveHistory; +using CounterMoveHistory = Stats; /// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] -typedef Stats CapturePieceToHistory; +using CapturePieceToHistory = Stats; /// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] -typedef Stats PieceToHistory; +using PieceToHistory = Stats; /// ContinuationHistory is the combined history of a given pair of moves, usually /// the current one given a previous one. The nested history table is based on /// PieceToHistory instead of ButterflyBoards. /// (~63 elo) -typedef Stats ContinuationHistory; +using ContinuationHistory = Stats; /// MovePicker class is used to pick one pseudo-legal move at a time from the diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index b0d5743e..8087ea55 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -42,8 +42,8 @@ namespace Stockfish::Eval::NNUE { "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); #ifdef USE_AVX512 - typedef __m512i vec_t; - typedef __m256i psqt_vec_t; + using vec_t = __m512i; + using psqt_vec_t = __m256i; #define vec_load(a) _mm512_load_si512(a) #define vec_store(a,b) _mm512_store_si512(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b) @@ -66,8 +66,8 @@ namespace Stockfish::Eval::NNUE { #define MaxChunkSize 64 #elif USE_AVX2 - typedef __m256i vec_t; - typedef __m256i psqt_vec_t; + using vec_t = __m256i; + using psqt_vec_t = __m256i; #define vec_load(a) _mm256_load_si256(a) #define vec_store(a,b) _mm256_store_si256(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b) @@ -90,8 +90,8 @@ namespace Stockfish::Eval::NNUE { #define MaxChunkSize 32 #elif USE_SSE2 - typedef __m128i vec_t; - typedef __m128i psqt_vec_t; + using vec_t = __m128i; + using psqt_vec_t = __m128i; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_epi16(a,b) @@ -111,8 +111,8 @@ namespace Stockfish::Eval::NNUE { #define MaxChunkSize 16 #elif USE_MMX - typedef __m64 vec_t; - typedef __m64 psqt_vec_t; + using vec_t = __m64; + using psqt_vec_t = __m64; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_pi16(a,b) @@ -139,8 +139,8 @@ namespace Stockfish::Eval::NNUE { #define MaxChunkSize 8 #elif USE_NEON - typedef int16x8_t vec_t; - typedef int32x4_t psqt_vec_t; + using vec_t = int16x8_t; + using psqt_vec_t = int32x4_t; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) vaddq_s16(a,b) diff --git a/src/pawns.h b/src/pawns.h index 95c9ea3c..d20e7c2e 100644 --- a/src/pawns.h +++ b/src/pawns.h @@ -61,7 +61,7 @@ struct Entry { int blockedCount; }; -typedef HashTable Table; +using Table = HashTable; Entry* probe(const Position& pos); diff --git a/src/position.h b/src/position.h index 3de24f22..c82c7a8b 100644 --- a/src/position.h +++ b/src/position.h @@ -68,7 +68,7 @@ struct StateInfo { /// start position to the position just before the search starts). Needed by /// 'draw by repetition' detection. Use a std::deque because pointers to /// elements are not invalidated upon list resizing. -typedef std::unique_ptr> StateListPtr; +using StateListPtr = std::unique_ptr>; /// Position class stores information regarding the board representation as diff --git a/src/search.h b/src/search.h index 48a0f7ce..806e4be6 100644 --- a/src/search.h +++ b/src/search.h @@ -80,7 +80,7 @@ struct RootMove { std::vector pv; }; -typedef std::vector RootMoves; +using RootMoves = std::vector; /// LimitsType struct stores information sent by GUI about available time to diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index bbfd819d..b594ac37 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -142,7 +142,7 @@ struct SparseEntry { static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); -typedef uint16_t Sym; // Huffman symbol +using Sym = uint16_t; // Huffman symbol struct LR { enum Side { Left, Right }; @@ -327,7 +327,7 @@ struct PairsData { // first access, when the corresponding file is memory mapped. template struct TBTable { - typedef typename std::conditional::type Ret; + using Ret = typename std::conditional::type; static constexpr int Sides = Type == WDL ? 2 : 1; diff --git a/src/tune.h b/src/tune.h index f5b787af..440d950a 100644 --- a/src/tune.h +++ b/src/tune.h @@ -26,8 +26,8 @@ namespace Stockfish { -typedef std::pair Range; // Option's min-max values -typedef Range (RangeFun) (int); +using Range = std::pair; // Option's min-max values +using RangeFun = Range (int); // Default Range function, to calculate Option's min-max values inline Range default_range(int v) { @@ -75,7 +75,7 @@ struct SetRange { class Tune { - typedef void (PostUpdate) (); // Post-update function + using PostUpdate = void (); // Post-update function Tune() { read_results(); } Tune(const Tune&) = delete; diff --git a/src/types.h b/src/types.h index 8a021342..37ce343a 100644 --- a/src/types.h +++ b/src/types.h @@ -103,8 +103,8 @@ constexpr bool Is64Bit = true; constexpr bool Is64Bit = false; #endif -typedef uint64_t Key; -typedef uint64_t Bitboard; +using Key = uint64_t; +using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; @@ -218,7 +218,7 @@ constexpr Value PieceValue[PHASE_NB][PIECE_NB] = { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO } }; -typedef int Depth; +using Depth = int; enum : int { DEPTH_QS_CHECKS = 0, diff --git a/src/uci.h b/src/uci.h index 5d8ccf1a..70e45acc 100644 --- a/src/uci.h +++ b/src/uci.h @@ -45,12 +45,12 @@ struct CaseInsensitiveLess { }; /// The options container is defined as a std::map -typedef std::map OptionsMap; +using OptionsMap = std::map; /// The Option class implements each option as specified by the UCI protocol class Option { - typedef void (*OnChange)(const Option&); + using OnChange = void (*)(const Option&); public: Option(OnChange = nullptr); From 6adbc6fa05bbd2a574a8c4c6a6ef2307280217ab Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 22 Feb 2023 05:45:43 -0600 Subject: [PATCH 198/678] Late counter bonus: boost underestimated moves The idea here is very intuitive: since we've just proven that the move is good, then if it previously had poor stats, boost those stats more than otherwise. Passed STC: https://tests.stockfishchess.org/tests/view/63fb504ce74a12625bce4154 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 21128 W: 5763 L: 5481 D: 9884 Ptnml(0-2): 52, 2212, 5759, 2484, 57 Passed LTC: https://tests.stockfishchess.org/tests/view/63fb7825e74a12625bce491b LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 91904 W: 24764 L: 24359 D: 42781 Ptnml(0-2): 45, 8735, 27984, 9146, 42 closes https://github.com/official-stockfish/Stockfish/pull/4415 bench 4318808 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cfb569b9..6585f7bc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1383,8 +1383,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture) { - // Extra bonuses for PV/Cut nodes or bad fail lows - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } From 876906965b8d552866486c0e6eda1184fdb1d636 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 24 Feb 2023 09:16:55 -0500 Subject: [PATCH 199/678] Update default net to nn-52471d67216a.nnue Created by retraining the master net with modifications to the previous best dataset: * Improving T80 oct+nov 2022 endgame lambda accuracy by rescoring with 12-16tb of syzygy 7p tablebases * Filtering T78 jun+jul+aug 2022 with d6pv2 search to remove positions with bestmove captures or one good move * Adding T80 sep 2022 data, rescored with 16tb of 7p tablebases, unfiltered Trained with max-epoch 900, end-lambda 0.7, and early-fen-skipping 28. ``` python3 easy_train.py \ --experiment-name leela96-dfrc99-T80octnovT79aprmayT78junjulaugT60novdec-filt-v2-T78sep12tb7p-T77decT80sep16tb7p-lambda7-sk28 \ --training-dataset /data/leela96-dfrc99-T80octnovT79aprmayT78junjulaugT60novdec-filt-v2-T78sep12tb7p-T77decT80sep16tb7p.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/easy-train-early-fen-skipping \ --early-fen-skipping 28 \ --start-from-engine-test-net True \ --gpus "0," \ --max_epoch 900 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --gamma 0.995 \ --lr 4.375e-4 \ --tui False \ --seed $RANDOM ``` Training data was rescored and d6pv2 filtered in the same way as recent best datasets. For preparing the merged training dataset: ``` python3 interleave_binpacks.py \ leela96-eval-filt-v2.binpack \ dfrc99-eval-filt-v2.binpack \ test80-oct2022-16tb7p-eval-filt-v2-d6.binpack \ test80-nov2022-12tb7p-eval-filt-v2-d6.binpack \ T79-apr2022-12tb7p-eval-filt-v2.binpack \ T79-may2022-12tb7p-eval-filt-v2.binpack \ test78-junjulaug2022-16tb7p-eval-filt-v2-d6.binpack \ T60-nov2021-12tb7p-eval-filt-v2.binpack \ T60-dec2021-12tb7p-eval-filt-v2.binpack \ T78-sep2022-12tb7p.binpack \ test77-dec2021-16gb7p.binpack \ test80-sep2022-16tb7p.binpack \ /data/leela96-dfrc99-T80octnovT79aprmayT78junjulaugT60novdec-filt-v2-T78sep12tb7p-T77decT80sep16tb7p.binpack ``` Links for downloading the training data components can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch839.nnue : 0.6 +/- 1.4 Passed STC: https://tests.stockfishchess.org/tests/view/63f9ab4be74a12625bcdf02e LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 84656 W: 22681 L: 22302 D: 39673 Ptnml(0-2): 271, 9343, 22734, 9696, 284 Passed LTC: https://tests.stockfishchess.org/tests/view/63fa3833e74a12625bce0c0e LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 184664 W: 49933 L: 49344 D: 85387 Ptnml(0-2): 111, 17977, 55561, 18578, 105 closes https://github.com/official-stockfish/Stockfish/pull/4416 bench: 4814343 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 46f20259..5238cb81 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1337b1adec5b.nnue" + #define EvalFileDefaultName "nn-52471d67216a.nnue" namespace NNUE { From 5c75c1c2fbb7bb4f0bf7c44fb855c415b788cbf7 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 24 Feb 2023 12:09:45 +0300 Subject: [PATCH 200/678] Fix duplicated moves generation in movepicker in a some of cases movepicker returned some moves more than once which lead to them being searched more than once. This bug was possible because of how we use queen promotions - they are generated as a captures but are not included in position function which checks if move is a capture. Thus if any refutation (killer or countermove) was a queen promotion it was searched twice - once as a capture and one as a refutation. This patch affects various things, namely stats assignments for queen promotions and other moves if best move is queen promotion, also some heuristics in search and qsearch. With this patch every queen promotion is now considered a capture. After this patch number of found duplicated moves is 0 during normal 13 depth bench run. Passed STC: https://tests.stockfishchess.org/tests/view/63f77e01e74a12625bcd87d7 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 80920 W: 21455 L: 21289 D: 38176 Ptnml(0-2): 198, 8839, 22241, 8963, 219 Passed LTC: https://tests.stockfishchess.org/tests/view/63f7e020e74a12625bcd9a76 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 89712 W: 23674 L: 23533 D: 42505 Ptnml(0-2): 24, 8737, 27202, 8860, 33 closes https://github.com/official-stockfish/Stockfish/pull/4405 bench 4681731 --- src/movepick.cpp | 6 +++--- src/position.h | 12 ++++++++---- src/search.cpp | 12 ++++++------ src/syzygy/tbprobe.cpp | 4 ++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 65155a73..36ee46b5 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -93,7 +93,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece { assert(!pos.checkers()); - stage = PROBCUT_TT + !(ttm && pos.capture(ttm) + stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); } @@ -141,7 +141,7 @@ void MovePicker::score() { + bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384; else // Type == EVASIONS { - if (pos.capture(m)) + if (pos.capture_stage(m)) m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + (1 << 28); @@ -216,7 +216,7 @@ top: case REFUTATION: if (select([&](){ return *cur != MOVE_NONE - && !pos.capture(*cur) + && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); })) return *(cur - 1); ++stage; diff --git a/src/position.h b/src/position.h index c82c7a8b..485540ef 100644 --- a/src/position.h +++ b/src/position.h @@ -125,7 +125,7 @@ public: // Properties of moves bool legal(Move m) const; bool pseudo_legal(const Move m) const; - bool capture(Move m) const; + bool capture_stage(Move m) const; bool gives_check(Move m) const; Piece moved_piece(Move m) const; Piece captured_piece() const; @@ -381,10 +381,14 @@ inline bool Position::is_chess960() const { return chess960; } -inline bool Position::capture(Move m) const { +// returns true if a move is generated from the capture stage +// having also queen promotions covered, i.e. consistency with the capture stage move generation +// is needed to avoid the generation of duplicate moves. +inline bool Position::capture_stage(Move m) const { assert(is_ok(m)); - // Castling is encoded as "king captures rook" - return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; + return (!empty(to_sq(m)) && type_of(m) != CASTLING) + || (type_of(m) == PROMOTION && promotion_type(m) == QUEEN) + || type_of(m) == EN_PASSANT; } inline Piece Position::captured_piece() const { diff --git a/src/search.cpp b/src/search.cpp index 6585f7bc..fcdb8d67 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -623,7 +623,7 @@ namespace { ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() : MOVE_NONE; - ttCapture = ttMove && pos.capture(ttMove); + ttCapture = ttMove && pos.capture_stage(ttMove); // At this point, if excluded, skip straight to step 6, static eval. However, // to save indentation, we list the condition in all code between here and there. @@ -852,7 +852,7 @@ namespace { probCutBeta = beta + 186 - 54 * improving; // Step 10. ProbCut (~10 Elo) - // If we have a good enough capture and a reduced search returns a value + // 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. if ( !PvNode && depth > 4 @@ -873,7 +873,7 @@ namespace { while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) { - assert(pos.capture(move) || promotion_type(move) == QUEEN); + assert(pos.capture_stage(move)); ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] @@ -985,7 +985,7 @@ moves_loop: // When in check, search starts here (ss+1)->pv = nullptr; extension = 0; - capture = pos.capture(move); + capture = pos.capture_stage(move); movedPiece = pos.moved_piece(move); givesCheck = pos.gives_check(move); @@ -1542,7 +1542,7 @@ moves_loop: // When in check, search starts here continue; givesCheck = pos.gives_check(move); - capture = pos.capture(move); + capture = pos.capture_stage(move); moveCount++; @@ -1715,7 +1715,7 @@ moves_loop: // When in check, search starts here PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); int bonus1 = stat_bonus(depth + 1); - if (!pos.capture(bestMove)) + if (!pos.capture_stage(bestMove)) { int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index b594ac37..2a9e1b68 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1203,7 +1203,7 @@ WDLScore search(Position& pos, ProbeState* result) { for (const Move move : moveList) { - if ( !pos.capture(move) + if ( !pos.capture_stage(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) continue; @@ -1472,7 +1472,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { for (const Move move : MoveList(pos)) { - bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; + bool zeroing = pos.capture_stage(move) || type_of(pos.moved_piece(move)) == PAWN; pos.do_move(move, st); From 5c589142ae5714ecb9ded29bec12c591a1ba8f3f Mon Sep 17 00:00:00 2001 From: disservin Date: Sat, 4 Mar 2023 16:34:34 +0100 Subject: [PATCH 201/678] Add wiki to artifacts snapshot the wiki https://github.com/official-stockfish/stockfish/wiki as part of the artifacts generated. This will allow future release to include the wiki pages as a form of documentation closes https://github.com/official-stockfish/Stockfish/pull/4420 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 7 +++++++ .github/workflows/stockfish_binaries.yml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index a1b3cdab..e088c441 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -99,10 +99,17 @@ jobs: - name: Remove non src files run: rm -f *.o .depend *.nnue + - name: Download wiki + run: | + git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki + cd ../wiki + rm -rf .git + - name: Create tar archive. run: | cd .. mkdir stockfish + cp -r wiki stockfish/ cp -r src stockfish/ cp stockfish-android-$BINARY$EXT stockfish/ cp "Top CPU Contributors.txt" stockfish/ diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 06b13a9e..b22897cf 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -83,10 +83,17 @@ jobs: - name: Remove non src files run: rm -f *.o .depend *.nnue + - name: Download wiki + run: | + git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki + cd ../wiki + rm -rf .git + - name: Create tar archive. run: | cd .. mkdir stockfish + cp -r wiki stockfish/ cp -r src stockfish/ cp stockfish-$OS-$BINARY$EXT stockfish/ cp "Top CPU Contributors.txt" stockfish/ From 3a634f5282700484445c0f66b21b35e96fcb947b Mon Sep 17 00:00:00 2001 From: dav1312 <63931154+dav1312@users.noreply.github.com> Date: Sat, 4 Mar 2023 13:24:35 +0100 Subject: [PATCH 202/678] Update README.md Update and simplify the readme, removing duplicated and outdated stuff and pointing to the new wiki closes https://github.com/official-stockfish/Stockfish/pull/4421 No functional change --- README.md | 376 ++++++++++++------------------------------------------ 1 file changed, 81 insertions(+), 295 deletions(-) diff --git a/README.md b/README.md index ca90d5d4..1f462d31 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,21 @@ [![Stockfish][stockfish128-logo]][website-link] +

Stockfish

+ + A free and strong UCI chess engine. +
+ [Explore Stockfish docs »][wiki-link] +
+
+ [Report bug][issue-link] + · + [Open a discussion][discussions-link] + · + [Discord][discord-link] + · + [Blog][website-blog-link] + [![Build][build-badge]][build-link] [![License][license-badge]][license-link]
@@ -16,19 +31,16 @@ ## Overview -[Stockfish][website-link] is a free, powerful UCI chess engine derived from -Glaurung 2.1. Stockfish is not a complete chess program and requires a UCI-compatible -graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, -Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably. -Read the documentation for your GUI of choice for information about how to use +[Stockfish][website-link] is a **free and strong UCI chess engine** derived from +Glaurung 2.1 that analyzes chess positions and computes the optimal moves. + +Stockfish **does not include a graphical user interface** (GUI) that is required +to display a chessboard and to make it easy to input moves. These GUIs are +developed independently from Stockfish and are available online. **Read the +documentation for your GUI** of choice for information about how to use Stockfish with it. -The Stockfish engine features two evaluation functions for chess. The efficiently -updatable neural network (NNUE) based evaluation is the default and by far the strongest. -The classical evaluation based on handcrafted terms remains available. The strongest -network is integrated in the binary and downloaded automatically during the build process. -The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2, -avx2, neon, or similar). +See also the Stockfish [documentation][wiki-usage-link] for further usage help. ## Files @@ -36,338 +48,112 @@ This distribution of Stockfish consists of the following files: * [README.md][readme-link], the file you are currently reading. - * [Copying.txt][license-link], a text file containing the GNU General Public License - version 3. + * [Copying.txt][license-link], a text file containing the GNU General Public + License version 3. * [AUTHORS][authors-link], a text file with the list of authors for the project. - * [src][src-link], a subdirectory containing the full source code, including a Makefile - that can be used to compile Stockfish on Unix-like systems. + * [src][src-link], a subdirectory containing the full source code, including a + Makefile that can be used to compile Stockfish on Unix-like systems. - * a file with the .nnue extension, storing the neural network for the NNUE evaluation. - Binary distributions will have this file embedded. + * a file with the .nnue extension, storing the neural network for the NNUE + evaluation. Binary distributions will have this file embedded. -## The UCI protocol and available options +## The UCI protocol -The Universal Chess Interface (UCI) is a standard protocol used to communicate with -a chess engine, and is the recommended way to do so for typical graphical user interfaces -(GUI) or chess tools. Stockfish implements the majority of its options as described -in [the UCI protocol][uci-link]. +The [Universal Chess Interface][uci-link] (UCI) is a standard text-based protocol +used to communicate with a chess engine and is the recommended way to do so for +typical graphical user interfaces (GUI) or chess tools. Stockfish implements the +majority of its options. -Developers can see the default values for UCI options available in Stockfish by typing -`./stockfish uci` in a terminal, but the majority of users will typically see them and -change them via a chess GUI. This is a list of available UCI options in Stockfish: +Developers can see the default values for the UCI options available in Stockfish +by typing `./stockfish uci` in a terminal, but most users should typically use a +chess GUI to interact with Stockfish. - * #### Threads - The number of CPU threads used for searching a position. For best performance, set - this equal to the number of CPU cores available. +For more information on UCI or debug commands, see our [documentation][wiki-commands-link]. - * #### Hash - The size of the hash table in MB. It is recommended to set Hash after setting Threads. +## Compiling Stockfish - * #### Clear Hash - Clear the hash table. +Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions, +big-endian machines such as Power PC, and other platforms. - * #### Ponder - Let Stockfish ponder its next move while the opponent is thinking. - - * #### MultiPV - Output the N best lines (principal variations, PVs) when searching. - Leave at 1 for best performance. - - * #### Use NNUE - Toggle between the NNUE and classical evaluation functions. If set to "true", - the network parameters must be available to load from file (see also EvalFile), - if they are not embedded in the binary. - - * #### EvalFile - The name of the file of the NNUE evaluation parameters. Depending on the GUI the - filename might have to include the full path to the folder/directory that contains - the file. Other locations, such as the directory that contains the binary and the - working directory, are also searched. - - * #### UCI_AnalyseMode - An option handled by your GUI. - - * #### UCI_Chess960 - An option handled by your GUI. If true, Stockfish will play Chess960. - - * #### UCI_ShowWDL - If enabled, show approximate WDL statistics as part of the engine output. - These WDL numbers model expected game outcomes for a given evaluation and - game ply for engine self-play at fishtest LTC conditions (60+0.6s per game). - - * #### UCI_LimitStrength - Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level. - - * #### UCI_Elo - If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo. - This Elo rating has been calibrated at a time control of 120s+1.0s and anchored to +- 100 Elo to CCRL Blitz. - - * #### Skill Level - Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength). - Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a - weaker move will be played. - - * #### SyzygyPath - Path to the folders/directories storing the Syzygy tablebase files. Multiple - directories are to be separated by ";" on Windows and by ":" on Unix-based - operating systems. Do not use spaces around the ";" or ":". - - Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6` - - It is recommended to store .rtbw files on an SSD. There is no loss in storing - the .rtbz files on a regular HDD. It is recommended to verify all md5 checksums - of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will - lead to engine crashes. - - * #### SyzygyProbeDepth - Minimum remaining search depth for which a position is probed. Set this option - to a higher value to probe less aggressively if you experience too much slowdown - (in terms of nps) due to tablebase probing. - - * #### Syzygy50MoveRule - Disable to let fifty-move rule draws detected by Syzygy tablebase probes count - as wins or losses. This is useful for ICCF correspondence games. - - * #### SyzygyProbeLimit - Limit Syzygy tablebase probing to positions with at most this many pieces left - (including kings and pawns). - - * #### Move Overhead - Assume a time delay of x ms due to network and GUI overheads. This is useful to - avoid losses on time in those cases. - - * #### Slow Mover - Lower values will make Stockfish take less time in games, higher values will - make it think longer. - - * #### nodestime - Tells the engine to use nodes searched instead of wall time to account for - elapsed time. Useful for engine testing. - - * #### Debug Log File - Write all communication to and from the engine into a text file. - -For developers the following non-standard commands might be of interest, mainly useful for debugging: - - * #### bench *ttSize threads limit fenFile limitType evalType* - Performs a standard benchmark using various options. The signature of a version - (standard node count) is obtained using all defaults. `bench` is currently - `bench 16 1 13 default depth mixed`. - - * #### compiler - Give information about the compiler and environment used for building a binary. - - * #### d - Display the current position, with ascii art and fen. - - * #### eval - Return the evaluation of the current position. - - * #### export_net [filename] - Exports the currently loaded network to a file. - If the currently loaded network is the embedded network and the filename - is not specified then the network is saved to the file matching the name - of the embedded network, as defined in evaluate.h. - If the currently loaded network is not the embedded network (some net set - through the UCI setoption) then the filename parameter is required and the - network is saved into that file. - - * #### flip - Flips the side to move. - - -## A note on classical evaluation versus NNUE evaluation - -Both approaches assign a value to a position that is used in alpha-beta (PVS) search -to find the best move. The classical evaluation computes this value as a function -of various chess concepts, handcrafted by experts, tested and tuned using fishtest. -The NNUE evaluation computes this value with a neural network based on basic -inputs (e.g. piece positions only). The network is optimized and trained -on the evaluations of millions of positions at moderate search depth. - -The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward. -It can be evaluated efficiently on CPUs, and exploits the fact that only parts -of the neural network need to be updated after a typical chess move. -[The nodchip repository][nodchip-link] provided the first version of the needed tools -to train and develop the NNUE networks. Today, more advanced training tools are -available in [the nnue-pytorch repository][pytorch-link], while data generation tools -are available in [a dedicated branch][tools-link]. - -On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation -results in much stronger playing strength, even if the nodes per second computed by -the engine is somewhat lower (roughly 80% of nps is typical). - -Notes: - -1) the NNUE evaluation depends on the Stockfish binary and the network parameter file -(see the EvalFile UCI option). Not every parameter file is compatible with a given -Stockfish binary, but the default value of the EvalFile UCI option is the name of a -network that is guaranteed to be compatible with that binary. - -2) to use the NNUE evaluation, the additional data file with neural network parameters -needs to be available. Normally, this file is already embedded in the binary or it can -be downloaded. The filename for the default (recommended) net can be found as the default -value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue` -(for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from -``` -https://tests.stockfishchess.org/api/nn/[filename] -``` -replacing `[filename]` as needed. - -## What to expect from the Syzygy tablebases? - -If the engine is searching a position that is not in the tablebases (e.g. -a position with 8 pieces), it will access the tablebases during the search. -If the engine reports a very large score (typically 153.xx), this means -it has found a winning line into a tablebase position. - -If the engine is given a position to search that is in the tablebases, it -will use the tablebases at the beginning of the search to preselect all -good moves, i.e. all moves that preserve the win or preserve the draw while -taking into account the 50-move rule. -It will then perform a search only on those moves. **The engine will not move -immediately**, unless there is only a single good move. **The engine likely -will not report a mate score, even if the position is known to be won.** - -It is therefore clear that this behaviour is not identical to what one might -be used to with Nalimov tablebases. There are technical reasons for this -difference, the main technical reason being that Nalimov tablebases use the -DTM metric (distance-to-mate), while the Syzygy tablebases use a variation of the -DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move -counter). This special metric is one of the reasons that the Syzygy tablebases are -more compact than Nalimov tablebases, while still storing all information -needed for optimal play and in addition being able to take into account -the 50-move rule. - -## Large Pages - -Stockfish supports large pages on Linux and Windows. Large pages make -the hash access more efficient, improving the engine speed, especially -on large hash sizes. Typical increases are 5..10% in terms of nodes per -second, but speed increases up to 30% have been measured. The support is -automatic. Stockfish attempts to use large pages when available and -will fall back to regular memory allocation when this is not the case. - -### Support on Linux - -Large page support on Linux is obtained by the Linux kernel -transparent huge pages functionality. Typically, transparent huge pages -are already enabled, and no configuration is needed. - -### Support on Windows - -The use of large pages requires "Lock Pages in Memory" privilege. See -[Enable the Lock Pages in Memory Option (Windows)][lockpages-link] -on how to enable this privilege, then run [RAMMap][rammap-link] -to double-check that large pages are used. We suggest that you reboot -your computer after you have enabled large pages, because long Windows -sessions suffer from memory fragmentation, which may prevent Stockfish -from getting large pages: a fresh session is better in this regard. - -## Compiling Stockfish yourself from the sources - -Stockfish has support for 32 or 64-bit CPUs, certain hardware -instructions, big-endian machines such as Power PC, and other platforms. - -On Unix-like systems, it should be easy to compile Stockfish -directly from the source code with the included Makefile in the folder -`src`. In general it is recommended to run `make help` to see a list of make -targets with corresponding descriptions. +On Unix-like systems, it should be easy to compile Stockfish directly from the +source code with the included Makefile in the folder `src`. In general, it is +recommended to run `make help` to see a list of make targets with corresponding +descriptions. ``` - cd src - make help - make net - make build ARCH=x86-64-modern +cd src +make -j build ARCH=x86-64-modern ``` -When not using the Makefile to compile (for instance, with Microsoft MSVC) you -need to manually set/unset some switches in the compiler command line; see -file *types.h* for a quick reference. +Detailed compilation instructions for all platforms can be found in our +[documentation][wiki-compile-link]. -When reporting an issue or a bug, please tell us which Stockfish version -and which compiler you used to create your executable. This information -can be found by typing the following command in a console: - -``` - ./stockfish compiler -``` - -## Understanding the code base and participating in the project - -Stockfish's improvement over the last decade has been a great community -effort. There are a few ways to help contribute to its growth. +## Contributing ### Donating hardware -Improving Stockfish requires a massive amount of testing. You can donate -your hardware resources by installing the [Fishtest Worker][worker-link] -and view the current tests on [Fishtest][fishtest-link]. +Improving Stockfish requires a massive amount of testing. You can donate your +hardware resources by installing the [Fishtest Worker][worker-link] and viewing +the current tests on [Fishtest][fishtest-link]. ### Improving the code -If you want to help improve the code, there are several valuable resources: - -* [In this wiki,][programming-link] many techniques used in +In the [chessprogramming wiki][programming-link], many techniques used in Stockfish are explained with a lot of background information. +The [section on Stockfish][programmingsf-link] describes many features +and techniques used by Stockfish. However, it is generic rather than +focused on Stockfish's precise implementation. -* [The section on Stockfish][programmingsf-link] -describes many features and techniques used by Stockfish. However, it is -generic rather than being focused on Stockfish's precise implementation. -Nevertheless, a helpful resource. - -* The latest source can always be found on [GitHub][github-link]. -Discussions about Stockfish take place these days mainly in the [FishCooking][fishcooking-link] -group and on the [Stockfish Discord channel][discord-link]. The engine testing is done on [Fishtest][fishtest-link]. If you want to help improve Stockfish, please read this [guideline][guideline-link] first, where the basics of Stockfish development are explained. +Discussions about Stockfish take place these days mainly in the Stockfish +[Discord server][discord-link]. This is also the best place to ask questions +about the codebase and how to improve it. ## Terms of use -Stockfish is free, and distributed under the **GNU General Public License version 3** -(GPL v3). Essentially, this means you are free to do almost exactly -what you want with the program, including distributing it among your -friends, making it available for download from your website, selling -it (either by itself or as part of some bigger software package), or -using it as the starting point for a software project of your own. +Stockfish is free and distributed under the +[**GNU General Public License version 3**][license-link] (GPL v3). Essentially, +this means you are free to do almost exactly what you want with the program, +including distributing it among your friends, making it available for download +from your website, selling it (either by itself or as part of some bigger +software package), or using it as the starting point for a software project of +your own. -The only real limitation is that whenever you distribute Stockfish in -some way, you MUST always include the license and the full source code -(or a pointer to where the source code can be found) to generate the -exact binary you are distributing. If you make any changes to the -source code, these changes must also be made available under the GPL v3. - -For full details, read the copy of the GPL v3 found in the file named -[*Copying.txt*][license-link]. +The only real limitation is that whenever you distribute Stockfish in some way, +you MUST always include the license and the full source code (or a pointer to +where the source code can be found) to generate the exact binary you are +distributing. If you make any changes to the source code, these changes must +also be made available under GPL v3. [authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS [build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml [commits-link]: https://github.com/official-stockfish/Stockfish/commits/master [discord-link]: https://discord.gg/GWDRS3kU6R -[fishcooking-link]: https://groups.google.com/g/fishcooking +[issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml +[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new [fishtest-link]: https://tests.stockfishchess.org/tests -[github-link]: https://github.com/official-stockfish/Stockfish [guideline-link]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test [license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt -[lockpages-link]: https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows -[nodchip-link]: https://github.com/nodchip/Stockfish [programming-link]: https://www.chessprogramming.org/Main_Page [programmingsf-link]: https://www.chessprogramming.org/Stockfish -[pytorch-link]: https://github.com/glinscott/nnue-pytorch -[rammap-link]: https://docs.microsoft.com/en-us/sysinternals/downloads/rammap [readme-link]: https://github.com/official-stockfish/Stockfish/blob/master/README.md [release-link]: https://github.com/official-stockfish/Stockfish/releases/latest [src-link]: https://github.com/official-stockfish/Stockfish/tree/master/src [stockfish128-logo]: https://stockfishchess.org/images/logo/icon_128x128.png -[tools-link]: https://github.com/official-stockfish/Stockfish/tree/tools -[uci-link]: https://www.shredderchess.com/download/div/uci.zip +[uci-link]: https://backscattering.de/chess/uci/ [website-link]: https://stockfishchess.org -[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview +[website-blog-link]: https://stockfishchess.org/blog/ +[wiki-link]: https://github.com/official-stockfish/Stockfish/wiki +[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage +[wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source +[wiki-commands-link]: https://github.com/official-stockfish/Stockfish/wiki/Commands +[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker [build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github [commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge From cdec775a1555ef83cb9c878c42a11b7dde0a627b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 4 Mar 2023 18:14:23 +0100 Subject: [PATCH 203/678] Add CITATION.cff file Make the stockfish software more easily citable, for example in academic papers. fixes https://github.com/official-stockfish/Stockfish/issues/4419 closes https://github.com/official-stockfish/Stockfish/pull/4422 No functional change --- CITATION.cff | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..bc0889a8 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,23 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: Stockfish +message: >- + Please cite this software using the metadata from this + file. +type: software +authors: + - name: The Stockfish developers (see AUTHORS file) +repository-code: 'https://github.com/official-stockfish/Stockfish' +url: 'https://stockfishchess.org/' +repository-artifact: 'https://stockfishchess.org/download/' +abstract: Stockfish is a free and strong UCI chess engine. +keywords: + - chess + - artificial intelligence (AI) + - tree search + - alpha-beta search + - neural networks (NN) + - efficiently updatable neural networks (NNUE) +license: GPL-3.0 From 70dfa141d560c18cd1aa28884b7cd8ab0f094944 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Sun, 5 Mar 2023 17:10:52 +0200 Subject: [PATCH 204/678] Clarify the description of the x86-64-vnni256 and x86-64-avxvnni architectures Now it is clearly explained that "x86-64-vnni256" requires full support of AVX512-VNNI, but only 256-bit operands are used. closes https://github.com/official-stockfish/Stockfish/pull/4427 No functional change --- AUTHORS | 1 + src/Makefile | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 634de4a3..49f7009f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -134,6 +134,7 @@ Matt Ginsberg (mattginsberg) Matthew Lai (matthewlai) Matthew Sullivan (Matt14916) Max A. (Disservin) +Maxim Masiutin (maximmasiutin) Maxim Molchanov (Maxim) Michael An (man) Michael Byrne (MichaelB7) diff --git a/src/Makefile b/src/Makefile index 3d6432fd..774ba6ea 100644 --- a/src/Makefile +++ b/src/Makefile @@ -92,7 +92,7 @@ VPATH = syzygy:nnue:nnue/features # avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 # avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX # avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 -# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256 +# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands # vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 # neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture # dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions @@ -773,10 +773,10 @@ help: @echo "" @echo "Supported archs:" @echo "" - @echo "x86-64-vnni512 > x86 64-bit with vnni support 512bit wide" - @echo "x86-64-vnni256 > x86 64-bit with vnni support 256bit wide" + @echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" + @echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" @echo "x86-64-avx512 > x86 64-bit with avx512 support" - @echo "x86-64-avxvnni > x86 64-bit with avxvnni support" + @echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" @echo "x86-64-bmi2 > x86 64-bit with bmi2 support" @echo "x86-64-avx2 > x86 64-bit with avx2 support" @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" From 6ce225bb4c31298b131714eff67b56de3b8ee78d Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 5 Mar 2023 17:26:12 +0100 Subject: [PATCH 205/678] Fix TB after capture_stage fix https://github.com/official-stockfish/Stockfish/commit/5c75c1c2fbb7bb4f0bf7c44fb855c415b788cbf7 introduced a capture_stage() function, but TB usage needs a pure capture() function. closes https://github.com/official-stockfish/Stockfish/pull/4428 No functional change. --- src/position.h | 11 ++++++++--- src/syzygy/tbprobe.cpp | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/position.h b/src/position.h index 485540ef..1fc6b3b8 100644 --- a/src/position.h +++ b/src/position.h @@ -125,6 +125,7 @@ public: // Properties of moves bool legal(Move m) const; bool pseudo_legal(const Move m) const; + bool capture(Move m) const; bool capture_stage(Move m) const; bool gives_check(Move m) const; Piece moved_piece(Move m) const; @@ -381,14 +382,18 @@ inline bool Position::is_chess960() const { return chess960; } +inline bool Position::capture(Move m) const { + assert(is_ok(m)); + return (!empty(to_sq(m)) && type_of(m) != CASTLING) + || type_of(m) == EN_PASSANT; +} + // returns true if a move is generated from the capture stage // having also queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { assert(is_ok(m)); - return (!empty(to_sq(m)) && type_of(m) != CASTLING) - || (type_of(m) == PROMOTION && promotion_type(m) == QUEEN) - || type_of(m) == EN_PASSANT; + return capture(m) || (type_of(m) == PROMOTION && promotion_type(m) == QUEEN); } inline Piece Position::captured_piece() const { diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 2a9e1b68..b594ac37 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1203,7 +1203,7 @@ WDLScore search(Position& pos, ProbeState* result) { for (const Move move : moveList) { - if ( !pos.capture_stage(move) + if ( !pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) continue; @@ -1472,7 +1472,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { for (const Move move : MoveList(pos)) { - bool zeroing = pos.capture_stage(move) || type_of(pos.moved_piece(move)) == PAWN; + bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; pos.do_move(move, st); From 39da50ed23ee3f1bd32a58c8f02471faa9a9fd63 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 7 Mar 2023 01:54:20 +0300 Subject: [PATCH 206/678] Do more negative extensions This patch does negatively extend transposition table move if singular search failed and tt value is not bigger than alpha. Logic is close to what we had before recent simplification of negative extensions but uses or condition instead of and condition. Passed STC: https://tests.stockfishchess.org/tests/view/6404c8102644b62c33934607 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 119040 W: 31841 L: 31416 D: 55783 Ptnml(0-2): 356, 13070, 32292, 13397, 405 Passed LTC: https://tests.stockfishchess.org/tests/view/6405abda2644b62c33937119 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 47216 W: 12816 L: 12496 D: 21904 Ptnml(0-2): 12, 4500, 14286, 4776, 34 closes https://github.com/official-stockfish/Stockfish/pull/4430 bench 4747020 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index fcdb8d67..349d4389 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1109,6 +1109,10 @@ moves_loop: // When in check, search starts here // If the eval of ttMove is less than value, we reduce it (negative extension) else if (ttValue <= value) extension = -1; + + // If the eval of ttMove is less than alpha, we reduce it (negative extension) + else if (ttValue <= alpha) + extension = -1; } // Check extensions (~1 Elo) From a48573e15fbd18fecb087c9fc02c38a07cece68b Mon Sep 17 00:00:00 2001 From: Dubslow Date: Thu, 9 Mar 2023 18:33:13 -0600 Subject: [PATCH 207/678] More negative extensions on nonsingular nonpv nodes. Following up the previous gainer also in this nonsingular node section of code. Credit shared with @FauziAkram for realizing this nonsingular node stuff had some potential, and @XInTheDark for reminding us that !PvNodes better handle extensions/reductions than Pv. Passed STC: https://tests.stockfishchess.org/tests/view/640a7bb32644b62c339457c3 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 136776 W: 36598 L: 36149 D: 64029 Ptnml(0-2): 439, 14834, 37384, 15301, 430 Passed LTC: https://tests.stockfishchess.org/tests/view/640c43a02644b62c3394b23c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 79536 W: 21363 L: 20984 D: 37189 Ptnml(0-2): 28, 7525, 24285, 7900, 30 closes https://github.com/official-stockfish/Stockfish/pull/4441 Bench: 4444953 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 349d4389..582e4457 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1104,7 +1104,7 @@ moves_loop: // When in check, search starts here // If the eval of ttMove is greater than beta, we reduce it (negative extension) else if (ttValue >= beta) - extension = -2; + extension = -2 - !PvNode; // If the eval of ttMove is less than value, we reduce it (negative extension) else if (ttValue <= value) From 78532af9dc585a81730d5d28763cc9d5273f96d1 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 13 Mar 2023 15:57:58 +0300 Subject: [PATCH 208/678] Do more singular extensions This patch continues trend of last VLTC tuning - as measured by dubslow most of it gains was in lowering marging in calculation of singularBeta. This patch is a manual adjustment on top of it - it lowers multiplier of depth in calculation of singularBeta even further, from 2/3 to 1,5/2,5. Was negative at STC: https://tests.stockfishchess.org/tests/view/64089c632644b62c3393fc12 Elo: -2.49 +-1.7 (95%) LOS: 0.2% Total: 40000 W: 10601 L: 10888 D: 18511 Ptnml(0-2): 123, 4580, 10875, 4305, 117 nElo: -5.03 +-3.4 (95%) PairsRatio: 0.94 Passed 180+1.8 SPRT: https://tests.stockfishchess.org/tests/view/640096dae74a12625bcf3b33 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 160952 W: 43753 L: 43342 D: 73857 Ptnml(0-2): 25, 13984, 52039, 14411, 17 Passed 60+0.6 8 threads SPRT: https://tests.stockfishchess.org/tests/view/640dca8e65775d3b539cb7f6 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 38824 W: 10825 L: 10554 D: 17445 Ptnml(0-2): 0, 2939, 13268, 3200, 5 closes https://github.com/official-stockfish/Stockfish/pull/4443 bench 4776866 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 582e4457..7eb4a0c5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1071,7 +1071,7 @@ moves_loop: // When in check, search starts here && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (2 + (ss->ttPv && !PvNode)) * depth; + Value singularBeta = ttValue - (3 + 2 * (ss->ttPv && !PvNode)) * depth / 2; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; From 55896a1384b022624a8c33b9f7f51b6b551eed50 Mon Sep 17 00:00:00 2001 From: Alfredo Menezes Date: Sun, 12 Mar 2023 22:29:07 -0300 Subject: [PATCH 209/678] Change mode of incbin.h Keep incbin.h with the same mode as the other source files. A mode diff might show up when working with patch files or sending the source code between devices. This patch should fix such behaviour. closes https://github.com/official-stockfish/Stockfish/pull/4442 No functional change --- src/incbin/incbin.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/incbin/incbin.h diff --git a/src/incbin/incbin.h b/src/incbin/incbin.h old mode 100755 new mode 100644 From d1e17989b51f220629e4e81a4bf413974f4b18e2 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sat, 11 Mar 2023 22:08:35 +0100 Subject: [PATCH 210/678] Fix Makefile for clang 16 The clang 16 release will remove the -fexperimental-new-pass-manager flag (see https://github.com/llvm/llvm-project/commit/69b2b7282e92a1b576b7bd26f3b16716a5027e8e). Thus, the commit adapts the Makefile to use this flag only for older clang versions. closes https://github.com/official-stockfish/Stockfish/pull/4437 No functional change --- src/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 774ba6ea..e257bc63 100644 --- a/src/Makefile +++ b/src/Makefile @@ -584,7 +584,10 @@ ifeq ($(optimize),yes) endif ifeq ($(comp),clang) - CXXFLAGS += -fexperimental-new-pass-manager + clangmajorversion = $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) + ifeq ($(shell expr $(clangmajorversion) \< 16),1) + CXXFLAGS += -fexperimental-new-pass-manager + endif endif endif From 7077fbdd1481829a0a20b6975c4245609118b938 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 11 Mar 2023 11:51:08 -0800 Subject: [PATCH 211/678] Remove redundant condition from capture_stage() Change a non functional promotion check to an assert. closes https://github.com/official-stockfish/Stockfish/pull/4436 No functional change --- src/position.cpp | 3 +-- src/position.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37aa2e9e..632a40b5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -569,8 +569,7 @@ bool Position::pseudo_legal(const Move m) const { : MoveList(*this).contains(m); // Is not a promotion, so promotion piece must be empty - if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) - return false; + assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); // If the 'from' square is not occupied by a piece belonging to the side to // move, the move is obviously not legal. diff --git a/src/position.h b/src/position.h index 1fc6b3b8..cc606a5a 100644 --- a/src/position.h +++ b/src/position.h @@ -393,7 +393,7 @@ inline bool Position::capture(Move m) const { // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { assert(is_ok(m)); - return capture(m) || (type_of(m) == PROMOTION && promotion_type(m) == QUEEN); + return capture(m) || promotion_type(m) == QUEEN; } inline Piece Position::captured_piece() const { From f0556dcbe3ba2fc804ab26d4552446602a75f064 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Wed, 1 Mar 2023 10:29:51 +0100 Subject: [PATCH 212/678] Small cleanups remove some unneeded assignments, typos, incorrect comments, add authors entry. closes https://github.com/official-stockfish/Stockfish/pull/4417 no functional change --- AUTHORS | 1 + src/nnue/nnue_feature_transformer.h | 2 +- src/position.cpp | 3 --- src/search.cpp | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 49f7009f..9b36111e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -190,6 +190,7 @@ Sergei Antonov (saproj) Sergei Ivanov (svivanov72) Sergio Vieri (sergiovieri) sf-x +Shahin M. Shahin (peregrine) Shane Booth (shane31) Shawn Varghese (xXH4CKST3RXx) Siad Daboul (Topologist) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 8087ea55..a1888c7a 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -363,7 +363,7 @@ namespace Stockfish::Eval::NNUE { // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. // All states must be sequential, that is states_to_update[i] must either be reachable // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. - // computed_st must be reachable by repeatadly applying ->previous on states_to_update[0], if not nullptr. + // computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr. template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { static_assert(N > 0); diff --git a/src/position.cpp b/src/position.cpp index 632a40b5..aeac23a3 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -765,9 +765,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update board and piece lists remove_piece(capsq); - if (type_of(m) == EN_PASSANT) - board[capsq] = NO_PIECE; - // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; diff --git a/src/search.cpp b/src/search.cpp index 7eb4a0c5..d6571a14 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -601,7 +601,6 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss+1)->ttPv = false; (ss+1)->excludedMove = bestMove = MOVE_NONE; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; (ss+2)->cutoffCnt = 0; @@ -1075,7 +1074,6 @@ moves_loop: // When in check, search starts here Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; - // the search with excludedMove will update ss->staticEval value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); ss->excludedMove = MOVE_NONE; From 515b66f18833ed87e97313d2ec4dfa4e2329d3df Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Sun, 12 Mar 2023 01:22:55 +0300 Subject: [PATCH 213/678] Fix null move issue Fix altering for stats landing on B1 Square after a null move and fix considering counter-moves on A1 for root node. fixes https://github.com/official-stockfish/Stockfish/issues/4333 by preventing calls to from_sq and to_sq functions over null-moves and none-moves. closes https://github.com/official-stockfish/Stockfish/pull/4448 bench: 4980082 --- src/search.cpp | 19 ++++++++++++------- src/types.h | 10 ++++++---- src/uci.cpp | 6 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d6571a14..466e0d6f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -605,7 +605,7 @@ namespace { (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; (ss+2)->cutoffCnt = 0; ss->doubleExtensions = (ss-1)->doubleExtensions; - Square prevSq = to_sq((ss-1)->currentMove); + Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; // Initialize statScore to zero for the grandchildren of the current position. // So statScore is shared between all grandchildren and only the first grandchild @@ -647,7 +647,7 @@ namespace { update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) - if ((ss-1)->moveCount <= 2 && !priorCapture) + if (prevSq != SQ_NONE && (ss-1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); } // Penalty for a quiet ttMove that fails low (~1 Elo) @@ -935,7 +935,7 @@ moves_loop: // When in check, search starts here nullptr , (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; - Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; + Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, @@ -1383,7 +1383,7 @@ moves_loop: // When in check, search starts here quietsSearched, quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low - else if (!priorCapture) + else if (!priorCapture && prevSq != SQ_NONE) { int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1525,7 +1525,7 @@ moves_loop: // When in check, search starts here // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = to_sq((ss-1)->currentMove); + Square prevSq = (ss-1)->currentMove != MOVE_NULL ? to_sq((ss-1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, @@ -1714,7 +1714,8 @@ moves_loop: // When in check, search starts here Thread* thisThread = pos.this_thread(); CapturePieceToHistory& captureHistory = thisThread->captureHistory; Piece moved_piece = pos.moved_piece(bestMove); - PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); + PieceType captured; + int bonus1 = stat_bonus(depth + 1); if (!pos.capture_stage(bestMove)) @@ -1733,12 +1734,16 @@ moves_loop: // When in check, search starts here } } else + { // Increase stats for the best move in case it was a capture move + captured = type_of(pos.piece_on(to_sq(bestMove))); captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; + } // Extra penalty for a quiet early move that was not a TT move or // main killer move in previous ply when it gets refuted. - if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) + if ( prevSq != SQ_NONE + && ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) && !pos.captured_piece()) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); diff --git a/src/types.h b/src/types.h index 37ce343a..06b0a059 100644 --- a/src/types.h +++ b/src/types.h @@ -416,6 +416,10 @@ inline Color color_of(Piece pc) { return Color(pc >> 3); } +constexpr bool is_ok(Move m) { + return m != MOVE_NONE && m != MOVE_NULL; +} + constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } @@ -445,10 +449,12 @@ constexpr Direction pawn_push(Color c) { } constexpr Square from_sq(Move m) { + assert(is_ok(m)); return Square((m >> 6) & 0x3F); } constexpr Square to_sq(Move m) { + assert(is_ok(m)); return Square(m & 0x3F); } @@ -473,10 +479,6 @@ constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -constexpr bool is_ok(Move m) { - return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE -} - /// Based on a congruential pseudo random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; diff --git a/src/uci.cpp b/src/uci.cpp index 3883b3d3..d3d99243 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -358,15 +358,15 @@ std::string UCI::square(Square s) { string UCI::move(Move m, bool chess960) { - Square from = from_sq(m); - Square to = to_sq(m); - if (m == MOVE_NONE) return "(none)"; if (m == MOVE_NULL) return "0000"; + Square from = from_sq(m); + Square to = to_sq(m); + if (type_of(m) == CASTLING && !chess960) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); From af4b62a593cc4fa6d7d34110c41301028a5c9695 Mon Sep 17 00:00:00 2001 From: disservin Date: Mon, 13 Mar 2023 19:35:27 +0100 Subject: [PATCH 214/678] NNUE namespace cleanup This patch moves the nnue namespace in the appropiate header that correspondes with the definition. It also makes navigation a bit easier. closes https://github.com/official-stockfish/Stockfish/pull/4445 No functional change --- src/evaluate.cpp | 6 +++--- src/evaluate.h | 8 -------- src/nnue/evaluate_nnue.h | 8 ++++++++ src/search.cpp | 1 + src/uci.cpp | 1 + 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index cf6f23ea..99b87300 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -36,7 +36,7 @@ #include "timeman.h" #include "uci.h" #include "incbin/incbin.h" - +#include "nnue/evaluate_nnue.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). @@ -95,7 +95,7 @@ namespace Eval { if (directory != "") { ifstream stream(directory + eval_file, ios::binary); - if (load_eval(eval_file, stream)) + if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } @@ -111,7 +111,7 @@ namespace Eval { (void) gEmbeddedNNUEEnd; // Silence warning on unused variable istream stream(&buffer); - if (load_eval(eval_file, stream)) + if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } } diff --git a/src/evaluate.h b/src/evaluate.h index 5238cb81..3615fe6d 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -43,17 +43,9 @@ namespace Eval { namespace NNUE { - std::string trace(Position& pos); - Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); - void hint_common_parent_position(const Position& pos); - void init(); void verify(); - bool load_eval(std::string name, std::istream& stream); - bool save_eval(std::ostream& stream); - bool save_eval(const std::optional& filename); - } // namespace NNUE } // namespace Eval diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 15638cae..b84bed8b 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -55,6 +55,14 @@ namespace Stockfish::Eval::NNUE { template using LargePagePtr = std::unique_ptr>; + std::string trace(Position& pos); + Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); + void hint_common_parent_position(const Position& pos); + + bool load_eval(std::string name, std::istream& stream); + bool save_eval(std::ostream& stream); + bool save_eval(const std::optional& filename); + } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 466e0d6f..17c1c28b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -34,6 +34,7 @@ #include "tt.h" #include "uci.h" #include "syzygy/tbprobe.h" +#include "nnue/evaluate_nnue.h" namespace Stockfish { diff --git a/src/uci.cpp b/src/uci.cpp index d3d99243..8f9684ee 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -32,6 +32,7 @@ #include "tt.h" #include "uci.h" #include "syzygy/tbprobe.h" +#include "nnue/evaluate_nnue.h" using namespace std; From 02e4697055519ed206fa76e4ef9abb9f156cd1a0 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Mon, 13 Mar 2023 18:32:40 +0100 Subject: [PATCH 215/678] Remove 'si' StateInfo variable/parameter. Since st is a member of position we don't need to pass it separately as parameter. While being there also remove some line in pos_is_ok, where a copy of StateInfo was made by using default copy constructor and then verified it's correctedness by doing a memcmp. There is no point in doing that. Passed non-regression test https://tests.stockfishchess.org/tests/view/64098d562644b62c33942b35 LLR: 3.24 (-2.94,2.94) <-1.75,0.25> Total: 548960 W: 145834 L: 146134 D: 256992 Ptnml(0-2): 1617, 57652, 156261, 57314, 1636 closes https://github.com/official-stockfish/Stockfish/pull/4444 No functional change --- src/position.cpp | 58 ++++++++++++++++++++++-------------------------- src/position.h | 4 ++-- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index aeac23a3..171193ec 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -282,7 +282,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th chess960 = isChess960; thisThread = th; - set_state(st); + set_state(); assert(pos_is_ok()); @@ -313,19 +313,19 @@ void Position::set_castling_right(Color c, Square rfrom) { /// Position::set_check_info() sets king attacks to detect if a move gives check -void Position::set_check_info(StateInfo* si) const { +void Position::set_check_info() const { - si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), si->pinners[BLACK]); - si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), si->pinners[WHITE]); + st->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), st->pinners[BLACK]); + st->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), st->pinners[WHITE]); Square ksq = square(~sideToMove); - si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); - si->checkSquares[KNIGHT] = attacks_bb(ksq); - si->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); - si->checkSquares[ROOK] = attacks_bb(ksq, pieces()); - si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK]; - si->checkSquares[KING] = 0; + st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[KNIGHT] = attacks_bb(ksq); + st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); + st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); + st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; + st->checkSquares[KING] = 0; } @@ -334,39 +334,39 @@ void Position::set_check_info(StateInfo* si) const { /// The function is only used when a new position is set up, and to verify /// the correctness of the StateInfo data when running in debug mode. -void Position::set_state(StateInfo* si) const { +void Position::set_state() const { - si->key = si->materialKey = 0; - si->pawnKey = Zobrist::noPawns; - si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; - si->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + st->key = st->materialKey = 0; + st->pawnKey = Zobrist::noPawns; + st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; + st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); - set_check_info(si); + set_check_info(); for (Bitboard b = pieces(); b; ) { Square s = pop_lsb(b); Piece pc = piece_on(s); - si->key ^= Zobrist::psq[pc][s]; + st->key ^= Zobrist::psq[pc][s]; if (type_of(pc) == PAWN) - si->pawnKey ^= Zobrist::psq[pc][s]; + st->pawnKey ^= Zobrist::psq[pc][s]; else if (type_of(pc) != KING) - si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; + st->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; } - if (si->epSquare != SQ_NONE) - si->key ^= Zobrist::enpassant[file_of(si->epSquare)]; + if (st->epSquare != SQ_NONE) + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; if (sideToMove == BLACK) - si->key ^= Zobrist::side; + st->key ^= Zobrist::side; - si->key ^= Zobrist::castling[si->castlingRights]; + st->key ^= Zobrist::castling[st->castlingRights]; for (Piece pc : Pieces) for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - si->materialKey ^= Zobrist::psq[pc][cnt]; + st->materialKey ^= Zobrist::psq[pc][cnt]; } @@ -865,7 +865,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { sideToMove = ~sideToMove; // Update king attacks used for fast check detection - set_check_info(st); + set_check_info(); // Calculate the repetition info. It is the ply distance from the previous // occurrence of the same position, negative in the 3-fold case, or zero @@ -1017,7 +1017,7 @@ void Position::do_null_move(StateInfo& newSt) { sideToMove = ~sideToMove; - set_check_info(st); + set_check_info(); st->repetition = 0; @@ -1320,12 +1320,6 @@ bool Position::pos_is_ok() const { if (p1 != p2 && (pieces(p1) & pieces(p2))) assert(0 && "pos_is_ok: Bitboards"); - StateInfo si = *st; - ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize); - - set_state(&si); - if (std::memcmp(&si, st, sizeof(StateInfo))) - assert(0 && "pos_is_ok: State"); for (Piece pc : Pieces) if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) diff --git a/src/position.h b/src/position.h index cc606a5a..d3483bcf 100644 --- a/src/position.h +++ b/src/position.h @@ -179,8 +179,8 @@ public: private: // Initialization helpers (used while setting up a position) void set_castling_right(Color c, Square rfrom); - void set_state(StateInfo* si) const; - void set_check_info(StateInfo* si) const; + void set_state() const; + void set_check_info() const; // Other helpers void move_piece(Square from, Square to); From 24b37e4586ba610d331048446bd036bec5544c4f Mon Sep 17 00:00:00 2001 From: pb00067 Date: Mon, 20 Mar 2023 08:56:44 +0100 Subject: [PATCH 216/678] Verified SEE pruning for capturing and checking moves. Patch analyzes field after SEE exchanges concluded with a recapture by the opponent: if opponent Queen/Rook/King results under attack after the exchanges, we consider the move sharp and don't prune it. Important note: By accident I forgot to adjust 'occupied' when the king takes part in the exchanges. As result of this a move is considered sharp too, when opponent king apparently can evade check by recapturing. Surprisingly this seems contribute to patch's strength. STC: https://tests.stockfishchess.org/tests/view/640b16132644b62c33947397 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 116400 W: 31239 L: 30817 D: 54344 Ptnml(0-2): 350, 12742, 31618, 13116, 374 LTC: https://tests.stockfishchess.org/tests/view/640c88092644b62c3394c1c5 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 177600 W: 47988 L: 47421 D: 82191 Ptnml(0-2): 62, 16905, 54317, 17436, 80 closes https://github.com/official-stockfish/Stockfish/pull/4453 bench: 5012145 --- src/movepick.cpp | 6 +++--- src/movepick.h | 1 + src/position.cpp | 15 +++++++-------- src/position.h | 2 +- src/search.cpp | 30 +++++++++++++++++++++++++----- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 36ee46b5..855f2b1d 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -95,7 +95,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) - && pos.see_ge(ttm, threshold)); + && pos.see_ge(ttm, occupied, threshold)); } /// MovePicker::score() assigns a numerical value to each move in a list, used @@ -197,7 +197,7 @@ top: case GOOD_CAPTURE: if (select([&](){ - return pos.see_ge(*cur, Value(-cur->value)) ? + return pos.see_ge(*cur, occupied, Value(-cur->value)) ? // Move losing capture to endBadCaptures to be tried later true : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); @@ -264,7 +264,7 @@ top: return select([](){ return true; }); case PROBCUT: - return select([&](){ return pos.see_ge(*cur, threshold); }); + return select([&](){ return pos.see_ge(*cur, occupied, threshold); }); case QCAPTURE: if (select([&](){ return depth > DEPTH_QS_RECAPTURES diff --git a/src/movepick.h b/src/movepick.h index b6c07378..725607b8 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -150,6 +150,7 @@ private: Value threshold; Depth depth; ExtMove moves[MAX_MOVES]; + Bitboard occupied; }; } // namespace Stockfish diff --git a/src/position.cpp b/src/position.cpp index 171193ec..ba6888eb 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1062,7 +1062,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value threshold) const { +bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { assert(is_ok(m)); @@ -1081,7 +1081,7 @@ bool Position::see_ge(Move m, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - Bitboard occupied = pieces() ^ from ^ to; + occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1112,45 +1112,44 @@ bool Position::see_ge(Move m, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = PawnValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { + occupied ^= least_significant_square_bb(bb); if ((swap = KnightValueMg - swap) < res) break; - - occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { + occupied ^= least_significant_square_bb(bb); if ((swap = BishopValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { + occupied ^= least_significant_square_bb(bb); if ((swap = RookValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = QueenValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); } diff --git a/src/position.h b/src/position.h index d3483bcf..670b621c 100644 --- a/src/position.h +++ b/src/position.h @@ -144,7 +144,7 @@ public: void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index 17c1c28b..7564c109 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1019,9 +1019,27 @@ moves_loop: // When in check, search starts here + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; + Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-206) * depth)) - continue; + if (!pos.see_ge(move, occupied, Value(-206) * depth)) + { + if (depth < 2 - capture) + continue; + // don't prune move if a heavy enemy piece (KQR) is under attack after the exchanges + Bitboard leftEnemies = (pos.pieces(~us, QUEEN, ROOK) | pos.pieces(~us, KING)) & occupied; + Bitboard attacks = 0; + occupied |= to_sq(move); + while (leftEnemies && !attacks) + { + Square sq = pop_lsb(leftEnemies); + attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; + // exclude Queen/Rook(s) which were already threatened before SEE + if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) + attacks = 0; + } + if (!attacks) + continue; + } } else { @@ -1047,8 +1065,9 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); + Bitboard occupied; // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, occupied, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } @@ -1533,6 +1552,7 @@ moves_loop: // When in check, search starts here prevSq); int quietCheckEvasions = 0; + Bitboard occupied; // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1569,7 +1589,7 @@ moves_loop: // When in check, search starts here continue; } - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, occupied, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1588,7 +1608,7 @@ moves_loop: // When in check, search starts here continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-110))) + if (!pos.see_ge(move, occupied, Value(-110))) continue; } From b973e40e45d75c3b3391141d149d4186494d4652 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 21 Mar 2023 23:58:25 +0300 Subject: [PATCH 217/678] Update Elo estimates for terms in search Setting the Elo value of some functions which were not set before. All tests run at 10+0.1 (STC), 25000 games (Same as #4294). Book used: UHO_XXL_+0.90_+1.19.epd Values are rounded to the nearest non-negative integer. Test links: https://tests.stockfishchess.org/tests/view/6419ab5b65775d3b539f46c6 https://tests.stockfishchess.org/tests/view/6419adb465775d3b539f4730 https://tests.stockfishchess.org/tests/view/6419ae9c65775d3b539f4756 https://tests.stockfishchess.org/tests/view/6419b03f65775d3b539f47a8 https://tests.stockfishchess.org/tests/view/6419b35d65775d3b539f4860 https://tests.stockfishchess.org/tests/view/6419b6b965775d3b539f48e6 https://tests.stockfishchess.org/tests/view/6419cade65775d3b539f4cd5 https://tests.stockfishchess.org/tests/view/6419cbb565775d3b539f4d01 https://tests.stockfishchess.org/tests/view/6419cc6965775d3b539f4d1e closes https://github.com/official-stockfish/Stockfish/pull/4459 No functional change --- src/search.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7564c109..b2983f66 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -732,7 +732,7 @@ namespace { } else if (excludedMove) { - // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 elo) + // 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); eval = ss->staticEval; complexity = abs(ss->staticEval - pos.psq_eg_stm()); @@ -1120,15 +1120,15 @@ moves_loop: // When in check, search starts here else if (singularBeta >= beta) return singularBeta; - // If the eval of ttMove is greater than beta, we reduce it (negative extension) + // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; - // If the eval of ttMove is less than value, we reduce it (negative extension) + // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; - // If the eval of ttMove is less than alpha, we reduce it (negative extension) + // If the eval of ttMove is less than alpha, we reduce it (negative extension) (~1 Elo) else if (ttValue <= alpha) extension = -1; } @@ -1182,7 +1182,7 @@ moves_loop: // When in check, search starts here if (ttCapture) r++; - // Decrease reduction for PvNodes based on depth + // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) r -= 1 + 12 / (3 + depth); @@ -1195,11 +1195,11 @@ moves_loop: // When in check, search starts here && (mp.threatenedPieces & from_sq(move))) r--; - // Increase reduction if next ply has a lot of fail high + // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss+1)->cutoffCnt > 3) r++; - // Decrease reduction if move is a killer and we have a good history + // Decrease reduction if move is a killer and we have a good history (~1 Elo) if (move == ss->killers[0] && (*contHist[0])[movedPiece][to_sq(move)] >= 3722) r--; @@ -1210,7 +1210,7 @@ moves_loop: // When in check, search starts here + (*contHist[3])[movedPiece][to_sq(move)] - 4182; - // Decrease/increase reduction for moves with a good/bad history (~30 Elo) + // Decrease/increase reduction for moves with a good/bad history (~25 Elo) r -= ss->statScore / (11791 + 3992 * (depth > 6 && depth < 19)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) @@ -1347,7 +1347,7 @@ moves_loop: // When in check, search starts here { alpha = value; - // Reduce other moves if we have found at least one score improvement + // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 && depth < 6 && beta < 10534 @@ -1413,7 +1413,7 @@ moves_loop: // When in check, search starts here 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. + // opponent move is probably good and the new position is added to the search tree. (~7 Elo) if (bestValue <= alpha) ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); @@ -1432,7 +1432,7 @@ moves_loop: // When in check, search starts here // qsearch() is the quiescence search function, which is called by the main search // function with zero depth, or recursively with further decreasing depth per call. - // (~155 elo) + // (~155 Elo) template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { From 1b5738e0c958ac8d3d140a3d182b85f8c0c0cd2c Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 24 Mar 2023 07:45:22 +0300 Subject: [PATCH 218/678] Simplify statScore initialization This patch simplifies initialization of statScore to "always set it up to 0" instead of setting it up to 0 two plies deeper. Reason for why it was done in previous way partially was because of LMR usage of previous statScore which was simplified long time ago so it makes sense to make in more simple there. Passed STC: https://tests.stockfishchess.org/tests/view/641a86d1db43ab2ba6f7b31d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 115648 W: 30895 L: 30764 D: 53989 Ptnml(0-2): 368, 12741, 31473, 12876, 366 Passed LTC: https://tests.stockfishchess.org/tests/view/641b1c31db43ab2ba6f7d17a LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 175576 W: 47122 L: 47062 D: 81392 Ptnml(0-2): 91, 17077, 53390, 17141, 89 closes https://github.com/official-stockfish/Stockfish/pull/4460 bench 5081969 --- src/search.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b2983f66..d2358ea2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -607,14 +607,7 @@ namespace { (ss+2)->cutoffCnt = 0; ss->doubleExtensions = (ss-1)->doubleExtensions; Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - - // Initialize statScore to zero for the grandchildren of the current position. - // So statScore is shared between all grandchildren and only the first grandchild - // starts with statScore = 0. Later grandchildren start with the last calculated - // statScore of the previous grandchild. This influences the reduction rules in - // LMR which are based on the statScore of parent position. - if (!rootNode) - (ss+2)->statScore = 0; + ss->statScore = 0; // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; From 587bc647d7d14b53d8625c4446006e23a4acd82a Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 22 Mar 2023 16:50:55 +0900 Subject: [PATCH 219/678] Remove non_pawn_material in NNUE::evaluate After "Use NNUE complexity in search, retune related parameters" commit, the effect of non-pawn material adjustment has been nearly diminished. This patch removes pos.non_pawn_material as a simplification, which passed non-regression tests with both STC and LTC. Passed non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 75152 W: 20030 L: 19856 D: 35266 Ptnml(0-2): 215, 8281, 20459, 8357, 264 https://tests.stockfishchess.org/tests/view/641ab471db43ab2ba6f7bc58 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 193864 W: 51870 L: 51829 D: 90165 Ptnml(0-2): 86, 18968, 58794, 18987, 97 https://tests.stockfishchess.org/tests/view/641b4fe6db43ab2ba6f7db96 closes https://github.com/official-stockfish/Stockfish/pull/4461 Bench: 5020718 --- src/nnue/evaluate_nnue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index f33aa3b8..329adfda 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -148,7 +148,7 @@ namespace Stockfish::Eval::NNUE { // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; - int delta = 24 - pos.non_pawn_material() / 9560; + constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType transformedFeaturesUnaligned[ From 43108a619899af084e45224e8744ca668a9efed2 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 28 Mar 2023 19:53:43 +0200 Subject: [PATCH 220/678] Reuse existing functions to read/write array of network parameters closes https://github.com/official-stockfish/Stockfish/pull/4463 No functional change --- src/nnue/layers/affine_transform.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 313b1568..f84f054e 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -256,8 +256,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Read network parameters bool read_parameters(std::istream& stream) { - for (IndexType i = 0; i < OutputDimensions; ++i) - biases[i] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) weights[get_weight_index(i)] = read_little_endian(stream); @@ -267,8 +266,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Write network parameters bool write_parameters(std::ostream& stream) const { - for (IndexType i = 0; i < OutputDimensions; ++i) - write_little_endian(stream, biases[i]); + write_little_endian(stream, biases, OutputDimensions); for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) write_little_endian(stream, weights[get_weight_index(i)]); @@ -452,8 +450,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Read network parameters bool read_parameters(std::istream& stream) { - for (IndexType i = 0; i < OutputDimensions; ++i) - biases[i] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) weights[get_weight_index(i)] = read_little_endian(stream); @@ -462,8 +459,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Write network parameters bool write_parameters(std::ostream& stream) const { - for (IndexType i = 0; i < OutputDimensions; ++i) - write_little_endian(stream, biases[i]); + write_little_endian(stream, biases, OutputDimensions); for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) write_little_endian(stream, weights[get_weight_index(i)]); From 37160c4b1632245d46d86cec7bd22b76f5a87531 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 27 Mar 2023 01:00:54 -0400 Subject: [PATCH 221/678] Update default net to nn-dabb1ed23026.nnue Created by retraining the master net with these modifications: * New filtering methods for existing data from T80 sep+oct2022, T79 apr2022, T78 jun+jul+aug+sep2022, T77 dec2021 * Adding new filtered data from T80 aug2022 and T78 apr+may2022 * Increasing early-fen-skipping from 28 to 30 ``` python3 easy_train.py \ --experiment-name leela96-dfrc99-T80novT79mayT60novdec-v2-T80augsepoctT79aprT78aprtosep-v6-T77dec-v3-sk30 \ --training-dataset /data/leela96-dfrc99-T80novT79mayT60novdec-v2-T80augsepoctT79aprT78aprtosep-v6-T77dec-v3.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes \ --start-from-engine-test-net True \ --early-fen-skipping 30 \ --max_epoch 900 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --gpus "0," \ --seed $RANDOM ``` The v3 filtering used for data from T77dec 2021 differs from v2 filtering in that: * To improve binpack compression, positions after ply 28 were skipped during training by setting position scores to VALUE_NONE (32002) instead of removing them entirely * All early-game positions with ply <= 28 were removed to maximize binpack compression * Only bestmove captures at d6pv2 search were skipped, not 2nd bestmove captures * Binpack compression was repaired for the remaining positions by effectively replacing bestmoves with "played moves" to maintain contiguous sequences of positions in the training game data After improving binpack compression, The T77 dec2021 data size was reduced from 95G to 19G. The v6 filtering used for data from T80augsepoctT79aprT78aprtosep 2022 differs from v2 in that: * All positions with only one legal move were removed * Tighter score differences at d6pv2 search were used to remove more positions with only one good move than before * d6pv2 search was not used to remove positions where the best 2 moves were captures ``` python3 interleave_binpacks.py \ nn-547-dataset/leela96-eval-filt-v2.binpack \ nn-547-dataset/dfrc99-eval-filt-v2.binpack \ nn-547-dataset/test80-nov2022-12tb7p-eval-filt-v2-d6.binpack \ nn-547-dataset/T79-may2022-12tb7p-eval-filt-v2.binpack \ nn-547-dataset/T60-nov2021-12tb7p-eval-filt-v2.binpack \ nn-547-dataset/T60-dec2021-12tb7p-eval-filt-v2.binpack \ filt-v6/test80-aug2022-16tb7p-filter-v6.binpack \ filt-v6/test80-sep2022-16tb7p-filter-v6.binpack \ filt-v6/test80-oct2022-16tb7p-filter-v6.binpack \ filt-v6/test79-apr2022-16tb7p-filter-v6.binpack \ filt-v6/test78-aprmay2022-16tb7p-filter-v6.binpack \ filt-v6/test78-junjulaug2022-16tb7p-filter-v6.binpack \ filt-v6/test78-sep2022-16tb7p-filter-v6.binpack \ filt-v3/test77-dec2021-16tb7p-filt-v3.binpack \ /data/leela96-dfrc99-T80novT79mayT60novdec-v2-T80augsepoctT79aprT78aprtosep-v6-T77dec-v3.binpack ``` The code for the new data filtering methods is available at: https://github.com/linrock/Stockfish/tree/nnue-data-v3/nnue-data The code for giving hexword names to .nnue files is at: https://github.com/linrock/nnue-namer Links for downloading the training data components can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch779.nnue : 0.6 +/- 3.1 Passed STC: https://tests.stockfishchess.org/tests/view/64212412db43ab2ba6f8efb0 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 82256 W: 22185 L: 21809 D: 38262 Ptnml(0-2): 286, 9065, 22067, 9407, 303 Passed LTC: https://tests.stockfishchess.org/tests/view/64223726db43ab2ba6f91d6c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 30840 W: 8437 L: 8149 D: 14254 Ptnml(0-2): 14, 2891, 9323, 3177, 15 closes https://github.com/official-stockfish/Stockfish/pull/4465 bench 5101970 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 3615fe6d..61846073 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-52471d67216a.nnue" + #define EvalFileDefaultName "nn-dabb1ed23026.nnue" namespace NNUE { From a9c26357deb01c764cd16ef4e61acb4f687cbd77 Mon Sep 17 00:00:00 2001 From: Miguel Lahoz Date: Tue, 28 Mar 2023 00:06:24 +0800 Subject: [PATCH 222/678] Clean up repetitive declarations for see_ge The occupied bitboard is only used in one place and is otherwise thrown away. To simplify use, see_ge function can instead be overloaded. Repetitive declarations for occupied bitboard can be removed. Passed non-regression test https://tests.stockfishchess.org/tests/view/6421c286db43ab2ba6f908eb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 48912 W: 13196 L: 13001 D: 22715 Ptnml(0-2): 146, 5003, 13967, 5190, 150 closes https://github.com/official-stockfish/Stockfish/pull/4469 No functional change. --- src/movepick.cpp | 6 +++--- src/movepick.h | 1 - src/position.cpp | 5 +++++ src/position.h | 1 + src/search.cpp | 8 +++----- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 855f2b1d..36ee46b5 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -95,7 +95,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) - && pos.see_ge(ttm, occupied, threshold)); + && pos.see_ge(ttm, threshold)); } /// MovePicker::score() assigns a numerical value to each move in a list, used @@ -197,7 +197,7 @@ top: case GOOD_CAPTURE: if (select([&](){ - return pos.see_ge(*cur, occupied, Value(-cur->value)) ? + return pos.see_ge(*cur, Value(-cur->value)) ? // Move losing capture to endBadCaptures to be tried later true : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); @@ -264,7 +264,7 @@ top: return select([](){ return true; }); case PROBCUT: - return select([&](){ return pos.see_ge(*cur, occupied, threshold); }); + return select([&](){ return pos.see_ge(*cur, threshold); }); case QCAPTURE: if (select([&](){ return depth > DEPTH_QS_RECAPTURES diff --git a/src/movepick.h b/src/movepick.h index 725607b8..b6c07378 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -150,7 +150,6 @@ private: Value threshold; Depth depth; ExtMove moves[MAX_MOVES]; - Bitboard occupied; }; } // namespace Stockfish diff --git a/src/position.cpp b/src/position.cpp index ba6888eb..e6fdb511 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1163,6 +1163,11 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return bool(res); } +bool Position::see_ge(Move m, Value threshold) const { + Bitboard occupied; + return see_ge(m, occupied, threshold); +} + /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. diff --git a/src/position.h b/src/position.h index 670b621c..bb45c44a 100644 --- a/src/position.h +++ b/src/position.h @@ -145,6 +145,7 @@ public: // Static Exchange Evaluation bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index d2358ea2..ac74cdaf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1058,9 +1058,8 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); - Bitboard occupied; // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, occupied, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } @@ -1545,7 +1544,6 @@ moves_loop: // When in check, search starts here prevSq); int quietCheckEvasions = 0; - Bitboard occupied; // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1582,7 +1580,7 @@ moves_loop: // When in check, search starts here continue; } - if (futilityBase <= alpha && !pos.see_ge(move, occupied, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1601,7 +1599,7 @@ moves_loop: // When in check, search starts here continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, occupied, Value(-110))) + if (!pos.see_ge(move, Value(-110))) continue; } From 3f01e3f41f11aa66befec2307a32ee023c699a2a Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Thu, 23 Mar 2023 13:35:34 +0300 Subject: [PATCH 223/678] Allow PvNode in futility pruning for captures. Passed non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 148128 W: 39428 L: 39333 D: 69367 Ptnml(0-2): 492, 16326, 40315, 16457, 474 https://tests.stockfishchess.org/tests/view/641c2dbfdb43ab2ba6f804e8 Passed non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 376256 W: 100906 L: 101039 D: 174311 Ptnml(0-2): 186, 36697, 114494, 36566, 185 https://tests.stockfishchess.org/tests/view/641d33b2db43ab2ba6f83338 closes https://github.com/official-stockfish/Stockfish/pull/4470 bench: 4935616 --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ac74cdaf..5f95a1bd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1005,7 +1005,6 @@ moves_loop: // When in check, search starts here { // Futility pruning for captures (~2 Elo) if ( !givesCheck - && !PvNode && lmrDepth < 6 && !ss->inCheck && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] From 7a6fa34f5f9f0f193d9350cd58c82a8f98a6505d Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Sun, 12 Mar 2023 15:16:51 +0200 Subject: [PATCH 224/678] Improve compatibility this makes it easier to compile under MSVC, even though we recommend gcc/clang for production compiles at the moment. In Win32 API, by default, most null-terminated character strings arguments are of wchar_t (UTF16, formerly UCS16-LE) type, i.e. 2 bytes (at least) per character. So, src/misc.cpp should have proper type. Respectively, for src/syzygy/tbprobe.cpp, in Widows, file paths should be std::wstring rather than std::string. However, this requires a very big number of changes, since the config files are also keeping the 8-bit-per-character std::string strings. Therefore, just one change of using 8-byte-per-character CreateFileA make it compile under MSVC. closes https://github.com/official-stockfish/Stockfish/pull/4438 No functional change --- src/misc.cpp | 4 ++-- src/syzygy/tbprobe.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index c22126af..6469c5cf 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -605,7 +605,7 @@ static int best_node(size_t idx) { DWORD byteOffset = 0; // Early exit if the needed API is not available at runtime - HMODULE k32 = GetModuleHandle("Kernel32.dll"); + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx"); if (!fun1) return -1; @@ -675,7 +675,7 @@ void bindThisThread(size_t idx) { return; // Early exit if the needed API are not available at runtime - HMODULE k32 = GetModuleHandle("Kernel32.dll"); + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"); auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2"); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index b594ac37..9cb0bfdb 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -234,7 +234,7 @@ public: } #else // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. - HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); if (fd == INVALID_HANDLE_VALUE) From e8742bdab35c63253b5110d1861f27337e18f9fc Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Wed, 29 Mar 2023 12:43:36 +0300 Subject: [PATCH 225/678] Made advanced Windows API calls dynamically linked Made advanced Windows API calls (those from Advapi32.dll) dynamically linked to avoid link errors when compiling using Intel icx compiler for Windows. https://github.com/official-stockfish/Stockfish/pull/4467 No functional change --- src/misc.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 6469c5cf..cac9dd94 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -38,6 +38,9 @@ using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY); using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); using fun5_t = WORD(*)(); +using fun6_t = bool(*)(HANDLE, DWORD, PHANDLE); +using fun7_t = bool(*)(LPCSTR, LPCSTR, PLUID); +using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); } #endif @@ -488,11 +491,26 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize if (!largePageSize) return nullptr; + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + HMODULE k32 = GetModuleHandle("Advapi32.dll"); + auto fun6 = (fun6_t)(void(*)())GetProcAddress(k32, "OpenProcessToken"); + if (!fun6) + return nullptr; + auto fun7 = (fun7_t)(void(*)())GetProcAddress(k32, "LookupPrivilegeValueA"); + if (!fun7) + return nullptr; + auto fun8 = (fun8_t)(void(*)())GetProcAddress(k32, "AdjustTokenPrivileges"); + if (!fun8) + return nullptr; + + // We need SeLockMemoryPrivilege, so try to enable it for the process - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + // OpenProcessToken() + if (!fun6(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) return nullptr; - if (LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)) + // LookupPrivilegeValueA() + if (fun7(nullptr, SE_LOCK_MEMORY_NAME, &luid)) { TOKEN_PRIVILEGES tp { }; TOKEN_PRIVILEGES prevTp { }; @@ -504,7 +522,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, // we still need to query GetLastError() to ensure that the privileges were actually obtained. - if (AdjustTokenPrivileges( + // AdjustTokenPrivileges() + if (fun8( hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && GetLastError() == ERROR_SUCCESS) { @@ -514,7 +533,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); // Privilege no longer needed, restore previous state - AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + // AdjustTokenPrivileges () + fun8(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); } } From c3c46feebba470dcbaa0a5a6ef83534091dffe6a Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:46:15 +0800 Subject: [PATCH 226/678] Remove reduction for moving threatened piece Simplify away "Decrease reduction if we move a threatened piece". Running a dbg_hit_on() shows that this line is only called ~0.12% of the time. Simplification STC: https://tests.stockfishchess.org/tests/view/641ec2dcdb43ab2ba6f88103 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 146128 W: 39168 L: 39070 D: 67890 Ptnml(0-2): 466, 16117, 39830, 16155, 496 Simplification LTC: https://tests.stockfishchess.org/tests/view/64200689db43ab2ba6f8bca8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 248016 W: 66703 L: 66714 D: 114599 Ptnml(0-2): 105, 24202, 75406, 24189, 106 closes https://github.com/official-stockfish/Stockfish/pull/4471 Bench: 4961236 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5f95a1bd..2fcbc7df 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1181,11 +1181,6 @@ moves_loop: // When in check, search starts here if (singularQuietLMR) r--; - // Decrease reduction if we move a threatened piece (~1 Elo) - if ( depth > 9 - && (mp.threatenedPieces & from_sq(move))) - r--; - // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss+1)->cutoffCnt > 3) r++; From 66bf45b99e2061c1ba74f9975bc5059ac0121dfd Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 1 Apr 2023 11:56:49 +0200 Subject: [PATCH 227/678] Stringify the git info passed avoid escaping the string in the Makefile. Alternative to https://github.com/official-stockfish/Stockfish/pull/4476 closes https://github.com/official-stockfish/Stockfish/pull/4481 No functional change. --- src/Makefile | 4 ++-- src/evaluate.cpp | 2 -- src/misc.cpp | 6 ++---- src/misc.h | 3 +++ 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Makefile b/src/Makefile index e257bc63..0b22fb4e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -705,13 +705,13 @@ endif ### 3.7.1 Try to include git commit sha for versioning GIT_SHA = $(shell git rev-parse --short HEAD 2>/dev/null) ifneq ($(GIT_SHA), ) - CXXFLAGS += -DGIT_SHA=\"$(GIT_SHA)\" + CXXFLAGS += -DGIT_SHA=$(GIT_SHA) endif ### 3.7.2 Try to include git commit date for versioning GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) ifneq ($(GIT_DATE), ) - CXXFLAGS += -DGIT_DATE=\"$(GIT_DATE)\" + CXXFLAGS += -DGIT_DATE=$(GIT_DATE) endif ### 3.8 Link Time Optimization diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 99b87300..12883fcc 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -82,8 +82,6 @@ namespace Eval { eval_file = EvalFileDefaultName; #if defined(DEFAULT_NNUE_DIRECTORY) - #define stringify2(x) #x - #define stringify(x) stringify2(x) vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; #else vector dirs = { "" , "" , CommandLine::binaryDirectory }; diff --git a/src/misc.cpp b/src/misc.cpp index cac9dd94..e36a04bc 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -160,7 +160,7 @@ string engine_info(bool to_uci) { { ss << "-"; #ifdef GIT_DATE - ss << GIT_DATE; + ss << stringify(GIT_DATE); #else constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); string month, day, year; @@ -173,7 +173,7 @@ string engine_info(bool to_uci) { ss << "-"; #ifdef GIT_SHA - ss << GIT_SHA; + ss << stringify(GIT_SHA); #else ss << "nogit"; #endif @@ -190,8 +190,6 @@ string engine_info(bool to_uci) { std::string compiler_info() { - #define stringify2(x) #x - #define stringify(x) stringify2(x) #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) /// Predefined macros hell: diff --git a/src/misc.h b/src/misc.h index c20a816e..d4965156 100644 --- a/src/misc.h +++ b/src/misc.h @@ -28,6 +28,9 @@ #include "types.h" +#define stringify2(x) #x +#define stringify(x) stringify2(x) + namespace Stockfish { std::string engine_info(bool to_uci = false); From 38a80c0b47397dbdd9167ec1476dfd3c033020d6 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Thu, 30 Mar 2023 11:14:30 +0000 Subject: [PATCH 228/678] Simplify away complexityAverage Instead of tracking the average of complexity values, calculate complexity of root position at the beginning of the search and use it as a scaling factor in time management. Passed non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 58752 W: 15738 L: 15551 D: 27463 Ptnml(0-2): 164, 6194, 16478, 6371, 169 https://tests.stockfishchess.org/tests/view/6423010edb43ab2ba6f9424a Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 92872 W: 24865 L: 24729 D: 43278 Ptnml(0-2): 38, 8652, 28929, 8770, 47 https://tests.stockfishchess.org/tests/view/6423c1f0db43ab2ba6f9644f closes https://github.com/official-stockfish/Stockfish/pull/4472 No functional change --- src/misc.h | 26 -------------------------- src/search.cpp | 17 ++++++++++------- src/thread.h | 2 +- 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/misc.h b/src/misc.h index d4965156..69d470c2 100644 --- a/src/misc.h +++ b/src/misc.h @@ -89,32 +89,6 @@ static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; static inline const bool IsLittleEndian = (Le.c[0] == 4); -// RunningAverage : a class to calculate a running average of a series of values. -// For efficiency, all computations are done with integers. -class RunningAverage { - public: - - // Reset the running average to rational value p / q - void set(int64_t p, int64_t q) - { average = p * PERIOD * RESOLUTION / q; } - - // Update average with value v - void update(int64_t v) - { average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; } - - // Test if average is strictly greater than rational a / b - bool is_greater(int64_t a, int64_t b) const - { return b * average > a * (PERIOD * RESOLUTION); } - - int64_t value() const - { return average / (PERIOD * RESOLUTION); } - - private : - static constexpr int64_t PERIOD = 4096; - static constexpr int64_t RESOLUTION = 1024; - int64_t average; -}; - template class ValueList { diff --git a/src/search.cpp b/src/search.cpp index 2fcbc7df..3136046d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -293,6 +293,15 @@ void Thread::search() { if (mainThread) { + + int rootComplexity; + if (Eval::useNNUE) + Eval::NNUE::evaluate(rootPos, true, &rootComplexity); + else + Eval::evaluate(rootPos, &rootComplexity); + + mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); + if (mainThread->bestPreviousScore == VALUE_INFINITE) for (int i = 0; i < 4; ++i) mainThread->iterValue[i] = VALUE_ZERO; @@ -311,8 +320,6 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); - complexityAverage.set(153, 1); - optimism[us] = optimism[~us] = VALUE_ZERO; int searchAgainCounter = 0; @@ -472,10 +479,8 @@ void Thread::search() { timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); - int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::min(1.03 + (complexity - 241) / 1552.0, 1.45); - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * mainThread->complexity; // Cap used time in case of a single legal move for a better viewer experience in tournaments // yielding correct scores and sufficiently fast moves. @@ -755,8 +760,6 @@ namespace { tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } - thisThread->complexityAverage.update(complexity); - // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { diff --git a/src/thread.h b/src/thread.h index 46cdb11c..d6a48eca 100644 --- a/src/thread.h +++ b/src/thread.h @@ -60,7 +60,6 @@ public: Pawns::Table pawnsTable; Material::Table materialTable; size_t pvIdx, pvLast; - RunningAverage complexityAverage; std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; Color nmpColor; @@ -87,6 +86,7 @@ struct MainThread : public Thread { void search() override; void check_time(); + double complexity; double previousTimeReduction; Value bestPreviousScore; Value bestPreviousAverageScore; From bc50378ff1915a8ad6ac3e4946193c65e4cacb56 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Fri, 31 Mar 2023 18:16:50 +0300 Subject: [PATCH 229/678] Replace deprecated icc with icx Replace the deprecated Intel compiler icc with its newer icx variant. This newer compiler is based on clang, and yields good performance. As before, currently only linux is supported. closes https://github.com/official-stockfish/Stockfish/pull/4478 No functional change --- src/Makefile | 70 +++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/src/Makefile b/src/Makefile index 0b22fb4e..abcf11b0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -425,10 +425,11 @@ ifeq ($(COMP),mingw) CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations endif -ifeq ($(COMP),icc) - comp=icc - CXX=icpc - CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi +ifeq ($(COMP),icx) + comp=icx + CXX=icpx + CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \ + -Wconditional-uninitialized -Wabi -Wdeprecated endif ifeq ($(COMP),clang) @@ -499,9 +500,9 @@ ifeq ($(COMP),ndk) LDFLAGS += -static-libstdc++ -pie -lm -latomic endif -ifeq ($(comp),icc) - profile_make = icc-profile-make - profile_use = icc-profile-use +ifeq ($(comp),icx) + profile_make = icx-profile-make + profile_use = icx-profile-use else ifeq ($(comp),clang) profile_make = clang-profile-make profile_use = clang-profile-use @@ -572,7 +573,7 @@ ifeq ($(optimize),yes) endif ifeq ($(KERNEL),Darwin) - ifeq ($(comp),$(filter $(comp),clang icc)) + ifeq ($(comp),$(filter $(comp),clang icx)) CXXFLAGS += -mdynamic-no-pic endif @@ -608,8 +609,6 @@ endif ifeq ($(popcnt),yes) ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64)) CXXFLAGS += -DUSE_POPCNT - else ifeq ($(comp),icc) - CXXFLAGS += -msse3 -DUSE_POPCNT else CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT endif @@ -618,63 +617,63 @@ endif ### 3.6 SIMD architectures ifeq ($(avx2),yes) CXXFLAGS += -DUSE_AVX2 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mavx2 -mbmi endif endif ifeq ($(avxvnni),yes) CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mavxvnni endif endif ifeq ($(avx512),yes) CXXFLAGS += -DUSE_AVX512 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mavx512f -mavx512bw endif endif ifeq ($(vnni256),yes) CXXFLAGS += -DUSE_VNNI - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256 endif endif ifeq ($(vnni512),yes) CXXFLAGS += -DUSE_VNNI - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) - CXXFLAGS += -mavx512vnni -mavx512dq -mavx512vl + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512 endif endif ifeq ($(sse41),yes) CXXFLAGS += -DUSE_SSE41 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -msse4.1 endif endif ifeq ($(ssse3),yes) CXXFLAGS += -DUSE_SSSE3 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mssse3 endif endif ifeq ($(sse2),yes) CXXFLAGS += -DUSE_SSE2 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -msse2 endif endif ifeq ($(mmx),yes) CXXFLAGS += -DUSE_MMX - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mmmx endif endif @@ -697,7 +696,7 @@ endif ### 3.7 pext ifeq ($(pext),yes) CXXFLAGS += -DUSE_PEXT - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mbmi2 endif endif @@ -719,8 +718,11 @@ endif ### needs access to the optimization flags. ifeq ($(optimize),yes) ifeq ($(debug), no) - ifeq ($(comp),clang) + ifeq ($(comp),$(filter $(comp),clang icx)) CXXFLAGS += -flto=full + ifeq ($(comp),icx) + CXXFLAGS += -fwhole-program-vtables + endif ifeq ($(target_windows),yes) CXXFLAGS += -fuse-ld=lld endif @@ -807,7 +809,7 @@ help: @echo "gcc > Gnu compiler (default)" @echo "mingw > Gnu compiler with MinGW under Windows" @echo "clang > LLVM Clang compiler" - @echo "icc > Intel compiler" + @echo "icx > Intel oneAPI DPC++/C++ Compiler" @echo "ndk > Google NDK to cross-compile for Android" @echo "" @echo "Simple examples. If you don't know what to do, you likely want to run one of: " @@ -833,8 +835,10 @@ endif .PHONY: help build profile-build strip install clean net objclean profileclean \ - config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ - clang-profile-use clang-profile-make FORCE + config-sanity \ + icx-profile-use icx-profile-make \ + gcc-profile-use gcc-profile-make \ + clang-profile-use clang-profile-make FORCE build: net config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all @@ -949,7 +953,9 @@ config-sanity: net @echo "vnni256: '$(vnni256)'" @echo "vnni512: '$(vnni512)'" @echo "neon: '$(neon)'" + @echo "dotprod: '$(dotprod)'" @echo "arm_version: '$(arm_version)'" + @echo "target_windows: '$(target_windows)'" @echo "" @echo "Flags:" @echo "CXX: $(CXX)" @@ -978,7 +984,7 @@ config-sanity: net @test "$(vnni256)" = "yes" || test "$(vnni256)" = "no" @test "$(vnni512)" = "yes" || test "$(vnni512)" = "no" @test "$(neon)" = "yes" || test "$(neon)" = "no" - @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \ + @test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \ || test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang" $(EXE): $(OBJS) @@ -1016,15 +1022,17 @@ gcc-profile-use: EXTRALDFLAGS='-lgcov' \ all -icc-profile-make: - @mkdir -p profdir +icx-profile-make: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ - EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \ + EXTRACXXFLAGS='-fprofile-instr-generate ' \ + EXTRALDFLAGS=' -fprofile-instr-generate' \ all -icc-profile-use: +icx-profile-use: + $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ - EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \ + EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ all .depend: $(SRCS) From 6a6e32dfc80488dfdcd6c23e601063b47729e890 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 1 Apr 2023 15:22:53 +0300 Subject: [PATCH 230/678] Decrease Depth more for positions not in TT. If the position is not in TT, decrease depth by 2 or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth. Many thanks to Vizvezdenec as the main idea was his. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 70664 W: 18995 L: 18639 D: 33030 Ptnml(0-2): 228, 7712, 19090, 8080, 222 https://tests.stockfishchess.org/tests/view/64258a8bdb43ab2ba6f9b682 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 85040 W: 23227 L: 22836 D: 38977 Ptnml(0-2): 26, 8115, 25867, 8466, 46 https://tests.stockfishchess.org/tests/view/64262057db43ab2ba6f9d0e7 closes https://github.com/official-stockfish/Stockfish/pull/4482 bench: 4380438 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3136046d..46cca50f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -899,11 +899,11 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); } - // Step 11. If the position is not in TT, decrease depth by 3. + // Step 11. If the position is not in TT, decrease depth by 2 (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). // Use qsearch if depth is equal or below zero (~9 Elo) if ( PvNode && !ttMove) - depth -= 3; + depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); From 1fee996999364bedbd9ca4c29649d5c7321947c5 Mon Sep 17 00:00:00 2001 From: Miguel Lahoz Date: Sun, 2 Apr 2023 17:28:39 +0800 Subject: [PATCH 231/678] Remove unneeded bitboard from MP Recent simplification has removed the need for an extra bitboard in MP struct. Use a local variable instead. STC: Passed Non-regression test https://tests.stockfishchess.org/tests/view/64294ae677ff3301150cba16 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 64872 W: 17383 L: 17203 D: 30286 Ptnml(0-2): 179, 6675, 18546, 6859, 177 closes https://github.com/official-stockfish/Stockfish/pull/4490 No functional change. --- src/movepick.cpp | 3 +-- src/movepick.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 36ee46b5..6fbcb2c3 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -69,7 +69,6 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); - threatenedPieces = 0; } /// MovePicker constructor for quiescence search @@ -106,7 +105,7 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook; + [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, threatenedPieces; if constexpr (Type == QUIETS) { Color us = pos.side_to_move(); diff --git a/src/movepick.h b/src/movepick.h index b6c07378..0b44557f 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -131,8 +131,6 @@ public: MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); - Bitboard threatenedPieces; - private: template Move select(Pred); template void score(); From 77e2b915e1e4f2469a414712e52b469633fb3273 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sat, 1 Apr 2023 18:48:31 -0500 Subject: [PATCH 232/678] Simplifiy TM's root complexity Also requires moving optimism initialization, this is a very early `evaluate()` call. STC: https://tests.stockfishchess.org/tests/view/6428c39677ff3301150ca0d7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 51256 W: 13805 L: 13612 D: 23839 Ptnml(0-2): 145, 5283, 14592, 5450, 158 LTC: https://tests.stockfishchess.org/tests/view/64296ff377ff3301150cc519 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 106968 W: 28951 L: 28830 D: 49187 Ptnml(0-2): 47, 9746, 33789, 9843, 59 closes https://github.com/official-stockfish/Stockfish/pull/4492 no functional change --- src/search.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 46cca50f..becaee3a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -290,15 +290,13 @@ void Thread::search() { bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; + optimism[us] = optimism[~us] = VALUE_ZERO; if (mainThread) { int rootComplexity; - if (Eval::useNNUE) - Eval::NNUE::evaluate(rootPos, true, &rootComplexity); - else - Eval::evaluate(rootPos, &rootComplexity); + Eval::evaluate(rootPos, &rootComplexity); mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); @@ -320,8 +318,6 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); - optimism[us] = optimism[~us] = VALUE_ZERO; - int searchAgainCounter = 0; // Iterative deepening loop until requested to stop or the target depth is reached From 9a42bbdf3163222db5e0fa764d48ca0a09a0dec2 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 4 Apr 2023 14:26:29 +0300 Subject: [PATCH 233/678] Parameters Tweak Passed STC LLR: 3.22 (-2.94,2.94) <0.00,2.00> Total: 664048 W: 177526 L: 176301 D: 310221 Ptnml(0-2): 2002, 72968, 180891, 74129, 2034 https://tests.stockfishchess.org/tests/view/64219901db43ab2ba6f901fa Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 77576 W: 21125 L: 20750 D: 35701 Ptnml(0-2): 24, 7350, 23669, 7717, 28 https://tests.stockfishchess.org/tests/view/642abe3377ff3301150d3a16 closes https://github.com/official-stockfish/Stockfish/pull/4493 bench: 4522076 --- src/search.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index becaee3a..749de792 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1449 - int(delta) * 1032 / int(rootDelta)) / 1024 + (!i && r > 941); + return (r + 1449 - int(delta) * 1001 / int(rootDelta)) / 1024 + (!i && r > 941); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(340 * d - 470, 1855); + return std::min(341 * d - 470, 1855); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -1057,7 +1057,7 @@ 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, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 16 * lmrDepth))) continue; } } @@ -1193,10 +1193,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4182; + - 4082; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (11791 + 3992 * (depth > 6 && depth < 19)); + r -= ss->statScore / (11079 + 4626 * (depth > 6 && depth < 19)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has From a2737d8bb5e480563823820fb12a8887d61c991e Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 5 Apr 2023 07:25:00 +0800 Subject: [PATCH 234/678] Simplify away piece count condition for useClassical Simplify away the piece count condition for useClassical. In compensation, the psq requirement is increased by 15%. Also updated the Elo estimate for useClassical, based on recent testing. Simplification STC: https://tests.stockfishchess.org/tests/view/642acbb577ff3301150d3ef5 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 51984 W: 13906 L: 13707 D: 24371 Ptnml(0-2): 150, 5638, 14227, 5817, 160 Simplification LTC: https://tests.stockfishchess.org/tests/view/642b9c5777ff3301150d778a LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 119696 W: 32412 L: 32300 D: 54984 Ptnml(0-2): 53, 11529, 36567, 11651, 48 closes https://github.com/official-stockfish/Stockfish/pull/4494 Bench: 5089321 --- src/evaluate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 12883fcc..703cf869 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1053,8 +1053,8 @@ Value Eval::evaluate(const Position& pos, int* complexity) { // We use the much less accurate but faster Classical eval when the NNUE // option is set to false. Otherwise we use the NNUE eval unless the - // PSQ advantage is decisive and several pieces remain. (~3 Elo) - bool useClassical = !useNNUE || (pos.count() > 7 && abs(psq) > 1781); + // PSQ advantage is decisive. (~4 Elo at STC, 1 Elo at LTC) + bool useClassical = !useNNUE || abs(psq) > 2048; if (useClassical) v = Evaluation(pos).value(); From 510aca1ef62279dc35941fa45ed61fb9d3796f10 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 5 Apr 2023 05:48:34 +0300 Subject: [PATCH 235/678] Assign negative stat bonuses for quiet moves at Pv nodes This patch assigns negative stat bonuses for quiet moves at pv nodes which are searched at depth greater than this node assumes, so are extended. Passed STC: https://tests.stockfishchess.org/tests/view/6426198bdb43ab2ba6f9cfa2 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 548944 W: 147287 L: 146254 D: 255403 Ptnml(0-2): 1662, 59772, 150671, 60605, 1762 Passed LTC: https://tests.stockfishchess.org/tests/view/642be4f177ff3301150d892d LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 93352 W: 25400 L: 24994 D: 42958 Ptnml(0-2): 44, 8817, 28547, 9225, 43 closes https://github.com/official-stockfish/Stockfish/pull/4495 bench 5044536 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 749de792..aa1a3e8c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -290,7 +290,7 @@ void Thread::search() { bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; - optimism[us] = optimism[~us] = VALUE_ZERO; + optimism[WHITE] = optimism[BLACK] = VALUE_ZERO; if (mainThread) { @@ -1257,6 +1257,9 @@ moves_loop: // When in check, search starts here (ss+1)->pv[0] = MOVE_NONE; value = -search(pos, ss+1, -beta, -alpha, newDepth, false); + + if (moveCount > 1 && newDepth >= depth && !capture) + update_continuation_histories(ss, movedPiece, to_sq(move), -stat_bonus(newDepth)); } // Step 19. Undo move From 59f2085469a7dd96146905a5d8d0c1a5d987187d Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 6 Apr 2023 00:35:05 +0300 Subject: [PATCH 236/678] Depth Tweak and tuning tunes reduction related parameters, and introduces more reduction on found good moves. credit for this patch goes also to candirufish Yoshie2000 dubslow peregrineshahin Vizvezdenec Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 38424 W: 10346 L: 10040 D: 18038 Ptnml(0-2): 103, 4111, 10473, 4427, 98 https://tests.stockfishchess.org/tests/view/642ca74277ff3301150db511 Passed LTC: LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 136968 W: 37151 L: 36660 D: 63157 Ptnml(0-2): 43, 13052, 41808, 13533, 48 https://tests.stockfishchess.org/tests/view/642d632377ff3301150dddbe closes https://github.com/official-stockfish/Stockfish/pull/4499 bench: 3672914 --- src/search.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index aa1a3e8c..fba9685b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1449 - int(delta) * 1001 / int(rootDelta)) / 1024 + (!i && r > 941); + return (r + 1449 - int(delta) * 937 / int(rootDelta)) / 1024 + (!i && r > 941); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(341 * d - 470, 1855); + return std::min(341 * d - 470, 1710); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -775,7 +775,7 @@ namespace { // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 426 - 252 * depth * depth) + if (eval < alpha - 426 - 256 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -797,7 +797,7 @@ namespace { && (ss-1)->statScore < 18755 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 19 * depth - improvement / 13 + 253 + complexity / 25 + && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -805,7 +805,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 168, 6) + depth / 3 + 4 - (complexity > 825); + Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4 - (complexity > 825); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -1333,16 +1333,18 @@ moves_loop: // When in check, search starts here if (PvNode && value < beta) // Update alpha! Always alpha < beta { - alpha = value; // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && depth < 6 - && beta < 10534 - && alpha > -10534) - depth -= 1; + && ((improving && complexity > 971) || (value < (5 * alpha + 75 * beta) / 87) || depth < 6) + && beta < 12535 + && value > -12535) { + bool extraReduction = depth > 2 && alpha > -12535 && bestValue != -VALUE_INFINITE && (value - bestValue) > (7 * (beta - alpha)) / 8; + depth -= 1 + extraReduction; + } assert(depth > 0); + alpha = value; } else { From b36d39de3d61b8f31c11d85233631aafaf760ee1 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 9 Apr 2023 09:18:29 +0200 Subject: [PATCH 237/678] Fix rootComplexity calculation The calculation of rootComplexity can't call eval when in check. Doing so triggers an assert if compiled in debug mode when the rootpos is evaluated using classical eval. Fixes https://github.com/official-stockfish/Stockfish/issues/4512 Passed STC: https://tests.stockfishchess.org/tests/view/6432697431feee5c6d306876 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 41096 W: 11017 L: 10815 D: 19264 Ptnml(0-2): 113, 4172, 11780, 4366, 117 Running LTC: https://tests.stockfishchess.org/tests/view/6432974d31feee5c6d306fc0 LLR: 1.76 (-2.94,2.94) <-1.75,0.25> Total: 73200 W: 19792 L: 19728 D: 33680 Ptnml(0-2): 24, 6659, 23182, 6699, 36 closes https://github.com/official-stockfish/Stockfish/pull/4515 No functional change --- src/evaluate.cpp | 2 ++ src/search.cpp | 10 ++++++---- src/thread.cpp | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 703cf869..2d0df89c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1048,6 +1048,8 @@ make_v: Value Eval::evaluate(const Position& pos, int* complexity) { + assert(!pos.checkers()); + Value v; Value psq = pos.psq_eg_stm(); diff --git a/src/search.cpp b/src/search.cpp index fba9685b..5d54a15d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -295,10 +295,12 @@ void Thread::search() { if (mainThread) { - int rootComplexity; - Eval::evaluate(rootPos, &rootComplexity); - - mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); + if (!rootPos.checkers()) + { + int rootComplexity; + Eval::evaluate(rootPos, &rootComplexity); + mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); + } if (mainThread->bestPreviousScore == VALUE_INFINITE) for (int i = 0; i < 4; ++i) diff --git a/src/thread.cpp b/src/thread.cpp index c680393e..202768c8 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -160,6 +160,7 @@ void ThreadPool::clear() { main()->bestPreviousScore = VALUE_INFINITE; main()->bestPreviousAverageScore = VALUE_INFINITE; main()->previousTimeReduction = 1.0; + main()->complexity = 1.0; } From 5d258e168f7ea9019ed640ae2e56f04b26aea6a2 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Sat, 1 Apr 2023 20:14:41 +0300 Subject: [PATCH 238/678] Fix linking / character types of windows API calls ensures large pages can be allocated again. closes https://github.com/official-stockfish/Stockfish/pull/4509 No functional change --- src/misc.cpp | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index e36a04bc..f1554060 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -490,25 +490,29 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize return nullptr; // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges - HMODULE k32 = GetModuleHandle("Advapi32.dll"); - auto fun6 = (fun6_t)(void(*)())GetProcAddress(k32, "OpenProcessToken"); + + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + + auto fun6 = (fun6_t)(void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken"); if (!fun6) return nullptr; - auto fun7 = (fun7_t)(void(*)())GetProcAddress(k32, "LookupPrivilegeValueA"); + auto fun7 = (fun7_t)(void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"); if (!fun7) return nullptr; - auto fun8 = (fun8_t)(void(*)())GetProcAddress(k32, "AdjustTokenPrivileges"); + auto fun8 = (fun8_t)(void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"); if (!fun8) return nullptr; - // We need SeLockMemoryPrivilege, so try to enable it for the process - // OpenProcessToken() - if (!fun6(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) - return nullptr; + if (!fun6( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; - // LookupPrivilegeValueA() - if (fun7(nullptr, SE_LOCK_MEMORY_NAME, &luid)) + if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) + nullptr, "SeLockMemoryPrivilege", &luid)) { TOKEN_PRIVILEGES tp { }; TOKEN_PRIVILEGES prevTp { }; @@ -520,8 +524,7 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, // we still need to query GetLastError() to ensure that the privileges were actually obtained. - // AdjustTokenPrivileges() - if (fun8( + if (fun8( // AdjustTokenPrivileges() hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && GetLastError() == ERROR_SUCCESS) { @@ -531,8 +534,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); // Privilege no longer needed, restore previous state - // AdjustTokenPrivileges () - fun8(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + fun8( // AdjustTokenPrivileges () + hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); } } From 6e63dd63a4f1e3074a9f5a8d7f64fdd0eba19c7e Mon Sep 17 00:00:00 2001 From: MinetaS Date: Fri, 7 Apr 2023 15:23:04 +0000 Subject: [PATCH 239/678] Use int conversion for Option class The current implementation generates warnings on MSVC. However, we have no real use cases for double-typed UCI option values now. Also parameter tuning only accepts following three types: int, Value, Score closes https://github.com/official-stockfish/Stockfish/pull/4505 No functional change --- src/tt.cpp | 4 ++-- src/uci.h | 2 +- src/ucioption.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index 39f18d3d..3339c993 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -87,7 +87,7 @@ void TranspositionTable::clear() { std::vector threads; - for (size_t idx = 0; idx < Options["Threads"]; ++idx) + for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) { threads.emplace_back([this, idx]() { @@ -98,7 +98,7 @@ void TranspositionTable::clear() { // Each thread will zero its part of the hash table const size_t stride = size_t(clusterCount / Options["Threads"]), start = size_t(stride * idx), - len = idx != Options["Threads"] - 1 ? + len = idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start; std::memset(&table[start], 0, len * sizeof(Cluster)); diff --git a/src/uci.h b/src/uci.h index 70e45acc..9ca0ed36 100644 --- a/src/uci.h +++ b/src/uci.h @@ -61,7 +61,7 @@ public: Option& operator=(const std::string&); void operator<<(const Option&); - operator double() const; + operator int() const; operator std::string() const; bool operator==(const char*) const; diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 39933ea5..f6342e5c 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -128,9 +128,9 @@ Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(min Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) { defaultValue = v; currentValue = cur; } -Option::operator double() const { +Option::operator int() const { assert(type == "check" || type == "spin"); - return (type == "spin" ? stof(currentValue) : currentValue == "true"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { From a5643b89fda5060d4b40dff54afe02816c899dd4 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Fri, 7 Apr 2023 14:30:59 +0000 Subject: [PATCH 240/678] Remove extraReduction Since bestValue becomes value and beta - alpha is always non-negative, extraReduction is always false, hence it has no effect. This patch includes small changes to improve readability. closes https://github.com/official-stockfish/Stockfish/pull/4505 No functional change --- src/search.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5d54a15d..4463b42a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1335,15 +1335,14 @@ moves_loop: // When in check, search starts here if (PvNode && value < beta) // Update alpha! Always alpha < beta { - // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && ((improving && complexity > 971) || (value < (5 * alpha + 75 * beta) / 87) || depth < 6) + && ( (improving && complexity > 971) + || value < (5 * alpha + 75 * beta) / 87 + || depth < 6) && beta < 12535 - && value > -12535) { - bool extraReduction = depth > 2 && alpha > -12535 && bestValue != -VALUE_INFINITE && (value - bestValue) > (7 * (beta - alpha)) / 8; - depth -= 1 + extraReduction; - } + && value > -12535) + depth -= 1; assert(depth > 0); alpha = value; From 2f2f45f9f47c1212f3229c22304456c9bad8f843 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Thu, 6 Apr 2023 14:08:50 +0300 Subject: [PATCH 241/678] Made two specializations for affine transform easier to understand. Added AVX-512 for the specialization for small inputs closes https://github.com/official-stockfish/Stockfish/pull/4502 No functional change --- src/nnue/layers/affine_transform.h | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index f84f054e..9e2f2f97 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -31,7 +31,7 @@ This file contains the definition for a fully connected layer (aka affine transform). Two approaches are employed, depending on the sizes of the transform. - Approach 1: + Approach 1 (a specialization for large inputs): - used when the PaddedInputDimensions >= 128 - uses AVX512 if possible - processes inputs in batches of 2*InputSimdWidth @@ -42,9 +42,8 @@ depends on the architecture (the amount of registers) - accumulate + hadd is used - Approach 2: + Approach 2 (a specialization for small inputs): - used when the PaddedInputDimensions < 128 - - does not use AVX512 - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32. - that's why AVX512 is hard to implement - expected use-case is small layers @@ -169,7 +168,7 @@ namespace Stockfish::Eval::NNUE::Layers { constexpr IndexType LargeInputSize = std::numeric_limits::max(); #endif - // A specialization for large inputs. + // A specialization for large inputs template class AffineTransform(InDims, MaxSimdWidth) >= LargeInputSize)>> { public: @@ -188,7 +187,7 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization should not have been chosen."); + static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization (for large inputs) should not have been chosen."); #if defined (USE_AVX512) static constexpr IndexType InputSimdWidth = 64; @@ -396,6 +395,7 @@ namespace Stockfish::Eval::NNUE::Layers { alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; }; + // A specialization for small inputs template class AffineTransform(InDims, MaxSimdWidth) < LargeInputSize)>> { public: @@ -415,12 +415,7 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization should not have been chosen."); - -#if defined (USE_SSSE3) - static constexpr IndexType OutputSimdWidth = SimdWidth / 4; - static constexpr IndexType InputSimdWidth = SimdWidth; -#endif + static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization (for small inputs) should not have been chosen."); // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { @@ -470,7 +465,14 @@ namespace Stockfish::Eval::NNUE::Layers { const OutputType* propagate( const InputType* input, OutputType* output) const { -#if defined (USE_AVX2) +#if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 + #define vec_hadd Simd::m512_hadd +#elif defined (USE_AVX2) using vec_t = __m256i; #define vec_setzero _mm256_setzero_si256 #define vec_set_32 _mm256_set1_epi32 @@ -489,6 +491,8 @@ namespace Stockfish::Eval::NNUE::Layers { #if defined (USE_SSSE3) const auto inputVector = reinterpret_cast(input); + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1); if constexpr (OutputDimensions % OutputSimdWidth == 0) From 7a9f67747f23e837a8691ba9e6e4f0d1fdafff73 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 4 Apr 2023 19:44:09 -0700 Subject: [PATCH 242/678] Reduce Position::pieces() overloads Reduce the number of overloads for pieces() by using a more general template implementation. Secondly simplify some code in search.cpp using the new general functionality. TC https://tests.stockfishchess.org/tests/view/642ce27877ff3301150dc193 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 269640 W: 71775 L: 71809 D: 126056 Ptnml(0-2): 687, 27294, 78885, 27274, 680 closes https://github.com/official-stockfish/Stockfish/pull/4501 No functional change. --- src/position.h | 19 ++++++++----------- src/search.cpp | 12 +++++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/position.h b/src/position.h index bb45c44a..a736f3e6 100644 --- a/src/position.h +++ b/src/position.h @@ -92,10 +92,9 @@ public: // Position representation Bitboard pieces(PieceType pt) const; - Bitboard pieces(PieceType pt1, PieceType pt2) const; + template Bitboard pieces(PieceType pt, PieceTypes... pts) const; Bitboard pieces(Color c) const; - Bitboard pieces(Color c, PieceType pt) const; - Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const; + template Bitboard pieces(Color c, PieceTypes... pts) const; Piece piece_on(Square s) const; Square ep_square() const; bool empty(Square s) const; @@ -229,20 +228,18 @@ inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const { return byTypeBB[pt]; } -inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const { - return pieces(pt1) | pieces(pt2); +template +inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const { + return pieces(pt) | pieces(pts...); } inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; } -inline Bitboard Position::pieces(Color c, PieceType pt) const { - return pieces(c) & pieces(pt); -} - -inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const { - return pieces(c) & (pieces(pt1) | pieces(pt2)); +template +inline Bitboard Position::pieces(Color c, PieceTypes... pts) const { + return pieces(c) & pieces(pts...); } template inline int Position::count(Color c) const { diff --git a/src/search.cpp b/src/search.cpp index 4463b42a..4187117b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1018,16 +1018,18 @@ moves_loop: // When in check, search starts here { if (depth < 2 - capture) continue; - // don't prune move if a heavy enemy piece (KQR) is under attack after the exchanges - Bitboard leftEnemies = (pos.pieces(~us, QUEEN, ROOK) | pos.pieces(~us, KING)) & occupied; + // Don't prune the move if opp. King/Queen/Rook is attacked by a slider after the exchanges. + // Since in see_ge we don't update occupied when the king recaptures, we also don't prune the + // move when the opp. King gets a discovered slider attack DURING the exchanges. + Bitboard leftEnemies = pos.pieces(~us, ROOK, QUEEN, KING) & occupied; Bitboard attacks = 0; occupied |= to_sq(move); while (leftEnemies && !attacks) { Square sq = pop_lsb(leftEnemies); - attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // exclude Queen/Rook(s) which were already threatened before SEE - if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) + attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; + // Exclude Queen/Rook(s) which were already threatened before SEE + if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) attacks = 0; } if (!attacks) From 1a64afb1c65591ccd374504c791eb27b762f6c8f Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Sat, 1 Apr 2023 23:49:03 +0300 Subject: [PATCH 243/678] Do no initialize TM in all cases Avoid doing full TM initialization if it won't be used, avoids division by zero. closes https://github.com/official-stockfish/Stockfish/pull/4484 No functional change --- src/timeman.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 5c826b4f..061de018 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -36,6 +36,12 @@ TimeManagement Time; // Our global time management object void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { + // if we have no time, no need to initialize TM, except for the start time, + // which is used by movetime. + startTime = limits.startTime; + if (limits.time[us] == 0) + return; + TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); TimePoint slowMover = TimePoint(Options["Slow Mover"]); TimePoint npmsec = TimePoint(Options["nodestime"]); @@ -59,8 +65,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { limits.npmsec = npmsec; } - startTime = limits.startTime; - // Maximum move horizon of 50 moves int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; From 7bd23d4d04d6644b6ccae8ea63cfc6646e4248dd Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 7 Apr 2023 12:33:28 -0400 Subject: [PATCH 244/678] Simplify away nnue scale pawn count multiplier Removes 2x multipliers in nnue scale calculation along with the pawn count term that was recently reintroduced. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/64305bc720eb941419bdf72e LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 38008 W: 10234 L: 10021 D: 17753 Ptnml(0-2): 96, 4151, 10323, 4312, 122 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6430b76a028b029b01ac9bfd LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 91232 W: 24686 L: 24547 D: 41999 Ptnml(0-2): 30, 8721, 27986, 8838, 41 closes https://github.com/official-stockfish/Stockfish/pull/4510 bench 4017320 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 2d0df89c..873dc5d2 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { else { int nnueComplexity; - int scale = 1001 + 5 * pos.count() + 61 * pos.non_pawn_material() / 4096; + int scale = 1001 + pos.non_pawn_material() / 64; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; From 4ad2713e19cbf8db1e588c4d24d1fe4af5c6e917 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Fri, 17 Mar 2023 00:04:41 +0300 Subject: [PATCH 245/678] Fix capturing underpromotions issue Fix underpromotion captures are generated amongst quiets although dealt with as a capture_stage in search, this makes not skipping them when move count pruning kicks-in consistent with updating their histories amongst captures. Passed STC: https://tests.stockfishchess.org/tests/view/6415579f65775d3b539e7537 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 118896 W: 31678 L: 31553 D: 55665 Ptnml(0-2): 356, 12911, 32793, 13028, 360 Passed LTC: https://tests.stockfishchess.org/tests/view/641633b965775d3b539e9e95 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 126800 W: 34255 L: 34148 D: 58397 Ptnml(0-2): 57, 12216, 38763, 12291, 73 see also discussion in https://github.com/official-stockfish/Stockfish/pull/4436 closes https://github.com/official-stockfish/Stockfish/pull/4452 bench: 3979409 --- src/movegen.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 255dce04..6b28a52e 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -25,13 +25,21 @@ namespace Stockfish { namespace { - template + template ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { *moveList++ = make(to - D, to, QUEEN); + if constexpr (Enemy && Type == CAPTURES) + { + *moveList++ = make(to - D, to, ROOK); + *moveList++ = make(to - D, to, BISHOP); + *moveList++ = make(to - D, to, KNIGHT); + } + } - if constexpr (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) + if constexpr ((Type == QUIETS && !Enemy) || Type == EVASIONS || Type == NON_EVASIONS) { *moveList++ = make(to - D, to, ROOK); *moveList++ = make(to - D, to, BISHOP); @@ -106,13 +114,13 @@ namespace { b3 &= target; while (b1) - moveList = make_promotions(moveList, pop_lsb(b1)); + moveList = make_promotions(moveList, pop_lsb(b1)); while (b2) - moveList = make_promotions(moveList, pop_lsb(b2)); + moveList = make_promotions(moveList, pop_lsb(b2)); while (b3) - moveList = make_promotions(moveList, pop_lsb(b3)); + moveList = make_promotions(moveList, pop_lsb(b3)); } // Standard and en passant captures From f66c36277fe57d0ce4f10a4aeb5b41eb0cb9ebd1 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 5 Apr 2023 20:30:01 -0500 Subject: [PATCH 246/678] Remove nmpColor no benefit seen, neither in game play nor for zugzwang test positions STC: https://tests.stockfishchess.org/tests/view/642e293977ff3301150e9b55 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 161848 W: 43332 L: 43254 D: 75262 Ptnml(0-2): 418, 16987, 46058, 17021, 440 LTC: https://tests.stockfishchess.org/tests/view/642fea9420eb941419bde296 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 120208 W: 32529 L: 32418 D: 55261 Ptnml(0-2): 35, 11424, 37080, 11525, 40 closes https://github.com/official-stockfish/Stockfish/pull/4511 bench 3979409 --- src/search.cpp | 5 ++--- src/thread.h | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4187117b..04299dbd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -802,7 +802,7 @@ namespace { && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25 && !excludedMove && pos.non_pawn_material(us) - && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) + && (ss->ply >= thisThread->nmpMinPly)) { assert(eval - beta >= 0); @@ -830,9 +830,8 @@ namespace { assert(!thisThread->nmpMinPly); // Recursive verification is not allowed // Do verification search at high depths, with null move pruning disabled - // for us, until ply exceeds nmpMinPly. + // until ply exceeds nmpMinPly. thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; - thisThread->nmpColor = us; Value v = search(pos, ss, beta-1, beta, depth-R, false); diff --git a/src/thread.h b/src/thread.h index d6a48eca..3a114c79 100644 --- a/src/thread.h +++ b/src/thread.h @@ -62,7 +62,6 @@ public: size_t pvIdx, pvLast; std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; - Color nmpColor; Value bestValue, optimism[COLOR_NB]; Position rootPos; From 9829bceda90d025a5f5d7c04457902413e367041 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Tue, 11 Apr 2023 01:20:29 +0200 Subject: [PATCH 247/678] Remove good killer reduction rule. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 246544 W: 65646 L: 65657 D: 115241 Ptnml(0-2): 706, 27350, 67138, 27405, 673 https://tests.stockfishchess.org/tests/view/642e253277ff3301150e9aa2 LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 165136 W: 44878 L: 44809 D: 75449 Ptnml(0-2): 64, 15991, 50378, 16082, 53 https://tests.stockfishchess.org/tests/view/6430db07028b029b01acd87f closes https://github.com/official-stockfish/Stockfish/pull/4519 Bench: 3746080 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 04299dbd..411befde 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1187,11 +1187,6 @@ moves_loop: // When in check, search starts here if ((ss+1)->cutoffCnt > 3) r++; - // Decrease reduction if move is a killer and we have a good history (~1 Elo) - if (move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 3722) - r--; - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] From acb0d204d56e16398c58822df2cc60b90ef1ae85 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 11 Apr 2023 19:53:36 +0300 Subject: [PATCH 248/678] Simplify stats assignment for Pv nodes This patch is a simplification of my recent elo gainer. Logically the Elo gainer didn't make much sense and this patch simplifies it into smth more logical. Instead of assigning negative bonuses to all non-first moves that enter PV nodes we assign positive bonuses in full depth search after LMR only for moves that will result in a fail high - thus not assigning positive bonuses for moves that will go to pv search - so doing "almost" the same as we do in master now for them. Logic differs for some other moves, though, but this removes some lines of code. Passed STC: https://tests.stockfishchess.org/tests/view/642cf5cf77ff3301150dc5ec LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 409320 W: 109124 L: 109308 D: 190888 Ptnml(0-2): 1149, 45385, 111751, 45251, 1124 Passed LTC: https://tests.stockfishchess.org/tests/view/642fe75d20eb941419bde200 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 260336 W: 70280 L: 70303 D: 119753 Ptnml(0-2): 99, 25236, 79528, 25199, 106 closes https://github.com/official-stockfish/Stockfish/pull/4522 Bench: 4286815 --- src/search.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 411befde..390c6b1c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1229,8 +1229,9 @@ moves_loop: // When in check, search starts here if (newDepth > d) value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); - int bonus = value > alpha ? stat_bonus(newDepth) - : -stat_bonus(newDepth); + int bonus = value <= alpha ? -stat_bonus(newDepth) + : value >= beta ? stat_bonus(newDepth) + : 0; update_continuation_histories(ss, movedPiece, to_sq(move), bonus); } @@ -1255,9 +1256,6 @@ moves_loop: // When in check, search starts here (ss+1)->pv[0] = MOVE_NONE; value = -search(pos, ss+1, -beta, -alpha, newDepth, false); - - if (moveCount > 1 && newDepth >= depth && !capture) - update_continuation_histories(ss, movedPiece, to_sq(move), -stat_bonus(newDepth)); } // Step 19. Undo move From 96b6c0b36f28f0ef5fa58f48405db710542521df Mon Sep 17 00:00:00 2001 From: MinetaS Date: Fri, 7 Apr 2023 14:49:05 +0000 Subject: [PATCH 249/678] Remove some conditions at PV improvement reduction Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 63664 W: 17007 L: 16823 D: 29834 Ptnml(0-2): 163, 6998, 17336, 7162, 173 https://tests.stockfishchess.org/tests/view/6430b124028b029b01ac99f2 Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 90016 W: 24399 L: 24258 D: 41359 Ptnml(0-2): 52, 8672, 27405, 8841, 38 https://tests.stockfishchess.org/tests/view/64310e74028b029b01ad3131 closes https://github.com/official-stockfish/Stockfish/pull/4526 Bench: 3661938 --- src/search.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 390c6b1c..ed81263a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1331,9 +1331,6 @@ moves_loop: // When in check, search starts here { // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && ( (improving && complexity > 971) - || value < (5 * alpha + 75 * beta) / 87 - || depth < 6) && beta < 12535 && value > -12535) depth -= 1; From f9d9c69bc33dc7a17c28cd586d7e67c1bfff66f6 Mon Sep 17 00:00:00 2001 From: Torom Date: Thu, 13 Apr 2023 21:55:13 +0200 Subject: [PATCH 250/678] Set the length of GIT_SHA to 8 characters Previously, the length of git commit hashes could vary depending on the git environment. closes https://github.com/official-stockfish/Stockfish/pull/4527 No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index abcf11b0..82664618 100644 --- a/src/Makefile +++ b/src/Makefile @@ -702,7 +702,7 @@ ifeq ($(pext),yes) endif ### 3.7.1 Try to include git commit sha for versioning -GIT_SHA = $(shell git rev-parse --short HEAD 2>/dev/null) +GIT_SHA = $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) ifneq ($(GIT_SHA), ) CXXFLAGS += -DGIT_SHA=$(GIT_SHA) endif From c90dd38903206ede56fa73c15d7d2b366d56ebdb Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:48:22 +0800 Subject: [PATCH 251/678] Simplify away complexity in evaluation Simplification STC: https://tests.stockfishchess.org/tests/view/64394bc0605991a801b4f6f0 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 72360 W: 19313 L: 19138 D: 33909 Ptnml(0-2): 206, 7883, 19800, 8112, 179 Simplification LTC: https://tests.stockfishchess.org/tests/view/6439e788c233ce943b6bdac1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 224992 W: 60665 L: 60654 D: 103673 Ptnml(0-2): 96, 21875, 68526, 21920, 79 closes https://github.com/official-stockfish/Stockfish/pull/4530 Bench: 3709369 --- src/evaluate.cpp | 10 +--------- src/evaluate.h | 2 +- src/search.cpp | 27 ++++++++------------------- src/thread.cpp | 1 - src/thread.h | 1 - 5 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 873dc5d2..851ccfe1 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1046,7 +1046,7 @@ make_v: /// evaluate() is the evaluator for the outer world. It returns a static /// evaluation of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos, int* complexity) { +Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); @@ -1075,10 +1075,6 @@ Value Eval::evaluate(const Position& pos, int* complexity) { + (424 + optimism) * abs(psq - nnue) ) / 1024; - // Return hybrid NNUE complexity to caller - if (complexity) - *complexity = nnueComplexity; - optimism = optimism * (272 + nnueComplexity) / 256; v = (nnue * scale + optimism * (scale - 748)) / 1024; } @@ -1089,10 +1085,6 @@ Value Eval::evaluate(const Position& pos, int* complexity) { // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - // When not using NNUE, return classical complexity to caller - if (complexity && useClassical) - *complexity = abs(v - psq); - return v; } diff --git a/src/evaluate.h b/src/evaluate.h index 61846073..48076670 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -31,7 +31,7 @@ class Position; namespace Eval { std::string trace(Position& pos); - Value evaluate(const Position& pos, int* complexity = nullptr); + Value evaluate(const Position& pos); extern bool useNNUE; extern std::string currentEvalFileName; diff --git a/src/search.cpp b/src/search.cpp index ed81263a..3a7f85a8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -294,14 +294,6 @@ void Thread::search() { if (mainThread) { - - if (!rootPos.checkers()) - { - int rootComplexity; - Eval::evaluate(rootPos, &rootComplexity); - mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); - } - if (mainThread->bestPreviousScore == VALUE_INFINITE) for (int i = 0; i < 4; ++i) mainThread->iterValue[i] = VALUE_ZERO; @@ -478,7 +470,7 @@ void Thread::search() { double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * mainThread->complexity; + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; // Cap used time in case of a single legal move for a better viewer experience in tournaments // yielding correct scores and sufficiently fast moves. @@ -561,7 +553,7 @@ namespace { bool givesCheck, improving, priorCapture, singularQuietLMR; bool capture, moveCountPruning, ttCapture; Piece movedPiece; - int moveCount, captureCount, quietCount, improvement, complexity; + int moveCount, captureCount, quietCount, improvement; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); @@ -723,7 +715,6 @@ namespace { ss->staticEval = eval = VALUE_NONE; improving = false; improvement = 0; - complexity = 0; goto moves_loop; } else if (excludedMove) @@ -731,17 +722,15 @@ namespace { // 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); eval = ss->staticEval; - complexity = abs(ss->staticEval - pos.psq_eg_stm()); } else if (ss->ttHit) { // Never assume anything about values stored in TT ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - ss->staticEval = eval = evaluate(pos, &complexity); - else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost + ss->staticEval = eval = evaluate(pos); + else { - complexity = abs(ss->staticEval - pos.psq_eg_stm()); if (PvNode) Eval::NNUE::hint_common_parent_position(pos); } @@ -753,7 +742,7 @@ namespace { } else { - ss->staticEval = eval = evaluate(pos, &complexity); + ss->staticEval = eval = evaluate(pos); // Save static evaluation into transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } @@ -799,15 +788,15 @@ namespace { && (ss-1)->statScore < 18755 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25 + && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly)) { assert(eval - beta >= 0); - // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4 - (complexity > 825); + // Null move dynamic reduction based on depth and eval + Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4; ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; diff --git a/src/thread.cpp b/src/thread.cpp index 202768c8..c680393e 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -160,7 +160,6 @@ void ThreadPool::clear() { main()->bestPreviousScore = VALUE_INFINITE; main()->bestPreviousAverageScore = VALUE_INFINITE; main()->previousTimeReduction = 1.0; - main()->complexity = 1.0; } diff --git a/src/thread.h b/src/thread.h index 3a114c79..09bdb470 100644 --- a/src/thread.h +++ b/src/thread.h @@ -85,7 +85,6 @@ struct MainThread : public Thread { void search() override; void check_time(); - double complexity; double previousTimeReduction; Value bestPreviousScore; Value bestPreviousAverageScore; From 7b9b793fd544aa7a599b113a40533cde16de640b Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Mon, 17 Apr 2023 11:38:26 +0200 Subject: [PATCH 252/678] Simplification of SEE verification logic Use same logic for all handled pieces. Don't prune the move if opponent King, Queen, Rook gets a discovered attack while or after the exchanges. remove an obsolete comment in position.cpp Passed STC non regression: https://tests.stockfishchess.org/tests/view/6437907594daa91835c290d0 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 107432 W: 28359 L: 28221 D: 50852 Ptnml(0-2): 298, 11724, 29524, 11882, 288 Passed LTC non-regression: https://tests.stockfishchess.org/tests/view/6438ed2ebd1a5470263c51e8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 236288 W: 63656 L: 63656 D: 108976 Ptnml(0-2): 99, 22960, 72011, 22990, 84 closes https://github.com/official-stockfish/Stockfish/pull/4533 bench: 3741125 --- src/position.cpp | 3 +-- src/search.cpp | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index e6fdb511..2a9d798f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -331,8 +331,7 @@ void Position::set_check_info() const { /// Position::set_state() computes the hash keys of the position, and other /// data that once computed is updated incrementally as moves are made. -/// The function is only used when a new position is set up, and to verify -/// the correctness of the StateInfo data when running in debug mode. +/// The function is only used when a new position is set up void Position::set_state() const { diff --git a/src/search.cpp b/src/search.cpp index 3a7f85a8..5205fb57 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1006,17 +1006,15 @@ moves_loop: // When in check, search starts here { if (depth < 2 - capture) continue; - // Don't prune the move if opp. King/Queen/Rook is attacked by a slider after the exchanges. - // Since in see_ge we don't update occupied when the king recaptures, we also don't prune the - // move when the opp. King gets a discovered slider attack DURING the exchanges. - Bitboard leftEnemies = pos.pieces(~us, ROOK, QUEEN, KING) & occupied; + // Don't prune the move if opp. King/Queen/Rook gets a discovered attack during or after the exchanges + Bitboard leftEnemies = pos.pieces(~us, KING, QUEEN, ROOK); Bitboard attacks = 0; occupied |= to_sq(move); while (leftEnemies && !attacks) { Square sq = pop_lsb(leftEnemies); attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // Exclude Queen/Rook(s) which were already threatened before SEE + // Exclude Queen/Rook(s) which were already threatened before SEE (opp King can't be in check when it's our turn) if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) attacks = 0; } From d64d4ac426e06cdd52249b0464d22f3cdb7fcf79 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:33:33 +0800 Subject: [PATCH 253/678] Simplify away depth condition for aspiration window adjust Simplification STC: https://tests.stockfishchess.org/tests/view/64351654596a20f264276ded LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 374664 W: 98942 L: 99089 D: 176633 Ptnml(0-2): 1049, 41767, 101878, 41558, 1080 Simplification LTC: https://tests.stockfishchess.org/tests/view/6439499f605991a801b4f684 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 457880 W: 123021 L: 123233 D: 211626 Ptnml(0-2): 166, 44739, 139335, 44541, 159 closes https://github.com/official-stockfish/Stockfish/pull/4534 Bench: 3879281 --- src/search.cpp | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5205fb57..e322a1c9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -288,9 +288,7 @@ void Thread::search() { ss->pv = pv; - bestValue = delta = alpha = -VALUE_INFINITE; - beta = VALUE_INFINITE; - optimism[WHITE] = optimism[BLACK] = VALUE_ZERO; + bestValue = -VALUE_INFINITE; if (mainThread) { @@ -349,18 +347,15 @@ void Thread::search() { selDepth = 0; // Reset aspiration window starting size - if (rootDepth >= 4) - { - Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 16502; - alpha = std::max(prev - delta,-VALUE_INFINITE); - beta = std::min(prev + delta, VALUE_INFINITE); + Value prev = rootMoves[pvIdx].averageScore; + delta = Value(10) + int(prev) * prev / 16502; + alpha = std::max(prev - delta,-VALUE_INFINITE); + beta = std::min(prev + delta, VALUE_INFINITE); - // Adjust optimism based on root move's previousScore - int opt = 120 * prev / (std::abs(prev) + 161); - optimism[ us] = Value(opt); - optimism[~us] = -optimism[us]; - } + // Adjust optimism based on root move's previousScore + int opt = 120 * prev / (std::abs(prev) + 161); + optimism[ us] = Value(opt); + optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail From ba06c480a752458a8159db0c9110bd3b7e34145a Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Fri, 21 Apr 2023 16:22:55 +0200 Subject: [PATCH 254/678] Less reduction for tt move. This idea is a result of my second condition combination tuning for reductions: https://tests.stockfishchess.org/tests/view/643ed5573806eca398f06d61 There were used two parameters per combination: one for the 'sign' of the first and the second condition in a combination. Values >= 50 indicate using a condition directly and values <= -50 means use the negation of a condition. Each condition pair (X,Y) had two occurances dependent of the order of the two conditions: - if X < Y the parameters used for more reduction - if X > Y the parameters used for less reduction - if X = Y then only one condition is present and A[X][X][0]/A[X][X][1] stands for using more/less reduction for only this condition. The parameter pair A[7][2][0] (value = -94.70) and A[7][2][1] (value = 93.60) was one of the strongest signals with values near 100/-100. Here condition nr. 7 was '(ss+1)->cutoffCnt > 3' and condition nr. 2 'move == ttMove'. For condition nr. 7 the negation is used because A[7][2][0] is negative. This translates finally to less reduction (because 7 > 2) for tt moves if child cutoffs <= 3. STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 65728 W: 17704 L: 17358 D: 30666 Ptnml(0-2): 184, 7092, 18008, 7354, 226 https://tests.stockfishchess.org/tests/view/643ff767ef2529086a7ed042 LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 139200 W: 37776 L: 37282 D: 64142 Ptnml(0-2): 58, 13241, 42509, 13733, 59 https://tests.stockfishchess.org/tests/view/6440bfa9ef2529086a7edbc7 closes https://github.com/official-stockfish/Stockfish/pull/4538 Bench: 3548023 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index e322a1c9..366065b8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1169,6 +1169,9 @@ moves_loop: // When in check, search starts here if ((ss+1)->cutoffCnt > 3) r++; + else if (move == ttMove) + r--; + ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] From b22a1b10bbae2bb773afb50eba23dbf15e426365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bo=C5=A1tjan=20Mejak?= Date: Tue, 11 Apr 2023 13:55:14 +0200 Subject: [PATCH 255/678] Update AUTHORS Improved some comments in the AUTHORS file, sort contributors closes https://github.com/official-stockfish/Stockfish/pull/4520 No functional change --- AUTHORS | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9b36111e..b6723246 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,17 +1,15 @@ -# List of authors for Stockfish - -# Founders of the Stockfish project and fishtest infrastructure +# Founders of the Stockfish project and Fishtest infrastructure Tord Romstad (romstad) Marco Costalba (mcostalba) Joona Kiiski (zamar) Gary Linscott (glinscott) -# Authors and inventors of NNUE, training, NNUE port +# Authors and inventors of NNUE, training, and NNUE port Yu Nasu (ynasu87) Motohiro Isozaki (yaneurao) Hisayori Noda (nodchip) -# all other authors of the code in alphabetical order +# All other authors of Stockfish code (in alphabetical order) Aditya (absimaldata) Adrian Petrescu (apetresc) Ajith Chandy Jose (ajithcj) @@ -47,12 +45,12 @@ Chess13234 Chris Cain (ceebo) clefrks Dale Weiler (graphitemaster) -Dan Schmidt (dfannius) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) +Dan Schmidt (dfannius) Dariusz Orzechowski (dorzechowski) -David Zar David (dav1312) +David Zar Daylen Yang (daylen) Deshawn Mohan-Smith (GoldenRare) Dieter Dobbelaere (ddobbelaere) @@ -66,7 +64,6 @@ Eelco de Groot (KingDefender) Elvin Liu (solarlight2) erbsenzaehler Ernesto Gatti -Linmiao Xu (linrock) Fabian Beuke (madnight) Fabian Fichter (ianfab) Fanael Linithien (Fanael) @@ -83,30 +80,30 @@ Gontran Lemaire (gonlem) Goodkov Vasiliy Aleksandrovich (goodkov) Gregor Cramer GuardianRM -Günther Demetz (pb00067, pb00068) Guy Vreuls (gvreuls) +Günther Demetz (pb00067, pb00068) Henri Wiechers Hiraoka Takuya (HiraokaTakuya) homoSapiensSapiens Hongzhi Cheng Ivan Ivec (IIvec) Jacques B. (Timshel) +Jake Senne (w1wwwwww) Jan Ondruš (hxim) Jared Kish (Kurtbusch, kurt22i) -Jake Senne (w1wwwwww) Jarrod Torriero (DU-jdto) -Jean Gauthier (OuaisBla) Jean-Francois Romang (jromang) +Jean Gauthier (OuaisBla) Jekaa Jerry Donald Watson (jerrydonaldwatson) jjoshua2 -Jonathan Calovski (Mysseno) Jonathan Buladas Dumale (SFisGOD) +Jonathan Calovski (Mysseno) Jonathan McDermid (jonathanmcdermid) Joost VandeVondele (vondele) -Jörg Oster (joergoster) Joseph Ellis (jhellis3) Joseph R. Prostko +Jörg Oster (joergoster) Julian Willemer (NightlyKing) jundery Justin Blanchard (UncombedCoconut) @@ -120,6 +117,7 @@ Krystian Kuzniarek (kuzkry) Leonardo Ljubičić (ICCF World Champion) Leonid Pechenik (lp--) Liam Keegan (lkeegan) +Linmiao Xu (linrock) Linus Arver (listx) loco-loco Lub van den Berg (ElbertoOne) @@ -151,11 +149,11 @@ Moez Jellouli (MJZ1977) Mohammed Li (tthsqe12) Muzhen J (XInTheDark) Nathan Rugg (nmrugg) -Nick Pelling (nickpelling) +Nguyen Pham (nguyenpham) Nicklas Persson (NicklasPersson) +Nick Pelling (nickpelling) Niklas Fiekas (niklasf) Nikolay Kostov (NikolayIT) -Nguyen Pham (nguyenpham) Norman Schmidt (FireFather) notruck Ofek Shochat (OfekShochat, ghostway) @@ -170,6 +168,7 @@ Peter Schneider (pschneider1968) Peter Zsifkovits (CoffeeOne) PikaCat Praveen Kumar Tummala (praveentml) +Prokop Randáček (ProkopRandacek) Rahul Dsilva (silversolver1) Ralph Stößer (Ralph Stoesser) Raminder Singh @@ -178,8 +177,8 @@ Reuven Peleg (R-Peleg) Richard Lloyd (Richard-Lloyd) Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) -Ron Britvich (Britvich) Ronald de Man (syzygy1, syzygy) +Ron Britvich (Britvich) rqs Rui Coelho (ruicoelhopedro) Ryan Schmitt @@ -200,13 +199,12 @@ Stefano Di Martino (StefanoD) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Syine Mineta (MinetaS) -Prokop Randáček (ProkopRandacek) Thanar2 thaspel theo77186 +Tomasz Sobczyk (Sopel97) Tom Truscott Tom Vijlbrief (tomtor) -Tomasz Sobczyk (Sopel97) Torsten Franz (torfranz, tfranzer) Torsten Hellwig (Torom) Tracey Emery (basepr1me) @@ -217,8 +215,7 @@ Vince Negri (cuddlestmonkey) xefoci7612 zz4032 - # Additionally, we acknowledge the authors and maintainers of fishtest, -# an amazing and essential framework for the development of Stockfish! +# an amazing and essential framework for Stockfish development! # # https://github.com/glinscott/fishtest/blob/master/AUTHORS From c3ce2204083400267592dc088b8ad9e88aed56b1 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 15 Apr 2023 09:51:01 -0400 Subject: [PATCH 256/678] Created by retraining the master net with these changes to the dataset: * Extending v6 filtering to data from T77 dec2021, T79 may2022, and T80 nov2022 * Reducing the number of duplicate positions, prioritizing position scores seen later in time * Using a binpack minimizer to reduce the overall data size Trained the same way as the previous master net, aside from the dataset changes: ``` python3 easy_train.py \ --experiment-name leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovT79aprmayT78jantosepT77dec-v6dd \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovT79aprmayT78jantosepT77dec-v6dd.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes \ --start-from-engine-test-net True \ --early-fen-skipping 30 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --max_epoch 900 \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --gpus "0," \ --seed $RANDOM ``` The new v6-dd filtering reduces duplicate positions by iterating over hourly data files within leela test runs, starting with the most recent, then keeping positions the first time they're seen and ignoring positions that are seen again. This ordering was done with the assumption that position scores seen later in time are generally more accurate than scores seen earlier in the test run. Positions are de-duplicated based on piece orientations, the first token in fen strings. The binpack minimizer was run with default settings after first merging monthly data into single binpacks. ``` python3 interleave_binpacks.py \ leela96-filt-v2.binpack \ dfrc99-filt-v2.binpack \ T60-nov2021-12tb7p-eval-filt-v2.binpack \ T60-dec2021-12tb7p-eval-filt-v2.binpack \ filt-v6/test80-aug2022-16tb7p-filter-v6.min-mar2023.binpack \ filt-v6/test80-sep2022-16tb7p-filter-v6.min-mar2023.binpack \ filt-v6-dd/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test80-jul2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-oct2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-nov2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test79-apr2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test79-may2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test78-juntosep2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test77-dec2021-16tb7p-filter-v6-dd.binpack \ /data/leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovT79aprmayT78jantosepT77dec-v6dd.binpack ``` The code for v6-dd filtering is available along with training data preparation scripts at: https://github.com/linrock/nnue-data Links for downloading the training data components: https://robotmoon.com/nnue-training-data/ The binpack minimizer is from: #4447 Local elo at 25k nodes per move: nn-epoch859.nnue : 1.2 +/- 2.6 Passed STC: https://tests.stockfishchess.org/tests/view/643aad7db08900ff1bc5a832 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 565040 W: 150225 L: 149162 D: 265653 Ptnml(0-2): 1875, 62137, 153229, 63608, 1671 Passed LTC: https://tests.stockfishchess.org/tests/view/643ecf2fa43cf30e719d2042 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 1014840 W: 274645 L: 272456 D: 467739 Ptnml(0-2): 515, 98565, 306970, 100956, 414 closes https://github.com/official-stockfish/Stockfish/pull/4545 bench 3476305 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 48076670..9dc45371 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-dabb1ed23026.nnue" + #define EvalFileDefaultName "nn-1ceb1a57d117.nnue" namespace NNUE { From 41f50b2c83a0ba36a2b9c507c1783e57c9b13485 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 21 Apr 2023 09:37:29 -0400 Subject: [PATCH 257/678] Update default net to nn-e1fb1ade4432.nnue Created by retraining nn-dabb1ed23026.nnue with a dataset composed of: * The previous best dataset (nn-1ceb1a57d117.nnue dataset) * Adding de-duplicated T80 data from feb2023 and the last 10 days of jan2023, filtered with v6-dd Initially trained with the same options as the recent master net (nn-1ceb1a57d117.nnue). Around epoch 890, training was manually stopped and max epoch increased to 1000. ``` python3 easy_train.py \ --experiment-name leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovjanfebT79aprmayT78jantosepT77dec-v6dd \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovjanfebT79aprmayT78jantosepT77dec-v6dd.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes \ --start-from-engine-test-net True \ --early-fen-skipping 30 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --max_epoch 900 \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --gpus "0," \ --seed $RANDOM ``` The same v6-dd filtering and binpack minimizer was used for preparing the recent nn-1ceb1a57d117.nnue dataset. ``` python3 interleave_binpacks.py \ leela96-filt-v2.binpack \ dfrc99-filt-v2.binpack \ T60-nov2021-12tb7p-eval-filt-v2.binpack \ T60-dec2021-12tb7p-eval-filt-v2.binpack \ filt-v6/test80-aug2022-16tb7p-filter-v6.min-mar2023.binpack \ filt-v6/test80-sep2022-16tb7p-filter-v6.min-mar2023.binpack \ filt-v6-dd/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test80-jul2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-oct2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-nov2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-jan2022-3of3-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test79-apr2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test79-may2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test78-juntosep2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test77-dec2021-16tb7p-filter-v6-dd.binpack \ /data/leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovjanfebT79aprmayT78jantosepT77dec-v6dd.binpack ``` Links for downloading the training data components can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch919.nnue : 2.6 +/- 2.8 Passed STC vs. nn-dabb1ed23026.nnue https://tests.stockfishchess.org/tests/view/644420df94ff3db5625f2af5 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 125960 W: 33898 L: 33464 D: 58598 Ptnml(0-2): 351, 13920, 34021, 14320, 368 Passed LTC vs. nn-1ceb1a57d117.nnue https://tests.stockfishchess.org/tests/view/64469f128d30316529b3dc46 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 24544 W: 6817 L: 6542 D: 11185 Ptnml(0-2): 8, 2252, 7488, 2505, 19 closes https://github.com/official-stockfish/Stockfish/pull/4546 bench 3714847 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 9dc45371..f5db1c1e 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1ceb1a57d117.nnue" + #define EvalFileDefaultName "nn-e1fb1ade4432.nnue" namespace NNUE { From 21d6b69f7c8d0c0a71fe627714913a59d39a3b57 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 29 Apr 2023 15:45:22 -0400 Subject: [PATCH 258/678] Update 7 eval and optimism params Params found using spsa at 30+0.3 with this tuning config: ``` // evaluate.cpp int nnueOptScaleBase = 1001; int nnueComplexityMult = 406; int nnueComplexityOptOffset = 424; int evalOptComplexityOffset = 272; int evalOptScaleOffset = 748; TUNE(SetRange(801, 1201), nnueOptScaleBase); TUNE(SetRange(306, 506), nnueComplexityMult); TUNE(SetRange(324, 524), nnueComplexityOptOffset); TUNE(SetRange(172, 372), evalOptComplexityOffset); TUNE(SetRange(648, 848), evalOptScaleOffset); // search.cpp int searchOptBase = 120; int searchOptDenom = 161; TUNE(SetRange(20, 220), searchOptBase); TUNE(SetRange(111, 211), searchOptDenom); ``` Passed STC: https://tests.stockfishchess.org/tests/view/644dda8accf5e93df5e50cbe LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 136800 W: 36682 L: 36237 D: 63881 Ptnml(0-2): 353, 14910, 37492, 15229, 416 Passed LTC: https://tests.stockfishchess.org/tests/view/644eaedb3f31c3bbe4a3d345 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 64548 W: 17624 L: 17272 D: 29652 Ptnml(0-2): 33, 6112, 19631, 6466, 32 closes https://github.com/official-stockfish/Stockfish/pull/4550 bench 3670343 --- src/evaluate.cpp | 10 +++++----- src/search.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 851ccfe1..d8f4e2e1 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos) { else { int nnueComplexity; - int scale = 1001 + pos.non_pawn_material() / 64; + int scale = 967 + pos.non_pawn_material() / 64; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; @@ -1071,12 +1071,12 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 406 * nnueComplexity - + (424 + optimism) * abs(psq - nnue) + nnueComplexity = ( 402 * nnueComplexity + + (454 + optimism) * abs(psq - nnue) ) / 1024; - optimism = optimism * (272 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 748)) / 1024; + optimism = optimism * (274 + nnueComplexity) / 256; + v = (nnue * scale + optimism * (scale - 791)) / 1024; } // Damp down the evaluation linearly when shuffling diff --git a/src/search.cpp b/src/search.cpp index 366065b8..8ce9c56e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -353,7 +353,7 @@ void Thread::search() { beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 120 * prev / (std::abs(prev) + 161); + int opt = 102 * prev / (std::abs(prev) + 147); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; From 72d542f00026fd8437e6033c95802714e4cd45d1 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Fri, 28 Apr 2023 19:05:56 +0200 Subject: [PATCH 259/678] Adjust reductions Decrease further on cutNodes with tte->depth() >= depth + 3 condition. LTC: https://tests.stockfishchess.org/tests/view/644dc84bccf5e93df5e50c13 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 155346 W: 42184 L: 41660 D: 71502 Ptnml(0-2): 59, 14765, 47504, 15283, 62 STC: https://tests.stockfishchess.org/tests/view/644d05de68e01d8194cd9bbb LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 121888 W: 32868 L: 32444 D: 56576 Ptnml(0-2): 332, 13273, 33343, 13631, 365 closes https://github.com/official-stockfish/Stockfish/pull/4552 bench: 3739675 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8ce9c56e..a6618c5b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -884,7 +884,7 @@ namespace { // Use qsearch if depth is equal or below zero (~9 Elo) if ( PvNode && !ttMove) - depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); + depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); @@ -1141,9 +1141,10 @@ moves_loop: // When in check, search starts here // Decrease reduction if position is or has been on the PV // and node is not likely to fail low. (~3 Elo) + // Decrease further on cutNodes. (~1 Elo) if ( ss->ttPv && !likelyFailLow) - r -= 2; + r -= cutNode && tte->depth() >= depth + 3 ? 3 : 2; // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss-1)->moveCount > 7) From 2429e162890478a48ab9b1cf0c431de9ebaf9429 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 5 May 2023 07:54:12 +0300 Subject: [PATCH 260/678] Reduce more if current node has a lot of refuted moves. This patch refines idea of cutoff count - in master we reduce more if current node has at least 4 moves that are refuted by search, this patch increases this count by 1 if refutation happened without having a tt move. Passed STC: https://tests.stockfishchess.org/tests/view/645363c36206ee34ebf8191d LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 67616 W: 18220 L: 17874 D: 31522 Ptnml(0-2): 142, 7346, 18504, 7656, 160 Passed LTC: https://tests.stockfishchess.org/tests/view/6453a0ea6206ee34ebf82796 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 195228 W: 52741 L: 52140 D: 90347 Ptnml(0-2): 53, 18718, 59482, 19297, 64 closes https://github.com/official-stockfish/Stockfish/pull/4556 bench 3448916 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a6618c5b..a1f916d0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1326,7 +1326,7 @@ moves_loop: // When in check, search starts here } else { - ss->cutoffCnt++; + ss->cutoffCnt += 1 + !ttMove; assert(value >= beta); // Fail high break; } From 28442195c7d168a87221c6f1ae9ac51893427250 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Wed, 3 May 2023 14:11:55 +0300 Subject: [PATCH 261/678] Clean up after "Simplify away complexity in evaluation" closes https://github.com/official-stockfish/Stockfish/pull/4555 No functional change. --- src/search.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a1f916d0..8d9dc04f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -724,11 +724,8 @@ namespace { ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) ss->staticEval = eval = evaluate(pos); - else - { - if (PvNode) - Eval::NNUE::hint_common_parent_position(pos); - } + else if (PvNode) + Eval::NNUE::hint_common_parent_position(pos); // ttValue can be used as a better position evaluation (~7 Elo) if ( ttValue != VALUE_NONE From 464ebdf127273db0ccb0084c881b255b880e922e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 5 May 2023 23:32:56 +0300 Subject: [PATCH 262/678] Small cleanup In search remove one condition check and reorder conditions. Removes some code. Passed non-regression test: https://tests.stockfishchess.org/tests/view/64548fa06206ee34ebf853ad LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 282976 W: 75327 L: 75374 D: 132275 Ptnml(0-2): 604, 29673, 80995, 29598, 618 closes https://github.com/official-stockfish/Stockfish/pull/4557 No functional change --- src/search.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8d9dc04f..45fc1a7e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1310,7 +1310,13 @@ moves_loop: // When in check, search starts here if (PvNode && !rootNode) // Update pv even in fail-high case update_pv(ss->pv, move, (ss+1)->pv); - if (PvNode && value < beta) // Update alpha! Always alpha < beta + if (value >= beta) + { + ss->cutoffCnt += 1 + !ttMove; + assert(value >= beta); // Fail high + break; + } + else { // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 @@ -1319,13 +1325,7 @@ moves_loop: // When in check, search starts here depth -= 1; assert(depth > 0); - alpha = value; - } - else - { - ss->cutoffCnt += 1 + !ttMove; - assert(value >= beta); // Fail high - break; + alpha = value; // Update alpha! Always alpha < beta } } } From 65e2150501b87e6ce00fae4e3f056444f39462fd Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 7 May 2023 23:33:04 +0300 Subject: [PATCH 263/678] Refine deeper post-lmr searches This patch improves logic conditions for performing deeper searches after passed LMR. Instead of exceeding alpha by some margin now it requires to exceed the current best value - which may be lower than alpha (but never bigger since we update alpha with bestvalue if it exceeds alpha). Passed STC: https://tests.stockfishchess.org/tests/view/6455f78008858de8313775b6 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 209344 W: 55993 L: 55448 D: 97903 Ptnml(0-2): 507, 22798, 57526, 23325, 516 Passed LTC: https://tests.stockfishchess.org/tests/view/64572d46eb75932ccfebff97 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 66288 W: 17867 L: 17514 D: 30907 Ptnml(0-2): 21, 6240, 20269, 6593, 21 closes https://github.com/official-stockfish/Stockfish/pull/4559 bench 3808503 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 45fc1a7e..bc495c0e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1201,7 +1201,7 @@ moves_loop: // When in check, search starts here { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (alpha + 58 + 12 * (newDepth - d)); + const bool doDeeperSearch = value > (bestValue + 68 + 12 * (newDepth - d)); const bool doEvenDeeperSearch = value > alpha + 588 && ss->doubleExtensions <= 5; const bool doShallowerSearch = value < bestValue + newDepth; From 5f7b26aaa0d0bbdeb50ea6b17f049f167c1eb996 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 19 May 2023 23:02:27 +0200 Subject: [PATCH 264/678] Update WLD model using data of May, recalibrate the WLD model. closes https://github.com/official-stockfish/Stockfish/pull/4577 No functional change --- src/uci.cpp | 4 ++-- src/uci.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 8f9684ee..523d551e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -207,8 +207,8 @@ namespace { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = { 0.33677609, -4.30175627, 33.08810557, 365.60223431}; - constexpr double bs[] = { -2.50471102, 14.23235405, -14.33066859, 71.42705250 }; + constexpr double as[] = { 1.07390458, -6.94334517, 31.95090161, 317.75424048}; + constexpr double bs[] = { -2.82843814, 16.64518180, -19.74439200, 68.39499088 }; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); diff --git a/src/uci.h b/src/uci.h index 9ca0ed36..680d2d2c 100644 --- a/src/uci.h +++ b/src/uci.h @@ -35,7 +35,7 @@ namespace UCI { // the win_rate_model() such that Stockfish outputs an advantage of // "100 centipawns" for a position if the engine has a 50% probability to win // from this position in selfplay at fishtest LTC time control. -const int NormalizeToPawnValue = 394; +const int NormalizeToPawnValue = 343; class Option; From f030a1c592eabfa602ff8560d365e516298363ce Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Tue, 16 May 2023 21:30:53 +0800 Subject: [PATCH 265/678] Search tuning at very long time control Many search parameter changes, tuned (https://tests.stockfishchess.org/tests/view/645e4c67d55cccb2e64220ff) at ~300k games @ VLTC (120+1.2). Failed STC: https://tests.stockfishchess.org/tests/view/6465fcd77968ca827c1410c2 LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 33824 W: 8863 L: 9067 D: 15894 Ptnml(0-2): 89, 3833, 9266, 3641, 83 Neutral LTC: https://tests.stockfishchess.org/tests/view/646385ce87f6567dd4df4e37 Elo: -0.48 +-1.2 (95%) LOS: 22.2% Total: 60000 W: 16235 L: 16318 D: 27447 Ptnml(0-2): 27, 5831, 18366, 5750, 26 nElo: -1.08 +-2.8 (95%) PairsRatio: 0.99 Passed VLTC 180+1.8: https://tests.stockfishchess.org/tests/view/646385f787f6567dd4df4e3e LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 126448 W: 34704 L: 34258 D: 57486 Ptnml(0-2): 9, 10970, 40825, 11406, 14 Passed VLTC SMP 60+0.6 8thread: https://tests.stockfishchess.org/tests/view/646628de884ce93b65df2ac9 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 59456 W: 16791 L: 16487 D: 26178 Ptnml(0-2): 5, 4473, 20467, 4779, 4 closes https://github.com/official-stockfish/Stockfish/pull/4574 Bench: 3347573 --- src/search.cpp | 94 +++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bc495c0e..429db9a5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -64,7 +64,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(154 * (d - improving)); + return Value(148 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1449 - int(delta) * 937 / int(rootDelta)) / 1024 + (!i && r > 941); + return (r + 1356 - int(delta) * 983 / int(rootDelta)) / 1024 + (!i && r > 901); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(341 * d - 470, 1710); + return std::min(337 * d - 497, 1632); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -162,7 +162,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((19.47 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((20.89 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -348,12 +348,12 @@ void Thread::search() { // Reset aspiration window starting size Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 16502; + delta = Value(11) + int(prev) * prev / 15368; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 102 * prev / (std::abs(prev) + 147); + int opt = 116 * prev / (std::abs(prev) + 143); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; @@ -742,7 +742,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1920, 1920); + int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1717, 1717); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -752,13 +752,13 @@ namespace { // margin and the improving flag are used in various pruning heuristics. improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 156; + : 163; improving = improvement > 0; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 426 - 256 * depth * depth) + if (eval < alpha - 467 - 266 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -769,18 +769,18 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 9 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 280 >= beta + && eval - futility_margin(depth, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta - && eval < 25128) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 22761) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 18755 + && (ss-1)->statScore < 18404 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + && ss->staticEval >= beta - 19 * depth - improvement / 13 + 257 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly)) @@ -823,13 +823,13 @@ namespace { } } - probCutBeta = beta + 186 - 54 * improving; + probCutBeta = beta + 174 - 60 * improving; // Step 10. ProbCut (~10 Elo) // 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. if ( !PvNode - && depth > 4 + && depth > 3 && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY // 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 @@ -887,20 +887,20 @@ namespace { return qsearch(pos, ss, alpha, beta); if ( cutNode - && depth >= 7 + && depth >= 8 && !ttMove) depth -= 2; moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 391; + probCutBeta = beta + 430; if ( ss->inCheck && !PvNode && depth >= 2 && ttCapture && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 3 + && tte->depth() >= depth - 4 && ttValue >= probCutBeta && abs(ttValue) <= VALUE_KNOWN_WIN && abs(beta) <= VALUE_KNOWN_WIN) @@ -986,15 +986,15 @@ moves_loop: // When in check, search starts here { // Futility pruning for captures (~2 Elo) if ( !givesCheck - && lmrDepth < 6 + && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) + && ss->staticEval + 207 + 223 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] * 1078 / 7000 < alpha) continue; Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-206) * depth)) + if (!pos.see_ge(move, occupied, Value(-205) * depth)) { if (depth < 2 - capture) continue; @@ -1022,24 +1022,24 @@ moves_loop: // When in check, search starts here // Continuation history based pruning (~2 Elo) if ( lmrDepth < 5 - && history < -4405 * (depth - 1)) + && history < -3792 * (depth - 1)) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7278; + lmrDepth += history / 7019; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck - && lmrDepth < 13 - && ss->staticEval + 103 + 138 * lmrDepth <= alpha) + && lmrDepth < 12 + && ss->staticEval + 111 + 136 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 16 * lmrDepth))) + if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth - 33 * lmrDepth / 2))) continue; } } @@ -1054,7 +1054,7 @@ moves_loop: // When in check, search starts here // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 21) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ @@ -1062,7 +1062,7 @@ moves_loop: // When in check, search starts here && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (3 + 2 * (ss->ttPv && !PvNode)) * depth / 2; + Value singularBeta = ttValue - (99 + 65 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; @@ -1076,8 +1076,8 @@ moves_loop: // When in check, search starts here // Avoid search explosion by limiting the number of double extensions if ( !PvNode - && value < singularBeta - 25 - && ss->doubleExtensions <= 10) + && value < singularBeta - 22 + && ss->doubleExtensions <= 11) { extension = 2; depth += depth < 13; @@ -1107,15 +1107,15 @@ moves_loop: // When in check, search starts here // Check extensions (~1 Elo) else if ( givesCheck - && depth > 10 - && abs(ss->staticEval) > 88) + && depth > 9 + && abs(ss->staticEval) > 87) extension = 1; // Quiet ttMove extensions (~1 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5705) + && (*contHist[0])[movedPiece][to_sq(move)] >= 5480) extension = 1; } @@ -1144,7 +1144,7 @@ moves_loop: // When in check, search starts here r -= cutNode && tte->depth() >= depth + 3 ? 3 : 2; // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss-1)->moveCount > 7) + if ((ss-1)->moveCount > 8) r--; // Increase reduction for cut nodes (~3 Elo) @@ -1157,7 +1157,7 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) - r -= 1 + 12 / (3 + depth); + r -= 1 + 11 / (3 + depth); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) @@ -1174,10 +1174,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4082; + - 3755; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (11079 + 4626 * (depth > 6 && depth < 19)); + r -= ss->statScore / (10445 + 4762 * (depth > 6 && depth < 21)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1201,8 +1201,8 @@ moves_loop: // When in check, search starts here { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (bestValue + 68 + 12 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 588 && ss->doubleExtensions <= 5; + const bool doDeeperSearch = value > (bestValue + 63 + 11 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 662 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; @@ -1227,7 +1227,7 @@ moves_loop: // When in check, search starts here if (!ttMove && cutNode) r += 2; - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode); + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail @@ -1320,8 +1320,8 @@ moves_loop: // When in check, search starts here { // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && beta < 12535 - && value > -12535) + && beta < 14001 + && value > -12754) depth -= 1; assert(depth > 0); @@ -1370,7 +1370,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 100 * depth) + ((ss-1)->moveCount > 11); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } @@ -1499,7 +1499,7 @@ moves_loop: // When in check, search starts here if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 168; + futilityBase = bestValue + 190; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1572,7 +1572,7 @@ moves_loop: // When in check, search starts here continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-110))) + if (!pos.see_ge(move, Value(-94))) continue; } @@ -1705,7 +1705,7 @@ moves_loop: // When in check, search starts here if (!pos.capture_stage(bestMove)) { - int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus + int bonus2 = bestValue > beta + 143 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 4f24ee086828e28df7d0b2dce5c13732139e7c19 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Fri, 19 May 2023 23:16:48 +0200 Subject: [PATCH 266/678] Small simplification in history pruning. Remove the constant term of the history threshold which lowers the chance that pruning occurs. As compensation allow pruning at a slightly higher depth. Passed STC: https://tests.stockfishchess.org/tests/view/64634c9a87f6567dd4df4901 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 101536 W: 27156 L: 27012 D: 47368 Ptnml(0-2): 266, 11165, 27772, 11289, 276 Passed LTC: https://tests.stockfishchess.org/tests/view/6463d68b17982fde89d2bc2b LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 32154 W: 8741 L: 8543 D: 14870 Ptnml(0-2): 8, 3093, 9687, 3271, 18 Passed LTC: retest on top of VLTC tuning PR 4571 because this changes the history depth factor (use this new factor here) https://tests.stockfishchess.org/tests/view/6467300e165c4b29ec0afd3f LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 99270 W: 26840 L: 26707 D: 45723 Ptnml(0-2): 36, 9753, 29928, 9878, 40 closes https://github.com/official-stockfish/Stockfish/pull/4578 Bench: 2984341 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 429db9a5..10844746 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1021,8 +1021,8 @@ moves_loop: // When in check, search starts here + (*contHist[3])[movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) - if ( lmrDepth < 5 - && history < -3792 * (depth - 1)) + if ( lmrDepth < 6 + && history < -3792 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; From 4b085c4777c36939bd0a598f4bc3e0c04606e31b Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Fri, 19 May 2023 19:58:18 +0100 Subject: [PATCH 267/678] Simplify optimism calculation This change removes one of the constants in the calculation of optimism. It also changes the 2 constants used with the scale value so that they are independent, instead of applying a constant to the scale and then adjusting it again when it is applied to the optimism. This might make the tuning of these constants cleaner and more reliable in the future. STC 10+0.1 (accidentally run as an Elo gainer: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 154080 W: 41119 L: 40651 D: 72310 Ptnml(0-2): 375, 16840, 42190, 17212, 423 https://tests.stockfishchess.org/tests/live_elo/64653eabf3b1a4e86c317f77 LTC 60+0.6: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 217434 W: 58382 L: 58363 D: 100689 Ptnml(0-2): 66, 21075, 66419, 21088, 69 https://tests.stockfishchess.org/tests/live_elo/6465d077f3b1a4e86c318d6c closes https://github.com/official-stockfish/Stockfish/pull/4576 bench: 3190961 --- src/evaluate.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d8f4e2e1..cc789e35 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos) { else { int nnueComplexity; - int scale = 967 + pos.non_pawn_material() / 64; + int npm = pos.non_pawn_material() / 64; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; @@ -1071,12 +1071,12 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 402 * nnueComplexity - + (454 + optimism) * abs(psq - nnue) + nnueComplexity = ( 397 * nnueComplexity + + (477 + optimism) * abs(psq - nnue) ) / 1024; - optimism = optimism * (274 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 791)) / 1024; + optimism += optimism * nnueComplexity / 256; + v = (nnue * (945 + npm) + optimism * (174 + npm)) / 1024; } // Damp down the evaluation linearly when shuffling From 7cd650f435715f73550d1f8031315e65b701d631 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Wed, 17 May 2023 09:22:02 +0200 Subject: [PATCH 268/678] Simplify SEE verfication logic Passed STC https://tests.stockfishchess.org/tests/view/6461d51887f6567dd4df27d0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 177056 W: 47181 L: 47118 D: 82757 Ptnml(0-2): 456, 19381, 48792, 19442, 457 Passed LTC https://tests.stockfishchess.org/tests/view/64631a9287f6567dd4df4502 2.94 (-2.94,2.94) <-1.75,0.25> Total: 104346 W: 28062 L: 27935 D: 48349 Ptnml(0-2): 25, 10190, 31631, 10287, 40 closes https://github.com/official-stockfish/Stockfish/pull/4578 bench: 2903251 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 10844746..653cbf33 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -996,9 +996,7 @@ moves_loop: // When in check, search starts here // SEE based pruning (~11 Elo) if (!pos.see_ge(move, occupied, Value(-205) * depth)) { - if (depth < 2 - capture) - continue; - // Don't prune the move if opp. King/Queen/Rook gets a discovered attack during or after the exchanges + // Don't prune the move if opponent King/Queen/Rook gets a discovered attack during or after the exchanges Bitboard leftEnemies = pos.pieces(~us, KING, QUEEN, ROOK); Bitboard attacks = 0; occupied |= to_sq(move); @@ -1006,7 +1004,7 @@ moves_loop: // When in check, search starts here { Square sq = pop_lsb(leftEnemies); attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // Exclude Queen/Rook(s) which were already threatened before SEE (opp King can't be in check when it's our turn) + // Exclude Queen/Rook(s) which were already threatened before SEE (opponent King can't be in check when it's our turn) if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) attacks = 0; } From df0fb8471e5015bb4ba0b398c203b7faad45840e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 14 May 2023 00:30:35 +0300 Subject: [PATCH 269/678] Small simplification in low depth pruning Uncap low depth pruning lmr depth. It's anyway capped for most cases apart from futility pruning for captures - removes one std::min call. Passed STC: https://tests.stockfishchess.org/tests/view/645e8fa6d55cccb2e64225a1 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 184064 W: 49039 L: 48982 D: 86043 Ptnml(0-2): 462, 20353, 50349, 20402, 466 Passed LTC: https://tests.stockfishchess.org/tests/view/645f4d48d55cccb2e6423335 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 83886 W: 22613 L: 22465 D: 38808 Ptnml(0-2): 31, 8090, 25546, 8252, 24 closes https://github.com/official-stockfish/Stockfish/pull/4566 bench 3201883 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 653cbf33..f58def60 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -979,7 +979,7 @@ moves_loop: // When in check, search starts here moveCountPruning = moveCount >= futility_move_count(improving, depth); // Reduced depth of the next LMR search - int lmrDepth = std::max(newDepth - r, 0); + int lmrDepth = newDepth - r; if ( capture || givesCheck) From d7e72d801fd68f2ee3c7d6b814bbc82916c30041 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Mon, 22 May 2023 00:14:55 +0200 Subject: [PATCH 270/678] More Depth Reduction Reduce more for depth > 3 and depth < 12 LTC: https://tests.stockfishchess.org/tests/view/646c5abbd1f14fd69a6f2fab LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 197280 W: 53405 L: 52797 D: 91078 Ptnml(0-2): 62, 19025, 59886, 19577, 90 STC: https://tests.stockfishchess.org/tests/view/646bee71d1f14fd69a6f259d LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 100832 W: 26861 L: 26466 D: 47505 Ptnml(0-2): 240, 10985, 27622, 11278, 291 https://github.com/official-stockfish/Stockfish/pull/4585 bench: 2276617 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f58def60..130855c1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1317,10 +1317,11 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~1 Elo) + // Reduce more for depth > 3 and depth < 12 (~1 Elo) if ( depth > 1 && beta < 14001 && value > -12754) - depth -= 1; + depth -= depth > 3 && depth < 12 ? 2 : 1; assert(depth > 0); alpha = value; // Update alpha! Always alpha < beta From b64c97825eb473d9b5cbdb67afe65a8ac0d5ec9f Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 20 May 2023 21:28:54 +0800 Subject: [PATCH 271/678] Simplify delta calculation in aspiration window Simplification STC: https://tests.stockfishchess.org/tests/view/6468cb200db5177f2b76ecbb LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 220416 W: 58503 L: 58487 D: 103426 Ptnml(0-2): 596, 24384, 60188, 24488, 552 Simplification LTC: https://tests.stockfishchess.org/tests/view/646a15840db5177f2b770704 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 177756 W: 47882 L: 47825 D: 82049 Ptnml(0-2): 55, 17430, 53858, 17473, 62 closes https://github.com/official-stockfish/Stockfish/pull/4581 Bench: 2304063 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 130855c1..3632a469 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -409,7 +409,7 @@ void Thread::search() { else break; - delta += delta / 4 + 2; + delta += delta / 3; assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); } From a989aa1825503ab39e6b2cf77bba2f1f022f367c Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 21 May 2023 16:22:28 +0300 Subject: [PATCH 272/678] Simplify Prune moves with negative SEE Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 57760 W: 15472 L: 15286 D: 27002 Ptnml(0-2): 123, 6025, 16430, 6147, 155 https://tests.stockfishchess.org/tests/view/6468eb6b0db5177f2b76ef62 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 93966 W: 25274 L: 25141 D: 43551 Ptnml(0-2): 33, 8498, 29792, 8623, 37 https://tests.stockfishchess.org/tests/view/6469570b0db5177f2b76f81b closes: https://github.com/official-stockfish/Stockfish/pull/4579 Bench: 2304063 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 3632a469..270c5e7c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1037,7 +1037,7 @@ 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, Value(-27 * lmrDepth * lmrDepth - 33 * lmrDepth / 2))) + if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth - 16 * lmrDepth))) continue; } } From cedd73f4aa9f83c2891105695ac11e743e6ceab7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 23 May 2023 20:36:47 +0300 Subject: [PATCH 273/678] Simplify Futility pruning for captures Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 412928 W: 109433 L: 109620 D: 193875 Ptnml(0-2): 1071, 45929, 112650, 45744, 1070 https://tests.stockfishchess.org/tests/view/6468eac40db5177f2b76ef4d Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 190200 W: 51465 L: 51420 D: 87315 Ptnml(0-2): 58, 18585, 57788, 18592, 77 https://tests.stockfishchess.org/tests/view/646b66520db5177f2b772a84 closes https://github.com/official-stockfish/Stockfish/pull/4583 bench: 2486604 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 270c5e7c..32eeedab 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -989,7 +989,7 @@ moves_loop: // When in check, search starts here && lmrDepth < 7 && !ss->inCheck && ss->staticEval + 207 + 223 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] * 1078 / 7000 < alpha) + + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; Bitboard occupied; From c701745cf243f4816754167e85bf5fabf5b34e47 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Thu, 25 May 2023 14:34:52 +0800 Subject: [PATCH 274/678] Remove ss->ttHit condition where ttValue != VALUE_NONE Simplification is done at 3 separate places in the code. Thanks to peregrineshahin for helping me find 2 of such places. (See original PR #4584) Passed non-regression test LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 120256 W: 32204 L: 32085 D: 55967 Ptnml(0-2): 292, 12473, 34483, 12584, 296 https://tests.stockfishchess.org/tests/view/646f045968661bfd984325e3 closes https://github.com/official-stockfish/Stockfish/pull/4587 No functional change --- AUTHORS | 1 + src/search.cpp | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index b6723246..884bffab 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,6 +76,7 @@ George Sobala (gsobala) gguliash Giacomo Lorenzetti (G-Lorenz) Gian-Carlo Pascutto (gcp) +Goh CJ (cj5716) Gontran Lemaire (gonlem) Goodkov Vasiliy Aleksandrovich (goodkov) Gregor Cramer diff --git a/src/search.cpp b/src/search.cpp index 32eeedab..93212e23 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -615,10 +615,9 @@ namespace { // At non-PV nodes we check for an early TT cutoff if ( !PvNode - && ss->ttHit && !excludedMove && tte->depth() > depth - (tte->bound() == BOUND_EXACT) - && ttValue != VALUE_NONE // Possible in case of TT access race + && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) @@ -835,8 +834,7 @@ namespace { // 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 - && !( ss->ttHit - && tte->depth() >= depth - 3 + && !( tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) { @@ -1453,9 +1451,8 @@ moves_loop: // When in check, search starts here // At non-PV nodes we check for an early TT cutoff if ( !PvNode - && ss->ttHit && tte->depth() >= ttDepth - && ttValue != VALUE_NONE // Only in case of TT access race + && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; From 7f0b19dedf7bff7dbe2dd42e73788826486b36b6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 27 May 2023 18:01:08 +0200 Subject: [PATCH 275/678] Update CPU contributors list update CPU contributors list, the previous update was a couple of months ago, and unfortunately, was not quite accurate for the number of games played. This version is based clean calculation from the DB and an updated script that tracks things (see https://github.com/glinscott/fishtest/pull/1702). closes https://github.com/official-stockfish/Stockfish/pull/4589 No functional change --- Top CPU Contributors.txt | 196 ++++++++++++++++++++------------------- 1 file changed, 101 insertions(+), 95 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 30c963d7..7b279590 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,212 +1,218 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2022-11-19. +Contributors to Fishtest with >10,000 CPU hours, as of 2023-05-27. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 36475307 2748033975 -technologov 14570711 760073590 +noobpwnftw 37304027 2833556221 +technologov 13508659 714674674 +linrock 4121386 280027751 mlang 3026000 200065824 -dew 1689222 100034318 -grandphish2 1442171 86798057 -okrout 1439985 133471766 -pemo 1405374 44189811 -linrock 1299003 28382783 -TueRens 1163420 71159522 -JojoM 897158 55177114 +dew 1689162 100033738 +okrout 1541122 145085726 +pemo 1481818 47546583 +grandphish2 1459364 91364265 +TueRens 1178700 69951886 +JojoM 937875 60821044 tvijlbrief 796125 51897690 mibere 703840 46867607 -gvreuls 635982 40652394 -oz 590763 41201352 -sebastronomy 581517 23307132 -cw 517915 34865769 -fastgm 504266 30264740 -CSU_Dynasty 479901 31846710 -ctoks 433503 28180725 +sebastronomy 687502 35585318 +gvreuls 645570 42437926 +oz 541224 39133532 +cw 517856 34869499 +fastgm 503862 30260818 +CSU_Dynasty 464691 31166478 +leszek 460426 32840277 +ctoks 434323 28497451 crunchy 427035 27344275 -leszek 416883 27493447 -bcross 409982 28062127 -velislav 345954 22232274 +maximmasiutin 424154 26534660 +bcross 415722 29060963 +rpngn 344368 24218047 +velislav 342559 22138408 Fisherman 327231 21829379 +mgrabiak 297057 20260882 Dantist 296386 18031762 -mgrabiak 288928 18869896 -rpngn 259965 16281463 -robal 237653 15148350 -ncfish1 231764 15275003 -nordlandia 226923 14624832 +nordlandia 242642 15922516 +robal 240199 15544104 +marrco 234581 17714473 +ncfish1 227517 15233777 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 -thirdlife 198844 5453268 +olafm 192342 14968698 Thanar 179852 12365359 vdv 175544 9904472 -armo9494 168201 11136452 spams 157128 10319326 -marrco 151599 9551115 sqrt2 147963 9724586 -vdbergh 137690 8971569 +DesolatedDodo 144759 9408038 +Calis007 143165 9478764 +vdbergh 138436 9042073 CoffeeOne 137100 5024116 malala 136182 8002293 -DesolatedDodo 135276 8657464 +armo9494 136010 9447548 xoto 133759 9159372 davar 129023 8376525 +DMBK 122960 8980062 dsmith 122059 7570238 amicic 119661 7938029 Data 113305 8220352 BrunoBanani 112960 7436849 CypressChess 108331 7759788 -skiminki 106518 7062598 +skiminki 107583 7218170 MaZePallas 102823 6633619 sterni1971 100532 5880772 +jcAEie 100392 7788270 sunu 100167 7040199 zeryl 99331 6221261 +thirdlife 99124 2242380 ElbertoOne 99028 7023771 -DMBK 97572 6950312 -Calis007 96779 5611552 -cuistot 93111 5536500 +cuistot 98360 6017102 +bigpen0r 94809 6529203 brabos 92118 6186135 -Wolfgang 91769 5720158 +Wolfgang 90855 5998076 psk 89957 5984901 racerschmacer 85805 6122790 -jcAEie 85527 5630616 +Dubslow 84986 6042456 Vizvezdenec 83761 5344740 -sschnee 83557 4853690 +sschnee 83564 4853834 0x3C33 82614 5271253 BRAVONE 81239 5054681 -Dubslow 78461 5042980 +Fifis 77355 5158211 nssy 76497 5259388 jromang 76106 5236025 teddybaer 75125 5407666 -yurikvelo 73933 5031096 -tolkki963 73885 4721430 +Wencey 74181 4711488 +megaman7de 73866 4894960 Pking_cda 73776 5293873 -Bobo1239 71675 4860987 +tolkki963 73531 5020500 +yurikvelo 72847 4972808 +Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 -Gelma 69304 3980932 +markkulix 70278 5068326 manap 66273 4121774 -megaman7de 65419 4120200 -markkulix 65331 4114860 -bigpen0r 64932 4683883 tinker 64333 4268790 qurashee 61208 3429862 -AGI 58325 4258646 +Mineta 58759 4399960 +AGI 58147 4325994 +Spprtr 58106 3858759 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 +MarcusTullius 56746 3762951 ttruscott 56010 3680085 rkl 55132 4164467 renouve 53811 3501516 -Spprtr 52736 3410019 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 rap 49985 3219146 -unixwizard 49734 2536230 -pb00067 49727 3298270 +pb00067 49733 3298934 +javran 49178 4190632 +OuaisBla 48606 3442958 ronaldjerum 47654 3240695 biffhero 46564 3111352 -GPUex 45861 2926502 -Fifis 45843 3088497 -oryx 45578 3493978 VoyagerOne 45476 3452465 -Wencey 44943 2654490 +oryx 44532 3450170 +jmdana 43849 2955821 speedycpu 43842 3003273 jbwiebe 43305 2805433 Antihistamine 41788 2761312 mhunt 41735 2691355 -olafm 41277 3284344 +maposora 41534 3733078 +GPUex 41061 2998356 homyur 39893 2850481 gri 39871 2515779 -MarcusTullius 38303 2251097 Garf 37741 2999686 -kdave 37424 2557406 SC 37299 2731694 csnodgrass 36207 2688994 -jmdana 36157 2210661 strelock 34716 2074055 EthanOConnor 33370 2090311 slakovv 32915 2021889 -gopeto 31669 2060958 +Gelma 31771 1551204 +gopeto 31671 2060990 +szupaw 31248 2594920 +kdave 31157 2198362 manapbk 30987 1810399 Prcuvu 30377 2170122 anst 30301 2190091 jkiiski 30136 1904470 -spcc 30135 1903728 +spcc 29925 1901692 hyperbolic.tom 29840 2017394 -xwziegtm 29763 2347412 chuckstablers 29659 2093438 Pyafue 29650 1902349 belzedar94 28846 1811530 -OuaisBla 27636 1578800 chriswk 26902 1868317 +xwziegtm 26897 2124586 achambord 26582 1767323 Patrick_G 26276 1801617 yorkman 26193 1992080 -Ulysses 25289 1674274 +Ulysses 25285 1689346 SFTUser 25182 1675689 nabildanial 24942 1519409 Sharaf_DG 24765 1786697 rodneyc 24376 1416402 agg177 23890 1395014 -Ente 23747 1674582 -Karpovbot 23629 1313186 +Ente 23639 1671638 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 -cisco2015 22934 1763773 +Goatminola 23338 1910634 +cisco2015 22920 1763301 +Jopo12321 22890 1424926 Zirie 22542 1472937 team-oh 22272 1636708 Roady 22220 1465606 MazeOfGalious 21978 1629593 sg4032 21947 1643353 +jsys14 21935 1499128 ianh2105 21725 1632562 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 user213718 21454 1404128 -AndreasKrug 21227 1577833 sphinx 21211 1384728 jjoshua2 21001 1423089 +Zake9298 20938 1565848 +AndreasKrug 20911 1615673 horst.prack 20878 1465656 -jsys14 20729 1221010 0xB00B1ES 20590 1208666 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 -bonsi 20022 1300682 wei 19973 1745989 -dapper 19754 1167758 -Zake9298 19745 1458416 +Serpensin 19840 1697528 fishtester 19617 1257388 rstoesser 19569 1293588 eudhan 19274 1283717 +Gaster319 18934 1596772 vulcan 18871 1729392 -Jopo12321 18803 1036284 +Karpovbot 18766 1053178 jundery 18445 1115855 +votoanthuan 18012 1508836 ville 17883 1384026 -5t0ckf15hTr4in3r 17809 1105858 chris 17698 1487385 -dju 17697 994333 purplefishies 17595 1092533 +qoo_charly_cai 17494 1182667 +dju 17414 981289 iisiraider 17275 1049015 DragonLord 17014 1162790 -Karby 16457 1010138 -Goatminola 16278 1145026 +redstone59 16842 1461780 +Alb11747 16787 1213926 IgorLeMasson 16064 1147232 -Gaster319 16056 1109070 -redstone59 15953 1161664 +Karby 15982 979610 +notchris 15818 1426762 scuzzi 15757 968735 ako027ako 15671 1173203 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 Naven94 15054 834762 OssumOpossum 14857 1007129 -qoo_charly_cai 14490 847865 +ZacHFX 14783 1021842 enedene 14476 905279 -szupaw 14252 929130 bpfliegel 14233 882523 mpx86 14019 759568 jpulman 13982 870599 +Skiff84 13826 721996 crocogoat 13803 1117422 Nesa92 13786 1114691 joster 13710 946160 @@ -214,47 +220,47 @@ mbeier 13650 1044928 Hjax 13535 915487 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 +pirt 13100 1009897 Machariel 13010 863104 infinigon 12991 943216 -pirt 12925 985437 -Skiff84 12923 649994 +Maxim 12963 985594 mabichito 12903 749391 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 +korposzczur 12606 838168 +Nullvalue 12583 1048502 fatmurphy 12547 853210 -woutboat 12419 836696 SapphireBrand 12416 969604 -Oakwen 12406 840961 +Oakwen 12399 844109 deflectooor 12386 579392 modolief 12386 896470 Farseer 12249 694108 +Jackfish 12180 801372 pgontarz 12151 848794 +dbernier 12103 860824 +getraideBFF 12072 1024966 stocky 11954 699440 mschmidt 11941 803401 -MooTheCow 11871 773654 -Jackfish 11867 773550 -dbernier 11705 821780 +MooTheCow 11870 773598 +FormazChar 11689 877727 whelanh 11557 245188 -Maxim 11543 836024 -Nullvalue 11534 731410 -icewulf 11528 650470 -FormazChar 11523 861599 infinity 11470 727027 aga 11412 695127 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 -ali-al-zhrani 11272 781310 d64 11263 789184 -Bourbaki 11108 709144 +ali-al-zhrani 11245 779246 snicolet 11106 869170 -Alb11747 10855 696920 +dapper 11032 771402 +Karmatron 10828 677458 basepi 10637 744851 Cubox 10621 826448 -Karmatron 10616 674818 michaelrpg 10509 739239 OIVAS7572 10420 995586 -Garruk 10348 704905 +jojo2357 10419 929708 +WoodMan777 10380 873720 +Garruk 10365 706465 dzjp 10343 732529 ols 10259 570669 From 7e9b131efb832339ee6cd9e22b7c837c3e69a1b5 Mon Sep 17 00:00:00 2001 From: windfishballad Date: Mon, 22 May 2023 20:13:44 -0400 Subject: [PATCH 276/678] Removed quadratic term in optimism Remove term which is quadratic in optimism in the eval. Simplifies and should also remove the bias towards side to move making the eval better for analysis. STC: https://tests.stockfishchess.org/tests/view/6470a9d8c29e0d4352b0bca5 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 154432 W: 41127 L: 41040 D: 72265 Ptnml(0-2): 380, 17094, 42190, 17163, 389 LTC: https://tests.stockfishchess.org/tests/view/6471e9b3e549d9cf2fb219ef LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 127926 W: 34474 L: 34369 D: 59083 Ptnml(0-2): 43, 12505, 38776, 12582, 57 closes https://github.com/official-stockfish/Stockfish/pull/4590 Bench: 2541211 --- AUTHORS | 1 + src/evaluate.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 884bffab..d01d23cd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -213,6 +213,7 @@ tttak Unai Corzo (unaiic) Uri Blass (uriblass) Vince Negri (cuddlestmonkey) +windfishballad xefoci7612 zz4032 diff --git a/src/evaluate.cpp b/src/evaluate.cpp index cc789e35..7239fd1e 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1072,7 +1072,7 @@ Value Eval::evaluate(const Position& pos) { // Blend nnue complexity with (semi)classical complexity nnueComplexity = ( 397 * nnueComplexity - + (477 + optimism) * abs(psq - nnue) + + 477 * abs(psq - nnue) ) / 1024; optimism += optimism * nnueComplexity / 256; From c1fff71650e2f8bf5a2d63bdc043161cdfe8e460 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 12 May 2023 18:07:20 -0400 Subject: [PATCH 277/678] Update NNUE architecture to SFNNv6 with larger L1 size of 1536 Created by training a new net from scratch with L1 size increased from 1024 to 1536. Thanks to Vizvezdenec for the idea of exploring larger net sizes after recent training data improvements. A new net was first trained with lambda 1.0 and constant LR 8.75e-4. Then a strong net from a later epoch in the training run was chosen for retraining with start-lambda 1.0 and initial LR 4.375e-4 decaying with gamma 0.995. Retraining was performed a total of 3 times, for this 4-step process: 1. 400 epochs, lambda 1.0 on filtered T77+T79 v6 deduplicated data 2. 800 epochs, end-lambda 0.75 on T60T70wIsRightFarseerT60T74T75T76.binpack 3. 800 epochs, end-lambda 0.75 and early-fen-skipping 28 on the master dataset 4. 800 epochs, end-lambda 0.7 and early-fen-skipping 28 on the master dataset In the training sequence that reached the new nn-8d69132723e2.nnue net, the epochs used for the 3x retraining runs were: 1. epoch 379 trained on T77T79-filter-v6-dd.min.binpack 2. epoch 679 trained on T60T70wIsRightFarseerT60T74T75T76.binpack 3. epoch 799 trained on the master dataset For training from scratch: python3 easy_train.py \ --experiment-name new-L1-1536-T77T79-filter-v6dd \ --training-dataset /data/T77T79-filter-v6-dd.min.binpack \ --max_epoch 400 \ --lambda 1.0 \ --start-from-engine-test-net False \ --engine-test-branch linrock/Stockfish/L1-1536 \ --nnue-pytorch-branch linrock/Stockfish/misc-fixes-L1-1536 \ --tui False \ --gpus "0," \ --seed $RANDOM Retraining commands were similar to each other. For the 3rd retraining run: python3 easy_train.py \ --experiment-name L1-1536-T77T79-v6dd-Re1-LeelaFarseer-Re2-masterDataset-Re3-sameData \ --training-dataset /data/leela96-dfrc99-v2-T60novdecT80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd.binpack \ --early-fen-skipping 28 \ --max_epoch 800 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --lr 4.375e-4 \ --gamma 0.995 \ --start-from-engine-test-net False \ --start-from-model /data/L1-1536-T77T79-v6dd-Re1-LeelaFarseer-Re2-masterDataset-nn-epoch799.nnue \ --engine-test-branch linrock/Stockfish/L1-1536 \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-L1-1536 \ --tui False \ --gpus "0," \ --seed $RANDOM The T77+T79 data used is a subset of the master dataset available at: https://robotmoon.com/nnue-training-data/ T60T70wIsRightFarseerT60T74T75T76.binpack is available at: https://drive.google.com/drive/folders/1S9-ZiQa_3ApmjBtl2e8SyHxj4zG4V8gG Local elo at 25k nodes per move vs. nn-e1fb1ade4432.nnue (L1 size 1024): nn-epoch759.nnue : 26.9 +/- 1.6 Failed STC https://tests.stockfishchess.org/tests/view/64742485d29264e4cfa75f97 LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 13728 W: 3588 L: 3829 D: 6311 Ptnml(0-2): 71, 1661, 3610, 1482, 40 Failing LTC https://tests.stockfishchess.org/tests/view/64752d7c4a36543c4c9f3618 LLR: -1.91 (-2.94,2.94) <0.50,2.50> Total: 35424 W: 9522 L: 9603 D: 16299 Ptnml(0-2): 24, 3579, 10585, 3502, 22 Passed VLTC 180+1.8 https://tests.stockfishchess.org/tests/view/64752df04a36543c4c9f3638 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 47616 W: 13174 L: 12863 D: 21579 Ptnml(0-2): 13, 4261, 14952, 4566, 16 Passed VLTC SMP 60+0.6 th 8 https://tests.stockfishchess.org/tests/view/647446ced29264e4cfa761e5 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 19942 W: 5694 L: 5451 D: 8797 Ptnml(0-2): 6, 1504, 6707, 1749, 5 closes https://github.com/official-stockfish/Stockfish/pull/4593 bench 2222567 --- src/evaluate.h | 2 +- src/nnue/nnue_architecture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index f5db1c1e..0990111c 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-e1fb1ade4432.nnue" + #define EvalFileDefaultName "nn-8d69132723e2.nnue" namespace NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 508f3aae..d10434f3 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -39,7 +39,7 @@ namespace Stockfish::Eval::NNUE { using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 1024; +constexpr IndexType TransformedFeatureDimensions = 1536; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; From 07bd8adcbce41f076c36f4b65c7f9a786de0b02d Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 28 May 2023 14:45:24 -0400 Subject: [PATCH 278/678] Simplify nnue eval complexity calculation Remove a multiplier when blending nnue complexity with semi-classical complexity. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6473a71dd29264e4cfa75839 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 124768 W: 33180 L: 33060 D: 58528 Ptnml(0-2): 314, 13797, 34030, 13941, 302 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6474af3dd29264e4cfa768f4 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 108180 W: 29008 L: 28884 D: 50288 Ptnml(0-2): 29, 10420, 33075, 10530, 36 closes https://github.com/official-stockfish/Stockfish/pull/4592 bench 2316827 --- src/evaluate.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 7239fd1e..40c43d23 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1071,9 +1071,7 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 397 * nnueComplexity - + 477 * abs(psq - nnue) - ) / 1024; + nnueComplexity = 25 * (nnueComplexity + abs(psq - nnue)) / 64; optimism += optimism * nnueComplexity / 256; v = (nnue * (945 + npm) + optimism * (174 + npm)) / 1024; From d99942f25449789de78c9d36e3dcb67d4eb04e98 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 31 May 2023 09:22:26 +0300 Subject: [PATCH 279/678] Small simplification for probcut in check Remove depth condition from there as not longer needed. Passed STC: https://tests.stockfishchess.org/tests/view/647367cad29264e4cfa753e6 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 254336 W: 67830 L: 67847 D: 118659 Ptnml(0-2): 580, 28181, 69697, 28096, 614 Passed LTC: https://tests.stockfishchess.org/tests/view/647576184a36543c4c9f3af7 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 80706 W: 22048 L: 21898 D: 36760 Ptnml(0-2): 28, 7721, 24712, 7857, 35 closes https://github.com/official-stockfish/Stockfish/pull/4594 bench 2381945 --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 93212e23..41116eb2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -895,7 +895,6 @@ moves_loop: // When in check, search starts here probCutBeta = beta + 430; if ( ss->inCheck && !PvNode - && depth >= 2 && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 From 06186b786e4a73a29d6f0eef80fa7e20084a1e85 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:55:25 +0800 Subject: [PATCH 280/678] Search tuning at very long time control with new net MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The most significant change would be the singularBeta formula. It was first tested by cj5716 (see https://tests.stockfishchess.org/tests/view/647317c9d29264e4cfa74ec7), and I took much inspiration from that idea. LTC (fixed games): https://tests.stockfishchess.org/tests/view/6479d8da54dd118e1d990b12 Elo: 0.61 ± 1.2 (95%) LOS: 83.4% Total: 60000 W: 16278 L: 16172 D: 27550 Ptnml(0-2): 16, 5845, 18179, 5937, 23 nElo: 1.38 ± 2.8 (95%) PairsRatio: 1.02 VLTC 180+1.8: https://tests.stockfishchess.org/tests/view/6479da1454dd118e1d990b2b LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 33224 W: 9261 L: 8984 D: 14979 Ptnml(0-2): 5, 2809, 10710, 3080, 8 SMP VLTC 8-thread: https://tests.stockfishchess.org/tests/view/647b0fe354dd118e1d992425 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 61398 W: 17386 L: 17081 D: 26931 Ptnml(0-2): 7, 4571, 21232, 4888, 1 closes https://github.com/official-stockfish/Stockfish/pull/4603 Bench: 2805878 --- src/search.cpp | 72 ++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 41116eb2..16122315 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -64,7 +64,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(148 * (d - improving)); + return Value(140 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1356 - int(delta) * 983 / int(rootDelta)) / 1024 + (!i && r > 901); + return (r + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 + (!i && r > 936); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(337 * d - 497, 1632); + return std::min(336 * d - 547, 1561); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -162,7 +162,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.89 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((20.57 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -348,12 +348,12 @@ void Thread::search() { // Reset aspiration window starting size Value prev = rootMoves[pvIdx].averageScore; - delta = Value(11) + int(prev) * prev / 15368; + delta = Value(10) + int(prev) * prev / 15799; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 116 * prev / (std::abs(prev) + 143); + int opt = 109 * prev / (std::abs(prev) + 141); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; @@ -741,7 +741,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1717, 1717); + int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1817, 1817); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -751,13 +751,13 @@ namespace { // margin and the improving flag are used in various pruning heuristics. improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 163; + : 173; improving = improvement > 0; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 467 - 266 * depth * depth) + if (eval < alpha - 456 - 252 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -770,16 +770,16 @@ namespace { && depth < 9 && eval - futility_margin(depth, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta - && eval < 22761) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 18404 + && (ss-1)->statScore < 17329 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 19 * depth - improvement / 13 + 257 + && ss->staticEval >= beta - 21 * depth - improvement * 99 / 1300 + 258 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly)) @@ -787,7 +787,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 173, 6) + depth / 3 + 4; ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -822,7 +822,7 @@ namespace { } } - probCutBeta = beta + 174 - 60 * improving; + probCutBeta = beta + 168 - 61 * improving; // Step 10. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -892,7 +892,7 @@ namespace { moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 430; + probCutBeta = beta + 413; if ( ss->inCheck && !PvNode && ttCapture @@ -985,13 +985,13 @@ moves_loop: // When in check, search starts here if ( !givesCheck && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 207 + 223 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-205) * depth)) + if (!pos.see_ge(move, occupied, Value(-212) * depth)) { // Don't prune the move if opponent King/Queen/Rook gets a discovered attack during or after the exchanges Bitboard leftEnemies = pos.pieces(~us, KING, QUEEN, ROOK); @@ -1017,18 +1017,18 @@ moves_loop: // When in check, search starts here // Continuation history based pruning (~2 Elo) if ( lmrDepth < 6 - && history < -3792 * depth) + && history < -3832 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7019; + lmrDepth += history / 7011; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck && lmrDepth < 12 - && ss->staticEval + 111 + 136 * lmrDepth <= alpha) + && ss->staticEval + 112 + 138 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); @@ -1057,7 +1057,7 @@ moves_loop: // When in check, search starts here && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (99 + 65 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (82 + 65 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; @@ -1071,7 +1071,7 @@ moves_loop: // When in check, search starts here // Avoid search explosion by limiting the number of double extensions if ( !PvNode - && value < singularBeta - 22 + && value < singularBeta - 21 && ss->doubleExtensions <= 11) { extension = 2; @@ -1101,16 +1101,14 @@ moves_loop: // When in check, search starts here } // Check extensions (~1 Elo) - else if ( givesCheck - && depth > 9 - && abs(ss->staticEval) > 87) + else if ( givesCheck && depth > 8) extension = 1; // Quiet ttMove extensions (~1 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5480) + && (*contHist[0])[movedPiece][to_sq(move)] >= 5168) extension = 1; } @@ -1152,7 +1150,7 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) - r -= 1 + 11 / (3 + depth); + r -= 1 + 12 / (3 + depth); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) @@ -1169,10 +1167,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 3755; + - 4006; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (10445 + 4762 * (depth > 6 && depth < 21)); + r -= ss->statScore / (11124 + 4740 * (depth > 5 && depth < 22)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1196,8 +1194,8 @@ moves_loop: // When in check, search starts here { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (bestValue + 63 + 11 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 662 && ss->doubleExtensions <= 6; + const bool doDeeperSearch = value > (bestValue + 64 + 11 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 711 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; @@ -1316,8 +1314,8 @@ moves_loop: // When in check, search starts here // Reduce other moves if we have found at least one score improvement (~1 Elo) // Reduce more for depth > 3 and depth < 12 (~1 Elo) if ( depth > 1 - && beta < 14001 - && value > -12754) + && beta < 14362 + && value > -12393) depth -= depth > 3 && depth < 12 ? 2 : 1; assert(depth > 0); @@ -1366,7 +1364,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 100 * depth) + ((ss-1)->moveCount > 11); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 113 * depth) + ((ss-1)->moveCount > 12); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } @@ -1494,7 +1492,7 @@ moves_loop: // When in check, search starts here if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 190; + futilityBase = bestValue + 200; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1567,7 +1565,7 @@ moves_loop: // When in check, search starts here continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-94))) + if (!pos.see_ge(move, Value(-95))) continue; } @@ -1700,7 +1698,7 @@ moves_loop: // When in check, search starts here if (!pos.capture_stage(bestMove)) { - int bonus2 = bestValue > beta + 143 ? bonus1 // larger bonus + int bonus2 = bestValue > beta + 145 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 6cf8d938c5950ddedb8a92cdea4712f7d507c614 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Thu, 1 Jun 2023 09:50:19 -0400 Subject: [PATCH 281/678] Simplify blending nnue complexity with optimism Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6478a26d54dd118e1d98f21c LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 241248 W: 64058 L: 64063 D: 113127 Ptnml(0-2): 644, 26679, 65960, 26720, 621 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/647b464854dd118e1d9928b2 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 24336 W: 6658 L: 6451 D: 11227 Ptnml(0-2): 8, 2316, 7312, 2525, 7 closes https://github.com/official-stockfish/Stockfish/pull/4602 bench 2425813 --- src/evaluate.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 40c43d23..bf6dd69a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1070,10 +1070,8 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - // Blend nnue complexity with (semi)classical complexity - nnueComplexity = 25 * (nnueComplexity + abs(psq - nnue)) / 64; - - optimism += optimism * nnueComplexity / 256; + // Blend optimism with nnue complexity and (semi)classical complexity + optimism += 25 * optimism * (nnueComplexity + abs(psq - nnue)) / 16384; v = (nnue * (945 + npm) + optimism * (174 + npm)) / 1024; } From 5930c0defbe01576315d7d081447f94a01daf337 Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Wed, 31 May 2023 11:48:18 +0200 Subject: [PATCH 282/678] Simplify away SEE verification After 4 simplificatons over PR#4453 the idea does not yield significant improvement anymore. Maybe also https://tests.stockfishchess.org/tests/view/640c88092644b62c3394c1c5 was a fluke. Passed non-regression bounds: STC: https://tests.stockfishchess.org/tests/view/64705389c079b6583146d873 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 131936 W: 35040 L: 34930 D: 61966 Ptnml(0-2): 336, 14559, 36035, 14735, 303 LTC: https://tests.stockfishchess.org/tests/view/6471a2ade549d9cf2fb213cd LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 407700 W: 109999 L: 110164 D: 187537 Ptnml(0-2): 279, 39913, 123689, 39632, 337 closes https://github.com/official-stockfish/Stockfish/pull/4595 bench: 2675974 --- src/position.cpp | 19 +++++++------------ src/position.h | 1 - src/search.cpp | 18 +----------------- 3 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 2a9d798f..af274d3f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1061,7 +1061,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { +bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); @@ -1080,7 +1080,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1111,43 +1111,43 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { - occupied ^= least_significant_square_bb(bb); if ((swap = PawnValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { - occupied ^= least_significant_square_bb(bb); if ((swap = KnightValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { - occupied ^= least_significant_square_bb(bb); if ((swap = BishopValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { - occupied ^= least_significant_square_bb(bb); if ((swap = RookValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { - occupied ^= least_significant_square_bb(bb); if ((swap = QueenValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); @@ -1162,11 +1162,6 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return bool(res); } -bool Position::see_ge(Move m, Value threshold) const { - Bitboard occupied; - return see_ge(m, occupied, threshold); -} - /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. diff --git a/src/position.h b/src/position.h index a736f3e6..780d463c 100644 --- a/src/position.h +++ b/src/position.h @@ -143,7 +143,6 @@ public: void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; bool see_ge(Move m, Value threshold = VALUE_ZERO) const; // Accessing hash keys diff --git a/src/search.cpp b/src/search.cpp index 16122315..1e82203a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -989,25 +989,9 @@ moves_loop: // When in check, search starts here + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; - Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-212) * depth)) - { - // Don't prune the move if opponent King/Queen/Rook gets a discovered attack during or after the exchanges - Bitboard leftEnemies = pos.pieces(~us, KING, QUEEN, ROOK); - Bitboard attacks = 0; - occupied |= to_sq(move); - while (leftEnemies && !attacks) - { - Square sq = pop_lsb(leftEnemies); - attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // Exclude Queen/Rook(s) which were already threatened before SEE (opponent King can't be in check when it's our turn) - if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) - attacks = 0; - } - if (!attacks) + if (!pos.see_ge(move, Value(-205) * depth)) continue; - } } else { From ced0311890add58ab516b9c19608cbd1e1f295ed Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 30 May 2023 18:24:54 -0400 Subject: [PATCH 283/678] Remove static eval threshold for extensions when giving check Passed non-regression STC: https://tests.stockfishchess.org/tests/view/647685d54a36543c4c9f4f2a LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 114688 W: 30701 L: 30571 D: 53416 Ptnml(0-2): 336, 12708, 31136, 12818, 346 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/64774b02b81f005b572de770 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 107310 W: 28920 L: 28796 D: 49594 Ptnml(0-2): 33, 10427, 32621, 10531, 43 closes https://github.com/official-stockfish/Stockfish/pull/4599 bench 2597974 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1e82203a..4365b215 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1085,7 +1085,8 @@ moves_loop: // When in check, search starts here } // Check extensions (~1 Elo) - else if ( givesCheck && depth > 8) + else if ( givesCheck + && depth > 9) extension = 1; // Quiet ttMove extensions (~1 Elo) From 8dea070538dcad790de3c5b9720bdbb836a32440 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 1 Jun 2023 15:24:13 +0300 Subject: [PATCH 284/678] Move internal iterative reduction before probcut This patch moves IIR before probcut which allows probcut to be produced at lower depths. Comments in IIR are also slightly updated. Passed STC: https://tests.stockfishchess.org/tests/view/6472d604d29264e4cfa749fd LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 387616 W: 103295 L: 102498 D: 181823 Ptnml(0-2): 976, 42322, 106381, 43187, 942 Passed LTC: https://tests.stockfishchess.org/tests/view/6475eb8c4a36543c4c9f42e8 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 202836 W: 54901 L: 54281 D: 93654 Ptnml(0-2): 85, 19609, 61422, 20205, 97 closes https://github.com/official-stockfish/Stockfish/pull/4597 bench 2551691 --- src/search.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4365b215..0e82f04e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -822,9 +822,24 @@ namespace { } } + // Step 10. If the position doesn't a have ttMove, decrease depth by 2 + // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). + // Use qsearch if depth is equal or below zero (~9 Elo) + if ( PvNode + && !ttMove) + depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); + + if (depth <= 0) + return qsearch(pos, ss, alpha, beta); + + if ( cutNode + && depth >= 8 + && !ttMove) + depth -= 2; + probCutBeta = beta + 168 - 61 * improving; - // Step 10. ProbCut (~10 Elo) + // Step 11. ProbCut (~10 Elo) // 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. if ( !PvNode @@ -875,20 +890,6 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); } - // Step 11. If the position is not in TT, decrease depth by 2 (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). - // Use qsearch if depth is equal or below zero (~9 Elo) - if ( PvNode - && !ttMove) - depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); - - if (depth <= 0) - return qsearch(pos, ss, alpha, beta); - - if ( cutNode - && depth >= 8 - && !ttMove) - depth -= 2; - moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) From b60738e01b5393e6f85bc10d2f257dd0cd26a2f9 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Mon, 5 Jun 2023 00:59:31 +0300 Subject: [PATCH 285/678] Fix no previous moves on root. guards against no previous move existing if qSearch is called on the root node (i.e. when razoring). Passed Non-regression STC: https://tests.stockfishchess.org/tests/view/647d242d726f6b400e408143 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 53120 W: 14167 L: 13976 D: 24977 Ptnml(0-2): 109, 5597, 14981, 5740, 133 closes https://github.com/official-stockfish/Stockfish/pull/4604 Bench: 2551691 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 0e82f04e..c7b00766 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1489,7 +1489,7 @@ moves_loop: // When in check, search starts here // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = (ss-1)->currentMove != MOVE_NULL ? to_sq((ss-1)->currentMove) : SQ_NONE; + Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, From 295f57829ea189248c8b4afa8d11222d170f78da Mon Sep 17 00:00:00 2001 From: disservin Date: Mon, 17 Apr 2023 22:16:22 +0200 Subject: [PATCH 286/678] Add binaries to releases with github actions when a release is made with a tag matching sf_* the binaries will also be uploaded to the release as assets. closes https://github.com/official-stockfish/Stockfish/pull/4596 No functional change. --- .github/workflows/stockfish.yml | 6 +++-- .github/workflows/stockfish_arm_binaries.yml | 8 ++++++ .github/workflows/stockfish_binaries.yml | 27 +++++++++++++------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 6345b27c..082c65de 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -1,6 +1,8 @@ name: Stockfish on: push: + tags: + - '*' branches: - master - tools @@ -17,8 +19,8 @@ jobs: Compiles: uses: ./.github/workflows/stockfish_compile_test.yml Binaries: - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag') uses: ./.github/workflows/stockfish_binaries.yml ARM_Binaries: - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag') uses: ./.github/workflows/stockfish_arm_binaries.yml diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index e088c441..9a4734ee 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -115,6 +115,8 @@ jobs: cp "Top CPU Contributors.txt" stockfish/ cp Copying.txt stockfish/ cp AUTHORS stockfish/ + cp CITATION.cff stockfish/ + cp README.md stockfish/ tar -cvf stockfish-android-$BINARY.tar stockfish - name: Upload binaries @@ -122,3 +124,9 @@ jobs: with: name: stockfish-android-${{ matrix.binaries }} path: stockfish-android-${{ matrix.binaries }}.tar + + - name: Release + if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' + uses: softprops/action-gh-release@v1 + with: + files: stockfish-android-${{ matrix.binaries }}.tar \ No newline at end of file diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index b22897cf..86449b97 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -9,23 +9,26 @@ jobs: COMPILER: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} - OS: ${{ matrix.config.os }} + NAME: ${{ matrix.config.simple_name }} BINARY: ${{ matrix.binaries }} strategy: matrix: config: - name: Ubuntu 20.04 GCC os: ubuntu-20.04 + simple_name: ubuntu compiler: g++ comp: gcc shell: bash {0} - name: MacOS 12 Apple Clang os: macos-12 + simple_name: macos compiler: clang++ comp: clang shell: bash {0} - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 + simple_name: windows compiler: g++ comp: mingw msys_sys: mingw64 @@ -75,19 +78,17 @@ jobs: - name: Compile ${{ matrix.binaries }} build run: | - make clean make -j2 profile-build ARCH=$BINARY COMP=$COMP make strip ARCH=$BINARY COMP=$COMP - mv ./stockfish$EXT ../stockfish-$OS-$BINARY$EXT + mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT - name: Remove non src files - run: rm -f *.o .depend *.nnue + run: git clean -fx - name: Download wiki run: | git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki - cd ../wiki - rm -rf .git + rm -rf ../wiki/.git - name: Create tar archive. run: | @@ -95,14 +96,22 @@ jobs: mkdir stockfish cp -r wiki stockfish/ cp -r src stockfish/ - cp stockfish-$OS-$BINARY$EXT stockfish/ + cp stockfish-$NAME-$BINARY$EXT stockfish/ cp "Top CPU Contributors.txt" stockfish/ cp Copying.txt stockfish/ cp AUTHORS stockfish/ - tar -cvf stockfish-$OS-$BINARY.tar stockfish + cp CITATION.cff stockfish/ + cp README.md stockfish/ + tar -cvf stockfish-$NAME-$BINARY.tar stockfish - name: Upload binaries uses: actions/upload-artifact@v3 with: name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} - path: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}.tar + path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar + + - name: Release + if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' + uses: softprops/action-gh-release@v1 + with: + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar From 373359b44d0947cce2628a9a8c9b432a458615a8 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 4 Jun 2023 01:56:11 -0400 Subject: [PATCH 287/678] Update default net to nn-0dd1cebea573.nnue Created by retraining an earlier epoch of the experiment leading to the first SFNNv6 net on a more-randomized version of the nn-e1fb1ade4432.nnue dataset mixed with unfiltered T80 apr2023 data. Trained using early-fen-skipping 28 and max-epoch 960. The trainer settings and epochs used in the 5-step training sequence leading here were: 1. train from scratch for 400 epochs, lambda 1.0, constant LR 9.75e-4, T79T77-filter-v6-dd.min.binpack 2. retrain ep379, max-epoch 800, end-lambda 0.75, T60T70wIsRightFarseerT60T74T75T76.binpack 3. retrain ep679, max-epoch 800, end-lambda 0.75, skip 28, nn-e1fb1ade4432 dataset 4. retrain ep799, max-epoch 800, end-lambda 0.7, skip 28, nn-e1fb1ade4432 dataset 5. retrain ep439, max-epoch 960, end-lambda 0.7, skip 28, shuffled nn-e1fb1ade4432 + T80 apr2023 This net was epoch 559 of the final (step 5) retraining: ```bash python3 easy_train.py \ --experiment-name L1-1536-Re4-leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr-shuffled-sk28 \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-L1-1536 \ --early-fen-skipping 28 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --max_epoch 960 \ --start-from-engine-test-net False \ --start-from-model /data/L1-1536-Re3-nn-epoch439.nnue \ --engine-test-branch linrock/Stockfish/L1-1536 \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --seed $RANDOM \ --gpus "0," ``` During data preparation, most binpacks were unminimized by removing positions with score 32002 (`VALUE_NONE`). This makes the tradeoff of increasing dataset filesize on disk to increase the randomness of positions in interleaved datasets. The code used for unminimizing is at: https://github.com/linrock/Stockfish/tree/tools-unminify For preparing the dataset used in this experiment: ```bash python3 interleave_binpacks.py \ leela96-filt-v2.binpack \ dfrc99-16tb7p-eval-filt-v2.binpack \ filt-v6-dd-min/test60-novdec2021-12tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd-min/test80-aug2022-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd-min/test80-sep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd-min/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd/test80-jul2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-oct2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-nov2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd-min/test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd-min/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd/test79-apr2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test79-may2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd-min/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd/test78-juntosep2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test77-dec2021-16tb7p-filter-v6-dd.binpack \ test80-apr2023-2tb7p.binpack \ /data/leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr.binpack ``` T80 apr2023 data was converted using lc0-rescorer with ~2tb of tablebases and can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move vs. nn-e1fb1ade4432.nnue (L1 size 1024): nn-epoch559.nnue : 25.7 +/- 1.6 Passed STC: https://tests.stockfishchess.org/tests/view/647cd3b87cf638f0f53f9cbb LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 59200 W: 16000 L: 15660 D: 27540 Ptnml(0-2): 159, 6488, 15996, 6768, 189 Passed LTC: https://tests.stockfishchess.org/tests/view/647d58de726f6b400e4085d8 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 58800 W: 16002 L: 15657 D: 27141 Ptnml(0-2): 44, 5607, 17748, 5962, 39 closes https://github.com/official-stockfish/Stockfish/pull/4606 bench 2141197 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 0990111c..dcbe6b3c 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-8d69132723e2.nnue" + #define EvalFileDefaultName "nn-0dd1cebea573.nnue" namespace NNUE { From 54ad986768eec524aeab721713ea2009931b51b3 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 5 Jun 2023 01:08:38 -0400 Subject: [PATCH 288/678] Remove optimism multiplier in nnue eval calculation The same formula had passed SPRT against an earlier version of master. Passed non-regression STC vs. d99942f: https://tests.stockfishchess.org/tests/view/6478e76654dd118e1d98f72e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 118720 W: 31402 L: 31277 D: 56041 Ptnml(0-2): 301, 13148, 32344, 13259, 308 Passed non-regression LTC vs. d99942f: https://tests.stockfishchess.org/tests/view/647a22c154dd118e1d991146 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 74286 W: 20019 L: 19863 D: 34404 Ptnml(0-2): 31, 7189, 22540, 7359, 24 The earlier patch had conflicted with a faster SPRT passer, so this was tested again after rebasing on latest master. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/647d6e46726f6b400e408790 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 166176 W: 44309 L: 44234 D: 77633 Ptnml(0-2): 461, 18252, 45557, 18387, 431 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/647eb00ba268d1bc11255e7b LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 28170 W: 7713 L: 7513 D: 12944 Ptnml(0-2): 14, 2609, 8635, 2817, 10 closes https://github.com/official-stockfish/Stockfish/pull/4607 bench 2503095 --- src/evaluate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bf6dd69a..35d05427 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1071,8 +1071,8 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend optimism with nnue complexity and (semi)classical complexity - optimism += 25 * optimism * (nnueComplexity + abs(psq - nnue)) / 16384; - v = (nnue * (945 + npm) + optimism * (174 + npm)) / 1024; + optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; + v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; } // Damp down the evaluation linearly when shuffling From a9a6915e0839d3f3f54659c86f15868a7db0e386 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Mon, 5 Jun 2023 07:59:56 +0800 Subject: [PATCH 289/678] Simplify multiplier for improvement This simplifies a `* 99 / 1300` term into just `/ 13`. Passed non-regression STC: LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 58816 W: 15727 L: 15540 D: 27549 Ptnml(0-2): 149, 6370, 16203, 6517, 169 https://tests.stockfishchess.org/tests/view/647d25e4726f6b400e408165 Passed non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 154386 W: 41749 L: 41669 D: 70968 Ptnml(0-2): 94, 14992, 46956, 15042, 109 https://tests.stockfishchess.org/tests/view/647d9b3c726f6b400e408b2a closes https://github.com/official-stockfish/Stockfish/pull/4608 Bench: 2511327 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c7b00766..593fdc72 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -779,7 +779,7 @@ namespace { && (ss-1)->statScore < 17329 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 21 * depth - improvement * 99 / 1300 + 258 + && ss->staticEval >= beta - 21 * depth - improvement / 13 + 258 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly)) From e1dd005583bd6c2aaf58468efc5de86a3936380a Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Wed, 7 Jun 2023 09:01:05 +0200 Subject: [PATCH 290/678] Reintroduce SEE verification against discovered attacks Reintroduces https://github.com/official-stockfish/Stockfish/pull/4453 along with https://github.com/official-stockfish/Stockfish/pull/4469 Leaving out https://github.com/official-stockfish/Stockfish/pull/4533 https://github.com/official-stockfish/Stockfish/pull/4572 Passed STC: https://tests.stockfishchess.org/tests/view/647d8c37726f6b400e408a0a LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 143168 W: 38346 L: 37892 D: 66930 Ptnml(0-2): 352, 15672, 39164, 15962, 434 Passed LTC: https://tests.stockfishchess.org/tests/view/647ee8c528c4431bcb58e432 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 71538 W: 19560 L: 19190 D: 32788 Ptnml(0-2): 49, 6905, 21499, 7259, 57 closes https://github.com/official-stockfish/Stockfish/pull/4609 bench: 2595430 --- src/position.cpp | 19 ++++++++++++------- src/position.h | 1 + src/search.cpp | 23 +++++++++++++++++++++-- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index af274d3f..2a9d798f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1061,7 +1061,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value threshold) const { +bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { assert(is_ok(m)); @@ -1080,7 +1080,7 @@ bool Position::see_ge(Move m, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1111,43 +1111,43 @@ bool Position::see_ge(Move m, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = PawnValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { + occupied ^= least_significant_square_bb(bb); if ((swap = KnightValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { + occupied ^= least_significant_square_bb(bb); if ((swap = BishopValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { + occupied ^= least_significant_square_bb(bb); if ((swap = RookValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = QueenValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); @@ -1162,6 +1162,11 @@ bool Position::see_ge(Move m, Value threshold) const { return bool(res); } +bool Position::see_ge(Move m, Value threshold) const { + Bitboard occupied; + return see_ge(m, occupied, threshold); +} + /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. diff --git a/src/position.h b/src/position.h index 780d463c..2e6014db 100644 --- a/src/position.h +++ b/src/position.h @@ -144,6 +144,7 @@ public: // Static Exchange Evaluation bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index 593fdc72..b2c2344a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -990,9 +990,28 @@ moves_loop: // When in check, search starts here + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; + Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-205) * depth)) - continue; + if (!pos.see_ge(move, occupied, Value(-205) * depth)) + { + if (depth < 2 - capture) + continue; + // Don't prune the move if opponent Queen/Rook is under discovered attack after the exchanges + // Don't prune the move if opponent King is under discovered attack after or during the exchanges + Bitboard leftEnemies = (pos.pieces(~us, KING, QUEEN, ROOK)) & occupied; + Bitboard attacks = 0; + occupied |= to_sq(move); + while (leftEnemies && !attacks) + { + Square sq = pop_lsb(leftEnemies); + attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; + // don't consider pieces which were already threatened/hanging before SEE exchanges + if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) + attacks = 0; + } + if (!attacks) + continue; + } } else { From 932f5a2d657c846c282adcf2051faef7ca17ae15 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 7 Jun 2023 14:57:17 -0400 Subject: [PATCH 291/678] Update default net to nn-ea57bea57e32.nnue Created by retraining an earlier epoch (ep659) of the experiment that led to the first SFNNv6 net: - First retrained on the nn-0dd1cebea573 dataset - Then retrained with skip 20 on a smaller dataset containing unfiltered Leela data - And then retrained again with skip 27 on the nn-0dd1cebea573 dataset The equivalent 7-step training sequence from scratch that led here was: 1. max-epoch 400, lambda 1.0, constant LR 9.75e-4, T79T77-filter-v6-dd.min.binpack ep379 chosen for retraining in step2 2. max-epoch 800, end-lambda 0.75, T60T70wIsRightFarseerT60T74T75T76.binpack ep679 chosen for retraining in step3 3. max-epoch 800, end-lambda 0.75, skip 28, nn-e1fb1ade4432 dataset ep799 chosen for retraining in step4 4. max-epoch 800, end-lambda 0.7, skip 28, nn-e1fb1ade4432 dataset ep759 became nn-8d69132723e2.nnue (first SFNNv6 net) ep659 chosen for retraining in step5 5. max-epoch 800, end-lambda 0.7, skip 28, nn-0dd1cebea573 dataset ep759 chosen for retraining in step6 6. max-epoch 800, end-lambda 0.7, skip 20, leela-dfrc-v2-T77decT78janfebT79aprT80apr.binpack ep639 chosen for retraining in step7 7. max-epoch 800, end-lambda 0.7, skip 27, nn-0dd1cebea573 dataset ep619 became nn-ea57bea57e32.nnue For the last retraining (step7): python3 easy_train.py --experiment-name L1-1536-Re6-masterShuffled-ep639-sk27-Re5-leela-dfrc-v2-T77toT80small-Re4-masterShuffled-ep659-Re3-sameAs-Re2-leela96-dfrc99-16t-v2-T60novdecT80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-Re1-LeelaFarseer-new-T77T79 \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-L1-1536 \ --early-fen-skipping 27 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --max_epoch 800 \ --start-from-engine-test-net False \ --start-from-model /data/L1-1536-Re5-leela-dfrc-v2-T77toT80small-epoch639.nnue \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --seed $RANDOM \ --gpus "0," For preparing the step6 leela-dfrc-v2-T77decT78janfebT79aprT80apr.binpack dataset: python3 interleave_binpacks.py \ leela96-filt-v2.binpack \ dfrc99-16tb7p-eval-filt-v2.binpack \ test77-dec2021-16tb7p.no-db.min-mar2023.binpack \ test78-janfeb2022-16tb7p.no-db.min-mar2023.binpack \ test79-apr2022-16tb7p-filter-v6-dd.binpack \ test80-apr2022-16tb7p.no-db.min-mar2023.binpack \ /data/leela-dfrc-v2-T77decT78janfebT79aprT80apr.binpack The unfiltered Leela data used for the step6 dataset can be found at: https://robotmoon.com/nnue-training-data Local elo at 25k nodes per move: nn-epoch619.nnue : 2.3 +/- 1.9 Passed STC: https://tests.stockfishchess.org/tests/view/6480d43c6e6ce8d9fc6d7cc8 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 40992 W: 11017 L: 10706 D: 19269 Ptnml(0-2): 113, 4400, 11170, 4689, 124 Passed LTC: https://tests.stockfishchess.org/tests/view/648119ac6e6ce8d9fc6d8208 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 129174 W: 35059 L: 34579 D: 59536 Ptnml(0-2): 66, 12548, 38868, 13050, 55 closes https://github.com/official-stockfish/Stockfish/pull/4611 bench: 2370027 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index dcbe6b3c..fc852c8d 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-0dd1cebea573.nnue" + #define EvalFileDefaultName "nn-ea57bea57e32.nnue" namespace NNUE { From b7ee7290b552b21352491ec7f390565ff4748647 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 12 Jun 2023 09:51:28 +0200 Subject: [PATCH 292/678] Add network export to CI verify the network written by export_net matches the original closes https://github.com/official-stockfish/Stockfish/pull/4613 No functional change --- tests/instrumented.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/instrumented.sh b/tests/instrumented.sh index e9455eab..1b37c7a8 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -70,7 +70,8 @@ for args in "eval" \ "go depth 10" \ "go movetime 1000" \ "go wtime 8000 btime 8000 winc 500 binc 500" \ - "bench 128 $threads 8 default depth" + "bench 128 $threads 8 default depth" \ + "export_net verify.nnue" do echo "$prefix $exeprefix ./stockfish $args $postfix" @@ -78,6 +79,11 @@ do done +# verify the generated net equals the base net +network=`./stockfish uci | grep 'option name EvalFile type string default' | awk '{print $NF}'` +echo "Comparing $network to the written verify.nnue" +diff $network verify.nnue + # more general testing, following an uci protocol exchange cat << EOF > game.exp set timeout 240 From 38e61663d836e062af0bc002814ad5149c4b7729 Mon Sep 17 00:00:00 2001 From: AndrovT <31534597+AndrovT@users.noreply.github.com> Date: Sun, 11 Jun 2023 03:24:04 +0200 Subject: [PATCH 293/678] Use block sparse input for the first layer. Use block sparse input for the first fully connected layer on architectures with at least SSSE3. Depending on the CPU architecture, this yields a speedup of up to 10%, e.g. ``` Result of 100 runs of 'bench 16 1 13 default depth NNUE' base (...ockfish-base) = 959345 +/- 7477 test (...ckfish-patch) = 1054340 +/- 9640 diff = +94995 +/- 3999 speedup = +0.0990 P(speedup > 0) = 1.0000 CPU: 8 x AMD Ryzen 7 5700U with Radeon Graphics Hyperthreading: on ``` Passed STC: https://tests.stockfishchess.org/tests/view/6485aa0965ffe077ca12409c LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 8864 W: 2479 L: 2223 D: 4162 Ptnml(0-2): 13, 829, 2504, 1061, 25 This commit includes a net with reordered weights, to increase the likelihood of block sparse inputs, but otherwise equivalent to the previous master net (nn-ea57bea57e32.nnue). Activation data collected with https://github.com/AndrovT/Stockfish/tree/log-activations, running bench 16 1 13 varied_1000.epd depth NNUE on this data. Net parameters permuted with https://gist.github.com/AndrovT/9e3fbaebb7082734dc84d27e02094cb3. closes https://github.com/official-stockfish/Stockfish/pull/4612 No functional change --- AUTHORS | 1 + src/evaluate.h | 2 +- .../layers/affine_transform_sparse_input.h | 286 ++++++++++++++++++ src/nnue/nnue_architecture.h | 3 +- 4 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 src/nnue/layers/affine_transform_sparse_input.h diff --git a/AUTHORS b/AUTHORS index d01d23cd..63b862ce 100644 --- a/AUTHORS +++ b/AUTHORS @@ -159,6 +159,7 @@ Norman Schmidt (FireFather) notruck Ofek Shochat (OfekShochat, ghostway) Ondrej Mosnáček (WOnder93) +Ondřej Mišina (AndrovT) Oskar Werkelin Ahlin Pablo Vazquez Panthee diff --git a/src/evaluate.h b/src/evaluate.h index fc852c8d..94cd42cc 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-ea57bea57e32.nnue" + #define EvalFileDefaultName "nn-fdc1d0fe6455.nnue" namespace NNUE { diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h new file mode 100644 index 00000000..00b17c19 --- /dev/null +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -0,0 +1,286 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer AffineTransformSparseInput of NNUE evaluation function + +#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED +#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED + +#include +#include +#include +#include +#include "../nnue_common.h" +#include "affine_transform.h" +#include "simd.h" + +/* + This file contains the definition for a fully connected layer (aka affine transform) with block sparse input. +*/ + +namespace Stockfish::Eval::NNUE::Layers { +#if defined(__GNUC__) // GCC, Clang, ICC + + static inline IndexType lsb_(std::uint32_t b) { + assert(b); + return IndexType(__builtin_ctzl(b)); + } + +#elif defined(_MSC_VER) // MSVC + + static inline IndexType lsb_(std::uint32_t b) { + assert(b); + unsigned long idx; + _BitScanForward(&idx, b); + return (IndexType) idx; + } + +#else // Compiler is neither GCC nor MSVC compatible + +#error "Compiler not supported." + +#endif + + +#if defined(USE_SSSE3) + alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ + std::array, 256> v{}; + for (int i = 0; i < 256; ++i) + { + int j = i; + int k = 0; + while(j) + { + const IndexType lsbIndex = lsb_(std::uint32_t(j)); + j &= j - 1; + v[i][k] = lsbIndex; + ++k; + } + } + return v; + }(); + alignas(CacheLineSize) static inline const std::array lookup_count = [](){ + std::array v; + for (int i = 0; i < 256; ++i) + { + int j = i; + int k = 0; + while(j) + { + j &= j - 1; + ++k; + } + v[i] = k; + } + return v; + }(); + + // Find indices of nonzero numbers in an int32_t array + template + void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { +#if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) +#elif defined (USE_AVX2) + using vec_t = __m256i; + #define vec_nnz(a) _mm256_movemask_ps((__m256)_mm256_cmpgt_epi32(a, _mm256_setzero_si256())) +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_nnz(a) _mm_movemask_ps((__m128)_mm_cmpgt_epi32(a, _mm_setzero_si128())) +#endif + constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); + // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) + constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); + constexpr IndexType NumChunks = InputDimensions / ChunkSize; + constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; + constexpr IndexType OutputsPerChunk = ChunkSize / 8; + + const auto inputVector = reinterpret_cast(input); + IndexType count = 0; + __m128i base = _mm_set1_epi16(0); + __m128i increment = _mm_set1_epi16(8); + for (IndexType i = 0; i < NumChunks; ++i) + { + // bitmask of nonzero values in this chunk + unsigned nnz = 0; + for (IndexType j = 0; j < InputsPerChunk; ++j) + { + const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; + nnz |= (unsigned)vec_nnz(inputChunk) << (j * InputSimdWidth); + } + for (IndexType j = 0; j < OutputsPerChunk; ++j) + { + const auto lookup = (nnz >> (j * 8)) & 0xFF; + const auto offsets = _mm_loadu_si128(reinterpret_cast(&lookup_indices[lookup])); + _mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets)); + count += lookup_count[lookup]; + base = _mm_add_epi16(base, increment); + } + } + count_out = count; + } +# undef vec_nnz +#endif + + // Sparse input implementation + template + class AffineTransformSparseInput { + public: + // Input/output type + // Input/output type + using InputType = std::uint8_t; + using OutputType = std::int32_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = OutDims; + + static_assert(OutputDimensions % 16 == 0, "Only implemented for OutputDimensions divisible by 16."); + + static constexpr IndexType PaddedInputDimensions = + ceil_to_multiple(InputDimensions, MaxSimdWidth); + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, MaxSimdWidth); + +#if defined (USE_SSSE3) + static constexpr IndexType ChunkSize = 4; +#else + static constexpr IndexType ChunkSize = 1; +#endif + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; + } + + static IndexType get_weight_index_scrambled(IndexType i) + { + return + (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + + i / PaddedInputDimensions * ChunkSize + + i % ChunkSize; + } + + static IndexType get_weight_index(IndexType i) + { +#if defined (USE_SSSE3) + return get_weight_index_scrambled(i); +#else + return i; +#endif + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); + + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + write_little_endian(stream, biases, OutputDimensions); + + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); + + return !stream.fail(); + } + // Forward propagation + const OutputType* propagate( + const InputType* input, OutputType* output) const { + +#if defined (USE_SSSE3) +#if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 +#elif defined (USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 +#endif + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + std::uint16_t nnz[NumChunks]; + IndexType count; + + const auto input32 = reinterpret_cast(input); + + // Find indices of nonzero 32bit blocks + find_nnz(input32, nnz, count); + + const vec_t* biasvec = reinterpret_cast(biases); + vec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType j = 0; j < count; ++j) + { + const auto i = nnz[j]; + const vec_t in = vec_set_32(input32[i]); + const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in, col[k]); + } + + vec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; +# undef vec_setzero +# undef vec_set_32 +# undef vec_add_dpbusd_32 +#else + // Use dense implementation for the other architectures. + affine_transform_non_ssse3< + InputDimensions, + PaddedInputDimensions, + OutputDimensions>(output, weights, biases, input); +#endif + + return output; + } + + private: + using BiasType = OutputType; + using WeightType = std::int8_t; + + alignas(CacheLineSize) BiasType biases[OutputDimensions]; + alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; + }; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index d10434f3..413dbb3d 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -27,6 +27,7 @@ #include "features/half_ka_v2_hm.h" +#include "layers/affine_transform_sparse_input.h" #include "layers/affine_transform.h" #include "layers/clipped_relu.h" #include "layers/sqr_clipped_relu.h" @@ -48,7 +49,7 @@ struct Network static constexpr int FC_0_OUTPUTS = 15; static constexpr int FC_1_OUTPUTS = 32; - Layers::AffineTransform fc_0; + Layers::AffineTransformSparseInput fc_0; Layers::SqrClippedReLU ac_sqr_0; Layers::ClippedReLU ac_0; Layers::AffineTransform fc_1; From ece90bca9c513fe7b252da1521fc5ff701396f61 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 12 Jun 2023 22:13:08 +0300 Subject: [PATCH 294/678] Improve comments Fix comments for IIR, also document non-linear scaling in extensions. Also add explicitly the bench, to fix an issue after the last commit. closes https://github.com/official-stockfish/Stockfish/pull/4614 bench 2370027 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b2c2344a..7ee6d439 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -822,7 +822,7 @@ namespace { } } - // Step 10. If the position doesn't a have ttMove, decrease depth by 2 + // Step 10. If the position doesn't have a ttMove, decrease depth by 2 // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). // Use qsearch if depth is equal or below zero (~9 Elo) if ( PvNode @@ -1052,6 +1052,9 @@ moves_loop: // When in check, search starts here // then that move is singular and should be extended. To verify this we do // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. + // 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 this type of time controls. if ( !rootNode && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) && move == ttMove From 92c949e12e028cb4556de2786a77f2aec178d59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicolet?= Date: Mon, 12 Jun 2023 23:26:42 +0200 Subject: [PATCH 295/678] Clean-up code indentation in qsearch closes https://github.com/official-stockfish/Stockfish/pull/4615 No functional change --- src/search.cpp | 152 +++++++++++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7ee6d439..d3b5642a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1481,10 +1481,11 @@ moves_loop: // When in check, search starts here bestValue = ttValue; } else + { // In case of null move search use previous static eval with a different sign - ss->staticEval = bestValue = - (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) - : -(ss-1)->staticEval; + ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) + : -(ss-1)->staticEval; + } // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) @@ -1523,97 +1524,98 @@ moves_loop: // When in check, search starts here // or a beta cutoff occurs. while ((move = mp.next_move()) != MOVE_NONE) { - assert(is_ok(move)); + assert(is_ok(move)); - // Check for legality - if (!pos.legal(move)) - continue; + // Check for legality + if (!pos.legal(move)) + continue; - givesCheck = pos.gives_check(move); - capture = pos.capture_stage(move); + givesCheck = pos.gives_check(move); + capture = pos.capture_stage(move); - moveCount++; + moveCount++; - // Step 6. Pruning. - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY) - { - // Futility pruning and moveCount pruning (~10 Elo) - if ( !givesCheck - && to_sq(move) != prevSq - && futilityBase > -VALUE_KNOWN_WIN - && type_of(move) != PROMOTION) - { - if (moveCount > 2) - continue; + // Step 6. Pruning. + if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + { + // Futility pruning and moveCount pruning (~10 Elo) + if ( !givesCheck + && to_sq(move) != prevSq + && futilityBase > -VALUE_KNOWN_WIN + && type_of(move) != PROMOTION) + { + if (moveCount > 2) + continue; - futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; + futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; - if (futilityValue <= alpha) - { - bestValue = std::max(bestValue, futilityValue); - continue; - } + if (futilityValue <= alpha) + { + bestValue = std::max(bestValue, futilityValue); + continue; + } - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) - { - bestValue = std::max(bestValue, futilityBase); - continue; - } - } + if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + { + bestValue = std::max(bestValue, futilityBase); + continue; + } + } - // We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter - // and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead. - if (quietCheckEvasions > 1) - break; + // We prune after the second quiet check evasion move, where being 'in check' is + // implicitly checked through the counter, and being a 'quiet move' apart from + // being a tt move is assumed after an increment because captures are pushed ahead. + if (quietCheckEvasions > 1) + break; - // Continuation history based pruning (~3 Elo) - if ( !capture - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) - continue; + // Continuation history based pruning (~3 Elo) + if ( !capture + && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 + && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) + continue; - // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-95))) - continue; - } + // Do not search moves with bad enough SEE values (~5 Elo) + if (!pos.see_ge(move, Value(-95))) + continue; + } - // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + // Speculative prefetch as early as possible + prefetch(TT.first_entry(pos.key_after(move))); - // Update the current move - ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [capture] - [pos.moved_piece(move)] - [to_sq(move)]; + // Update the current move + ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] + [capture] + [pos.moved_piece(move)] + [to_sq(move)]; - quietCheckEvasions += !capture && ss->inCheck; + quietCheckEvasions += !capture && ss->inCheck; - // Step 7. Make and search the move - pos.do_move(move, st, givesCheck); - value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); - pos.undo_move(move); + // Step 7. Make and search the move + pos.do_move(move, st, givesCheck); + value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); + pos.undo_move(move); - assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // Step 8. Check for a new best move - if (value > bestValue) - { - bestValue = value; + // Step 8. Check for a new best move + if (value > bestValue) + { + bestValue = value; - if (value > alpha) - { - bestMove = move; + if (value > alpha) + { + bestMove = move; - if (PvNode) // Update pv even in fail-high case - update_pv(ss->pv, move, (ss+1)->pv); + if (PvNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss+1)->pv); - if (PvNode && value < beta) // Update alpha here! - alpha = value; - else - break; // Fail high - } - } + if (PvNode && value < beta) // Update alpha here! + alpha = value; + else + break; // Fail high + } + } } // Step 9. Check for mate From 7922e07af83dd472da6e5b38fb84214cfe46a630 Mon Sep 17 00:00:00 2001 From: Andreas Matthies Date: Tue, 13 Jun 2023 06:24:04 +0200 Subject: [PATCH 296/678] Fix for MSVC compilation. MSVC needs two more explicit casts to compile new affine_transform_sparse_input. See https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_castsi256_ps and https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castsi128_ps closes https://github.com/official-stockfish/Stockfish/pull/4616 No functional change --- AUTHORS | 1 + src/nnue/layers/affine_transform_sparse_input.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 63b862ce..a89dc130 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Alexander Kure Alexander Pagel (Lolligerhans) Alfredo Menezes (lonfom169) Ali AlZhrani (Cooffe) +Andreas Matthies (Matthies) Andrei Vetrov (proukornew) Andrew Grant (AndyGrant) Andrey Neporada (nepal) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 00b17c19..e0c3a8a0 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -98,10 +98,10 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) #elif defined (USE_AVX2) using vec_t = __m256i; - #define vec_nnz(a) _mm256_movemask_ps((__m256)_mm256_cmpgt_epi32(a, _mm256_setzero_si256())) + #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) #elif defined (USE_SSSE3) using vec_t = __m128i; - #define vec_nnz(a) _mm_movemask_ps((__m128)_mm_cmpgt_epi32(a, _mm_setzero_si128())) + #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) #endif constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) From 887bbd8b3df8a01307a38bfe529a49842f810a9c Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Tue, 13 Jun 2023 22:26:20 +0100 Subject: [PATCH 297/678] Remove setting of static to none if in check in qsearch Small simplification Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6487924d713491385c8034ae LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 59616 W: 15885 L: 15703 D: 28028 Ptnml(0-2): 144, 6130, 17086, 6296, 152 closes https://github.com/official-stockfish/Stockfish/pull/4618 No functional change. --- AUTHORS | 1 + src/search.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index a89dc130..ff224954 100644 --- a/AUTHORS +++ b/AUTHORS @@ -215,6 +215,7 @@ tttak Unai Corzo (unaiic) Uri Blass (uriblass) Vince Negri (cuddlestmonkey) +Viren windfishballad xefoci7612 zz4032 diff --git a/src/search.cpp b/src/search.cpp index d3b5642a..5de950eb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1464,7 +1464,6 @@ moves_loop: // When in check, search starts here // Step 4. Static evaluation of the position if (ss->inCheck) { - ss->staticEval = VALUE_NONE; bestValue = futilityBase = -VALUE_INFINITE; } else From 14bfec2a981e906d1bfc08331a2e15bddd07ffe4 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Wed, 14 Jun 2023 15:57:36 +0300 Subject: [PATCH 298/678] Consistent bench extraction with fishtest. Consistent with recent fishtest commit https://github.com/glinscott/fishtest/commit/c0d174396f7fb1c0b3243aaa6cc73769079f3ff9 closes https://github.com/official-stockfish/Stockfish/pull/4619 No functional change --- .github/workflows/stockfish_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 8c383fe7..28218402 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -115,7 +115,7 @@ jobs: - name: Extract the bench number from the commit history run: | - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig + git log HEAD | grep -o "\b[Bb]ench[ :]\+[1-9][0-9]\{5,9\}\b" | head -n 1 | sed "s/[^0-9]//g" > git_sig [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found" - name: Check compiler From 32d3284df5b2fd395504efa5319d64856902fef1 Mon Sep 17 00:00:00 2001 From: AndrovT <31534597+AndrovT@users.noreply.github.com> Date: Tue, 13 Jun 2023 03:00:04 +0200 Subject: [PATCH 299/678] Permute master net weights to increase sparsity Activation data collection using https://github.com/AndrovT/Stockfish/commit/ac468039ab544b03ad9a22c859a4217729c10a77 run as bench 16 1 13 varied_1000.epd depth NNUE log.bin on FENs from https://gist.github.com/AndrovT/7eae6918eb50764227e2bafe7938953c. Permutation found using https://gist.github.com/AndrovT/359c831b7223c637e9156b01eb96949e. Uses a greedy algorithm that goes sequentially through the output positions and chooses a neuron for that position such that the number of nonzero quartets is the smallest. Net weights permuted using https://gist.github.com/AndrovT/9e3fbaebb7082734dc84d27e02094cb3. Benchmark: Result of 100 runs of 'bench 16 1 13 default depth NNUE' ======================================================== base (...kfish-master) = 885869 +/- 7395 test (./stockfish ) = 895885 +/- 7368 diff = +10016 +/- 2984 speedup = +0.0113 P(speedup > 0) = 1.0000 Passed STC: https://tests.stockfishchess.org/tests/view/648866c4713491385c804728 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 126784 W: 34003 L: 33586 D: 59195 Ptnml(0-2): 283, 13001, 36437, 13358, 313 closes https://github.com/official-stockfish/Stockfish/pull/4620 No functional change. --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 94cd42cc..c35b2f07 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-fdc1d0fe6455.nnue" + #define EvalFileDefaultName "nn-cd2ff4716c34.nnue" namespace NNUE { From 0187546275cb015d42a7d99789f2f6a650b03651 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Thu, 15 Jun 2023 21:05:01 +0800 Subject: [PATCH 300/678] Small cleanup This non-functional change keeps formatting consistent. closes https://github.com/official-stockfish/Stockfish/pull/4623 Bench 2370027 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5de950eb..8ace674d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1463,9 +1463,7 @@ moves_loop: // When in check, search starts here // Step 4. Static evaluation of the position if (ss->inCheck) - { bestValue = futilityBase = -VALUE_INFINITE; - } else { if (ss->ttHit) @@ -1480,11 +1478,9 @@ moves_loop: // When in check, search starts here bestValue = ttValue; } else - { // In case of null move search use previous static eval with a different sign ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval; - } // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) From a46087ee30ace00e1edd8963ee33b0a1b87031b6 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 13 Jun 2023 23:09:05 +0300 Subject: [PATCH 301/678] Compressed network parameters Implemented LEB128 (de)compression for the feature transformer. Reduces embedded network size from 70 MiB to 39 Mib. The new nn-78bacfcee510.nnue corresponds to the master net compressed. closes https://github.com/official-stockfish/Stockfish/pull/4617 No functional change --- src/evaluate.h | 2 +- src/nnue/nnue_common.h | 77 +++++++++++++++++++++++++++++ src/nnue/nnue_feature_transformer.h | 12 ++--- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index c35b2f07..33effb1c 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-cd2ff4716c34.nnue" + #define EvalFileDefaultName "nn-78bacfcee510.nnue" namespace NNUE { diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 12309d26..d338527d 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -57,6 +57,9 @@ namespace Stockfish::Eval::NNUE { // Size of cache line (in bytes) constexpr std::size_t CacheLineSize = 64; + constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; + constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; + // SIMD width (in bytes) #if defined(USE_AVX2) constexpr std::size_t SimdWidth = 32; @@ -159,6 +162,80 @@ namespace Stockfish::Eval::NNUE { write_little_endian(stream, values[i]); } + template + inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + auto bytes_left = read_little_endian(stream); + std::uint32_t buf_pos = BUF_SIZE; + for (std::size_t i = 0; i < count; ++i) { + IntType result = 0; + size_t shift = 0; + do { + if (buf_pos == BUF_SIZE) { + stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); + buf_pos = 0; + } + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << shift; + shift += 7; + if ((byte & 0x80) == 0) { + out[i] = sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); + break; + } + } while (shift < sizeof(IntType) * 8); + } + assert(bytes_left == 0); + } + + template + inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + stream.write(Leb128MagicString, Leb128MagicStringSize); + std::uint32_t byte_count = 0; + for (std::size_t i = 0; i < count; ++i) { + IntType value = values[i]; + std::uint8_t byte; + do { + byte = value & 0x7f; + value >>= 7; + ++byte_count; + } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + write_little_endian(stream, byte_count); + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + std::uint32_t buf_pos = 0; + auto flush = [&]() { + if (buf_pos > 0) { + stream.write(reinterpret_cast(buf), buf_pos); + buf_pos = 0; + } + }; + auto write = [&](std::uint8_t byte) { + buf[buf_pos++] = byte; + if (buf_pos == BUF_SIZE) flush(); + }; + for (std::size_t i = 0; i < count; ++i) { + IntType value = values[i]; + while (true) { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((byte & 0x40) == 0 ? value == 0 : value == -1) { + write(byte); + break; + } + write(byte | 0x80); + } + } + flush(); + } + } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index a1888c7a..7571f398 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -253,9 +253,9 @@ namespace Stockfish::Eval::NNUE { // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases , HalfDimensions ); - read_little_endian(stream, weights , HalfDimensions * InputDimensions); - read_little_endian(stream, psqtWeights, PSQTBuckets * InputDimensions); + read_leb_128(stream, biases , HalfDimensions ); + read_leb_128(stream, weights , HalfDimensions * InputDimensions); + read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); return !stream.fail(); } @@ -263,9 +263,9 @@ namespace Stockfish::Eval::NNUE { // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases , HalfDimensions ); - write_little_endian(stream, weights , HalfDimensions * InputDimensions); - write_little_endian(stream, psqtWeights, PSQTBuckets * InputDimensions); + write_leb_128(stream, biases , HalfDimensions ); + write_leb_128(stream, weights , HalfDimensions * InputDimensions); + write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); return !stream.fail(); } From a68a1c11543eef6808181c92e0e7e5fb3f826f21 Mon Sep 17 00:00:00 2001 From: disservin Date: Tue, 13 Jun 2023 19:30:01 +0200 Subject: [PATCH 302/678] create prereleases upon push to master using github actions, create a prerelease for the latest commit to master. As such a development version will be available on github, in addition to the latest release. closes https://github.com/official-stockfish/Stockfish/pull/4622 No functional change --- .github/workflows/stockfish.yml | 24 +++++++++ .github/workflows/stockfish_arm_binaries.yml | 26 +++++++++ .github/workflows/stockfish_binaries.yml | 57 ++++++++++++++++++-- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 082c65de..ca52ffe0 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -12,6 +12,30 @@ on: - master - tools jobs: + Prerelease: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # returns null if no pre-release exists + - name: Get Commit SHA of Latest Pre-release + run: | + # Install required packages + sudo apt-get update + sudo apt-get install -y curl jq + + echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV + + # delete old previous pre-release and tag + - uses: dev-drprasad/delete-tag-and-release@v0.2.1 + if: env.COMMIT_SHA != 'null' + with: + tag_name: ${{ env.COMMIT_SHA }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + Sanitizers: uses: ./.github/workflows/stockfish_sanitizers.yml Tests: diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 9a4734ee..52105eb6 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -129,4 +129,30 @@ jobs: if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' uses: softprops/action-gh-release@v1 with: + files: stockfish-android-${{ matrix.binaries }}.tar + + - name: Get last commit sha + id: last_commit + run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV + + - name: Get commit date + id: commit_date + run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + + # Make sure that an old ci which still runs on master doesn't recreate a prerelease + - name: Check Pullable Commits + id: check_commits + run: | + git fetch + CHANGES=$(git rev-list HEAD..origin/master --count) + echo "CHANGES=$CHANGES" >> $GITHUB_ENV + + - name: Prerelease + if: github.ref_name == 'master' && env.CHANGES == '0' + continue-on-error: true + uses: softprops/action-gh-release@v1 + with: + name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + prerelease: true files: stockfish-android-${{ matrix.binaries }}.tar \ No newline at end of file diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 86449b97..0a53cb03 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -20,12 +20,14 @@ jobs: compiler: g++ comp: gcc shell: bash {0} + archive_ext: tar - name: MacOS 12 Apple Clang os: macos-12 simple_name: macos compiler: clang++ comp: clang shell: bash {0} + archive_ext: tar - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 simple_name: windows @@ -35,6 +37,7 @@ jobs: msys_env: x86_64-gcc shell: msys2 {0} ext: .exe + archive_ext: zip binaries: - x86-64 - x86-64-modern @@ -60,7 +63,7 @@ jobs: uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.config.msys_sys }} - install: mingw-w64-${{ matrix.config.msys_env }} make git + install: mingw-w64-${{ matrix.config.msys_env }} make git zip - name: Download the used network from the fishtest framework run: make net @@ -90,7 +93,7 @@ jobs: git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki rm -rf ../wiki/.git - - name: Create tar archive. + - name: Create directory. run: | cd .. mkdir stockfish @@ -102,16 +105,64 @@ jobs: cp AUTHORS stockfish/ cp CITATION.cff stockfish/ cp README.md stockfish/ + + - name: Create tar + if: runner.os != 'Windows' + run: | + cd .. tar -cvf stockfish-$NAME-$BINARY.tar stockfish + - name: Create zip + if: runner.os == 'Windows' + run: | + cd .. + zip -r stockfish-$NAME-$BINARY.zip stockfish + - name: Upload binaries + if: runner.os != 'Windows' uses: actions/upload-artifact@v3 with: name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar + # Artifacts automatically get zipped + # to avoid double zipping, we use the unzipped directory + - name: Upload binaries + if: runner.os == 'Windows' + uses: actions/upload-artifact@v3 + with: + name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} + path: stockfish + - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' uses: softprops/action-gh-release@v1 with: - files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} + + - name: Get last commit sha + id: last_commit + run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV + + - name: Get commit date + id: commit_date + run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + + # Make sure that an old ci which still runs on master doesn't recreate a prerelease + - name: Check Pullable Commits + id: check_commits + run: | + git fetch + CHANGES=$(git rev-list HEAD..origin/master --count) + echo "CHANGES=$CHANGES" >> $GITHUB_ENV + + - name: Prerelease + if: github.ref_name == 'master' && env.CHANGES == '0' + continue-on-error: true + uses: softprops/action-gh-release@v1 + with: + name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + prerelease: true + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} + From 6eaa1c3ecd297404b28f9e80cddf81c4c6926a51 Mon Sep 17 00:00:00 2001 From: Joerg Oster Date: Tue, 20 Jun 2023 10:40:31 +0200 Subject: [PATCH 303/678] Fix indentation in qsearch. https://github.com/official-stockfish/Stockfish/pull/4630 No functional change. --- src/search.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8ace674d..9b686a52 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1555,23 +1555,23 @@ moves_loop: // When in check, search starts here bestValue = std::max(bestValue, futilityBase); continue; } - } + } - // We prune after the second quiet check evasion move, where being 'in check' is - // implicitly checked through the counter, and being a 'quiet move' apart from - // being a tt move is assumed after an increment because captures are pushed ahead. - if (quietCheckEvasions > 1) - break; + // We prune after the second quiet check evasion move, where being 'in check' is + // implicitly checked through the counter, and being a 'quiet move' apart from + // being a tt move is assumed after an increment because captures are pushed ahead. + if (quietCheckEvasions > 1) + break; - // Continuation history based pruning (~3 Elo) - if ( !capture - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) - continue; + // Continuation history based pruning (~3 Elo) + if ( !capture + && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 + && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) + continue; - // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-95))) - continue; + // Do not search moves with bad enough SEE values (~5 Elo) + if (!pos.see_ge(move, Value(-95))) + continue; } // Speculative prefetch as early as possible From aec8fb3fdec8309a0cc78222a4f674ea6fea9411 Mon Sep 17 00:00:00 2001 From: disservin Date: Tue, 20 Jun 2023 18:27:20 +0200 Subject: [PATCH 304/678] Fix failing CI of pull requests adds a guard to prevent pull requests from trying to delete the previous pre-release closing https://github.com/official-stockfish/Stockfish/pull/4631 No functional change. --- .github/workflows/stockfish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index ca52ffe0..99c4259a 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -13,6 +13,7 @@ on: - tools jobs: Prerelease: + if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 02728736edd5915fc0abc8698635e633a3cba201 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Tue, 20 Jun 2023 09:46:02 +0200 Subject: [PATCH 305/678] Update top CPU contributors closes https://github.com/official-stockfish/Stockfish/pull/4629 No functional change --- Top CPU Contributors.txt | 117 ++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 7b279590..74c471b7 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,55 +1,55 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2023-05-27. +Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 37304027 2833556221 -technologov 13508659 714674674 -linrock 4121386 280027751 +noobpwnftw 37457426 2850540907 +technologov 14135647 742892808 +linrock 4423514 303254809 mlang 3026000 200065824 dew 1689162 100033738 -okrout 1541122 145085726 -pemo 1481818 47546583 -grandphish2 1459364 91364265 -TueRens 1178700 69951886 -JojoM 937875 60821044 +okrout 1578136 148855886 +pemo 1508508 48814305 +grandphish2 1461406 91540343 +TueRens 1194790 70400852 +JojoM 947612 61773190 tvijlbrief 796125 51897690 +sebastronomy 742434 38218524 mibere 703840 46867607 -sebastronomy 687502 35585318 -gvreuls 645570 42437926 -oz 541224 39133532 -cw 517856 34869499 +gvreuls 651026 42988582 +oz 543438 39314736 +cw 517858 34869755 fastgm 503862 30260818 -CSU_Dynasty 464691 31166478 -leszek 460426 32840277 -ctoks 434323 28497451 +leszek 467278 33514883 +CSU_Dynasty 464940 31177118 +ctoks 434416 28506889 crunchy 427035 27344275 -maximmasiutin 424154 26534660 +maximmasiutin 424795 26577722 bcross 415722 29060963 -rpngn 344368 24218047 -velislav 342559 22138408 +olafm 395922 32268020 +rpngn 348378 24560289 +velislav 342567 22138992 Fisherman 327231 21829379 -mgrabiak 297057 20260882 +mgrabiak 300612 20608380 Dantist 296386 18031762 -nordlandia 242642 15922516 -robal 240199 15544104 +nordlandia 246201 16189678 +robal 241300 15656382 marrco 234581 17714473 ncfish1 227517 15233777 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 -olafm 192342 14968698 Thanar 179852 12365359 vdv 175544 9904472 spams 157128 10319326 sqrt2 147963 9724586 -DesolatedDodo 144759 9408038 +DesolatedDodo 146350 9536172 Calis007 143165 9478764 -vdbergh 138436 9042073 +vdbergh 138650 9064413 CoffeeOne 137100 5024116 +armo9494 136191 9460264 malala 136182 8002293 -armo9494 136010 9447548 xoto 133759 9159372 davar 129023 8376525 DMBK 122960 8980062 @@ -59,43 +59,43 @@ Data 113305 8220352 BrunoBanani 112960 7436849 CypressChess 108331 7759788 skiminki 107583 7218170 +jcAEie 105675 8238962 MaZePallas 102823 6633619 sterni1971 100532 5880772 -jcAEie 100392 7788270 sunu 100167 7040199 zeryl 99331 6221261 thirdlife 99124 2242380 ElbertoOne 99028 7023771 -cuistot 98360 6017102 +cuistot 98853 6069816 bigpen0r 94809 6529203 brabos 92118 6186135 -Wolfgang 90855 5998076 +Wolfgang 91939 6105872 psk 89957 5984901 +sschnee 88235 5268000 racerschmacer 85805 6122790 +Fifis 85722 5709729 Dubslow 84986 6042456 Vizvezdenec 83761 5344740 -sschnee 83564 4853834 0x3C33 82614 5271253 BRAVONE 81239 5054681 -Fifis 77355 5158211 nssy 76497 5259388 jromang 76106 5236025 teddybaer 75125 5407666 +tolkki963 74762 5149662 +megaman7de 74351 4940352 Wencey 74181 4711488 -megaman7de 73866 4894960 Pking_cda 73776 5293873 -tolkki963 73531 5020500 -yurikvelo 72847 4972808 +yurikvelo 73150 5004382 +markkulix 72607 5304642 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 -markkulix 70278 5068326 manap 66273 4121774 tinker 64333 4268790 qurashee 61208 3429862 -Mineta 58759 4399960 +Mineta 59357 4418202 +Spprtr 58723 3911011 AGI 58147 4325994 -Spprtr 58106 3858759 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 @@ -103,35 +103,35 @@ MarcusTullius 56746 3762951 ttruscott 56010 3680085 rkl 55132 4164467 renouve 53811 3501516 +javran 53785 4627608 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 rap 49985 3219146 pb00067 49733 3298934 -javran 49178 4190632 -OuaisBla 48606 3442958 +OuaisBla 48626 3445134 ronaldjerum 47654 3240695 biffhero 46564 3111352 VoyagerOne 45476 3452465 -oryx 44532 3450170 -jmdana 43849 2955821 +jmdana 44893 3065205 +maposora 44597 4039578 +oryx 44570 3454238 speedycpu 43842 3003273 jbwiebe 43305 2805433 +GPUex 42378 3133332 Antihistamine 41788 2761312 mhunt 41735 2691355 -maposora 41534 3733078 -GPUex 41061 2998356 homyur 39893 2850481 gri 39871 2515779 Garf 37741 2999686 SC 37299 2731694 csnodgrass 36207 2688994 strelock 34716 2074055 +szupaw 34102 2880346 EthanOConnor 33370 2090311 slakovv 32915 2021889 Gelma 31771 1551204 gopeto 31671 2060990 -szupaw 31248 2594920 kdave 31157 2198362 manapbk 30987 1810399 Prcuvu 30377 2170122 @@ -147,52 +147,54 @@ xwziegtm 26897 2124586 achambord 26582 1767323 Patrick_G 26276 1801617 yorkman 26193 1992080 -Ulysses 25285 1689346 +Ulysses 25288 1689730 SFTUser 25182 1675689 nabildanial 24942 1519409 Sharaf_DG 24765 1786697 +Maxim 24705 1502062 rodneyc 24376 1416402 agg177 23890 1395014 +Goatminola 23763 1956036 Ente 23639 1671638 +Jopo12321 23467 1483172 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 -Goatminola 23338 1910634 cisco2015 22920 1763301 -Jopo12321 22890 1424926 +jsys14 22824 1591906 Zirie 22542 1472937 team-oh 22272 1636708 Roady 22220 1465606 MazeOfGalious 21978 1629593 sg4032 21947 1643353 -jsys14 21935 1499128 ianh2105 21725 1632562 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 user213718 21454 1404128 sphinx 21211 1384728 +AndreasKrug 21097 1634811 jjoshua2 21001 1423089 Zake9298 20938 1565848 -AndreasKrug 20911 1615673 horst.prack 20878 1465656 0xB00B1ES 20590 1208666 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 wei 19973 1745989 +notchris 19958 1800128 Serpensin 19840 1697528 +Gaster319 19712 1677310 fishtester 19617 1257388 rstoesser 19569 1293588 eudhan 19274 1283717 -Gaster319 18934 1596772 +votoanthuan 19108 1609992 vulcan 18871 1729392 Karpovbot 18766 1053178 +qoo_charly_cai 18543 1284937 jundery 18445 1115855 -votoanthuan 18012 1508836 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 -qoo_charly_cai 17494 1182667 dju 17414 981289 iisiraider 17275 1049015 DragonLord 17014 1162790 @@ -200,7 +202,6 @@ redstone59 16842 1461780 Alb11747 16787 1213926 IgorLeMasson 16064 1147232 Karby 15982 979610 -notchris 15818 1426762 scuzzi 15757 968735 ako027ako 15671 1173203 Nikolay.IT 15154 1068349 @@ -218,33 +219,33 @@ Nesa92 13786 1114691 joster 13710 946160 mbeier 13650 1044928 Hjax 13535 915487 +Nullvalue 13468 1140498 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 pirt 13100 1009897 Machariel 13010 863104 infinigon 12991 943216 -Maxim 12963 985594 mabichito 12903 749391 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 korposzczur 12606 838168 -Nullvalue 12583 1048502 fatmurphy 12547 853210 SapphireBrand 12416 969604 Oakwen 12399 844109 deflectooor 12386 579392 modolief 12386 896470 Farseer 12249 694108 -Jackfish 12180 801372 +Jackfish 12213 805008 pgontarz 12151 848794 dbernier 12103 860824 getraideBFF 12072 1024966 stocky 11954 699440 mschmidt 11941 803401 MooTheCow 11870 773598 -FormazChar 11689 877727 +FormazChar 11766 885707 whelanh 11557 245188 +3cho 11494 1031076 infinity 11470 727027 aga 11412 695127 torbjo 11395 729145 @@ -254,6 +255,7 @@ d64 11263 789184 ali-al-zhrani 11245 779246 snicolet 11106 869170 dapper 11032 771402 +ols 10947 624903 Karmatron 10828 677458 basepi 10637 744851 Cubox 10621 826448 @@ -263,4 +265,3 @@ jojo2357 10419 929708 WoodMan777 10380 873720 Garruk 10365 706465 dzjp 10343 732529 -ols 10259 570669 From 52e84e4b4675aae52a619c309479684dc5478bf5 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 22 Jun 2023 09:59:03 +0200 Subject: [PATCH 306/678] Update winrate model with June data Retained 748191776 scored positions for analysis const int NormalizeToPawnValue = 328; Corresponding spread = 60; Corresponding normalized spread = 0.18337766691628035; Draw rate at 0.0 eval at move 32 = 0.9914715947898592; closes https://github.com/official-stockfish/Stockfish/pull/4636 No functional change --- src/uci.cpp | 4 ++-- src/uci.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 523d551e..ed16f24c 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -207,8 +207,8 @@ namespace { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = { 1.07390458, -6.94334517, 31.95090161, 317.75424048}; - constexpr double bs[] = { -2.82843814, 16.64518180, -19.74439200, 68.39499088 }; + constexpr double as[] = { 0.38036525, -2.82015070, 23.17882135, 307.36768407}; + constexpr double bs[] = { -2.29434733, 13.27689788, -14.26828904, 63.45318330 }; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); diff --git a/src/uci.h b/src/uci.h index 680d2d2c..8f1be00c 100644 --- a/src/uci.h +++ b/src/uci.h @@ -35,7 +35,7 @@ namespace UCI { // the win_rate_model() such that Stockfish outputs an advantage of // "100 centipawns" for a position if the engine has a 50% probability to win // from this position in selfplay at fishtest LTC time control. -const int NormalizeToPawnValue = 343; +const int NormalizeToPawnValue = 328; class Option; From 48f7c74f15f4cae4b77596cd468802054314d701 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Mon, 12 Jun 2023 12:57:41 +0300 Subject: [PATCH 307/678] Fix Potential in TB cutoffs for NMP. Removes the second dependency on beta and caps the return value to VALUE_TB_WIN_IN_MAX_PLY - 1 Earlier tests: STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 193632 W: 51372 L: 51326 D: 90934 Ptnml(0-2): 447, 20111, 55687, 20091, 480 https://tests.stockfishchess.org/tests/view/6486ee4465ffe077ca125bc1 LTC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 331758 W: 89538 L: 89624 D: 152596 Ptnml(0-2): 114, 30121, 105516, 29993, 135 https://tests.stockfishchess.org/tests/view/6489401af42a44347ed7be42 updated constant: LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 100260 W: 27143 L: 27017 D: 46100 Ptnml(0-2): 34, 8842, 32248, 8976, 30 https://tests.stockfishchess.org/tests/view/6492fcafdc7002ce609c818c closes: https://github.com/official-stockfish/Stockfish/pull/4632 fixes: https://github.com/official-stockfish/Stockfish/issues/4598 bench: 2370027 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9b686a52..740ad71e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -801,10 +801,9 @@ namespace { if (nullValue >= beta) { // Do not return unproven mate or TB scores - if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY) - nullValue = beta; + nullValue = std::min(nullValue, VALUE_TB_WIN_IN_MAX_PLY-1); - if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 14)) + if (thisThread->nmpMinPly || depth < 14) return nullValue; assert(!thisThread->nmpMinPly); // Recursive verification is not allowed From a49b3ba7ed5d9be9151c8ceb5eed40efe3387c75 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 21 Jun 2023 00:43:46 -0400 Subject: [PATCH 308/678] Update default net to nn-5af11540bbfe.nnue Created by retraining the sparsified master net (nn-cd2ff4716c34.nnue) on a 100% minified dataset including Leela transformers data from T80 may2023. Weights permuted with the exact methods and code in: https://github.com/official-stockfish/Stockfish/pull/4620 LEB128 compression done with the new serialize.py param in: https://github.com/glinscott/nnue-pytorch/pull/251 Initially trained with max epoch 800. Around epoch 780, training was paused and max epoch raised to 960. python3 easy_train.py \ --experiment-name L1-1536-sparse-master-retrain \ --training-dataset /data/leela96-dfrc99-v2-T60novdecT77decT78jantosepT79aprmayT80juntonovjan-v6dd-T80febtomay2023.min.binpack \ --early-fen-skipping 27 \ --start-from-engine-test-net True \ --max_epoch 960 \ --lr 4.375e-4 \ --gamma 0.995 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --tui False \ --seed $RANDOM \ --gpus 0 For preparing the training dataset (interleaved size 328G): python3 interleave_binpacks.py \ leela96-filt-v2.min.binpack \ dfrc99-16tb7p-eval-filt-v2.min.binpack \ filt-v6-dd-min/test60-novdec2021-12tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test77-dec2021-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test78-jantomay2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test78-juntosep2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test79-apr2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test79-may2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-jun2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-jul2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-aug2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-sep2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-oct2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-nov2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-jan2023-16tb7p-filter-v6-dd.min.binpack \ test80-2023/test80-feb2023-16tb7p-no-db.min.binpack \ test80-2023/test80-mar2023-2tb7p-no-db.min.binpack \ test80-2023/test80-apr2023-2tb7p-no-db.min.binpack \ test80-2023/test80-may2023-2tb7p-no-db.min.binpack \ /data/leela96-dfrc99-v2-T60novdecT77decT78jantosepT79aprmayT80juntonovjan-v6dd-T80febtomay2023.min.binpack Minified binpacks and Leela T80 training data from 2023 available at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch879.nnue : 3.9 +/- 5.7 Passed STC: https://tests.stockfishchess.org/tests/view/64928c1bdc7002ce609c7690 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 72000 W: 19242 L: 18889 D: 33869 Ptnml(0-2): 182, 7787, 19716, 8126, 189 Passed LTC: https://tests.stockfishchess.org/tests/view/64930a37dc7002ce609c82e3 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 54552 W: 14978 L: 14647 D: 24927 Ptnml(0-2): 23, 5123, 16650, 5460, 20 closes https://github.com/official-stockfish/Stockfish/pull/4635 bench 2593605 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 33effb1c..b9d7231d 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-78bacfcee510.nnue" + #define EvalFileDefaultName "nn-5af11540bbfe.nnue" namespace NNUE { From 68e1e9b3811e16cad014b590d7443b9063b3eb52 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 24 Jun 2023 08:58:17 +0200 Subject: [PATCH 309/678] Stockfish 16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 16 Bench: 2593605 --- Stockfish 16 A new major release of Stockfish is now available at https://stockfishchess.org/download/ *Quality of chess play* Stockfish continues to demonstrate its ability to discover superior moves with remarkable speed. In self-play against Stockfish 15, this new release gains up to 50 Elo[1] and wins up to 12 times more game pairs[2] than it loses. In major chess engine tournaments, Stockfish reliably tops the rankings[3] winning the TCEC season 24 Superfinal, Swiss, Fischer Random, and Double Random Chess tournaments and the CCC 19 Bullet, 20 Blitz, and 20 Rapid competitions. Leela Chess Zero[4] was the challenger in most finals, putting top-engine chess now firmly in the hands of teams embracing free and open-source software. *Progress made* This updated version of Stockfish introduces several enhancements, including an upgraded neural net architecture (SFNNv6)[5], improved implementation, and refined parameterization. The ongoing utilization of Leela’s data combined with a novel inference approach exploiting sparsity[6], and network compression[7] ensure a speedy evaluation and modest binary sizes while allowing for more weights and higher accuracy. The search has undergone more optimization, leading to improved performance, particularly in longer analyses[8]. Additionally, the Fishtest framework has been improved and is now able to run the tests needed to validate new ideas with 10000s of CPU cores. *Usability improvements* Stockfish now comes with documentation, found in the wiki folder when downloading it or on GitHub[9]. Additionally, Stockfish now includes a clear and consistent forced tablebase win score, displaying a value of 200 minus the number of plies required to reach a tablebase win[10]. Furthermore, the UCI_Elo option, to reduce its strength, has been calibrated[11]. It is worth noting that the evaluation system remains consistent with Stockfish 15.1[12], maintaining the choice that 100cp means a 50% chance of winning the game against an equal opponent[13]. Finally, binaries of our latest development version are now provided continuously as pre-releases on GitHub making it easier for enthusiasts to download the latest and strongest version of the program[14], we thank Roman Korba for having provided a similar service for a long time. *Thank you* The success of the Stockfish project relies on the vibrant community of passionate enthusiasts (we appreciate each and every one of you!) who generously contribute their knowledge, time, and resources. Together, this dedicated community works towards the common goal of developing a powerful, freely accessible, and open-source chess engine. We invite all chess enthusiasts to join the Fishtest testing framework and contribute to the project[15]. The Stockfish team [1] https://tests.stockfishchess.org/tests/view/649409f0dc7002ce609c99cc [2] https://tests.stockfishchess.org/tests/view/649409d7dc7002ce609c99c6 [3] https://en.wikipedia.org/wiki/Stockfish_(chess)#Competition_results [4] https://lczero.org/ [5] https://github.com/official-stockfish/Stockfish/commit/c1fff71 [6] https://github.com/official-stockfish/Stockfish/commit/38e6166 [7] https://github.com/official-stockfish/Stockfish/commit/a46087e [8] https://github.com/official-stockfish/Stockfish/commit/472e726 [9] https://github.com/official-stockfish/Stockfish/wiki/ [10] https://github.com/official-stockfish/Stockfish/commit/def2966 [11] https://github.com/official-stockfish/Stockfish/commit/a08b8d4 [12] https://github.com/official-stockfish/Stockfish/commit/52e84e4 [13] https://github.com/official-stockfish/Stockfish/wiki/Stockfish-FAQ#interpretation-of-the-stockfish-evaluation [14] https://github.com/official-stockfish/Stockfish/releases?q=prerelease%3Atrue [15] https://stockfishchess.org/get-involved/ --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index f1554060..bbfa4061 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -73,7 +73,7 @@ namespace Stockfish { namespace { /// Version number or dev. -constexpr string_view version = "dev"; +constexpr string_view version = "16"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 0fd186fb28e7e1e5f2cc5ef8388115c950eaad9e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 1 Jul 2023 12:18:46 +0200 Subject: [PATCH 310/678] Restore development closes https://github.com/official-stockfish/Stockfish/pull/4651 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index bbfa4061..f1554060 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -73,7 +73,7 @@ namespace Stockfish { namespace { /// Version number or dev. -constexpr string_view version = "16"; +constexpr string_view version = "dev"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From ef94f77f8c827a2395f1c40f53311a3b1f20bc5b Mon Sep 17 00:00:00 2001 From: Daniel Monroe <39802758+Ergodice@users.noreply.github.com> Date: Fri, 23 Jun 2023 18:36:27 -0400 Subject: [PATCH 311/678] Update default net to nn-a3d1bfca1672.nnue faster permutation of master net weights Activation data taken from https://drive.google.com/drive/folders/1Ec9YuuRx4N03GPnVPoQOW70eucOKngQe?usp=sharing Permutation found using https://github.com/Ergodice/nnue-pytorch/blob/836387a0e5e690431d404158c46648710f13904d/ftperm.py See also https://github.com/glinscott/nnue-pytorch/pull/254 The algorithm greedily selects 2- and 3-cycles that can be permuted to increase the number of runs of zeroes. The percent of zero runs from the master net increased from 68.46 to 70.11 from 2-cycles and only increased to 70.32 when considering 3-cycles. Interestingly, allowing both halves of L1 to intermix when creating zero runs can give another 0.5% zero-run density increase with this method. Measured speedup: ``` CPU: 16 x AMD Ryzen 9 3950X 16-Core Processor Result of 50 runs base (./stockfish.master ) = 1561556 +/- 5439 test (./stockfish.patch ) = 1575788 +/- 5427 diff = +14231 +/- 2636 speedup = +0.0091 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/4640 No functional change --- AUTHORS | 1 + src/evaluate.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index ff224954..2e9ae780 100644 --- a/AUTHORS +++ b/AUTHORS @@ -48,6 +48,7 @@ clefrks Dale Weiler (graphitemaster) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) +Daniel Monroe (Ergodice) Dan Schmidt (dfannius) Dariusz Orzechowski (dorzechowski) David (dav1312) diff --git a/src/evaluate.h b/src/evaluate.h index b9d7231d..c3321965 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-5af11540bbfe.nnue" + #define EvalFileDefaultName "nn-a3d1bfca1672.nnue" namespace NNUE { From e355c7059468048bbb8b9f10e2b32606aa72eb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Tue, 27 Jun 2023 12:03:00 +0200 Subject: [PATCH 312/678] Document the LEB128 patch Add some comments and harmonize style for the LEB128 patch. closes https://github.com/official-stockfish/Stockfish/pull/4642 No functional change --- src/nnue/nnue_common.h | 78 +++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index d338527d..e8ed2bc6 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -86,6 +86,7 @@ namespace Stockfish::Eval::NNUE { return (n + base - 1) / base * base; } + // read_little_endian() is our utility to read an integer (signed or unsigned, any size) // from a stream in little-endian order. We swap the byte order after the read if // necessary to return a result with the byte ordering of the compiling machine. @@ -110,6 +111,7 @@ namespace Stockfish::Eval::NNUE { return result; } + // write_little_endian() is our utility to write an integer (signed or unsigned, any size) // to a stream in little-endian order. We swap the byte order before the write if // necessary to always write in little endian order, independently of the byte @@ -140,6 +142,7 @@ namespace Stockfish::Eval::NNUE { } } + // read_little_endian(s, out, N) : read integers in bulk from a little indian stream. // This reads N integers from stream s and put them in array out. template @@ -151,6 +154,7 @@ namespace Stockfish::Eval::NNUE { out[i] = read_little_endian(stream); } + // write_little_endian(s, values, N) : write integers in bulk to a little indian stream. // This takes N integers from array values and writes them on stream s. template @@ -162,77 +166,119 @@ namespace Stockfish::Eval::NNUE { write_little_endian(stream, values[i]); } + + // read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in + // the array out. The stream is assumed to be compressed using the signed LEB128 format. + // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { - static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + // Check the presence of our LEB128 magic string char leb128MagicString[Leb128MagicStringSize]; stream.read(leb128MagicString, Leb128MagicStringSize); assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + const std::uint32_t BUF_SIZE = 4096; std::uint8_t buf[BUF_SIZE]; + auto bytes_left = read_little_endian(stream); + std::uint32_t buf_pos = BUF_SIZE; - for (std::size_t i = 0; i < count; ++i) { + for (std::size_t i = 0; i < count; ++i) + { IntType result = 0; size_t shift = 0; - do { - if (buf_pos == BUF_SIZE) { + do + { + if (buf_pos == BUF_SIZE) + { stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); buf_pos = 0; } + std::uint8_t byte = buf[buf_pos++]; --bytes_left; result |= (byte & 0x7f) << shift; shift += 7; - if ((byte & 0x80) == 0) { - out[i] = sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); + + if ((byte & 0x80) == 0) + { + out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) ? result + : result | ~((1 << shift) - 1); break; } - } while (shift < sizeof(IntType) * 8); + } + while (shift < sizeof(IntType) * 8); } + assert(bytes_left == 0); } + + // write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression. + // This takes N integers from array values, compress them with the LEB128 algorithm and + // writes the result on the stream s. + // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { - static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + // Write our LEB128 magic string stream.write(Leb128MagicString, Leb128MagicStringSize); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + std::uint32_t byte_count = 0; - for (std::size_t i = 0; i < count; ++i) { + for (std::size_t i = 0; i < count; ++i) + { IntType value = values[i]; std::uint8_t byte; - do { + do + { byte = value & 0x7f; value >>= 7; ++byte_count; - } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + while ((byte & 0x40) == 0 ? value != 0 : value != -1); } + write_little_endian(stream, byte_count); + const std::uint32_t BUF_SIZE = 4096; std::uint8_t buf[BUF_SIZE]; std::uint32_t buf_pos = 0; + auto flush = [&]() { - if (buf_pos > 0) { + if (buf_pos > 0) + { stream.write(reinterpret_cast(buf), buf_pos); buf_pos = 0; } }; + auto write = [&](std::uint8_t byte) { buf[buf_pos++] = byte; - if (buf_pos == BUF_SIZE) flush(); + if (buf_pos == BUF_SIZE) + flush(); }; - for (std::size_t i = 0; i < count; ++i) { + + for (std::size_t i = 0; i < count; ++i) + { IntType value = values[i]; - while (true) { + while (true) + { std::uint8_t byte = value & 0x7f; value >>= 7; - if ((byte & 0x40) == 0 ? value == 0 : value == -1) { + if ((byte & 0x40) == 0 ? value == 0 : value == -1) + { write(byte); break; } write(byte | 0x80); } } + flush(); } From e551964ef63e4e4af4bb6132538b98fad4a51afe Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Mon, 26 Jun 2023 19:40:22 +0800 Subject: [PATCH 313/678] Negative extension on cutNodes based on depth This patch was inspired by candirufish original attempt at negative extensions here that failed yellow: https://tests.stockfishchess.org/tests/view/6486529065ffe077ca124f32 I tested some variations of the idea and tuned a depth condition for a modified version of it here https://tests.stockfishchess.org/tests/view/648db80a91c58631ce31fe00 after noticing abnormal scaling (ie many passed STC but not LTC) After some small tweaks I got the final version here Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 122208 W: 32776 L: 32350 D: 57082 Ptnml(0-2): 310, 13250, 33553, 13686, 305 https://tests.stockfishchess.org/tests/view/64997934dc7002ce609d01e3 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 145092 W: 39617 L: 39115 D: 66360 Ptnml(0-2): 54, 13691, 44552, 14197, 52 https://tests.stockfishchess.org/tests/view/649a1c5ddc7002ce609d0bff closes https://github.com/official-stockfish/Stockfish/pull/4644 Bench: 2637784 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 740ad71e..fbc1755b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1097,6 +1097,10 @@ moves_loop: // When in check, search starts here else if (ttValue >= beta) extension = -2 - !PvNode; + // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) + else if (cutNode) + extension = depth > 8 && depth < 17 ? -3 : -1; + // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; From 915532181f11812c80ef0b57bc018de4ea2155ec Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 25 Jun 2023 17:44:28 -0400 Subject: [PATCH 314/678] Update NNUE architecture to SFNNv7 with larger L1 size of 2048 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating this net involved: - a 5-step training process from scratch - greedy permuting L1 weights with https://github.com/official-stockfish/Stockfish/pull/4620 - leb128 compression with https://github.com/glinscott/nnue-pytorch/pull/251 - greedy 2- and 3- cycle permuting with https://github.com/official-stockfish/Stockfish/pull/4640 The 5 training steps were: 1. 400 epochs, lambda 1.0, lr 9.75e-4 UHOx2-wIsRight-multinet-dfrc-n5000-largeGensfen-d9.binpack (178G) nodes5000pv2_UHO.binpack data_pv-2_diff-100_nodes-5000.binpack wrongIsRight_nodes5000pv2.binpack multinet_pv-2_diff-100_nodes-5000.binpack dfrc_n5000.binpack large_gensfen_multipvdiff_100_d9.binpack ep399 chosen as start model for step2 2. 800 epochs, end-lambda 0.75, skip 16 LeelaFarseer-T78juntoaugT79marT80dec.binpack (141G) T60T70wIsRightFarseerT60T74T75T76.binpack test78-junjulaug2022-16tb7p.no-db.min.binpack test79-mar2022-16tb7p.no-db.min.binpack test80-dec2022-16tb7p.no-db.min.binpack ep559 chosen as start model for step3 3. 800 epochs, end-lambda 0.725, skip 20 leela96-dfrc99-v2-T80dectofeb-sk20-mar-v6-T77decT78janfebT79apr.binpack (223G) leela96-filt-v2.min.binpack dfrc99-16tb7p-eval-filt-v2.min.binpack test80-dec2022-16tb7p-filter-v6-sk20.min-mar2023.binpack test80-jan2023-16tb7p-filter-v6-sk20.min-mar2023.binpack test80-feb2023-16tb7p-filter-v6-sk20.min-mar2023.binpack test80-mar2023-2tb7p-filter-v6.min.binpack test77-dec2021-16tb7p.no-db.min.binpack test78-janfeb2022-16tb7p.no-db.min.binpack test79-apr2022-16tb7p.no-db.min.binpack ep499 chosen as start model for step4 4. 800 epochs, end-lambda 0.7, skip 24 0dd1cebea57 dataset https://github.com/official-stockfish/Stockfish/pull/4606 ep599 chosen as start model for step5 5. 800 epochs, end-lambda 0.7, skip 28 same dataset as step4 ep619 became nn-1b951f8b449d.nnue For the final step5 training: python3 easy_train.py \ --experiment-name L1-2048-S5-sameData-sk28-S4-0dd1cebea57-shuffled-S3-leela96-dfrc99-v2-T80dectofeb-sk20-mar-v6-T77decT78janfebT79apr-sk20-S2-LeelaFarseerT78T79T80-ep399-S1-UHOx2-wIsRight-multinet-dfrc-n5000-largeGensfen-d9 \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr.binpack \ --early-fen-skipping 28 \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-L1-2048 \ --engine-test-branch linrock/Stockfish/L1-2048 \ --start-from-engine-test-net False \ --start-from-model /data/experiments/experiment_L1-2048-S4-0dd1cebea57-shuffled-S3-leela96-dfrc99-v2-T80dectofeb-sk20-mar-v6-T77decT78janfebT79apr-sk20-S2-LeelaFarseerT78T79T80-ep399-S1-UHOx2-wIsRight-multinet-dfrc-n5000-largeGensfen-d9/training/run_0/nn-epoch599.nnue --max_epoch 800 \ --lr 4.375e-4 \ --gamma 0.995 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --tui False \ --seed $RANDOM \ --gpus 0 SF training data components for the step1 dataset: https://drive.google.com/drive/folders/1yLCEmioC3Xx9KQr4T7uB6GnLm5icAYGU Leela training data for steps 2-5 can be found at: https://robotmoon.com/nnue-training-data/ Due to larger L1 size and slower inference, the speed penalty loses elo at STC. Measurements from 100 bench runs at depth 13 with x86-64-modern on Intel Core i5-1038NG7 2.00GHz: sf_base = 1240730 +/- 3443 (95%) sf_test = 1153341 +/- 2832 (95%) diff = -87388 +/- 1616 (95%) speedup = -7.04330% +/- 0.130% (95%) Local elo at 25k nodes per move (vs. L1-1536 nn-fdc1d0fe6455.nnue): nn-epoch619.nnue : 21.1 +/- 3.2 Failed STC: https://tests.stockfishchess.org/tests/view/6498ee93dc7002ce609cf979 LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 11680 W: 3058 L: 3299 D: 5323 Ptnml(0-2): 44, 1422, 3149, 1181, 44 LTC: https://tests.stockfishchess.org/tests/view/649b32f5dc7002ce609d20cf Elo: 0.68 ± 1.5 (95%) LOS: 80.5% Total: 40000 W: 10887 L: 10809 D: 18304 Ptnml(0-2): 36, 3938, 11958, 4048, 20 nElo: 1.50 ± 3.4 (95%) PairsRatio: 1.02 Passed VLTC 180+1.8: https://tests.stockfishchess.org/tests/view/64992b43dc7002ce609cfd20 LLR: 3.06 (-2.94,2.94) <0.00,2.00> Total: 38086 W: 10612 L: 10338 D: 17136 Ptnml(0-2): 9, 3316, 12115, 3598, 5 Passed VLTC SMP 60+0.6 th 8: https://tests.stockfishchess.org/tests/view/649a21fedc7002ce609d0c7d LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 38936 W: 11091 L: 10820 D: 17025 Ptnml(0-2): 1, 2948, 13305, 3207, 7 closes https://github.com/official-stockfish/Stockfish/pull/4646 Bench: 2505168 --- src/evaluate.h | 2 +- src/nnue/nnue_architecture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index c3321965..a1d46111 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-a3d1bfca1672.nnue" + #define EvalFileDefaultName "nn-1b951f8b449d.nnue" namespace NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 413dbb3d..65319b14 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -40,7 +40,7 @@ namespace Stockfish::Eval::NNUE { using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 1536; +constexpr IndexType TransformedFeatureDimensions = 2048; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; From 9a2d50ecccfc737249245280586924ee3ef53abb Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Sat, 1 Jul 2023 14:36:52 +0200 Subject: [PATCH 315/678] Make posix and msys2 shells consistent in CI In CI, it is typical for the process to halt immediately when an error is encountered. However, with our `shell: bash {0}` configuration, the process continues despite errors for posix shells. This commit updates the behavior of posix and msys2 shells to ensure consistency in terms of pipeline exit codes and stop conditions. We adopt the most appropriate default behavior as recommended by the GitHub documentation. Update the code that searches for the bench value in the git log: - to be compatible with the new shell settings - to retry the value from the first line that contains only the template and spaces/tabs/newlines see also https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference https://github.com/msys2/setup-msys2/blob/main/main.js closes https://github.com/official-stockfish/Stockfish/pull/4653 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 4 ++-- .github/workflows/stockfish_binaries.yml | 4 ++-- .github/workflows/stockfish_compile_test.yml | 8 ++++---- .github/workflows/stockfish_sanitizers.yml | 2 +- .github/workflows/stockfish_test.yml | 16 ++++++++-------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 52105eb6..1afd8efa 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -20,13 +20,13 @@ jobs: compiler: aarch64-linux-android21-clang++ emu: qemu-aarch64 comp: ndk - shell: bash {0} + shell: bash - name: Android NDK arm os: ubuntu-22.04 compiler: armv7a-linux-androideabi21-clang++ emu: qemu-arm comp: ndk - shell: bash {0} + shell: bash binaries: - armv8 - armv7 diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 0a53cb03..5fe67d15 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -19,14 +19,14 @@ jobs: simple_name: ubuntu compiler: g++ comp: gcc - shell: bash {0} + shell: bash archive_ext: tar - name: MacOS 12 Apple Clang os: macos-12 simple_name: macos compiler: clang++ comp: clang - shell: bash {0} + shell: bash archive_ext: tar - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index c7280a85..41f61dab 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -15,22 +15,22 @@ jobs: os: ubuntu-20.04 compiler: g++ comp: gcc - shell: bash {0} + shell: bash - name: Ubuntu 20.04 Clang os: ubuntu-20.04 compiler: clang++ comp: clang - shell: bash {0} + shell: bash - name: MacOS 12 Apple Clang os: macos-12 compiler: clang++ comp: clang - shell: bash {0} + shell: bash - name: MacOS 12 GCC 11 os: macos-12 compiler: g++-11 comp: gcc - shell: bash {0} + shell: bash - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 compiler: g++ diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index 708c9227..ebfd809c 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -16,7 +16,7 @@ jobs: os: ubuntu-20.04 compiler: g++ comp: gcc - shell: bash {0} + shell: bash sanitizers: - name: Run with thread sanitizer make_option: sanitize=thread diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 28218402..8a71d76b 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -18,38 +18,38 @@ jobs: comp: gcc run_32bit_tests: true run_64bit_tests: true - shell: bash {0} + shell: bash - name: Ubuntu 20.04 Clang os: ubuntu-20.04 compiler: clang++ comp: clang run_32bit_tests: true run_64bit_tests: true - shell: bash {0} + shell: bash - name: Android NDK aarch64 os: ubuntu-22.04 compiler: aarch64-linux-android21-clang++ comp: ndk run_armv8_tests: true - shell: bash {0} + shell: bash - name: Android NDK arm os: ubuntu-22.04 compiler: armv7a-linux-androideabi21-clang++ comp: ndk run_armv7_tests: true - shell: bash {0} + shell: bash - name: MacOS 12 Apple Clang os: macos-12 compiler: clang++ comp: clang run_64bit_tests: true - shell: bash {0} + shell: bash - name: MacOS 12 GCC 11 os: macos-12 compiler: g++-11 comp: gcc run_64bit_tests: true - shell: bash {0} + shell: bash - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 compiler: g++ @@ -115,8 +115,8 @@ jobs: - name: Extract the bench number from the commit history run: | - git log HEAD | grep -o "\b[Bb]ench[ :]\+[1-9][0-9]\{5,9\}\b" | head -n 1 | sed "s/[^0-9]//g" > git_sig - [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found" + benchref=$(git log HEAD | grep -m 1 -o -x "[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*" | sed "s/[^0-9]//g") || true + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "Reference bench: $benchref" || echo "No bench found" - name: Check compiler run: | From 8634717c6457f2b5fb0127cfb81c18505ff0072c Mon Sep 17 00:00:00 2001 From: disservin <45608332+Disservin@users.noreply.github.com> Date: Mon, 3 Jul 2023 08:20:56 +0200 Subject: [PATCH 316/678] Add bmi2 to CI generated binaries verify bench for avx2 and bmi2 as well closes https://github.com/official-stockfish/Stockfish/pull/4658 No functional change --- .github/workflows/stockfish_binaries.yml | 6 ++-- .github/workflows/stockfish_test.yml | 36 ++++++++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 5fe67d15..f7669b47 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -42,9 +42,12 @@ jobs: - x86-64 - x86-64-modern - x86-64-avx2 + - x86-64-bmi2 exclude: - binaries: x86-64-avx2 - config: {os: macos-12} + config: { os: macos-12 } + - binaries: x86-64-bmi2 + config: { os: macos-12 } defaults: run: working-directory: src @@ -165,4 +168,3 @@ jobs: tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} prerelease: true files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} - diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 8a71d76b..9d6bc20c 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -134,7 +134,7 @@ jobs: # x86-32 tests - name: Test debug x86-32 build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean @@ -142,28 +142,28 @@ jobs: ../tests/signature.sh $benchref - name: Test x86-32 build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | make clean make -j2 ARCH=x86-32 build ../tests/signature.sh $benchref - name: Test x86-32-sse41-popcnt build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | make clean make -j2 ARCH=x86-32-sse41-popcnt build ../tests/signature.sh $benchref - name: Test x86-32-sse2 build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | make clean make -j2 ARCH=x86-32-sse2 build ../tests/signature.sh $benchref - name: Test general-32 build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | make clean make -j2 ARCH=general-32 build @@ -172,36 +172,50 @@ jobs: # x86-64 tests - name: Test debug x86-64-modern build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean make -j2 ARCH=x86-64-modern optimize=no debug=yes build ../tests/signature.sh $benchref + - name: Test x86-64-bmi2 build + if: matrix.config.run_64bit_tests && runner.os != 'macOS' + run: | + make clean + make -j2 ARCH=x86-64-bmi2 build + ../tests/signature.sh $benchref + + - name: Test x86-64-avx2 build + if: matrix.config.run_64bit_tests && runner.os != 'macOS' + run: | + make clean + make -j2 ARCH=x86-64-avx2 build + ../tests/signature.sh $benchref + - name: Test x86-64-modern build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-modern build ../tests/signature.sh $benchref - name: Test x86-64-ssse3 build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-ssse3 build ../tests/signature.sh $benchref - name: Test x86-64-sse3-popcnt build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-sse3-popcnt build ../tests/signature.sh $benchref - name: Test x86-64 build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64 build @@ -248,7 +262,7 @@ jobs: # Other tests - name: Check perft and search reproducibility - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-modern build From 80564bcfcd3523c2a61e7a2c4bee36d4aada49d1 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 2 Jul 2023 19:48:18 -0700 Subject: [PATCH 317/678] Simplify lookup_count and clean up pieces(). https://github.com/official-stockfish/Stockfish/pull/4656 No functional change --- src/nnue/layers/affine_transform_sparse_input.h | 12 ++---------- src/position.h | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index e0c3a8a0..18c166cd 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "../nnue_common.h" #include "affine_transform.h" @@ -77,16 +78,7 @@ namespace Stockfish::Eval::NNUE::Layers { alignas(CacheLineSize) static inline const std::array lookup_count = [](){ std::array v; for (int i = 0; i < 256; ++i) - { - int j = i; - int k = 0; - while(j) - { - j &= j - 1; - ++k; - } - v[i] = k; - } + v[i] = unsigned(std::bitset<8>(i).count()); return v; }(); diff --git a/src/position.h b/src/position.h index 2e6014db..7d4b3771 100644 --- a/src/position.h +++ b/src/position.h @@ -91,7 +91,7 @@ public: std::string fen() const; // Position representation - Bitboard pieces(PieceType pt) const; + Bitboard pieces(PieceType pt = ALL_PIECES) const; template Bitboard pieces(PieceType pt, PieceTypes... pts) const; Bitboard pieces(Color c) const; template Bitboard pieces(Color c, PieceTypes... pts) const; @@ -224,7 +224,7 @@ inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); } -inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const { +inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } From fa143922aecaab6f22fe818a5ef23b6ac42fe307 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Tue, 27 Jun 2023 06:07:20 +0300 Subject: [PATCH 318/678] Fix pruning to (in TB loss) in Null move pruning. Current logic can apply Null move pruning on a dead-lost position returning an unproven loss (i.e. in TB loss score or mated in losing score) on nonPv nodes. on a default bench, this can be observed by adding this debugging line: ``` if (nullValue >= beta) { // Do not return unproven mate or TB scores nullValue = std::min(nullValue, VALUE_TB_WIN_IN_MAX_PLY-1); dbg_hit_on(nullValue <= VALUE_TB_LOSS_IN_MAX_PLY); // Hit #0: Total 73983 Hits 1 Hit Rate (%) 0.00135166 if (thisThread->nmpMinPly || depth < 14) return nullValue; ``` This fixes this very rare issue (happens at ~0.00135166% of the time) by eliminating the need to try Null Move Pruning with dead-lost positions and leaving it to be determined by a normal searching flow. The previous try to fix was not as safe enough because it was capping the returned value to (out of TB range) thus reviving the dead-lost position based on an artificial clamp (i.e. the in TB score/mate score can be lost on that nonPv node): https://tests.stockfishchess.org/tests/view/649756d5dc7002ce609cd794 Final fix: Passed STC: https://tests.stockfishchess.org/tests/view/649a5446dc7002ce609d1049 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 577280 W: 153613 L: 153965 D: 269702 Ptnml(0-2): 1320, 60594, 165190, 60190, 1346 Passed LTC: https://tests.stockfishchess.org/tests/view/649cd048dc7002ce609d4801 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 246432 W: 66769 L: 66778 D: 112885 Ptnml(0-2): 83, 22105, 78847, 22100, 81 closes https://github.com/official-stockfish/Stockfish/pull/4649 Bench: 2425978 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fbc1755b..8bd5ec9b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -782,7 +782,8 @@ namespace { && ss->staticEval >= beta - 21 * depth - improvement / 13 + 258 && !excludedMove && pos.non_pawn_material(us) - && (ss->ply >= thisThread->nmpMinPly)) + && ss->ply >= thisThread->nmpMinPly + && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); From eb9aaf94891f17a62798c59226642fa172972204 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:36:11 +0800 Subject: [PATCH 319/678] Simplify away improvement term in null move search passed STC: https://tests.stockfishchess.org/tests/view/649c0d2edc7002ce609d33b5 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 271104 W: 72181 L: 72217 D: 126706 Ptnml(0-2): 691, 30042, 74129, 29992, 698 passed LTC: https://tests.stockfishchess.org/tests/view/649d0dd7dc7002ce609d4efa LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 183120 W: 49469 L: 49418 D: 84233 Ptnml(0-2): 84, 17636, 56063, 17699, 78 closes https://github.com/official-stockfish/Stockfish/pull/4650 Bench: 2642851 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 8bd5ec9b..656558f8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -779,7 +779,7 @@ namespace { && (ss-1)->statScore < 17329 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 21 * depth - improvement / 13 + 258 + && ss->staticEval >= beta - 21 * depth + 258 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly From 5f8480a730cbc789d230dd28f276b8d35ce0a8a4 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Thu, 22 Jun 2023 19:19:21 +0800 Subject: [PATCH 320/678] Simplify score improvement reduction Reduce depth by 2 based on score improvement, only for depths 3 to 11. Simplification STC: https://tests.stockfishchess.org/tests/view/64929a53dc7002ce609c7807 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 238912 W: 63466 L: 63468 D: 111978 Ptnml(0-2): 564, 26262, 65805, 26262, 563 Simplification LTC: https://tests.stockfishchess.org/tests/view/64942e47dc7002ce609c9e07 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 64452 W: 17485 L: 17320 D: 29647 Ptnml(0-2): 19, 6161, 19706, 6316, 24 closes https://github.com/official-stockfish/Stockfish/pull/4637 Bench: 2740142 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 656558f8..ed2b5743 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1323,12 +1323,12 @@ moves_loop: // When in check, search starts here } else { - // Reduce other moves if we have found at least one score improvement (~1 Elo) - // Reduce more for depth > 3 and depth < 12 (~1 Elo) - if ( depth > 1 + // Reduce other moves if we have found at least one score improvement (~2 Elo) + if ( depth > 2 + && depth < 12 && beta < 14362 && value > -12393) - depth -= depth > 3 && depth < 12 ? 2 : 1; + depth -= 2; assert(depth > 0); alpha = value; // Update alpha! Always alpha < beta From 9cd563cb54b4091c48e16b524b3c9c15b7824c4f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 27 Jun 2023 15:13:59 +0300 Subject: [PATCH 321/678] Improving grammar and readability of comments closes https://github.com/official-stockfish/Stockfish/pull/4643 No functional change --- src/search.cpp | 62 +++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ed2b5743..76d055e3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -67,7 +67,7 @@ namespace { return Value(140 * (d - improving)); } - // Reductions lookup table, initialized at startup + // Reductions lookup table initialized at startup int Reductions[MAX_MOVES]; // [depth or moveNumber] Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { @@ -92,7 +92,7 @@ namespace { // Skill structure is used to implement strength limit. If we have an uci_elo then // we convert it to a suitable fractional skill level using anchoring to CCRL Elo - // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for match (TC 60+0.6) + // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for a match (TC 60+0.6) // results spanning a wide range of k values. struct Skill { Skill(int skill_level, int uci_elo) { @@ -304,7 +304,7 @@ void Thread::search() { Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); // When playing with strength handicap enable MultiPV search that we will - // use behind the scenes to retrieve a set of possible moves. + // use behind-the-scenes to retrieve a set of possible moves. if (skill.enabled()) multiPV = std::max(multiPV, (size_t)4); @@ -321,7 +321,7 @@ void Thread::search() { if (mainThread) totBestMoveChanges /= 2; - // Save the last iteration's scores before first PV line is searched and + // Save the last iteration's scores before the first PV line is searched and // all the move scores except the (new) PV are set to -VALUE_INFINITE. for (RootMove& rm : rootMoves) rm.previousScore = rm.score; @@ -363,16 +363,16 @@ void Thread::search() { int failedHighCnt = 0; while (true) { - // Adjust the effective depth searched, but ensuring at least one effective increment for every + // Adjust the effective depth searched, but ensure at least one effective increment for every // four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the - // first and eventually the new best one are set to -VALUE_INFINITE + // first and eventually the new best one is set to -VALUE_INFINITE // and we want to keep the same order for all the moves except the - // new PV that goes to the front. Note that in case of MultiPV + // new PV that goes to the front. Note that in the case of MultiPV // search the already searched PV lines are preserved. std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); @@ -440,7 +440,7 @@ void Thread::search() { if (!mainThread) continue; - // If skill level is enabled and time is up, pick a sub-optimal best move + // If the skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(multiPV); @@ -498,7 +498,7 @@ void Thread::search() { mainThread->previousTimeReduction = timeReduction; - // If skill level is enabled, swap best PV line with the sub-optimal one + // If the skill level is enabled, swap the best PV line with the sub-optimal one if (skill.enabled()) std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), skill.best ? skill.best : skill.pick_best(multiPV))); @@ -515,7 +515,7 @@ namespace { constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; - // Check if we have an upcoming move which draws by repetition, or + // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( !rootNode && pos.rule50_count() >= 3 @@ -580,8 +580,8 @@ namespace { // would be at best mate_in(ss->ply+1), but if alpha is already bigger because // a shorter mate was found upward in the tree then there is no need to search // because we will never beat the current alpha. Same logic but with reversed - // signs applies also in the opposite condition of being mated instead of giving - // mate. In this case return a fail-high score. + // signs apply also in the opposite condition of being mated instead of giving + // mate. In this case, return a fail-high score. alpha = std::max(mated_in(ss->ply), alpha); beta = std::min(mate_in(ss->ply+1), beta); if (alpha >= beta) @@ -734,7 +734,7 @@ namespace { else { ss->staticEval = eval = evaluate(pos); - // Save static evaluation into transposition table + // Save static evaluation into the transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } @@ -845,10 +845,10 @@ namespace { if ( !PvNode && depth > 3 && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY - // if value from transposition table is lower than probCutBeta, don't attempt probCut + // 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 + // So effective depth is equal to depth - 3 && !( tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) @@ -920,7 +920,7 @@ moves_loop: // When in check, search starts here moveCountPruning = singularQuietLMR = false; // Indicate PvNodes that will probably fail low if the node was searched - // at a depth equal or greater than the current depth, and the result of this search was a fail low. + // at a depth equal to or greater than the current depth, and the result of this search was a fail low. bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) @@ -936,8 +936,8 @@ moves_loop: // When in check, search starts here continue; // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. As a consequence any illegal move is also skipped. In MultiPV - // mode we also skip PV moves which have been already searched and those + // Move List. As a consequence, any illegal move is also skipped. In MultiPV + // mode we also skip PV moves that have been already searched and those // of lower "TB rank" if we are in a TB root position. if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, thisThread->rootMoves.begin() + thisThread->pvLast, move)) @@ -1005,7 +1005,7 @@ moves_loop: // When in check, search starts here { Square sq = pop_lsb(leftEnemies); attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // don't consider pieces which were already threatened/hanging before SEE exchanges + // Don't consider pieces that were already threatened/hanging before SEE exchanges if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) attacks = 0; } @@ -1090,7 +1090,7 @@ moves_loop: // When in check, search starts here // Our ttMove is assumed to fail high, and now we failed high also on a reduced // search without the ttMove. So we assume this expected Cut-node is not singular, // that multiple moves fail high, and we can prune the whole subtree by returning - // a soft bound. + // a softbound. else if (singularBeta >= beta) return singularBeta; @@ -1186,7 +1186,7 @@ moves_loop: // When in check, search starts here // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has - // been searched. In general we would like to reduce them, but there are many + // been searched. In general, we would like to reduce them, but there are many // cases where we extend a son if it has good chances to be "interesting". if ( depth >= 2 && moveCount > 1 + (PvNode && ss->ply <= 1) @@ -1201,10 +1201,10 @@ moves_loop: // When in check, search starts here value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); - // Do full depth search when reduced LMR search fails high + // Do a full-depth search when reduced LMR search fails high if (value > alpha && d < newDepth) { - // Adjust full depth search based on LMR results - if result + // 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 + 64 + 11 * (newDepth - d)); const bool doEvenDeeperSearch = value > alpha + 711 && ss->doubleExtensions <= 6; @@ -1225,7 +1225,7 @@ moves_loop: // When in check, search starts here } } - // Step 18. Full depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. + // Step 18. Full-depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. else if (!PvNode || moveCount > 1) { // Increase reduction for cut nodes and not ttMove (~1 Elo) @@ -1298,7 +1298,7 @@ moves_loop: // When in check, search starts here ++thisThread->bestMoveChanges; } else - // All other moves but the PV are set to the lowest value: this + // All other moves but the PV, are set to the lowest value: this // is not a problem when sorting because the sort is stable and the // move position in the list is preserved - just the PV is pushed up. rm.score = -VALUE_INFINITE; @@ -1337,7 +1337,7 @@ moves_loop: // When in check, search starts here } - // If the move is worse than some previously searched move, remember it to update its stats later + // If the move is worse than some previously searched move, remember it, to update its stats later if (move != bestMove) { if (capture && captureCount < 32) @@ -1349,7 +1349,7 @@ moves_loop: // When in check, search starts here } // The following condition would detect a stop only after move loop has been - // completed. But in this case bestValue is valid because we have fully + // completed. But in this case, bestValue is valid because we have fully // searched our subtree, and we can anyhow save the result in TT. /* if (Threads.stop) @@ -1368,7 +1368,7 @@ moves_loop: // When in check, search starts here ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; - // If there is a move which produces search value greater than alpha we update stats of searched moves + // If there is a move that produces search value greater than alpha we update the stats of searched moves else if (bestMove) update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, capturesSearched, captureCount, depth); @@ -1751,7 +1751,7 @@ moves_loop: // When in check, search starts here for (int i : {1, 2, 4, 6}) { - // Only update first 2 continuation histories if we are in check + // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (is_ok((ss-i)->currentMove)) @@ -1784,7 +1784,7 @@ moves_loop: // When in check, search starts here } } - // When playing with strength handicap, choose best move among a set of RootMoves + // When playing with strength handicap, choose the best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. Move Skill::pick_best(size_t multiPV) { @@ -1915,7 +1915,7 @@ string UCI::pv(const Position& pos, Depth depth) { /// RootMove::extract_ponder_from_tt() is called in case we have no ponder move /// before exiting the search, for instance, in case we stop the search during a /// fail high at root. We try hard to have a ponder move to return to the GUI, -/// otherwise in case of 'ponder on' we have nothing to think on. +/// otherwise in case of 'ponder on' we have nothing to think about. bool RootMove::extract_ponder_from_tt(Position& pos) { From 95ce443aaacadea777f34d87b0abf984e724f0dd Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Fri, 16 Jun 2023 18:49:31 +0200 Subject: [PATCH 322/678] simplified gives check castling tested verifying perft and bench is unchanged on a larger set of epds for both standard and FRC chess. Passed non-regression STC: https://tests.stockfishchess.org/tests/live_elo/648587be65ffe077ca123d78 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 153632 W: 41015 L: 40928 D: 71689 Ptnml(0-2): 377, 16077, 43816, 16174, 372 closes https://github.com/official-stockfish/Stockfish/pull/4628 No functional change --- AUTHORS | 1 + src/position.cpp | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 2e9ae780..79289394 100644 --- a/AUTHORS +++ b/AUTHORS @@ -179,6 +179,7 @@ Raminder Singh renouve Reuven Peleg (R-Peleg) Richard Lloyd (Richard-Lloyd) +rn5f107s2 Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) Ronald de Man (syzygy1, syzygy) diff --git a/src/position.cpp b/src/position.cpp index 2a9d798f..a052cf32 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -638,9 +638,9 @@ bool Position::gives_check(Move m) const { return true; // Is there a discovered check? - if ( (blockers_for_king(~sideToMove) & from) - && !aligned(from, to, square(~sideToMove))) - return true; + if (blockers_for_king(~sideToMove) & from) + return !aligned(from, to, square(~sideToMove)) + || type_of(m) == CASTLING; switch (type_of(m)) { @@ -665,11 +665,9 @@ bool Position::gives_check(Move m) const { default: //CASTLING { // Castling is encoded as 'king captures the rook' - Square ksq = square(~sideToMove); Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); - return (attacks_bb(rto) & ksq) - && (attacks_bb(rto, pieces() ^ from ^ to) & ksq); + return check_squares(ROOK) & rto; } } } From ca5d9a5ff0739a63f7b0a184193dfb9de3c57156 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Fri, 16 Jun 2023 13:01:20 +0200 Subject: [PATCH 323/678] Extract bench according to wiki instructions - loop through the commits starting from the latest one - read the bench value from the last match, if any, of the template in the commit body text closes https://github.com/official-stockfish/Stockfish/pull/4627 No functional change --- .github/workflows/stockfish_test.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 9d6bc20c..1ea4b309 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -103,6 +103,10 @@ jobs: echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV fi + - name: Download required macOS packages + if: runner.os == 'macOS' + run: brew install coreutils + - name: Setup msys and install required packages if: runner.os == 'Windows' uses: msys2/setup-msys2@v2 @@ -115,8 +119,10 @@ jobs: - name: Extract the bench number from the commit history run: | - benchref=$(git log HEAD | grep -m 1 -o -x "[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*" | sed "s/[^0-9]//g") || true - [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "Reference bench: $benchref" || echo "No bench found" + for ((n=0; n<100; n++)); do + benchref=$(git log HEAD~$n -1 | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + done + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $(git rev-parse HEAD~$n)" && echo "Reference bench: $benchref" || echo "No bench found" - name: Check compiler run: | From e87e103ca994570d42f30f61f923986656a5df14 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Mon, 3 Jul 2023 19:41:13 +0200 Subject: [PATCH 324/678] Remove leftover braces for if conditional in CI closes https://github.com/official-stockfish/Stockfish/pull/4660 No functional change --- .github/workflows/stockfish_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 1ea4b309..b53d7e27 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -237,7 +237,7 @@ jobs: # armv8 tests - name: Test armv8 build - if: ${{ matrix.config.run_armv8_tests }} + if: matrix.config.run_armv8_tests run: | export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" @@ -248,7 +248,7 @@ jobs: # armv7 tests - name: Test armv7 build - if: ${{ matrix.config.run_armv7_tests }} + if: matrix.config.run_armv7_tests run: | export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" @@ -257,7 +257,7 @@ jobs: ../tests/signature.sh $benchref - name: Test armv7-neon build - if: ${{ matrix.config.run_armv7_tests }} + if: matrix.config.run_armv7_tests run: | export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" From 19e2a8850483c67835c0829ef016c6ede988817b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 6 Jul 2023 18:30:51 +0200 Subject: [PATCH 325/678] Revise extract bench from git log in CI order commits differently closes https://github.com/official-stockfish/Stockfish/pull/4668 No functional change --- .github/workflows/stockfish_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index b53d7e27..05592dae 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -119,8 +119,8 @@ jobs: - name: Extract the bench number from the commit history run: | - for ((n=0; n<100; n++)); do - benchref=$(git log HEAD~$n -1 | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + for hash in $(git rev-list -100 HEAD); do + benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true done [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $(git rev-parse HEAD~$n)" && echo "Reference bench: $benchref" || echo "No bench found" From f8e65d82ebf5754427a63116532733b7b7002f29 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 3 Jul 2023 12:59:42 -0700 Subject: [PATCH 326/678] Simplify away lookup_count. https://tests.stockfishchess.org/tests/view/64a3c1a93ee09aa549c53167 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 32832 W: 8497 L: 8280 D: 16055 Ptnml(0-2): 80, 3544, 8967, 3729, 96 closes https://github.com/official-stockfish/Stockfish/pull/4662 No functional change --- src/nnue/layers/affine_transform_sparse_input.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 18c166cd..3c7defcc 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -24,7 +24,6 @@ #include #include #include -#include #include #include "../nnue_common.h" #include "affine_transform.h" @@ -75,12 +74,6 @@ namespace Stockfish::Eval::NNUE::Layers { } return v; }(); - alignas(CacheLineSize) static inline const std::array lookup_count = [](){ - std::array v; - for (int i = 0; i < 256; ++i) - v[i] = unsigned(std::bitset<8>(i).count()); - return v; - }(); // Find indices of nonzero numbers in an int32_t array template @@ -120,7 +113,7 @@ namespace Stockfish::Eval::NNUE::Layers { const auto lookup = (nnz >> (j * 8)) & 0xFF; const auto offsets = _mm_loadu_si128(reinterpret_cast(&lookup_indices[lookup])); _mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets)); - count += lookup_count[lookup]; + count += popcount(lookup); base = _mm_add_epi16(base, increment); } } From 9ba24912c1bac753fdbde0ae78e19867dccb7500 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 5 Jul 2023 20:15:49 +0200 Subject: [PATCH 327/678] Add armv8-dotprod to CI binaries also generate binaries for more recent Android hardware. closes https://github.com/official-stockfish/Stockfish/pull/4663 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 1afd8efa..4db216eb 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -28,10 +28,13 @@ jobs: comp: ndk shell: bash binaries: + - armv8-dotprod - armv8 - armv7 - armv7-neon exclude: + - binaries: armv8-dotprod + config: {compiler: armv7a-linux-androideabi21-clang++} - binaries: armv8 config: {compiler: armv7a-linux-androideabi21-clang++} - binaries: armv7 @@ -155,4 +158,4 @@ jobs: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} prerelease: true - files: stockfish-android-${{ matrix.binaries }}.tar \ No newline at end of file + files: stockfish-android-${{ matrix.binaries }}.tar From e699fee513ce26b3794ac43d08826c89106e10ea Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 25 Jun 2023 20:57:50 -0400 Subject: [PATCH 328/678] Update default net to nn-c38c3d8d3920.nnue This was a later epoch from the same experiment that led to the previous master net. After training, it was prepared the same way: 1. greedy permuting L1 weights with https://github.com/official-stockfish/Stockfish/pull/4620 2. leb128 compression with https://github.com/glinscott/nnue-pytorch/pull/251 3. greedy 2- and 3- cycle permuting with https://github.com/official-stockfish/Stockfish/pull/4640 Local elo at 25k nodes per move (vs. L1-1536 nn-fdc1d0fe6455.nnue): nn-epoch739.nnue : 20.2 +/- 1.7 Passed STC: https://tests.stockfishchess.org/tests/view/64a050b33ee09aa549c4e4c8 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 195552 W: 49977 L: 49430 D: 96145 Ptnml(0-2): 556, 22775, 50607, 23242, 596 Passed LTC: https://tests.stockfishchess.org/tests/view/64a127bd3ee09aa549c4f60c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 235452 W: 60327 L: 59609 D: 115516 Ptnml(0-2): 119, 25173, 66426, 25887, 121 closes https://github.com/official-stockfish/Stockfish/pull/4666 bench 2427629 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index a1d46111..abdbef90 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1b951f8b449d.nnue" + #define EvalFileDefaultName "nn-c38c3d8d3920.nnue" namespace NNUE { From ee023d7fd78a96c10ae157c0d3174f091a4e09d1 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 6 Jul 2023 23:29:11 +0200 Subject: [PATCH 329/678] Fix CI output closes https://github.com/official-stockfish/Stockfish/pull/4669 No functional change --- .github/workflows/stockfish_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 05592dae..cd80e223 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -122,7 +122,7 @@ jobs: for hash in $(git rev-list -100 HEAD); do benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true done - [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $(git rev-parse HEAD~$n)" && echo "Reference bench: $benchref" || echo "No bench found" + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" - name: Check compiler run: | From 6a8767a0d5d9502e6d4de1bef97468b5d6fab80a Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 7 Jul 2023 20:19:31 +0800 Subject: [PATCH 330/678] Simplify PvNode reduction Simplification STC: https://tests.stockfishchess.org/tests/view/64a415803ee09aa549c539c3 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 37856 W: 9719 L: 9504 D: 18633 Ptnml(0-2): 98, 4277, 9977, 4464, 112 Simplification LTC: https://tests.stockfishchess.org/tests/view/64a5ffe202cd07745c60f360 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 55878 W: 14323 L: 14138 D: 27417 Ptnml(0-2): 21, 5993, 15732, 6166, 27 closes https://github.com/official-stockfish/Stockfish/pull/4673 Bench: 2604965 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 76d055e3..1f8f361c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1162,7 +1162,7 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) - r -= 1 + 12 / (3 + depth); + r -= 1 + (depth < 6); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) From af110e02ec96cdb46cf84c68252a1da15a902395 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 1 Jun 2023 08:09:07 +0200 Subject: [PATCH 331/678] Remove classical evaluation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit since the introduction of NNUE (first released with Stockfish 12), we have maintained the classical evaluation as part of SF in frozen form. The idea that this code could lead to further inputs to the NN or search did not materialize. Now, after five releases, this PR removes the classical evaluation from SF. Even though this evaluation is probably the best of its class, it has become unimportant for the engine's strength, and there is little need to maintain this code (roughly 25% of SF) going forward, or to expend resources on trying to improve its integration in the NNUE eval. Indeed, it had still a very limited use in the current SF, namely for the evaluation of positions that are nearly decided based on material difference, where the speed of the classical evaluation outweights its inaccuracies. This impact on strength is small, roughly 2Elo, and probably decreasing in importance as the TC grows. Potentially, removal of this code could lead to the development of techniques to have faster, but less accurate NN evaluation, for certain positions. STC https://tests.stockfishchess.org/tests/view/64a320173ee09aa549c52157 Elo: -2.35 ± 1.1 (95%) LOS: 0.0% Total: 100000 W: 24916 L: 25592 D: 49492 Ptnml(0-2): 287, 12123, 25841, 11477, 272 nElo: -4.62 ± 2.2 (95%) PairsRatio: 0.95 LTC https://tests.stockfishchess.org/tests/view/64a320293ee09aa549c5215b Elo: -1.74 ± 1.0 (95%) LOS: 0.0% Total: 100000 W: 25010 L: 25512 D: 49478 Ptnml(0-2): 44, 11069, 28270, 10579, 38 nElo: -3.72 ± 2.2 (95%) PairsRatio: 0.96 VLTC SMP https://tests.stockfishchess.org/tests/view/64a3207c3ee09aa549c52168 Elo: -1.70 ± 0.9 (95%) LOS: 0.0% Total: 100000 W: 25673 L: 26162 D: 48165 Ptnml(0-2): 8, 9455, 31569, 8954, 14 nElo: -3.95 ± 2.2 (95%) PairsRatio: 0.95 closes https://github.com/official-stockfish/Stockfish/pull/4674 Bench: 1444646 --- src/Makefile | 4 +- src/benchmark.cpp | 9 - src/bitbase.cpp | 172 ------- src/bitboard.h | 7 - src/endgame.cpp | 747 ---------------------------- src/endgame.h | 126 ----- src/evaluate.cpp | 992 +------------------------------------ src/main.cpp | 3 - src/material.cpp | 229 --------- src/material.h | 71 --- src/nnue/evaluate_nnue.cpp | 3 +- src/pawns.cpp | 305 ------------ src/pawns.h | 70 --- src/position.cpp | 38 +- src/thread.h | 4 - src/ucioption.cpp | 2 - 16 files changed, 37 insertions(+), 2745 deletions(-) delete mode 100644 src/bitbase.cpp delete mode 100644 src/endgame.cpp delete mode 100644 src/endgame.h delete mode 100644 src/material.cpp delete mode 100644 src/material.h delete mode 100644 src/pawns.cpp delete mode 100644 src/pawns.h diff --git a/src/Makefile b/src/Makefile index 82664618..a0f098fa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -56,8 +56,8 @@ else endif ### Source and object files -SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \ - material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ +SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ + misc.cpp movegen.cpp movepick.cpp position.cpp psqt.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp diff --git a/src/benchmark.cpp b/src/benchmark.cpp index a1ad0550..baa90140 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -153,24 +153,15 @@ vector setup_bench(const Position& current, istream& is) { list.emplace_back("setoption name Hash value " + ttSize); list.emplace_back("ucinewgame"); - size_t posCounter = 0; - for (const string& fen : fens) if (fen.find("setoption") != string::npos) list.emplace_back(fen); else { - if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0)) - list.emplace_back("setoption name Use NNUE value false"); - else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0)) - list.emplace_back("setoption name Use NNUE value true"); list.emplace_back("position fen " + fen); list.emplace_back(go); - ++posCounter; } - list.emplace_back("setoption name Use NNUE value true"); - return list; } diff --git a/src/bitbase.cpp b/src/bitbase.cpp deleted file mode 100644 index e21d1fe9..00000000 --- a/src/bitbase.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include -#include - -#include "bitboard.h" -#include "types.h" - -namespace Stockfish { - -namespace { - - // There are 24 possible pawn squares: files A to D and ranks from 2 to 7. - // Positions with the pawn on files E to H will be mirrored before probing. - constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608 - - std::bitset KPKBitbase; - - // A KPK bitbase index is an integer in [0, IndexMax] range - // - // Information is mapped in a way that minimizes the number of iterations: - // - // bit 0- 5: white king square (from SQ_A1 to SQ_H8) - // bit 6-11: black king square (from SQ_A1 to SQ_H8) - // bit 12: side to move (WHITE or BLACK) - // bit 13-14: white pawn file (from FILE_A to FILE_D) - // bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2) - unsigned index(Color stm, Square bksq, Square wksq, Square psq) { - return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15); - } - - enum Result { - INVALID = 0, - UNKNOWN = 1, - DRAW = 2, - WIN = 4 - }; - - Result& operator|=(Result& r, Result v) { return r = Result(r | v); } - - struct KPKPosition { - KPKPosition() = default; - explicit KPKPosition(unsigned idx); - operator Result() const { return result; } - Result classify(const std::vector& db); - - Color stm; - Square ksq[COLOR_NB], psq; - Result result; - }; - -} // namespace - -bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) { - - assert(file_of(wpsq) <= FILE_D); - - return KPKBitbase[index(stm, bksq, wksq, wpsq)]; -} - - -void Bitbases::init() { - - std::vector db(MAX_INDEX); - unsigned idx, repeat = 1; - - // Initialize db with known win / draw positions - for (idx = 0; idx < MAX_INDEX; ++idx) - db[idx] = KPKPosition(idx); - - // Iterate through the positions until none of the unknown positions can be - // changed to either wins or draws (15 cycles needed). - while (repeat) - for (repeat = idx = 0; idx < MAX_INDEX; ++idx) - repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN); - - // Fill the bitbase with the decisive results - for (idx = 0; idx < MAX_INDEX; ++idx) - if (db[idx] == WIN) - KPKBitbase.set(idx); -} - -namespace { - - KPKPosition::KPKPosition(unsigned idx) { - - ksq[WHITE] = Square((idx >> 0) & 0x3F); - ksq[BLACK] = Square((idx >> 6) & 0x3F); - stm = Color ((idx >> 12) & 0x01); - psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); - - // Invalid if two pieces are on the same square or if a king can be captured - if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 - || ksq[WHITE] == psq - || ksq[BLACK] == psq - || (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK]))) - result = INVALID; - - // Win if the pawn can be promoted without getting captured - else if ( stm == WHITE - && rank_of(psq) == RANK_7 - && ksq[WHITE] != psq + NORTH - && ( distance(ksq[BLACK], psq + NORTH) > 1 - || (distance(ksq[WHITE], psq + NORTH) == 1))) - result = WIN; - - // Draw if it is stalemate or the black king can capture the pawn - else if ( stm == BLACK - && ( !(attacks_bb(ksq[BLACK]) & ~(attacks_bb(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq))) - || (attacks_bb(ksq[BLACK]) & ~attacks_bb(ksq[WHITE]) & psq))) - result = DRAW; - - // Position will be classified later - else - result = UNKNOWN; - } - - Result KPKPosition::classify(const std::vector& db) { - - // White to move: If one move leads to a position classified as WIN, the result - // of the current position is WIN. If all moves lead to positions classified - // as DRAW, the current position is classified as DRAW, otherwise the current - // position is classified as UNKNOWN. - // - // Black to move: If one move leads to a position classified as DRAW, the result - // of the current position is DRAW. If all moves lead to positions classified - // as WIN, the position is classified as WIN, otherwise the current position is - // classified as UNKNOWN. - const Result Good = (stm == WHITE ? WIN : DRAW); - const Result Bad = (stm == WHITE ? DRAW : WIN); - - Result r = INVALID; - Bitboard b = attacks_bb(ksq[stm]); - - while (b) - r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)] - : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)]; - - if (stm == WHITE) - { - if (rank_of(psq) < RANK_7) // Single push - r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)]; - - if ( rank_of(psq) == RANK_2 // Double push - && psq + NORTH != ksq[WHITE] - && psq + NORTH != ksq[BLACK]) - r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)]; - } - - return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad; - } - -} // namespace - -} // namespace Stockfish diff --git a/src/bitboard.h b/src/bitboard.h index 42fd0e97..d21d390b 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -25,13 +25,6 @@ namespace Stockfish { -namespace Bitbases { - -void init(); -bool probe(Square wksq, Square wpsq, Square bksq, Color us); - -} // namespace Stockfish::Bitbases - namespace Bitboards { void init(); diff --git a/src/endgame.cpp b/src/endgame.cpp deleted file mode 100644 index 9021f242..00000000 --- a/src/endgame.cpp +++ /dev/null @@ -1,747 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include - -#include "bitboard.h" -#include "endgame.h" -#include "movegen.h" - -namespace Stockfish { - -namespace { - - // Used to drive the king towards the edge of the board - // in KX vs K and KQ vs KR endgames. - // Values range from 27 (center squares) to 90 (in the corners) - inline int push_to_edge(Square s) { - int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s)); - return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2); - } - - // Used to drive the king towards A1H8 corners in KBN vs K endgames. - // Values range from 0 on A8H1 diagonal to 7 in A1H8 corners - inline int push_to_corner(Square s) { - return abs(7 - rank_of(s) - file_of(s)); - } - - // Drive a piece close to or away from another piece - inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); } - inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); } - -#ifndef NDEBUG - bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) { - return pos.non_pawn_material(c) == npm && pos.count(c) == pawnsCnt; - } -#endif - - // Map the square as if strongSide is white and strongSide's only pawn - // is on the left half of the board. - Square normalize(const Position& pos, Color strongSide, Square sq) { - - assert(pos.count(strongSide) == 1); - - if (file_of(pos.square(strongSide)) >= FILE_E) - sq = flip_file(sq); - - return strongSide == WHITE ? sq : flip_rank(sq); - } - -} // namespace - - -namespace Endgames { - - std::pair, Map> maps; - - void init() { - - add("KPK"); - add("KNNK"); - add("KBNK"); - add("KRKP"); - add("KRKB"); - add("KRKN"); - add("KQKP"); - add("KQKR"); - add("KNNKP"); - - add("KRPKR"); - add("KRPKB"); - add("KBPKB"); - add("KBPKN"); - add("KBPPKB"); - add("KRPPKRP"); - } -} - - -/// Mate with KX vs K. This function is used to evaluate positions with -/// king and plenty of material vs a lone king. It simply gives the -/// attacking side a bonus for driving the defending king towards the edge -/// of the board, and for keeping the distance between the two kings small. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - assert(!pos.checkers()); // Eval is never called when in check - - // Stalemate detection with lone king - if (pos.side_to_move() == weakSide && !MoveList(pos).size()) - return VALUE_DRAW; - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - Value result = pos.non_pawn_material(strongSide) - + pos.count(strongSide) * PawnValueEg - + push_to_edge(weakKing) - + push_close(strongKing, weakKing); - - if ( pos.count(strongSide) - || pos.count(strongSide) - ||(pos.count(strongSide) && pos.count(strongSide)) - || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) - && (pos.pieces(strongSide, BISHOP) & DarkSquares))) - result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the -/// defending king towards a corner square that our bishop attacks. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - Square strongKing = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - // If our bishop does not attack A1/H8, we flip the enemy king square - // to drive to opposite corners (A8/H1). - - Value result = (VALUE_KNOWN_WIN + 3520) - + push_close(strongKing, weakKing) - + 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing); - - assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KP vs K. This endgame is evaluated with the help of a bitbase -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - - Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; - - if (!Bitbases::probe(strongKing, strongPawn, weakKing, us)) - return VALUE_DRAW; - - Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn)); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without -/// a bitbase. The function below returns drawish scores when the pawn is -/// far advanced with support of the king, while the attacking king is far -/// away. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square strongRook = pos.square(strongSide); - Square weakPawn = pos.square(weakSide); - Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8)); - Value result; - - // If the stronger side's king is in front of the pawn, it's a win - if (forward_file_bb(strongSide, strongKing) & weakPawn) - result = RookValueEg - distance(strongKing, weakPawn); - - // If the weaker side's king is too far from the pawn and the rook, - // it's a win. - else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide) - && distance(weakKing, strongRook) >= 3) - result = RookValueEg - distance(strongKing, weakPawn); - - // If the pawn is far advanced and supported by the defending king, - // the position is drawish - else if ( relative_rank(strongSide, weakKing) <= RANK_3 - && distance(weakKing, weakPawn) == 1 - && relative_rank(strongSide, strongKing) >= RANK_4 - && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide)) - result = Value(80) - 8 * distance(strongKing, weakPawn); - - else - result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide)) - - distance(weakKing, weakPawn + pawn_push(weakSide)) - - distance(weakPawn, queeningSquare)); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KB. This is very simple, and always returns drawish scores. The -/// score is slightly bigger when the defending king is close to the edge. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Value result = Value(push_to_edge(pos.square(weakSide))); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KN. The attacking side has slightly better winning chances than -/// in KR vs KB, particularly if the king and the knight are far apart. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, KnightValueMg, 0)); - - Square weakKing = pos.square(weakSide); - Square weakKnight = pos.square(weakSide); - Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight)); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KQ vs KP. In general, this is a win for the stronger side, but there are a -/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files -/// with a king positioned next to it can be a draw, so in that case, we only -/// use the distance between the kings. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square weakPawn = pos.square(weakSide); - - Value result = Value(push_close(strongKing, weakKing)); - - if ( relative_rank(weakSide, weakPawn) != RANK_7 - || distance(weakKing, weakPawn) != 1 - || ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn)) - result += QueenValueEg - PawnValueEg; - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KQ vs KR. This is almost identical to KX vs K: we give the attacking -/// king a bonus for having the kings close together, and for forcing the -/// defending king towards the edge. If we also take care to avoid null move for -/// the defending side in the search, this is usually sufficient to win KQ vs KR. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(verify_material(pos, weakSide, RookValueMg, 0)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - Value result = QueenValueEg - - RookValueEg - + push_to_edge(weakKing) - + push_close(strongKing, weakKing); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KNN vs KP. Very drawish, but there are some mate opportunities if we can -/// press the weakSide King to a corner before the pawn advances too much. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square weakKing = pos.square(weakSide); - Square weakPawn = pos.square(weakSide); - - Value result = PawnValueEg - + 2 * push_to_edge(weakKing) - - 10 * relative_rank(weakSide, weakPawn); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// Some cases of trivial draws -template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } - - -/// KB and one or more pawns vs K. It checks for draws with rook pawns and -/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW -/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling -/// will be used. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(pos.non_pawn_material(strongSide) == BishopValueMg); - assert(pos.count(strongSide) >= 1); - - // No assertions about the material of weakSide, because we want draws to - // be detected even when the weaker side has some pawns. - - Bitboard strongPawns = pos.pieces(strongSide, PAWN); - Bitboard allPawns = pos.pieces(PAWN); - - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square strongKing = pos.square(strongSide); - - // All strongSide pawns are on a single rook file? - if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB)) - { - Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8)); - - if ( opposite_colors(queeningSquare, strongBishop) - && distance(queeningSquare, weakKing) <= 1) - return SCALE_FACTOR_DRAW; - } - - // If all the pawns are on the same B or G file, then it's potentially a draw - if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB)) - && pos.non_pawn_material(weakSide) == 0 - && pos.count(weakSide) >= 1) - { - // Get the least advanced weakSide pawn - Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN)); - - // There's potential for a draw if our pawn is blocked on the 7th rank, - // the bishop cannot attack it or they only have one pawn left. - if ( relative_rank(strongSide, weakPawn) == RANK_7 - && (strongPawns & (weakPawn + pawn_push(weakSide))) - && (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns))) - { - int strongKingDist = distance(weakPawn, strongKing); - int weakKingDist = distance(weakPawn, weakKing); - - // It's a draw if the weak king is on its back two ranks, within 2 - // squares of the blocking pawn and the strong king is not - // closer. (I think this rule only fails in practically - // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w - // and positions where qsearch will immediately correct the - // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w). - if ( relative_rank(strongSide, weakKing) >= RANK_7 - && weakKingDist <= 2 - && weakKingDist <= strongKingDist) - return SCALE_FACTOR_DRAW; - } - } - - return SCALE_FACTOR_NONE; -} - - -/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on -/// the third rank defended by a pawn. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(pos.count(weakSide) == 1); - assert(pos.count(weakSide) >= 1); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square weakRook = pos.square(weakSide); - - if ( relative_rank(weakSide, weakKing) <= RANK_2 - && relative_rank(weakSide, strongKing) >= RANK_4 - && relative_rank(weakSide, weakRook) == RANK_3 - && ( pos.pieces(weakSide, PAWN) - & attacks_bb(weakKing) - & pawn_attacks_bb(strongSide, weakRook))) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KRP vs KR. This function knows a handful of the most important classes of -/// drawn positions, but is far from perfect. It would probably be a good idea -/// to add more knowledge in the future. -/// -/// It would also be nice to rewrite the actual code for this function, -/// which is mostly copied from Glaurung 1.x, and isn't very pretty. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 1)); - assert(verify_material(pos, weakSide, RookValueMg, 0)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square strongRook = normalize(pos, strongSide, pos.square(strongSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - Square weakRook = normalize(pos, strongSide, pos.square(weakSide)); - - File pawnFile = file_of(strongPawn); - Rank pawnRank = rank_of(strongPawn); - Square queeningSquare = make_square(pawnFile, RANK_8); - int tempo = (pos.side_to_move() == strongSide); - - // If the pawn is not too far advanced and the defending king defends the - // queening square, use the third-rank defence. - if ( pawnRank <= RANK_5 - && distance(weakKing, queeningSquare) <= 1 - && strongKing <= SQ_H5 - && (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6))) - return SCALE_FACTOR_DRAW; - - // The defending side saves a draw by checking from behind in case the pawn - // has advanced to the 6th rank with the king behind. - if ( pawnRank == RANK_6 - && distance(weakKing, queeningSquare) <= 1 - && rank_of(strongKing) + tempo <= RANK_6 - && (rank_of(weakRook) == RANK_1 || (!tempo && distance(weakRook, strongPawn) >= 3))) - return SCALE_FACTOR_DRAW; - - if ( pawnRank >= RANK_6 - && weakKing == queeningSquare - && rank_of(weakRook) == RANK_1 - && (!tempo || distance(strongKing, strongPawn) >= 2)) - return SCALE_FACTOR_DRAW; - - // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7 - // and the black rook is behind the pawn. - if ( strongPawn == SQ_A7 - && strongRook == SQ_A8 - && (weakKing == SQ_H7 || weakKing == SQ_G7) - && file_of(weakRook) == FILE_A - && (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5)) - return SCALE_FACTOR_DRAW; - - // If the defending king blocks the pawn and the attacking king is too far - // away, it's a draw. - if ( pawnRank <= RANK_5 - && weakKing == strongPawn + NORTH - && distance(strongKing, strongPawn) - tempo >= 2 - && distance(strongKing, weakRook) - tempo >= 2) - return SCALE_FACTOR_DRAW; - - // Pawn on the 7th rank supported by the rook from behind usually wins if the - // attacking king is closer to the queening square than the defending king, - // and the defending king cannot gain tempi by threatening the attacking rook. - if ( pawnRank == RANK_7 - && pawnFile != FILE_A - && file_of(strongRook) == pawnFile - && strongRook != queeningSquare - && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) - && (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo)) - return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare)); - - // Similar to the above, but with the pawn further back - if ( pawnFile != FILE_A - && file_of(strongRook) == pawnFile - && strongRook < strongPawn - && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) - && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo) - && ( distance(weakKing, strongRook) + tempo >= 3 - || ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo - && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo)))) - return ScaleFactor( SCALE_FACTOR_MAX - - 8 * distance(strongPawn, queeningSquare) - - 2 * distance(strongKing, queeningSquare)); - - // If the pawn is not far advanced and the defending king is somewhere in - // the pawn's path, it's probably a draw. - if (pawnRank <= RANK_4 && weakKing > strongPawn) - { - if (file_of(weakKing) == file_of(strongPawn)) - return ScaleFactor(10); - if ( distance(weakKing, strongPawn) == 1 - && distance(strongKing, weakKing) > 2) - return ScaleFactor(24 - 2 * distance(strongKing, weakKing)); - } - return SCALE_FACTOR_NONE; -} - -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 1)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - // Test for a rook pawn - if (pos.pieces(PAWN) & (FileABB | FileHBB)) - { - Square weakKing = pos.square(weakSide); - Square weakBishop = pos.square(weakSide); - Square strongKing = pos.square(strongSide); - Square strongPawn = pos.square(strongSide); - Rank pawnRank = relative_rank(strongSide, strongPawn); - Direction push = pawn_push(strongSide); - - // If the pawn is on the 5th rank and the pawn (currently) is on - // the same color square as the bishop then there is a chance of - // a fortress. Depending on the king position give a moderate - // reduction or a stronger one if the defending king is near the - // corner but not trapped there. - if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn)) - { - int d = distance(strongPawn + 3 * push, weakKing); - - if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push)) - return ScaleFactor(24); - else - return ScaleFactor(48); - } - - // When the pawn has moved to the 6th rank we can be fairly sure - // it's drawn if the bishop attacks the square in front of the - // pawn from a reasonable distance and the defending king is near - // the corner - if ( pawnRank == RANK_6 - && distance(strongPawn + 2 * push, weakKing) <= 1 - && (attacks_bb(weakBishop) & (strongPawn + push)) - && distance(weakBishop, strongPawn) >= 2) - return ScaleFactor(8); - } - - return SCALE_FACTOR_NONE; -} - -/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed -/// pawns and the defending king is actively placed, the position is drawish. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 2)); - assert(verify_material(pos, weakSide, RookValueMg, 1)); - - Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); - Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); - Square weakKing = pos.square(weakSide); - - // Does the stronger side have a passed pawn? - if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2)) - return SCALE_FACTOR_NONE; - - Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2)); - - if ( distance(weakKing, strongPawn1) <= 1 - && distance(weakKing, strongPawn2) <= 1 - && relative_rank(strongSide, weakKing) > pawnRank) - { - assert(pawnRank > RANK_1 && pawnRank < RANK_7); - return ScaleFactor(7 * pawnRank); - } - return SCALE_FACTOR_NONE; -} - - -/// K and two or more pawns vs K. There is just a single rule here: if all pawns -/// are on the same rook file and are blocked by the defending king, it's a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(pos.non_pawn_material(strongSide) == VALUE_ZERO); - assert(pos.count(strongSide) >= 2); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - Square weakKing = pos.square(weakSide); - Bitboard strongPawns = pos.pieces(strongSide, PAWN); - - // If all pawns are ahead of the king on a single rook file, it's a draw. - if ( !(strongPawns & ~(FileABB | FileHBB)) - && !(strongPawns & ~passed_pawn_span(weakSide, weakKing))) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KBP vs KB. There are two rules: if the defending king is somewhere along the -/// path of the pawn, and the square of the king is not of the same color as the -/// stronger side's bishop, it's a draw. If the two bishops have opposite color, -/// it's almost always a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 1)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Square strongPawn = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakBishop = pos.square(weakSide); - Square weakKing = pos.square(weakSide); - - // Case 1: Defending king blocks the pawn, and cannot be driven away - if ( (forward_file_bb(strongSide, strongPawn) & weakKing) - && ( opposite_colors(weakKing, strongBishop) - || relative_rank(strongSide, weakKing) <= RANK_6)) - return SCALE_FACTOR_DRAW; - - // Case 2: Opposite colored bishops - if (opposite_colors(strongBishop, weakBishop)) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 2)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Square strongBishop = pos.square(strongSide); - Square weakBishop = pos.square(weakSide); - - if (!opposite_colors(strongBishop, weakBishop)) - return SCALE_FACTOR_NONE; - - Square weakKing = pos.square(weakSide); - Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); - Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); - Square blockSq1, blockSq2; - - if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2)) - { - blockSq1 = strongPawn1 + pawn_push(strongSide); - blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1)); - } - else - { - blockSq1 = strongPawn2 + pawn_push(strongSide); - blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2)); - } - - switch (distance(strongPawn1, strongPawn2)) - { - case 0: - // Both pawns are on the same file. It's an easy draw if the defender firmly - // controls some square in the frontmost pawn's path. - if ( file_of(weakKing) == file_of(blockSq1) - && relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1) - && opposite_colors(weakKing, strongBishop)) - return SCALE_FACTOR_DRAW; - else - return SCALE_FACTOR_NONE; - - case 1: - // Pawns on adjacent files. It's a draw if the defender firmly controls the - // square in front of the frontmost pawn's path, and the square diagonally - // behind this square on the file of the other pawn. - if ( weakKing == blockSq1 - && opposite_colors(weakKing, strongBishop) - && ( weakBishop == blockSq2 - || (attacks_bb(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP)) - || distance(strongPawn1, strongPawn2) >= 2)) - return SCALE_FACTOR_DRAW; - - else if ( weakKing == blockSq2 - && opposite_colors(weakKing, strongBishop) - && ( weakBishop == blockSq1 - || (attacks_bb(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP)))) - return SCALE_FACTOR_DRAW; - else - return SCALE_FACTOR_NONE; - - default: - // The pawns are not on the same file or adjacent files. No scaling. - return SCALE_FACTOR_NONE; - } -} - - -/// KBP vs KN. There is a single rule: if the defending king is somewhere along -/// the path of the pawn, and the square of the king is not of the same color as -/// the stronger side's bishop, it's a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 1)); - assert(verify_material(pos, weakSide, KnightValueMg, 0)); - - Square strongPawn = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - if ( file_of(weakKing) == file_of(strongPawn) - && relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing) - && ( opposite_colors(weakKing, strongBishop) - || relative_rank(strongSide, weakKing) <= RANK_6)) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KP vs KP. This is done by removing the weakest side's pawn and probing the -/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably -/// has at least a draw with the pawn as well. The exception is when the stronger -/// side's pawn is far advanced and not on a rook file; in this case it is often -/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1). -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - - Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; - - // If the pawn has advanced to the fifth rank or further, and is not a - // rook pawn, it's too dangerous to assume that it's at least a draw. - if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A) - return SCALE_FACTOR_NONE; - - // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, - // it's probably at least a draw even with the pawn. - return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; -} - -} // namespace Stockfish diff --git a/src/endgame.h b/src/endgame.h deleted file mode 100644 index c184cb3f..00000000 --- a/src/endgame.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef ENDGAME_H_INCLUDED -#define ENDGAME_H_INCLUDED - -#include -#include -#include -#include -#include - -#include "position.h" -#include "types.h" - -namespace Stockfish { - -/// EndgameCode lists all supported endgame functions by corresponding codes - -enum EndgameCode { - - EVALUATION_FUNCTIONS, - KNNK, // KNN vs K - KNNKP, // KNN vs KP - KXK, // Generic "mate lone king" eval - KBNK, // KBN vs K - KPK, // KP vs K - KRKP, // KR vs KP - KRKB, // KR vs KB - KRKN, // KR vs KN - KQKP, // KQ vs KP - KQKR, // KQ vs KR - - SCALING_FUNCTIONS, - KBPsK, // KB and pawns vs K - KQKRPs, // KQ vs KR and pawns - KRPKR, // KRP vs KR - KRPKB, // KRP vs KB - KRPPKRP, // KRPP vs KRP - KPsK, // K and pawns vs K - KBPKB, // KBP vs KB - KBPPKB, // KBPP vs KB - KBPKN, // KBP vs KN - KPKP // KP vs KP -}; - - -/// Endgame functions can be of two types depending on whether they return a -/// Value or a ScaleFactor. - -template using -eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; - - -/// Base and derived functors for endgame evaluation and scaling functions - -template -struct EndgameBase { - - explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {} - virtual ~EndgameBase() = default; - virtual T operator()(const Position&) const = 0; - - const Color strongSide, weakSide; -}; - - -template> -struct Endgame : public EndgameBase { - - explicit Endgame(Color c) : EndgameBase(c) {} - T operator()(const Position&) const override; -}; - - -/// The Endgames namespace handles the pointers to endgame evaluation and scaling -/// base objects in two std::map. We use polymorphism to invoke the actual -/// endgame function by calling its virtual operator(). - -namespace Endgames { - - template using Ptr = std::unique_ptr>; - template using Map = std::unordered_map>; - - extern std::pair, Map> maps; - - void init(); - - template - Map& map() { - return std::get::value>(maps); - } - - template> - void add(const std::string& code) { - - StateInfo st; - map()[Position().set(code, WHITE, &st).material_key()] = Ptr(new Endgame(WHITE)); - map()[Position().set(code, BLACK, &st).material_key()] = Ptr(new Endgame(BLACK)); - } - - template - const EndgameBase* probe(Key key) { - auto it = map().find(key); - return it != map().end() ? it->second.get() : nullptr; - } -} - -} // namespace Stockfish - -#endif // #ifndef ENDGAME_H_INCLUDED diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 35d05427..2ab4fa40 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -18,8 +18,6 @@ #include #include -#include -#include // For std::memset #include #include #include @@ -29,9 +27,7 @@ #include "bitboard.h" #include "evaluate.h" -#include "material.h" #include "misc.h" -#include "pawns.h" #include "thread.h" #include "timeman.h" #include "uci.h" @@ -60,9 +56,10 @@ namespace Stockfish { namespace Eval { - bool useNNUE; string currentEvalFileName = "None"; + static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } + /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" /// The name of the NNUE network is always retrieved from the EvalFile option. @@ -73,10 +70,6 @@ namespace Eval { void NNUE::init() { - useNNUE = Options["Use NNUE"]; - if (!useNNUE) - return; - string eval_file = string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; @@ -122,10 +115,10 @@ namespace Eval { if (eval_file.empty()) eval_file = EvalFileDefaultName; - if (useNNUE && currentEvalFileName != eval_file) + if (currentEvalFileName != eval_file) { - string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; + string msg1 = "Network evaluation parameters compatible with the engine must be available."; string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); @@ -140,909 +133,10 @@ namespace Eval { exit(EXIT_FAILURE); } - if (useNNUE) - sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; - else - sync_cout << "info string classical evaluation enabled" << sync_endl; + sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; } } -namespace Trace { - - enum Tracing { NO_TRACE, TRACE }; - - enum Term { // The first 8 entries are reserved for PieceType - MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB - }; - - Score scores[TERM_NB][COLOR_NB]; - - static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } - - static void add(int idx, Color c, Score s) { - scores[idx][c] = s; - } - - static void add(int idx, Score w, Score b = SCORE_ZERO) { - scores[idx][WHITE] = w; - scores[idx][BLACK] = b; - } - - static std::ostream& operator<<(std::ostream& os, Score s) { - os << std::setw(5) << to_cp(mg_value(s)) << " " - << std::setw(5) << to_cp(eg_value(s)); - return os; - } - - static std::ostream& operator<<(std::ostream& os, Term t) { - - if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL) - os << " ---- ----" << " | " << " ---- ----"; - else - os << scores[t][WHITE] << " | " << scores[t][BLACK]; - - os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n"; - return os; - } -} - -using namespace Trace; - -namespace { - - // Threshold for lazy and space evaluation - constexpr Value LazyThreshold1 = Value(3622); - constexpr Value LazyThreshold2 = Value(1962); - constexpr Value SpaceThreshold = Value(11551); - - // KingAttackWeights[PieceType] contains king attack weights by piece type - constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 76, 46, 45, 14 }; - - // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type, - // higher if multiple safe checks are possible for that piece type. - constexpr int SafeCheck[][2] = { - {}, {}, {805, 1292}, {650, 984}, {1071, 1886}, {730, 1128} - }; - -#define S(mg, eg) make_score(mg, eg) - - // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game, - // indexed by piece type and number of attacked squares in the mobility area. - constexpr Score MobilityBonus[][32] = { - { S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight - S( 21, 16), S( 28, 21), S( 37, 26) }, - { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop - S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87), - S( 91, 88), S( 96, 98) }, - { S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook - S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160), - S( 57,165), S( 58,170), S( 67,175) }, - { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen - S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101), - S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140), - S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171), - S(112,178), S(114,185), S(114,187), S(119,221) } - }; - - // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on - // squares of the same color as our bishop. - constexpr Score BishopPawns[int(FILE_NB) / 2] = { - S(3, 8), S(3, 9), S(2, 7), S(3, 7) - }; - - // KingProtector[knight/bishop] contains penalty for each distance unit to own king - constexpr Score KingProtector[] = { S(9, 9), S(7, 9) }; - - // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a - // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. - constexpr Score Outpost[] = { S(54, 34), S(31, 25) }; - - // PassedRank[Rank] contains a bonus according to the rank of a passed pawn - constexpr Score PassedRank[RANK_NB] = { - S(0, 0), S(2, 38), S(15, 36), S(22, 50), S(64, 81), S(166, 184), S(284, 269) - }; - - constexpr Score RookOnClosedFile = S(10, 5); - constexpr Score RookOnOpenFile[] = { S(18, 8), S(49, 26) }; - - // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to - // which piece type attacks which one. Attacks on lesser pieces which are - // pawn-defended are not considered. - constexpr Score ThreatByMinor[PIECE_TYPE_NB] = { - S(0, 0), S(6, 37), S(64, 50), S(82, 57), S(103, 130), S(81, 163) - }; - - constexpr Score ThreatByRook[PIECE_TYPE_NB] = { - S(0, 0), S(3, 44), S(36, 71), S(44, 59), S(0, 39), S(60, 39) - }; - - constexpr Value CorneredBishop = Value(50); - - // Assorted bonuses and penalties - constexpr Score UncontestedOutpost = S( 0, 10); - constexpr Score BishopOnKingRing = S( 24, 0); - constexpr Score BishopXRayPawns = S( 4, 5); - constexpr Score FlankAttacks = S( 8, 0); - constexpr Score Hanging = S( 72, 40); - constexpr Score KnightOnQueen = S( 16, 11); - constexpr Score LongDiagonalBishop = S( 45, 0); - constexpr Score MinorBehindPawn = S( 18, 3); - constexpr Score PassedFile = S( 13, 8); - constexpr Score PawnlessFlank = S( 19, 97); - constexpr Score ReachableOutpost = S( 33, 19); - constexpr Score RestrictedPiece = S( 6, 7); - constexpr Score RookOnKingRing = S( 16, 0); - constexpr Score SliderOnQueen = S( 62, 21); - constexpr Score ThreatByKing = S( 24, 87); - constexpr Score ThreatByPawnPush = S( 48, 39); - constexpr Score ThreatBySafePawn = S(167, 99); - constexpr Score TrappedRook = S( 55, 13); - constexpr Score WeakQueenProtection = S( 14, 0); - constexpr Score WeakQueen = S( 57, 19); - - -#undef S - - // Evaluation class computes and stores attacks tables and other working data - template - class Evaluation { - - public: - Evaluation() = delete; - explicit Evaluation(const Position& p) : pos(p) {} - Evaluation& operator=(const Evaluation&) = delete; - Value value(); - - private: - template void initialize(); - template Score pieces(); - template Score king() const; - template Score threats() const; - template Score passed() const; - template Score space() const; - Value winnable(Score score) const; - - const Position& pos; - Material::Entry* me; - Pawns::Entry* pe; - Bitboard mobilityArea[COLOR_NB]; - Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; - - // attackedBy[color][piece type] is a bitboard representing all squares - // attacked by a given color and piece type. Special "piece types" which - // is also calculated is ALL_PIECES. - Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; - - // attackedBy2[color] are the squares attacked by at least 2 units of a given - // color, including x-rays. But diagonal x-rays through pawns are not computed. - Bitboard attackedBy2[COLOR_NB]; - - // kingRing[color] are the squares adjacent to the king plus some other - // very near squares, depending on king position. - Bitboard kingRing[COLOR_NB]; - - // kingAttackersCount[color] is the number of pieces of the given color - // which attack a square in the kingRing of the enemy king. - int kingAttackersCount[COLOR_NB]; - - // kingAttackersWeight[color] is the sum of the "weights" of the pieces of - // the given color which attack a square in the kingRing of the enemy king. - // The weights of the individual piece types are given by the elements in - // the KingAttackWeights array. - int kingAttackersWeight[COLOR_NB]; - - // kingAttacksCount[color] is the number of attacks by the given color to - // squares directly adjacent to the enemy king. Pieces which attack more - // than one square are counted multiple times. For instance, if there is - // a white knight on g5 and black's king is on g8, this white knight adds 2 - // to kingAttacksCount[WHITE]. - int kingAttacksCount[COLOR_NB]; - }; - - - // Evaluation::initialize() computes king and pawn attacks, and the king ring - // bitboard for a given color. This is done at the beginning of the evaluation. - - template template - void Evaluation::initialize() { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB); - - const Square ksq = pos.square(Us); - - Bitboard dblAttackByPawn = pawn_double_attacks_bb(pos.pieces(Us, PAWN)); - - // Find our pawns that are blocked or on the first two ranks - Bitboard b = pos.pieces(Us, PAWN) & (shift(pos.pieces()) | LowRanks); - - // Squares occupied by those pawns, by our king or queen, by blockers to attacks on our king - // or controlled by enemy pawns are excluded from the mobility area. - mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them)); - - // Initialize attackedBy[] for king and pawns - attackedBy[Us][KING] = attacks_bb(ksq); - attackedBy[Us][PAWN] = pe->pawn_attacks(Us); - attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN]; - attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]); - - // Init our king safety tables - Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G), - std::clamp(rank_of(ksq), RANK_2, RANK_7)); - kingRing[Us] = attacks_bb(s) | s; - - kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them)); - kingAttacksCount[Them] = kingAttackersWeight[Them] = 0; - - // Remove from kingRing[] the squares defended by two pawns - kingRing[Us] &= ~dblAttackByPawn; - } - - - // Evaluation::pieces() scores pieces of a given color and type - - template template - Score Evaluation::pieces() { - - constexpr Color Them = ~Us; - [[maybe_unused]] constexpr Direction Down = -pawn_push(Us); - [[maybe_unused]] constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB - : Rank5BB | Rank4BB | Rank3BB); - Bitboard b1 = pos.pieces(Us, Pt); - Bitboard b, bb; - Score score = SCORE_ZERO; - - attackedBy[Us][Pt] = 0; - - while (b1) - { - Square s = pop_lsb(b1); - - // Find attacked squares, including x-ray attacks for bishops and rooks - b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) - : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) - : attacks_bb(s, pos.pieces()); - - if (pos.blockers_for_king(Us) & s) - b &= line_bb(pos.square(Us), s); - - attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; - attackedBy[Us][Pt] |= b; - attackedBy[Us][ALL_PIECES] |= b; - - if (b & kingRing[Them]) - { - kingAttackersCount[Us]++; - kingAttackersWeight[Us] += KingAttackWeights[Pt]; - kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); - } - - else if (Pt == ROOK && (file_bb(s) & kingRing[Them])) - score += RookOnKingRing; - - else if (Pt == BISHOP && (attacks_bb(s, pos.pieces(PAWN)) & kingRing[Them])) - score += BishopOnKingRing; - - int mob = popcount(b & mobilityArea[Us]); - mobility[Us] += MobilityBonus[Pt - 2][mob]; - - if constexpr (Pt == BISHOP || Pt == KNIGHT) - { - // Bonus if the piece is on an outpost square or can reach one - // Bonus for knights (UncontestedOutpost) if few relevant targets - bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) - & ~pe->pawn_attacks_span(Them); - Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); - - if ( Pt == KNIGHT - && bb & s & ~CenterFiles // on a side outpost - && !(b & targets) // no relevant attacks - && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) - score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide)); - else if (bb & s) - score += Outpost[Pt == BISHOP]; - else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) - score += ReachableOutpost; - - // Bonus for a knight or bishop shielded by pawn - if (shift(pos.pieces(PAWN)) & s) - score += MinorBehindPawn; - - // Penalty if the piece is far from the king - score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); - - if constexpr (Pt == BISHOP) - { - // Penalty according to the number of our pawns on the same color square as the - // bishop, bigger when the center files are blocked with pawns and smaller - // when the bishop is outside the pawn chain. - Bitboard blocked = pos.pieces(Us, PAWN) & shift(pos.pieces()); - - score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s) - * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles)); - - // Penalty for all enemy pawns x-rayed - score -= BishopXRayPawns * popcount(attacks_bb(s) & pos.pieces(Them, PAWN)); - - // Bonus for bishop on a long diagonal which can "see" both center squares - if (more_than_one(attacks_bb(s, pos.pieces(PAWN)) & Center)) - score += LongDiagonalBishop; - - // An important Chess960 pattern: a cornered bishop blocked by a friendly - // pawn diagonally in front of it is a very serious problem, especially - // when that pawn is also blocked. - if ( pos.is_chess960() - && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) - { - Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); - if (pos.piece_on(s + d) == make_piece(Us, PAWN)) - score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop) - : 3 * make_score(CorneredBishop, CorneredBishop); - } - } - } - - if constexpr (Pt == ROOK) - { - // Bonuses for rook on a (semi-)open or closed file - if (pos.is_on_semiopen_file(Us, s)) - { - score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)]; - } - else - { - // If our pawn on this file is blocked, increase penalty - if ( pos.pieces(Us, PAWN) - & shift(pos.pieces()) - & file_bb(s)) - { - score -= RookOnClosedFile; - } - - // Penalty when trapped by the king, even more if the king cannot castle - if (mob <= 3) - { - File kf = file_of(pos.square(Us)); - if ((kf < FILE_E) == (file_of(s) < kf)) - score -= TrappedRook * (1 + !pos.castling_rights(Us)); - } - } - } - - if constexpr (Pt == QUEEN) - { - // Penalty if any relative pin or discovered attack against the queen - Bitboard queenPinners; - if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners)) - score -= WeakQueen; - } - } - if constexpr (T) - Trace::add(Pt, Us, score); - - return score; - } - - - // Evaluation::king() assigns bonuses and penalties to a king of a given color - - template template - Score Evaluation::king() const { - - constexpr Color Them = ~Us; - constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB - : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); - - Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0; - Bitboard rookChecks, queenChecks, bishopChecks, knightChecks; - int kingDanger = 0; - const Square ksq = pos.square(Us); - - // Init the score with king shelter and enemy pawns storm - Score score = pe->king_safety(pos); - - // Attacked squares defended at most once by our queen or king - weak = attackedBy[Them][ALL_PIECES] - & ~attackedBy2[Us] - & (~attackedBy[Us][ALL_PIECES] | attackedBy[Us][KING] | attackedBy[Us][QUEEN]); - - // Analyse the safe enemy's checks which are possible on next move - safe = ~pos.pieces(Them); - safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); - - b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); - b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); - - // Enemy rooks checks - rookChecks = b1 & attackedBy[Them][ROOK] & safe; - if (rookChecks) - kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)]; - else - unsafeChecks |= b1 & attackedBy[Them][ROOK]; - - // Enemy queen safe checks: count them only if the checks are from squares from - // which opponent cannot give a rook check, because rook checks are more valuable. - queenChecks = (b1 | b2) & attackedBy[Them][QUEEN] & safe - & ~(attackedBy[Us][QUEEN] | rookChecks); - if (queenChecks) - kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)]; - - // Enemy bishops checks: count them only if they are from squares from which - // opponent cannot give a queen check, because queen checks are more valuable. - bishopChecks = b2 & attackedBy[Them][BISHOP] & safe - & ~queenChecks; - if (bishopChecks) - kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)]; - - else - unsafeChecks |= b2 & attackedBy[Them][BISHOP]; - - // Enemy knights checks - knightChecks = attacks_bb(ksq) & attackedBy[Them][KNIGHT]; - if (knightChecks & safe) - kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)]; - else - unsafeChecks |= knightChecks; - - // Find the squares that opponent attacks in our king flank, the squares - // which they attack twice in that flank, and the squares that we defend. - b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; - b2 = b1 & attackedBy2[Them]; - b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; - - int kingFlankAttack = popcount(b1) + popcount(b2); - int kingFlankDefense = popcount(b3); - - kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo) - + 183 * popcount(kingRing[Us] & weak) // (~15 Elo) - + 148 * popcount(unsafeChecks) // (~4 Elo) - + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo) - + 69 * kingAttacksCount[Them] // (~0.5 Elo) - + 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo) - + mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo) - - 873 * !pos.count(Them) // (~24 Elo) - - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo) - - 6 * mg_value(score) / 8 // (~8 Elo) - - 4 * kingFlankDefense // (~5 Elo) - + 37; // (~0.5 Elo) - - // Transform the kingDanger units into a Score, and subtract it from the evaluation - if (kingDanger > 100) - score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); - - // Penalty when our king is on a pawnless flank - if (!(pos.pieces(PAWN) & KingFlank[file_of(ksq)])) - score -= PawnlessFlank; - - // Penalty if king flank is under attack, potentially moving toward the king - score -= FlankAttacks * kingFlankAttack; - - if constexpr (T) - Trace::add(KING, Us, score); - - return score; - } - - - // Evaluation::threats() assigns bonuses according to the types of the - // attacking and the attacked pieces. - - template template - Score Evaluation::threats() const { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); - - Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe; - Score score = SCORE_ZERO; - - // Non-pawn enemies - nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN); - - // Squares strongly protected by the enemy, either because they defend the - // square with a pawn, or because they defend the square twice and we don't. - stronglyProtected = attackedBy[Them][PAWN] - | (attackedBy2[Them] & ~attackedBy2[Us]); - - // Non-pawn enemies, strongly protected - defended = nonPawnEnemies & stronglyProtected; - - // Enemies not strongly protected and under our attack - weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES]; - - // Bonus according to the kind of attacking pieces - if (defended | weak) - { - b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); - while (b) - score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))]; - - b = weak & attackedBy[Us][ROOK]; - while (b) - score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))]; - - if (weak & attackedBy[Us][KING]) - score += ThreatByKing; - - b = ~attackedBy[Them][ALL_PIECES] - | (nonPawnEnemies & attackedBy2[Us]); - score += Hanging * popcount(weak & b); - - // Additional bonus if weak piece is only protected by a queen - score += WeakQueenProtection * popcount(weak & attackedBy[Them][QUEEN]); - } - - // Bonus for restricting their piece moves - b = attackedBy[Them][ALL_PIECES] - & ~stronglyProtected - & attackedBy[Us][ALL_PIECES]; - score += RestrictedPiece * popcount(b); - - // Protected or unattacked squares - safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES]; - - // Bonus for attacking enemy pieces with our relatively safe pawns - b = pos.pieces(Us, PAWN) & safe; - b = pawn_attacks_bb(b) & nonPawnEnemies; - score += ThreatBySafePawn * popcount(b); - - // Find squares where our pawns can push on the next move - b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); - b |= shift(b & TRank3BB) & ~pos.pieces(); - - // Keep only the squares which are relatively safe - b &= ~attackedBy[Them][PAWN] & safe; - - // Bonus for safe pawn threats on the next move - b = pawn_attacks_bb(b) & nonPawnEnemies; - score += ThreatByPawnPush * popcount(b); - - // Bonus for threats on the next moves against enemy queen - if (pos.count(Them) == 1) - { - bool queenImbalance = pos.count() == 1; - - Square s = pos.square(Them); - safe = mobilityArea[Us] - & ~pos.pieces(Us, PAWN) - & ~stronglyProtected; - - b = attackedBy[Us][KNIGHT] & attacks_bb(s); - - score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance); - - b = (attackedBy[Us][BISHOP] & attacks_bb(s, pos.pieces())) - | (attackedBy[Us][ROOK ] & attacks_bb(s, pos.pieces())); - - score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); - } - - if constexpr (T) - Trace::add(THREAT, Us, score); - - return score; - } - - // Evaluation::passed() evaluates the passed pawns and candidate passed - // pawns of the given color. - - template template - Score Evaluation::passed() const { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - - auto king_proximity = [&](Color c, Square s) { - return std::min(distance(pos.square(c), s), 5); - }; - - Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers; - Score score = SCORE_ZERO; - - b = pe->passed_pawns(Us); - - blockedPassers = b & shift(pos.pieces(Them, PAWN)); - if (blockedPassers) - { - helpers = shift(pos.pieces(Us, PAWN)) - & ~pos.pieces(Them) - & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]); - - // Remove blocked candidate passers that don't have help to pass - b &= ~blockedPassers - | shift(helpers) - | shift(helpers); - } - - while (b) - { - Square s = pop_lsb(b); - - assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); - - int r = relative_rank(Us, s); - - Score bonus = PassedRank[r]; - - if (r > RANK_3) - { - int w = 5 * r - 13; - Square blockSq = s + Up; - - // Adjust bonus based on the king's proximity - bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4 - - king_proximity(Us, blockSq) * 2) * w); - - // If blockSq is not the queening square then consider also a second push - if (r != RANK_7) - bonus -= make_score(0, king_proximity(Us, blockSq + Up) * w); - - // If the pawn is free to advance, then increase the bonus - if (pos.empty(blockSq)) - { - squaresToQueen = forward_file_bb(Us, s); - unsafeSquares = passed_pawn_span(Us, s); - - bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); - - if (!(pos.pieces(Them) & bb)) - unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); - - // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus. - // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus. - // Otherwise assign a smaller bonus if the path to queen is not attacked - // and even smaller bonus if it is attacked but block square is not. - int k = !unsafeSquares ? 36 : - !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 : - !(unsafeSquares & squaresToQueen) ? 17 : - !(unsafeSquares & blockSq) ? 7 : - 0 ; - - // Assign a larger bonus if the block square is defended - if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq)) - k += 5; - - bonus += make_score(k * w, k * w); - } - } // r > RANK_3 - - score += bonus - PassedFile * edge_distance(file_of(s)); - } - - if constexpr (T) - Trace::add(PASSED, Us, score); - - return score; - } - - - // Evaluation::space() computes a space evaluation for a given side, aiming to improve game - // play in the opening. It is based on the number of safe squares on the four central files - // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice. - // Finally, the space bonus is multiplied by a weight which decreases according to occupancy. - - template template - Score Evaluation::space() const { - - // Early exit if, for example, both queens or 6 minor pieces have been exchanged - if (pos.non_pawn_material() < SpaceThreshold) - return SCORE_ZERO; - - constexpr Color Them = ~Us; - constexpr Direction Down = -pawn_push(Us); - constexpr Bitboard SpaceMask = - Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB) - : CenterFiles & (Rank7BB | Rank6BB | Rank5BB); - - // Find the available squares for our pieces inside the area defined by SpaceMask - Bitboard safe = SpaceMask - & ~pos.pieces(Us, PAWN) - & ~attackedBy[Them][PAWN]; - - // Find all squares which are at most three squares behind some friendly pawn - Bitboard behind = pos.pieces(Us, PAWN); - behind |= shift(behind); - behind |= shift(behind); - - // Compute space score based on the number of safe squares and number of our pieces - // increased with number of total blocked pawns in position. - int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); - int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); - Score score = make_score(bonus * weight * weight / 16, 0); - - if constexpr (T) - Trace::add(SPACE, Us, score); - - return score; - } - - - // Evaluation::winnable() adjusts the midgame and endgame score components, based on - // the known attacking/defending status of the players. The final value is derived - // by interpolation from the midgame and endgame values. - - template - Value Evaluation::winnable(Score score) const { - - int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) - + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); - - bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) - && (pos.pieces(PAWN) & KingSide); - - bool almostUnwinnable = outflanking < 0 - && !pawnsOnBothFlanks; - - bool infiltration = rank_of(pos.square(WHITE)) > RANK_4 - || rank_of(pos.square(BLACK)) < RANK_5; - - // Compute the initiative bonus for the attacking side - int complexity = 9 * pe->passed_count() - + 12 * pos.count() - + 9 * outflanking - + 21 * pawnsOnBothFlanks - + 24 * infiltration - + 51 * !pos.non_pawn_material() - - 43 * almostUnwinnable - -110 ; - - Value mg = mg_value(score); - Value eg = eg_value(score); - - // Now apply the bonus: note that we find the attacking side by extracting the - // sign of the midgame or endgame values, and that we carefully cap the bonus - // so that the midgame and endgame scores do not change sign after the bonus. - int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0); - int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg)); - - mg += u; - eg += v; - - // Compute the scale factor for the winning side - Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; - int sf = me->scale_factor(pos, strongSide); - - // If scale factor is not already specific, scale up/down via general heuristics - if (sf == SCALE_FACTOR_NORMAL) - { - if (pos.opposite_bishops()) - { - // For pure opposite colored bishops endgames use scale factor - // based on the number of passed pawns of the strong side. - if ( pos.non_pawn_material(WHITE) == BishopValueMg - && pos.non_pawn_material(BLACK) == BishopValueMg) - sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); - // For every other opposite colored bishops endgames use scale factor - // based on the number of all pieces of the strong side. - else - sf = 22 + 3 * pos.count(strongSide); - } - // For rook endgames with strong side not having overwhelming pawn number advantage - // and its pawns being on one flank and weak side protecting its pieces with a king - // use lower scale factor. - else if ( pos.non_pawn_material(WHITE) == RookValueMg - && pos.non_pawn_material(BLACK) == RookValueMg - && pos.count(strongSide) - pos.count(~strongSide) <= 1 - && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) - && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) - sf = 36; - // For queen vs no queen endgames use scale factor - // based on number of minors of side that doesn't have queen. - else if (pos.count() == 1) - sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) - : pos.count(WHITE) + pos.count(WHITE)); - // In every other case use scale factor based on - // the number of pawns of the strong side reduced if pawns are on a single flank. - else - sf = std::min(sf, 36 + 7 * pos.count(strongSide)) - 4 * !pawnsOnBothFlanks; - - // Reduce scale factor in case of pawns being on a single flank - sf -= 4 * !pawnsOnBothFlanks; - } - - // Interpolate between the middlegame and (scaled by 'sf') endgame score - v = mg * int(me->game_phase()) - + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; - v /= PHASE_MIDGAME; - - if constexpr (T) - { - Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); - Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); - } - - return Value(v); - } - - - // Evaluation::value() is the main function of the class. It computes the various - // parts of the evaluation and returns the value of the position from the point - // of view of the side to move. - - template - Value Evaluation::value() { - - assert(!pos.checkers()); - - // Probe the material hash table - me = Material::probe(pos); - - // If we have a specialized evaluation function for the current material - // configuration, call it and return. - if (me->specialized_eval_exists()) - return me->evaluate(pos); - - // Initialize score by reading the incrementally updated scores included in - // the position object (material + piece square tables) and the material - // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + me->imbalance(); - - // Probe the pawn hash table - pe = Pawns::probe(pos); - score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK); - - // Early exit if score is high - auto lazy_skip = [&](Value lazyThreshold) { - return abs(mg_value(score) + eg_value(score)) > lazyThreshold - + std::abs(pos.this_thread()->bestValue) * 5 / 4 - + pos.non_pawn_material() / 32; - }; - - if (lazy_skip(LazyThreshold1)) - goto make_v; - - // Main evaluation begins here - initialize(); - initialize(); - - // Pieces evaluated first (also populates attackedBy, attackedBy2). - // Note that the order of evaluation of the terms is left unspecified. - score += pieces() - pieces() - + pieces() - pieces() - + pieces() - pieces() - + pieces() - pieces(); - - score += mobility[WHITE] - mobility[BLACK]; - - // More complex interactions that require fully populated attack bitboards - score += king< WHITE>() - king< BLACK>() - + passed< WHITE>() - passed< BLACK>(); - - if (lazy_skip(LazyThreshold2)) - goto make_v; - - score += threats() - threats() - + space< WHITE>() - space< BLACK>(); - -make_v: - // Derive single value from mg and eg parts of score - Value v = winnable(score); - - // In case of tracing add all remaining individual evaluation terms - if constexpr (T) - { - Trace::add(MATERIAL, pos.psq_score()); - Trace::add(IMBALANCE, me->imbalance()); - Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK)); - Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); - } - - // Evaluation grain - v = (v / 16) * 16; - - // Side to move point of view - v = (pos.side_to_move() == WHITE ? v : -v); - - return v; - } - -} // namespace Eval - - /// evaluate() is the evaluator for the outer world. It returns a static /// evaluation of the position from the point of view of the side to move. @@ -1053,27 +147,17 @@ Value Eval::evaluate(const Position& pos) { Value v; Value psq = pos.psq_eg_stm(); - // We use the much less accurate but faster Classical eval when the NNUE - // option is set to false. Otherwise we use the NNUE eval unless the - // PSQ advantage is decisive. (~4 Elo at STC, 1 Elo at LTC) - bool useClassical = !useNNUE || abs(psq) > 2048; + int nnueComplexity; + int npm = pos.non_pawn_material() / 64; - if (useClassical) - v = Evaluation(pos).value(); - else - { - int nnueComplexity; - int npm = pos.non_pawn_material() / 64; + Color stm = pos.side_to_move(); + Value optimism = pos.this_thread()->optimism[stm]; - Color stm = pos.side_to_move(); - Value optimism = pos.this_thread()->optimism[stm]; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - - // Blend optimism with nnue complexity and (semi)classical complexity - optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; - v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; - } + // Blend optimism with nnue complexity and (semi)classical complexity + optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; + v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; // Damp down the evaluation linearly when shuffling v = v * (200 - pos.rule50_count()) / 214; @@ -1094,62 +178,26 @@ std::string Eval::trace(Position& pos) { if (pos.checkers()) return "Final evaluation: none (in check)"; - std::stringstream ss; - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - - Value v; - - std::memset(scores, 0, sizeof(scores)); - // Reset any global variable used in eval pos.this_thread()->bestValue = VALUE_ZERO; pos.this_thread()->optimism[WHITE] = VALUE_ZERO; pos.this_thread()->optimism[BLACK] = VALUE_ZERO; - v = Evaluation(pos).value(); - - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) - << " Contributing terms for the classical eval:\n" - << "+------------+-------------+-------------+-------------+\n" - << "| Term | White | Black | Total |\n" - << "| | MG EG | MG EG | MG EG |\n" - << "+------------+-------------+-------------+-------------+\n" - << "| Material | " << Term(MATERIAL) - << "| Imbalance | " << Term(IMBALANCE) - << "| Pawns | " << Term(PAWN) - << "| Knights | " << Term(KNIGHT) - << "| Bishops | " << Term(BISHOP) - << "| Rooks | " << Term(ROOK) - << "| Queens | " << Term(QUEEN) - << "| Mobility | " << Term(MOBILITY) - << "|King safety | " << Term(KING) - << "| Threats | " << Term(THREAT) - << "| Passed | " << Term(PASSED) - << "| Space | " << Term(SPACE) - << "| Winnable | " << Term(WINNABLE) - << "+------------+-------------+-------------+-------------+\n" - << "| Total | " << Term(TOTAL) - << "+------------+-------------+-------------+-------------+\n"; - - if (Eval::useNNUE) - ss << '\n' << NNUE::trace(pos) << '\n'; + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + Value v; + v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n"; - if (Eval::useNNUE) - { - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; - } + ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; v = evaluate(pos); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << to_cp(v) << " (white side)"; - if (Eval::useNNUE) - ss << " [with scaled NNUE, hybrid, ...]"; + ss << " [with scaled NNUE, ...]"; ss << "\n"; return ss.str(); diff --git a/src/main.cpp b/src/main.cpp index c40e0fa3..593408f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,6 @@ #include #include "bitboard.h" -#include "endgame.h" #include "position.h" #include "psqt.h" #include "search.h" @@ -40,8 +39,6 @@ int main(int argc, char* argv[]) { PSQT::init(); Bitboards::init(); Position::init(); - Bitbases::init(); - Endgames::init(); Threads.set(size_t(Options["Threads"])); Search::clear(); // After threads are up Eval::NNUE::init(); diff --git a/src/material.cpp b/src/material.cpp deleted file mode 100644 index 7102f879..00000000 --- a/src/material.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include // For std::memset - -#include "material.h" -#include "thread.h" - -using namespace std; - -namespace Stockfish { - -namespace { - #define S(mg, eg) make_score(mg, eg) - - // Polynomial material imbalance parameters - - // One Score parameter for each pair (our piece, another of our pieces) - constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = { - // OUR PIECE 2 - // bishop pair pawn knight bishop rook queen - {S(1419, 1455) }, // Bishop pair - {S( 101, 28), S( 37, 39) }, // Pawn - {S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1 - {S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop - {S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook - {S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen - }; - - // One Score parameter for each pair (our piece, their piece) - constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = { - // THEIR PIECE - // bishop pair pawn knight bishop rook queen - { }, // Bishop pair - {S( 33, 30) }, // Pawn - {S( 46, 18), S(106, 84) }, // Knight OUR PIECE - {S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop - {S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook - {S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen - }; - - #undef S - - // Endgame evaluation and scaling functions are accessed directly and not through - // the function maps because they correspond to more than one material hash key. - Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; - - Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; - - // Helper used to detect a given material distribution - bool is_KXK(const Position& pos, Color us) { - return !more_than_one(pos.pieces(~us)) - && pos.non_pawn_material(us) >= RookValueMg; - } - - bool is_KBPsK(const Position& pos, Color us) { - return pos.non_pawn_material(us) == BishopValueMg - && pos.count(us) >= 1; - } - - bool is_KQKRPs(const Position& pos, Color us) { - return !pos.count(us) - && pos.non_pawn_material(us) == QueenValueMg - && pos.count(~us) == 1 - && pos.count(~us) >= 1; - } - - - /// imbalance() calculates the imbalance by comparing the piece count of each - /// piece type for both colors. - - template - Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) { - - constexpr Color Them = ~Us; - - Score bonus = SCORE_ZERO; - - // Second-degree polynomial material imbalance, by Tord Romstad - for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) - { - if (!pieceCount[Us][pt1]) - continue; - - int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1]; - - for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2) - v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2] - + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2]; - - bonus += pieceCount[Us][pt1] * v; - } - - return bonus; - } - -} // namespace - -namespace Material { - - -/// Material::probe() looks up the current position's material configuration in -/// the material hash table. It returns a pointer to the Entry if the position -/// is found. Otherwise a new Entry is computed and stored there, so we don't -/// have to recompute all when the same material configuration occurs again. - -Entry* probe(const Position& pos) { - - Key key = pos.material_key(); - Entry* e = pos.this_thread()->materialTable[key]; - - if (e->key == key) - return e; - - std::memset(e, 0, sizeof(Entry)); - e->key = key; - e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; - - Value npm_w = pos.non_pawn_material(WHITE); - Value npm_b = pos.non_pawn_material(BLACK); - Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit); - - // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] - e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); - - // Let's look if we have a specialized evaluation function for this particular - // material configuration. Firstly we look for a fixed configuration one, then - // for a generic one if the previous search failed. - if ((e->evaluationFunction = Endgames::probe(key)) != nullptr) - return e; - - for (Color c : { WHITE, BLACK }) - if (is_KXK(pos, c)) - { - e->evaluationFunction = &EvaluateKXK[c]; - return e; - } - - // OK, we didn't find any special evaluation function for the current material - // configuration. Is there a suitable specialized scaling function? - const auto* sf = Endgames::probe(key); - - if (sf) - { - e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned - return e; - } - - // We didn't find any specialized scaling function, so fall back on generic - // ones that refer to more than one material distribution. Note that in this - // case we don't return after setting the function. - for (Color c : { WHITE, BLACK }) - { - if (is_KBPsK(pos, c)) - e->scalingFunction[c] = &ScaleKBPsK[c]; - - else if (is_KQKRPs(pos, c)) - e->scalingFunction[c] = &ScaleKQKRPs[c]; - } - - if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board - { - if (!pos.count(BLACK)) - { - assert(pos.count(WHITE) >= 2); - - e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; - } - else if (!pos.count(WHITE)) - { - assert(pos.count(BLACK) >= 2); - - e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; - } - else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) - { - // This is a special case because we set scaling functions - // for both colors instead of only one. - e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; - e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; - } - } - - // Zero or just one pawn makes it difficult to win, even with a small material - // advantage. This catches some trivial draws like KK, KBK and KNK and gives a - // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). - if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) - e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW : - npm_b <= BishopValueMg ? 4 : 14); - - if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) - e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW : - npm_w <= BishopValueMg ? 4 : 14); - - // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder - // for the bishop pair "extended piece", which allows us to be more flexible - // in defining bishop pair bonuses. - const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = { - { pos.count(WHITE) > 1, pos.count(WHITE), pos.count(WHITE), - pos.count(WHITE) , pos.count(WHITE), pos.count(WHITE) }, - { pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK), - pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } }; - - e->score = (imbalance(pieceCount) - imbalance(pieceCount)) / 16; - return e; -} - -} // namespace Material - -} // namespace Stockfish diff --git a/src/material.h b/src/material.h deleted file mode 100644 index 9acf78f5..00000000 --- a/src/material.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef MATERIAL_H_INCLUDED -#define MATERIAL_H_INCLUDED - -#include "endgame.h" -#include "misc.h" -#include "position.h" -#include "types.h" - -namespace Stockfish::Material { - -/// Material::Entry contains various information about a material configuration. -/// It contains a material imbalance evaluation, a function pointer to a special -/// endgame evaluation function (which in most cases is nullptr, meaning that the -/// standard evaluation function will be used), and scale factors. -/// -/// The scale factors are used to scale the evaluation score up or down. For -/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4, -/// which will result in scores of absolute value less than one pawn. - -struct Entry { - - Score imbalance() const { return score; } - Phase game_phase() const { return (Phase)gamePhase; } - bool specialized_eval_exists() const { return evaluationFunction != nullptr; } - Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } - - // scale_factor() takes a position and a color as input and returns a scale factor - // for the given color. We have to provide the position in addition to the color - // because the scale factor may also be a function which should be applied to - // the position. For instance, in KBP vs K endgames, the scaling function looks - // for rook pawns and wrong-colored bishops. - ScaleFactor scale_factor(const Position& pos, Color c) const { - ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos) - : SCALE_FACTOR_NONE; - return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]); - } - - Key key; - const EndgameBase* evaluationFunction; - const EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each - // side (e.g. KPKP, KBPsK) - Score score; - int16_t gamePhase; - uint8_t factor[COLOR_NB]; -}; - -using Table = HashTable; - -Entry* probe(const Position& pos); - -} // namespace Stockfish::Material - -#endif // #ifndef MATERIAL_H_INCLUDED diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 329adfda..a1a90023 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -137,8 +137,7 @@ namespace Stockfish::Eval::NNUE { } void hint_common_parent_position(const Position& pos) { - if (Eval::useNNUE) - featureTransformer->hint_common_access(pos); + featureTransformer->hint_common_access(pos); } // Evaluation function. Perform differential calculation. diff --git a/src/pawns.cpp b/src/pawns.cpp deleted file mode 100644 index 0ccafd9e..00000000 --- a/src/pawns.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include - -#include "bitboard.h" -#include "pawns.h" -#include "position.h" -#include "thread.h" - -namespace Stockfish { - -namespace { - - #define V Value - #define S(mg, eg) make_score(mg, eg) - - // Pawn penalties - constexpr Score Backward = S( 6, 19); - constexpr Score Doubled = S(11, 51); - constexpr Score DoubledEarly = S(17, 7); - constexpr Score Isolated = S( 1, 20); - constexpr Score WeakLever = S( 2, 57); - constexpr Score WeakUnopposed = S(15, 18); - - // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[2] = { S(-19, -8), S(-7, 3) }; - - constexpr Score BlockedStorm[RANK_NB] = { - S(0, 0), S(0, 0), S(64, 75), S(-3, 14), S(-12, 19), S(-7, 4), S(-10, 5) - }; - - // Connected pawn bonus - constexpr int Connected[RANK_NB] = { 0, 3, 7, 7, 15, 54, 86 }; - - // Strength of pawn shelter for our king by [distance from edge][rank]. - // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. - constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = { - { V(-2), V(85), V(95), V(53), V(39), V(23), V(25) }, - { V(-55), V(64), V(32), V(-55), V(-30), V(-11), V(-61) }, - { V(-11), V(75), V(19), V(-6), V(26), V(9), V(-47) }, - { V(-41), V(-11), V(-27), V(-58), V(-42), V(-66), V(-163) } - }; - - // Danger of enemy pawns moving toward our king by [distance from edge][rank]. - // RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn - // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn - // on edge, likely blocked by our king. - constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = { - { V(94), V(-280), V(-170), V(90), V(59), V(47), V(53) }, - { V(43), V(-17), V(128), V(39), V(26), V(-17), V(15) }, - { V(-9), V(62), V(170), V(34), V(-5), V(-20), V(-11) }, - { V(-27), V(-19), V(106), V(10), V(2), V(-13), V(-24) } - }; - - - // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties - // for king when the king is on a semi-open or open file. - constexpr Score KingOnFile[2][2] = {{ S(-18,11), S(-6,-3) }, - { S( 0, 0), S( 5,-4) }}; - - #undef S - #undef V - - - /// evaluate() calculates a score for the static pawn structure of the given position. - /// We cannot use the location of pieces or king in this function, as the evaluation - /// of the pawn structure will be stored in a small cache for speed reasons, and will - /// be re-used even when the pieces have moved. - - template - Score evaluate(const Position& pos, Pawns::Entry* e) { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - - Bitboard neighbours, stoppers, support, phalanx, opposed; - Bitboard lever, leverPush, blocked; - Square s; - bool backward, passed, doubled; - Score score = SCORE_ZERO; - Bitboard b = pos.pieces(Us, PAWN); - - Bitboard ourPawns = pos.pieces( Us, PAWN); - Bitboard theirPawns = pos.pieces(Them, PAWN); - - Bitboard doubleAttackThem = pawn_double_attacks_bb(theirPawns); - - e->passedPawns[Us] = 0; - e->kingSquares[Us] = SQ_NONE; - e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb(ourPawns); - e->blockedCount += popcount(shift(ourPawns) & (theirPawns | doubleAttackThem)); - - // Loop through all pawns of the current color and score each pawn - while (b) - { - s = pop_lsb(b); - - assert(pos.piece_on(s) == make_piece(Us, PAWN)); - - Rank r = relative_rank(Us, s); - - // Flag the pawn - opposed = theirPawns & forward_file_bb(Us, s); - blocked = theirPawns & (s + Up); - stoppers = theirPawns & passed_pawn_span(Us, s); - lever = theirPawns & pawn_attacks_bb(Us, s); - leverPush = theirPawns & pawn_attacks_bb(Us, s + Up); - doubled = ourPawns & (s - Up); - neighbours = ourPawns & adjacent_files_bb(s); - phalanx = neighbours & rank_bb(s); - support = neighbours & rank_bb(s - Up); - - if (doubled) - { - // Additional doubled penalty if none of their pawns is fixed - if (!(ourPawns & shift(theirPawns | pawn_attacks_bb(theirPawns)))) - score -= DoubledEarly; - } - - // A pawn is backward when it is behind all pawns of the same color on - // the adjacent files and cannot safely advance. - backward = !(neighbours & forward_ranks_bb(Them, s + Up)) - && (leverPush | blocked); - - // Compute additional span if pawn is not backward nor blocked - if (!backward && !blocked) - e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); - - // A pawn is passed if one of the three following conditions is true: - // (a) there is no stoppers except some levers - // (b) the only stoppers are the leverPush, but we outnumber them - // (c) there is only one front stopper which can be levered. - // (Refined in Evaluation::passed) - passed = !(stoppers ^ lever) - || ( !(stoppers ^ leverPush) - && popcount(phalanx) >= popcount(leverPush)) - || ( stoppers == blocked && r >= RANK_5 - && (shift(support) & ~(theirPawns | doubleAttackThem))); - - passed &= !(forward_file_bb(Us, s) & ourPawns); - - // Passed pawns will be properly scored later in evaluation when we have - // full attack info. - if (passed) - e->passedPawns[Us] |= s; - - // Score this pawn - if (support | phalanx) - { - int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) - + 22 * popcount(support); - - score += make_score(v, v * (r - 2) / 4); - } - - else if (!neighbours) - { - if ( opposed - && (ourPawns & forward_file_bb(Them, s)) - && !(theirPawns & adjacent_files_bb(s))) - score -= Doubled; - else - score -= Isolated - + WeakUnopposed * !opposed; - } - - else if (backward) - score -= Backward - + WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s); - - if (!support) - score -= Doubled * doubled - + WeakLever * more_than_one(lever); - - if (blocked && r >= RANK_5) - score += BlockedPawn[r - RANK_5]; - } - - return score; - } - -} // namespace - -namespace Pawns { - - -/// Pawns::probe() looks up the current position's pawns configuration in -/// the pawns hash table. It returns a pointer to the Entry if the position -/// is found. Otherwise a new Entry is computed and stored there, so we don't -/// have to recompute all when the same pawns configuration occurs again. - -Entry* probe(const Position& pos) { - - Key key = pos.pawn_key(); - Entry* e = pos.this_thread()->pawnsTable[key]; - - if (e->key == key) - return e; - - e->key = key; - e->blockedCount = 0; - e->scores[WHITE] = evaluate(pos, e); - e->scores[BLACK] = evaluate(pos, e); - - return e; -} - - -/// Entry::evaluate_shelter() calculates the shelter bonus and the storm -/// penalty for a king, looking at the king file and the two closest files. - -template -Score Entry::evaluate_shelter(const Position& pos, Square ksq) const { - - constexpr Color Them = ~Us; - - Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq); - Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them]; - Bitboard theirPawns = b & pos.pieces(Them); - - Score bonus = make_score(5, 5); - - File center = std::clamp(file_of(ksq), FILE_B, FILE_G); - for (File f = File(center - 1); f <= File(center + 1); ++f) - { - b = ourPawns & file_bb(f); - int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0; - - b = theirPawns & file_bb(f); - int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0; - - int d = edge_distance(f); - bonus += make_score(ShelterStrength[d][ourRank], 0); - - if (ourRank && (ourRank == theirRank - 1)) - bonus -= BlockedStorm[theirRank]; - else - bonus -= make_score(UnblockedStorm[d][theirRank], 0); - } - - // King On File - bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)]; - - return bonus; -} - - -/// Entry::do_king_safety() calculates a bonus for king safety. It is called only -/// when king square changes, which is about 20% of total king_safety() calls. - -template -Score Entry::do_king_safety(const Position& pos) { - - Square ksq = pos.square(Us); - kingSquares[Us] = ksq; - castlingRights[Us] = pos.castling_rights(Us); - auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); }; - - Score shelter = evaluate_shelter(pos, ksq); - - // If we can castle use the bonus after castling if it is bigger - - if (pos.can_castle(Us & KING_SIDE)) - shelter = std::max(shelter, evaluate_shelter(pos, relative_square(Us, SQ_G1)), compare); - - if (pos.can_castle(Us & QUEEN_SIDE)) - shelter = std::max(shelter, evaluate_shelter(pos, relative_square(Us, SQ_C1)), compare); - - // In endgame we like to bring our king near our closest pawn - Bitboard pawns = pos.pieces(Us, PAWN); - int minPawnDist = 6; - - if (pawns & attacks_bb(ksq)) - minPawnDist = 1; - else while (pawns) - minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns))); - - return shelter - make_score(0, 16 * minPawnDist); -} - -// Explicit template instantiation -template Score Entry::do_king_safety(const Position& pos); -template Score Entry::do_king_safety(const Position& pos); - -} // namespace Pawns - -} // namespace Stockfish diff --git a/src/pawns.h b/src/pawns.h deleted file mode 100644 index d20e7c2e..00000000 --- a/src/pawns.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef PAWNS_H_INCLUDED -#define PAWNS_H_INCLUDED - -#include "misc.h" -#include "position.h" -#include "types.h" - -namespace Stockfish::Pawns { - -/// Pawns::Entry contains various information about a pawn structure. A lookup -/// to the pawn hash table (performed by calling the probe function) returns a -/// pointer to an Entry object. - -struct Entry { - - Score pawn_score(Color c) const { return scores[c]; } - Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } - Bitboard passed_pawns(Color c) const { return passedPawns[c]; } - Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } - int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); } - int blocked_count() const { return blockedCount; } - - template - Score king_safety(const Position& pos) { - return kingSquares[Us] == pos.square(Us) && castlingRights[Us] == pos.castling_rights(Us) - ? kingSafety[Us] : (kingSafety[Us] = do_king_safety(pos)); - } - - template - Score do_king_safety(const Position& pos); - - template - Score evaluate_shelter(const Position& pos, Square ksq) const; - - Key key; - Score scores[COLOR_NB]; - Bitboard passedPawns[COLOR_NB]; - Bitboard pawnAttacks[COLOR_NB]; - Bitboard pawnAttacksSpan[COLOR_NB]; - Square kingSquares[COLOR_NB]; - Score kingSafety[COLOR_NB]; - int castlingRights[COLOR_NB]; - int blockedCount; -}; - -using Table = HashTable; - -Entry* probe(const Position& pos); - -} // namespace Stockfish::Pawns - -#endif // #ifndef PAWNS_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp index a052cf32..6ecc52f8 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -751,13 +751,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { else st->nonPawnMaterial[them] -= PieceValue[MG][captured]; - if (Eval::useNNUE) - { - dp.dirty_num = 2; // 1 piece moved, 1 piece captured - dp.piece[1] = captured; - dp.from[1] = capsq; - dp.to[1] = SQ_NONE; - } + dp.dirty_num = 2; // 1 piece moved, 1 piece captured + dp.piece[1] = captured; + dp.from[1] = capsq; + dp.to[1] = SQ_NONE; // Update board and piece lists remove_piece(capsq); @@ -765,7 +762,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; - prefetch(thisThread->materialTable[st->materialKey]); // Reset rule 50 counter st->rule50 = 0; @@ -792,12 +788,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Move the piece. The tricky Chess960 castling is handled earlier if (type_of(m) != CASTLING) { - if (Eval::useNNUE) - { - dp.piece[0] = pc; - dp.from[0] = from; - dp.to[0] = to; - } + dp.piece[0] = pc; + dp.from[0] = from; + dp.to[0] = to; move_piece(from, to); } @@ -823,15 +816,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { remove_piece(to); put_piece(promotion, to); - if (Eval::useNNUE) - { - // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE - dp.to[0] = SQ_NONE; - dp.piece[dp.dirty_num] = promotion; - dp.from[dp.dirty_num] = SQ_NONE; - dp.to[dp.dirty_num] = to; - dp.dirty_num++; - } + // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.piece[dp.dirty_num] = promotion; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = to; + dp.dirty_num++; // Update hash keys k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; @@ -961,7 +951,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); - if (Do && Eval::useNNUE) + if (Do) { auto& dp = st->dirtyPiece; dp.piece[0] = make_piece(us, KING); diff --git a/src/thread.h b/src/thread.h index 09bdb470..aa9db2f3 100644 --- a/src/thread.h +++ b/src/thread.h @@ -25,9 +25,7 @@ #include #include -#include "material.h" #include "movepick.h" -#include "pawns.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" @@ -57,8 +55,6 @@ public: void wait_for_search_finished(); size_t id() const { return idx; } - Pawns::Table pawnsTable; - Material::Table materialTable; size_t pvIdx, pvLast; std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; diff --git a/src/ucioption.cpp b/src/ucioption.cpp index f6342e5c..27f436d3 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -43,7 +43,6 @@ static void on_hash_size(const Option& o) { TT.resize(size_t(o)); } static void on_logger(const Option& o) { start_logger(o); } static void on_threads(const Option& o) { Threads.set(size_t(o)); } static void on_tb_path(const Option& o) { Tablebases::init(o); } -static void on_use_NNUE(const Option&) { Eval::NNUE::init(); } static void on_eval_file(const Option&) { Eval::NNUE::init(); } /// Our case insensitive less() function as required by UCI protocol @@ -79,7 +78,6 @@ void init(OptionsMap& o) { o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["Use NNUE"] << Option(true, on_use_NNUE); o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); } From f972947492c513b7c0c4046058354b44a9ae9f04 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Wed, 12 Jul 2023 19:46:33 +0200 Subject: [PATCH 332/678] Cleanup code after removal of classical evaluation This includes the following changes: - Remove declaration of removed global variable - Adapt string that mentions removed UCI option closes https://github.com/official-stockfish/Stockfish/pull/4675 No functional change --- src/evaluate.cpp | 2 +- src/evaluate.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 2ab4fa40..a1bcdd20 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -119,7 +119,7 @@ namespace Eval { { string msg1 = "Network evaluation parameters compatible with the engine must be available."; - string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; + string msg2 = "The network file " + eval_file + " was not loaded successfully."; string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); string msg5 = "The engine will be terminated now."; diff --git a/src/evaluate.h b/src/evaluate.h index abdbef90..586e3b81 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,6 @@ namespace Eval { std::string trace(Position& pos); Value evaluate(const Position& pos); - extern bool useNNUE; extern std::string currentEvalFileName; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue From 529d3be8e245c06b62a45525cb9325ed6e50b636 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 11 Jul 2023 22:19:48 -0700 Subject: [PATCH 333/678] More simplifications and cleanup in affine_transform_sparse_input.h closes https://github.com/official-stockfish/Stockfish/pull/4677 No functional change --- .../layers/affine_transform_sparse_input.h | 44 +++++-------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 3c7defcc..a5bea08e 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -34,43 +34,15 @@ */ namespace Stockfish::Eval::NNUE::Layers { -#if defined(__GNUC__) // GCC, Clang, ICC - - static inline IndexType lsb_(std::uint32_t b) { - assert(b); - return IndexType(__builtin_ctzl(b)); - } - -#elif defined(_MSC_VER) // MSVC - - static inline IndexType lsb_(std::uint32_t b) { - assert(b); - unsigned long idx; - _BitScanForward(&idx, b); - return (IndexType) idx; - } - -#else // Compiler is neither GCC nor MSVC compatible - -#error "Compiler not supported." - -#endif - #if defined(USE_SSSE3) alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ std::array, 256> v{}; - for (int i = 0; i < 256; ++i) + for (unsigned i = 0; i < 256; ++i) { - int j = i; - int k = 0; + std::uint64_t j = i, k = 0; while(j) - { - const IndexType lsbIndex = lsb_(std::uint32_t(j)); - j &= j - 1; - v[i][k] = lsbIndex; - ++k; - } + v[i][k++] = pop_lsb(j); } return v; }(); @@ -83,7 +55,11 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) #elif defined (USE_AVX2) using vec_t = __m256i; - #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif #elif defined (USE_SSSE3) using vec_t = __m128i; #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) @@ -97,8 +73,8 @@ namespace Stockfish::Eval::NNUE::Layers { const auto inputVector = reinterpret_cast(input); IndexType count = 0; - __m128i base = _mm_set1_epi16(0); - __m128i increment = _mm_set1_epi16(8); + __m128i base = _mm_setzero_si128(); + const __m128i increment = _mm_set1_epi16(8); for (IndexType i = 0; i < NumChunks; ++i) { // bitmask of nonzero values in this chunk From f5ab5832c6d02ec742b507bcb42181403a848bb9 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 12 Jul 2023 19:34:07 +0200 Subject: [PATCH 334/678] Generate binaries for more advanced architectures use intel's Software Development Emulator (SDE) in the actions that build the binaries. This allows for building on Windows and Linux binaries for - x86-64-avx512 - x86-64-vnni256 - x86-64-vnni512 (x86-64-avxvnni needs more recent gcc in the actions) also build x86-64-avx2 on macos. closes https://github.com/official-stockfish/Stockfish/pull/4679 No functional change --- .github/workflows/stockfish_binaries.yml | 26 +++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index f7669b47..fa330974 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -9,6 +9,7 @@ jobs: COMPILER: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} + SDE: ${{ matrix.config.sde }} NAME: ${{ matrix.config.simple_name }} BINARY: ${{ matrix.binaries }} strategy: @@ -21,6 +22,7 @@ jobs: comp: gcc shell: bash archive_ext: tar + sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future - name: MacOS 12 Apple Clang os: macos-12 simple_name: macos @@ -37,17 +39,28 @@ jobs: msys_env: x86_64-gcc shell: msys2 {0} ext: .exe + sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future archive_ext: zip binaries: - x86-64 - x86-64-modern - x86-64-avx2 - x86-64-bmi2 + # - x86-64-avxvnni needs more recent gcc + - x86-64-avx512 + - x86-64-vnni256 + - x86-64-vnni512 exclude: - - binaries: x86-64-avx2 - config: { os: macos-12 } - binaries: x86-64-bmi2 config: { os: macos-12 } + #- binaries: x86-64-avxvnni + # config: { os: macos-12 } + - binaries: x86-64-avx512 + config: { os: macos-12 } + - binaries: x86-64-vnni256 + config: { os: macos-12 } + - binaries: x86-64-vnni512 + config: { os: macos-12 } defaults: run: working-directory: src @@ -68,6 +81,13 @@ jobs: msystem: ${{ matrix.config.msys_sys }} install: mingw-w64-${{ matrix.config.msys_env }} make git zip + - name: Download SDE package + if: runner.os == 'Linux' || runner.os == 'Windows' + uses: petarpetrovt/setup-sde@v2.1 + with: + environmentVariableName: SDE_DIR + sdeVersion: 9.14.0 + - name: Download the used network from the fishtest framework run: make net @@ -84,7 +104,7 @@ jobs: - name: Compile ${{ matrix.binaries }} build run: | - make -j2 profile-build ARCH=$BINARY COMP=$COMP + make -j2 profile-build ARCH=$BINARY COMP=$COMP SDE_PATH="$SDE" make strip ARCH=$BINARY COMP=$COMP mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT From acdbf45171ba8bd0322e3bda0900e9cb2f8fb846 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 15 Jul 2023 00:51:14 +0300 Subject: [PATCH 335/678] Use more expressive names for bonus1 and bonus2 closes https://github.com/official-stockfish/Stockfish/pull/4683 No functional change --- src/search.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1f8f361c..11a52326 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1703,28 +1703,28 @@ moves_loop: // When in check, search starts here Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus1 = stat_bonus(depth + 1); + int quietMoveBonus = stat_bonus(depth + 1); if (!pos.capture_stage(bestMove)) { - int bonus2 = bestValue > beta + 145 ? bonus1 // larger bonus - : stat_bonus(depth); // smaller bonus + int bestMoveBonus = bestValue > beta + 145 ? quietMoveBonus // larger bonus + : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move - update_quiet_stats(pos, ss, bestMove, bonus2); + update_quiet_stats(pos, ss, bestMove, bestMoveBonus); // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; - update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus; + update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bestMoveBonus); } } else { // Increase stats for the best move in case it was a capture move captured = type_of(pos.piece_on(to_sq(bestMove))); - captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; + captureHistory[moved_piece][to_sq(bestMove)][captured] << quietMoveBonus; } // Extra penalty for a quiet early move that was not a TT move or @@ -1732,14 +1732,14 @@ moves_loop: // When in check, search starts here if ( prevSq != SQ_NONE && ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) && !pos.captured_piece()) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -quietMoveBonus); // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; + captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveBonus; } } From a3a91f3f9f4184a699a827c66e806d3a01656446 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 14 Jul 2023 17:43:56 +0200 Subject: [PATCH 336/678] Build and test more binaries in CI use a fixed compiler on Linux and Windows (right now gcc 11). build avxvnni on Windows (Linux needs updated core utils) build x86-32 on Linux (Windows needs other mingw) fix a Makefile issue where a failed PGOBENCH would not stop the build reuse the WINE_PATH for SDE as we do for QEMU use WINE_PATH variable also for the signature verify the bench for each of the binaries do not build x86-64-avx2 on macos closes https://github.com/official-stockfish/Stockfish/pull/4682 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 8 ++++ .github/workflows/stockfish_binaries.yml | 45 ++++++++++++++++---- src/Makefile | 11 ++--- tests/signature.sh | 2 +- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 4db216eb..dfe4e2a2 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -70,6 +70,13 @@ jobs: echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV fi + - name: Extract the bench number from the commit history + run: | + for hash in $(git rev-list -100 HEAD); do + benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + done + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" + - name: Download the used network from the fishtest framework run: make net @@ -97,6 +104,7 @@ jobs: make clean make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU make strip ARCH=$BINARY COMP=$COMP + WINE_PATH=$EMU ../tests/signature.sh $benchref mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT - name: Remove non src files diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index fa330974..e761c845 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -22,7 +22,7 @@ jobs: comp: gcc shell: bash archive_ext: tar - sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future + sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future -- - name: MacOS 12 Apple Clang os: macos-12 simple_name: macos @@ -39,22 +39,29 @@ jobs: msys_env: x86_64-gcc shell: msys2 {0} ext: .exe - sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future + sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future -- archive_ext: zip binaries: + - x86-32 - x86-64 - x86-64-modern - x86-64-avx2 - x86-64-bmi2 - # - x86-64-avxvnni needs more recent gcc + - x86-64-avxvnni - x86-64-avx512 - x86-64-vnni256 - x86-64-vnni512 exclude: + - binaries: x86-32 + config: { os: macos-12 } + - binaries: x86-64-avx2 + config: { os: macos-12 } - binaries: x86-64-bmi2 config: { os: macos-12 } - #- binaries: x86-64-avxvnni - # config: { os: macos-12 } + - binaries: x86-64-avxvnni + config: { os: macos-12 } + - binaries: x86-64-avxvnni + config: { ubuntu-20.04 } - binaries: x86-64-avx512 config: { os: macos-12 } - binaries: x86-64-vnni256 @@ -70,9 +77,17 @@ jobs: with: fetch-depth: 0 - - name: Download required linux packages + - name: Download required Linux packages if: runner.os == 'Linux' - run: sudo apt update + run: | + sudo apt update + sudo apt install g++-multilib g++-11-multilib + + - name: Install fixed GCC on Linux + if: runner.os == 'Linux' + uses: egor-tensin/setup-gcc@v1 + with: + version: 11 - name: Setup msys and install required packages if: runner.os == 'Windows' @@ -81,6 +96,12 @@ jobs: msystem: ${{ matrix.config.msys_sys }} install: mingw-w64-${{ matrix.config.msys_env }} make git zip + - name: Install fixed GCC on Windows + if: runner.os == 'Windows' + run: | + wget https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-11.3.0-2-any.pkg.tar.zst + pacman -U mingw-w64-x86_64-gcc-11.3.0-2-any.pkg.tar.zst --noconfirm + - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' uses: petarpetrovt/setup-sde@v2.1 @@ -91,6 +112,13 @@ jobs: - name: Download the used network from the fishtest framework run: make net + - name: Extract the bench number from the commit history + run: | + for hash in $(git rev-list -100 HEAD); do + benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + done + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" + - name: Check compiler run: $COMPILER -v @@ -104,8 +132,9 @@ jobs: - name: Compile ${{ matrix.binaries }} build run: | - make -j2 profile-build ARCH=$BINARY COMP=$COMP SDE_PATH="$SDE" + make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE" make strip ARCH=$BINARY COMP=$COMP + WINE_PATH="$SDE" ../tests/signature.sh $benchref mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT - name: Remove non src files diff --git a/src/Makefile b/src/Makefile index a0f098fa..966ac2a7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -49,11 +49,7 @@ PREFIX = /usr/local BINDIR = $(PREFIX)/bin ### Built-in benchmark for pgo-builds -ifeq ($(SDE_PATH),) - PGOBENCH = $(WINE_PATH) ./$(EXE) bench -else - PGOBENCH = $(SDE_PATH) -- $(WINE_PATH) ./$(EXE) bench -endif +PGOBENCH = $(WINE_PATH) ./$(EXE) bench ### Source and object files SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ @@ -849,7 +845,8 @@ profile-build: net config-sanity objclean profileclean $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) @echo "" @echo "Step 2/4. Running benchmark for pgo-build ..." - $(PGOBENCH) 2>&1 | tail -n 4 + $(PGOBENCH) > PGOBENCH.out 2>&1 + tail -n 4 PGOBENCH.out @echo "" @echo "Step 3/4. Building optimized executable ..." $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean @@ -913,7 +910,7 @@ objclean: # clean auxiliary profiling files profileclean: @rm -rf profdir - @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s + @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s PGOBENCH.out @rm -f stockfish.profdata *.profraw @rm -f stockfish.*args* @rm -f stockfish.*lt* diff --git a/tests/signature.sh b/tests/signature.sh index 2e5c183a..06bd1892 100755 --- a/tests/signature.sh +++ b/tests/signature.sh @@ -11,7 +11,7 @@ trap 'error ${LINENO}' ERR # obtain -signature=`./stockfish bench 2>&1 | grep "Nodes searched : " | awk '{print $4}'` +signature=`eval "$WINE_PATH ./stockfish bench 2>&1" | grep "Nodes searched : " | awk '{print $4}'` if [ $# -gt 0 ]; then # compare to given reference From ee53f8ed2f9418b55b05811da93ddf62f03c255f Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 2 Jul 2023 22:26:52 -0400 Subject: [PATCH 337/678] Reintroduce nnue eval pawn count multipliers again With separate multipliers for nnue eval and optimism scaling. This patch used 4 out of 7 params tuned with spsa at 30+0.3 using this tuning config: Value LazyThreshold1 = Value(3622); Value LazyThreshold2 = Value(1962); int psqThresh = 2048; int nnueNpmBase = 945; int nnuePcMult = 0; int optNpmBase = 150; int optPcMult = 0; TUNE(SetRange(3322, 3922), LazyThreshold1); TUNE(SetRange(1662, 2262), LazyThreshold2); TUNE(SetRange(1748, 2348), psqThresh); TUNE(SetRange(745, 1145), nnueNpmBase); TUNE(SetRange(-16, 16), nnuePcMult); TUNE(SetRange(0, 300), optNpmBase); TUNE(SetRange(-16, 16), optPcMult); Passed STC: https://tests.stockfishchess.org/tests/view/64a5a9b402cd07745c60ed07 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 173632 W: 44417 L: 43903 D: 85312 Ptnml(0-2): 547, 20025, 45068, 20719, 457 Passed LTC: https://tests.stockfishchess.org/tests/view/64a972a302cd07745c6136af LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 277644 W: 70955 L: 70147 D: 136542 Ptnml(0-2): 193, 29902, 77787, 30784, 156 closes https://github.com/official-stockfish/Stockfish/pull/4681 bench 1556301 --- src/evaluate.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index a1bcdd20..d4d8daee 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -157,7 +157,9 @@ Value Eval::evaluate(const Position& pos) { // Blend optimism with nnue complexity and (semi)classical complexity optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; - v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; + + v = ( nnue * (915 + npm + 9 * pos.count()) + + optimism * (154 + npm + pos.count())) / 1024; // Damp down the evaluation linearly when shuffling v = v * (200 - pos.rule50_count()) / 214; From a42ab95e1f2392406499b385149385a60cac5b66 Mon Sep 17 00:00:00 2001 From: AndrovT <31534597+AndrovT@users.noreply.github.com> Date: Sat, 15 Jul 2023 11:00:18 +0200 Subject: [PATCH 338/678] remove large input specialization Removes unused large input specialization for dense affine transform. It has been obsolete since #4612 was merged. closes https://github.com/official-stockfish/Stockfish/pull/4684 No functional change --- src/nnue/layers/affine_transform.h | 259 +---------------------------- 1 file changed, 2 insertions(+), 257 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 9e2f2f97..b0169306 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -29,25 +29,10 @@ /* This file contains the definition for a fully connected layer (aka affine transform). - Two approaches are employed, depending on the sizes of the transform. - Approach 1 (a specialization for large inputs): - - used when the PaddedInputDimensions >= 128 - - uses AVX512 if possible - - processes inputs in batches of 2*InputSimdWidth - - so in batches of 128 for AVX512 - - the weight blocks of size InputSimdWidth are transposed such that - access is sequential - - N columns of the weight matrix are processed a time, where N - depends on the architecture (the amount of registers) - - accumulate + hadd is used - - Approach 2 (a specialization for small inputs): - - used when the PaddedInputDimensions < 128 - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32. - that's why AVX512 is hard to implement - expected use-case is small layers - - not optimized as well as the approach 1 - inputs are processed in chunks of 4, weights are respectively transposed - accumulation happens directly to int32s */ @@ -55,7 +40,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Fallback implementation for older/other architectures. -// Identical for both approaches. Requires the input to be padded to at least 16 values. +// Requires the input to be padded to at least 16 values. #if !defined(USE_SSSE3) template static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) @@ -159,18 +144,8 @@ namespace Stockfish::Eval::NNUE::Layers { } #endif - template - class AffineTransform; - -#if defined (USE_AVX512) - constexpr IndexType LargeInputSize = 2 * 64; -#else - constexpr IndexType LargeInputSize = std::numeric_limits::max(); -#endif - - // A specialization for large inputs template - class AffineTransform(InDims, MaxSimdWidth) >= LargeInputSize)>> { + class AffineTransform { public: // Input/output type using InputType = std::uint8_t; @@ -187,236 +162,6 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization (for large inputs) should not have been chosen."); - -#if defined (USE_AVX512) - static constexpr IndexType InputSimdWidth = 64; - static constexpr IndexType MaxNumOutputRegs = 16; -#elif defined (USE_AVX2) - static constexpr IndexType InputSimdWidth = 32; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_SSSE3) - static constexpr IndexType InputSimdWidth = 16; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_NEON_DOTPROD) - static constexpr IndexType InputSimdWidth = 16; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_NEON) - static constexpr IndexType InputSimdWidth = 8; - static constexpr IndexType MaxNumOutputRegs = 8; -#else - // The fallback implementation will not have permuted weights. - // We define these to avoid a lot of ifdefs later. - static constexpr IndexType InputSimdWidth = 1; - static constexpr IndexType MaxNumOutputRegs = 1; -#endif - - // A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs]. - // A small block is a region of size [InputSimdWidth, 1] - - static constexpr IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions); - static constexpr IndexType SmallBlockSize = InputSimdWidth; - static constexpr IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions; - static constexpr IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize; - static constexpr IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize; - static constexpr IndexType NumBigBlocks = OutputDimensions / NumOutputRegs; - - static_assert(OutputDimensions % NumOutputRegs == 0); - - // Hash value embedded in the evaluation file - static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; - } - - /* - Transposes the small blocks within a block. - Effectively means that weights can be traversed sequentially during inference. - */ - static IndexType get_weight_index(IndexType i) - { - const IndexType smallBlock = (i / SmallBlockSize) % NumSmallBlocksInBigBlock; - const IndexType smallBlockCol = smallBlock / NumSmallBlocksPerOutput; - const IndexType smallBlockRow = smallBlock % NumSmallBlocksPerOutput; - const IndexType bigBlock = i / BigBlockSize; - const IndexType rest = i % SmallBlockSize; - - const IndexType idx = - bigBlock * BigBlockSize - + smallBlockRow * SmallBlockSize * NumOutputRegs - + smallBlockCol * SmallBlockSize - + rest; - - return idx; - } - - // Read network parameters - bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); - - return !stream.fail(); - } - - // Write network parameters - bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); - - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); - - return !stream.fail(); - } - - // Forward propagation - const OutputType* propagate( - const InputType* input, OutputType* output) const { - -#if defined (USE_AVX512) - using acc_vec_t = __m512i; - using bias_vec_t = __m128i; - using weight_vec_t = __m512i; - using in_vec_t = __m512i; - #define vec_zero _mm512_setzero_si512() - #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 - #define vec_hadd Simd::m512_hadd - #define vec_haddx4 Simd::m512_haddx4 -#elif defined (USE_AVX2) - using acc_vec_t = __m256i; - using bias_vec_t = __m128i; - using weight_vec_t = __m256i; - using in_vec_t = __m256i; - #define vec_zero _mm256_setzero_si256() - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_hadd Simd::m256_hadd - #define vec_haddx4 Simd::m256_haddx4 -#elif defined (USE_SSSE3) - using acc_vec_t = __m128i; - using bias_vec_t = __m128i; - using weight_vec_t = __m128i; - using in_vec_t = __m128i; - #define vec_zero _mm_setzero_si128() - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::m128_hadd - #define vec_haddx4 Simd::m128_haddx4 -#elif defined (USE_NEON_DOTPROD) - using acc_vec_t = int32x4_t; - using bias_vec_t = int32x4_t; - using weight_vec_t = int8x16_t; - using in_vec_t = int8x16_t; - #define vec_zero {0} - #define vec_add_dpbusd_32x2 Simd::dotprod_m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::neon_m128_hadd - #define vec_haddx4 Simd::neon_m128_haddx4 -#elif defined (USE_NEON) - using acc_vec_t = int32x4_t; - using bias_vec_t = int32x4_t; - using weight_vec_t = int8x8_t; - using in_vec_t = int8x8_t; - #define vec_zero {0} - #define vec_add_dpbusd_32x2 Simd::neon_m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::neon_m128_hadd - #define vec_haddx4 Simd::neon_m128_haddx4 -#endif - -#if defined (USE_SSSE3) || defined (USE_NEON) - const in_vec_t* invec = reinterpret_cast(input); - - // Perform accumulation to registers for each big block - for (IndexType bigBlock = 0; bigBlock < NumBigBlocks; ++bigBlock) - { - acc_vec_t acc[NumOutputRegs] = { vec_zero }; - - // Each big block has NumOutputRegs small blocks in each "row", one per register. - // We process two small blocks at a time to save on one addition without VNNI. - for (IndexType smallBlock = 0; smallBlock < NumSmallBlocksPerOutput; smallBlock += 2) - { - const weight_vec_t* weightvec = - reinterpret_cast( - weights - + bigBlock * BigBlockSize - + smallBlock * SmallBlockSize * NumOutputRegs); - - const in_vec_t in0 = invec[smallBlock + 0]; - const in_vec_t in1 = invec[smallBlock + 1]; - - for (IndexType k = 0; k < NumOutputRegs; ++k) - vec_add_dpbusd_32x2(acc[k], in0, weightvec[k], in1, weightvec[k + NumOutputRegs]); - } - - // Horizontally add all accumulators. - if constexpr (NumOutputRegs % 4 == 0) - { - bias_vec_t* outputvec = reinterpret_cast(output); - const bias_vec_t* biasvec = reinterpret_cast(biases); - - for (IndexType k = 0; k < NumOutputRegs; k += 4) - { - const IndexType idx = (bigBlock * NumOutputRegs + k) / 4; - outputvec[idx] = vec_haddx4(acc[k+0], acc[k+1], acc[k+2], acc[k+3], biasvec[idx]); - } - } - else - { - for (IndexType k = 0; k < NumOutputRegs; ++k) - { - const IndexType idx = (bigBlock * NumOutputRegs + k); - output[idx] = vec_hadd(acc[k], biases[idx]); - } - } - } - -# undef vec_zero -# undef vec_add_dpbusd_32x2 -# undef vec_hadd -# undef vec_haddx4 -#else - // Use old implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); - -#endif - - return output; - } - - private: - using BiasType = OutputType; - using WeightType = std::int8_t; - - alignas(CacheLineSize) BiasType biases[OutputDimensions]; - alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; - - // A specialization for small inputs - template - class AffineTransform(InDims, MaxSimdWidth) < LargeInputSize)>> { - public: - // Input/output type - // Input/output type - using InputType = std::uint8_t; - using OutputType = std::int32_t; - - // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; - static constexpr IndexType OutputDimensions = OutDims; - - static constexpr IndexType PaddedInputDimensions = - ceil_to_multiple(InputDimensions, MaxSimdWidth); - static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, MaxSimdWidth); - - using OutputBuffer = OutputType[PaddedOutputDimensions]; - - static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization (for small inputs) should not have been chosen."); - // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { std::uint32_t hashValue = 0xCC03DAE4u; From e89469925d9afd060f85d4047edbaad8903e67ef Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 15 Jul 2023 11:59:27 +0200 Subject: [PATCH 339/678] Remove some CI parts not yet working downgrading compiler didn't work for windows build. Stick to gcc 13 for now. Windows x86-32 not a 32bit binary, remove. closes https://github.com/official-stockfish/Stockfish/pull/4685 No functional change --- .github/workflows/stockfish_binaries.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index e761c845..f856d403 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -54,6 +54,8 @@ jobs: exclude: - binaries: x86-32 config: { os: macos-12 } + - binaries: x86-32 + config: { os: windows-2022} - binaries: x86-64-avx2 config: { os: macos-12 } - binaries: x86-64-bmi2 @@ -96,12 +98,6 @@ jobs: msystem: ${{ matrix.config.msys_sys }} install: mingw-w64-${{ matrix.config.msys_env }} make git zip - - name: Install fixed GCC on Windows - if: runner.os == 'Windows' - run: | - wget https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-11.3.0-2-any.pkg.tar.zst - pacman -U mingw-w64-x86_64-gcc-11.3.0-2-any.pkg.tar.zst --noconfirm - - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' uses: petarpetrovt/setup-sde@v2.1 From e8a5c64988d4c0d3d6c87e3ba3204ab4cf512bcb Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 15 Jul 2023 13:34:16 +0200 Subject: [PATCH 340/678] Consolidate to centipawns conversion avoid doing this calculations in multiple places. closes https://github.com/official-stockfish/Stockfish/pull/4686 No functional change --- src/evaluate.cpp | 6 ++---- src/nnue/evaluate_nnue.cpp | 8 ++++---- src/uci.cpp | 9 ++++++++- src/uci.h | 1 + 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d4d8daee..7f0ea4bc 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -58,8 +58,6 @@ namespace Eval { string currentEvalFileName = "None"; - static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } - /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" /// The name of the NNUE network is always retrieved from the EvalFile option. @@ -194,11 +192,11 @@ std::string Eval::trace(Position& pos) { Value v; v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; + ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; v = evaluate(pos); v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << to_cp(v) << " (white side)"; + ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; ss << "\n"; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index a1a90023..d90f59a2 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -224,7 +224,7 @@ namespace Stockfish::Eval::NNUE { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - int cp = std::abs(100 * v / UCI::NormalizeToPawnValue); + int cp = std::abs(UCI::to_cp(v)); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; cp %= 10000; @@ -249,15 +249,15 @@ namespace Stockfish::Eval::NNUE { } - // format_cp_aligned_dot() converts a Value into (centi)pawns, always keeping two decimals. + // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals. static void format_cp_aligned_dot(Value v, std::stringstream &stream) { - const double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue; + const double pawns = std::abs(0.01 * UCI::to_cp(v)); stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) - << cp; + << pawns; } diff --git a/src/uci.cpp b/src/uci.cpp index ed16f24c..f893bd9c 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -303,6 +303,13 @@ void UCI::loop(int argc, char* argv[]) { } +/// Turns a Value to an integer centipawn number, +/// without treatment of mate and similar special scores. +int UCI::to_cp(Value v) { + + return 100 * v / UCI::NormalizeToPawnValue; +} + /// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: /// /// cp The score from the engine's point of view in centipawns. @@ -316,7 +323,7 @@ string UCI::value(Value v) { stringstream ss; if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << v * 100 / NormalizeToPawnValue; + ss << "cp " << UCI::to_cp(v); else if (abs(v) < VALUE_MATE_IN_MAX_PLY) { const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply diff --git a/src/uci.h b/src/uci.h index 8f1be00c..2e40c912 100644 --- a/src/uci.h +++ b/src/uci.h @@ -76,6 +76,7 @@ private: void init(OptionsMap&); void loop(int argc, char* argv[]); +int to_cp(Value v); std::string value(Value v); std::string square(Square s); std::string move(Move m, bool chess960); From 18fdc2df3c1fe0eeaae26a9235fb9bc17221e9c0 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 13 Jul 2023 19:09:02 +0300 Subject: [PATCH 341/678] Remove pawnKey from StateInfo Remove pawnKey since it is not used anymore. Passed Non-regression STC: https://tests.stockfishchess.org/tests/view/64b023110cdec37b9573265c LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 334848 W: 85440 L: 85545 D: 163863 Ptnml(0-2): 1134, 38101, 89075, 37964, 1150 closes https://github.com/official-stockfish/Stockfish/pull/4687 No functional change --- src/position.cpp | 12 +----------- src/position.h | 5 ----- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 6ecc52f8..31cdbc06 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -336,7 +336,6 @@ void Position::set_check_info() const { void Position::set_state() const { st->key = st->materialKey = 0; - st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); @@ -348,10 +347,7 @@ void Position::set_state() const { Piece pc = piece_on(s); st->key ^= Zobrist::psq[pc][s]; - if (type_of(pc) == PAWN) - st->pawnKey ^= Zobrist::psq[pc][s]; - - else if (type_of(pc) != KING) + if (type_of(pc) != KING && type_of(pc) != PAWN) st->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; } @@ -745,8 +741,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(piece_on(to) == NO_PIECE); assert(piece_on(capsq) == make_piece(them, PAWN)); } - - st->pawnKey ^= Zobrist::psq[captured][capsq]; } else st->nonPawnMaterial[them] -= PieceValue[MG][captured]; @@ -825,7 +819,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // 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]]; @@ -833,9 +826,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[us] += PieceValue[MG][promotion]; } - // Update pawn hash key - st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - // Reset rule 50 draw counter st->rule50 = 0; } diff --git a/src/position.h b/src/position.h index 7d4b3771..e3917ede 100644 --- a/src/position.h +++ b/src/position.h @@ -40,7 +40,6 @@ namespace Stockfish { struct StateInfo { // Copied when making a move - Key pawnKey; Key materialKey; Value nonPawnMaterial[COLOR_NB]; int castlingRights; @@ -338,10 +337,6 @@ inline Key Position::adjust_key50(Key k) const ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); } -inline Key Position::pawn_key() const { - return st->pawnKey; -} - inline Key Position::material_key() const { return st->materialKey; } From 913574f42123d16b0b473dcd8e373e95d3103633 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 16 Jul 2023 11:52:37 +0300 Subject: [PATCH 342/678] Remove improvement variable No longer used in a meaningful way. Improve comments. Closes https://github.com/official-stockfish/Stockfish/pull/4688 No functional change --- src/search.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 11a52326..61c75d7d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -548,7 +548,7 @@ namespace { bool givesCheck, improving, priorCapture, singularQuietLMR; bool capture, moveCountPruning, ttCapture; Piece movedPiece; - int moveCount, captureCount, quietCount, improvement; + int moveCount, captureCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); @@ -708,7 +708,6 @@ namespace { // Skip early pruning when in check ss->staticEval = eval = VALUE_NONE; improving = false; - improvement = 0; goto moves_loop; } else if (excludedMove) @@ -745,14 +744,14 @@ namespace { thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } - // Set up the improvement variable, which is the difference between the current - // static evaluation and the previous static evaluation at our turn (if we were - // in check at our previous move we look at the move prior to it). The improvement - // margin and the improving flag are used in various pruning heuristics. - improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval - : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 173; - improving = improvement > 0; + // Set up the improving flag, which is true if current static evaluation is + // bigger than the previous static evaluation at our turn (if we were in + // check at our previous move we look at static evaluaion at move prior to it + // and if we were in check at move prior to it flag is set to true) and is + // false otherwise. The improving flag is used in various pruning heuristics. + improving = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval > (ss-2)->staticEval + : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval > (ss-4)->staticEval + : true; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, From d70a905ce3bc7372529affa8df898fc946b91282 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 16 Jul 2023 16:25:03 +0200 Subject: [PATCH 343/678] Deprecate the x86-64-modern arch Explicitly describe the architecture as deprecated, it remains available as its current alias x86-64-sse41-popcnt CPUs that support just this instruction set are now years old, any few years old Intel or AMD CPU supports x86-64-avx2. However, naming things 'modern' doesn't age well, so instead use explicit names. Adjust CI accordingly. Wiki, fishtest, downloader done as well. closes https://github.com/official-stockfish/Stockfish/pull/4691 No functional change. --- .github/workflows/stockfish_binaries.yml | 2 +- .github/workflows/stockfish_sanitizers.yml | 2 +- .github/workflows/stockfish_test.yml | 14 +++++++++++--- README.md | 4 ++-- src/Makefile | 12 +++++++----- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index f856d403..cd90507e 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -44,7 +44,7 @@ jobs: binaries: - x86-32 - x86-64 - - x86-64-modern + - x86-64-sse41-popcnt - x86-64-avx2 - x86-64-bmi2 - x86-64-avxvnni diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index ebfd809c..305b8557 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -62,5 +62,5 @@ jobs: run: | export CXXFLAGS="-O1 -fno-inline" make clean - make -j2 ARCH=x86-64-modern ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null + make -j2 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }} diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index cd80e223..c2ed7a4b 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -177,12 +177,12 @@ jobs: # x86-64 tests - - name: Test debug x86-64-modern build + - name: Test debug x86-64-sse41-popcnt build if: matrix.config.run_64bit_tests run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean - make -j2 ARCH=x86-64-modern optimize=no debug=yes build + make -j2 ARCH=x86-64-sse41-popcnt optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-64-bmi2 build @@ -199,6 +199,7 @@ jobs: make -j2 ARCH=x86-64-avx2 build ../tests/signature.sh $benchref + # Test a deprecated arch - name: Test x86-64-modern build if: matrix.config.run_64bit_tests run: | @@ -206,6 +207,13 @@ jobs: make -j2 ARCH=x86-64-modern build ../tests/signature.sh $benchref + - name: Test x86-64-sse41-popcnt build + if: matrix.config.run_64bit_tests + run: | + make clean + make -j2 ARCH=x86-64-sse41-popcnt build + ../tests/signature.sh $benchref + - name: Test x86-64-ssse3 build if: matrix.config.run_64bit_tests run: | @@ -271,6 +279,6 @@ jobs: if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-modern build + make -j2 ARCH=x86-64-sse41-popcnt build ../tests/perft.sh ../tests/reprosearch.sh diff --git a/README.md b/README.md index 1f462d31..e0e3da39 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,11 @@ big-endian machines such as Power PC, and other platforms. On Unix-like systems, it should be easy to compile Stockfish directly from the source code with the included Makefile in the folder `src`. In general, it is recommended to run `make help` to see a list of make targets with corresponding -descriptions. +descriptions. An example suitable for most Intel and AMD chips: ``` cd src -make -j build ARCH=x86-64-modern +make -j profile-build ARCH=x86-64-avx2 ``` Detailed compilation instructions for all platforms can be found in our diff --git a/src/Makefile b/src/Makefile index 966ac2a7..f66d84d5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -104,7 +104,7 @@ VPATH = syzygy:nnue:nnue/features ### 2.1. General and architecture defaults ifeq ($(ARCH),) - ARCH = x86-64-modern + ARCH = x86-64-avx2 help_skip_sanity = yes endif # explicitly check for the list of supported architectures (as listed with make help), @@ -189,6 +189,8 @@ ifeq ($(findstring -sse41,$(ARCH)),-sse41) endif ifeq ($(findstring -modern,$(ARCH)),-modern) + $(warning *** ARCH=$(ARCH) is deprecated, defaulting to ARCH=x86-64-sse41-popcnt. Execute `make help` for a list of available architectures. ***) + $(shell sleep 5) popcnt = yes sse = yes sse2 = yes @@ -781,7 +783,7 @@ help: @echo "x86-64-bmi2 > x86 64-bit with bmi2 support" @echo "x86-64-avx2 > x86 64-bit with avx2 support" @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" - @echo "x86-64-modern > common modern CPU, currently x86-64-sse41-popcnt" + @echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt" @echo "x86-64-ssse3 > x86 64-bit with ssse3 support" @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support" @echo "x86-64 > x86 64-bit generic (with sse2 support)" @@ -811,13 +813,13 @@ help: @echo "Simple examples. If you don't know what to do, you likely want to run one of: " @echo "" @echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " - @echo "make -j profile-build ARCH=x86-64-modern # A more portable compile for 64-bit systems " + @echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " @echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " @echo "" @echo "Advanced examples, for experienced users: " @echo "" - @echo "make -j profile-build ARCH=x86-64-bmi2" - @echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0" + @echo "make -j profile-build ARCH=x86-64-avxvnni" + @echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" @echo "make -j build ARCH=x86-64-ssse3 COMP=clang" @echo "" @echo "-------------------------------" From 34d0c1b5272a7af322825426e90c6fbc6b69c6b4 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 16 Jul 2023 18:14:38 +0200 Subject: [PATCH 344/678] Switch to macos 13 for CI allows for building x86-64-avx2 and x86-64-bmi2 binaries for mac install coreutils show hardware capabilities as seen by the compilers move some tests from sse41 to avx2 as platforms support it closes https://github.com/official-stockfish/Stockfish/pull/4692 No functional change --- .github/workflows/stockfish_binaries.yml | 38 ++++++++++++-------- .github/workflows/stockfish_compile_test.yml | 8 ++--- .github/workflows/stockfish_test.yml | 18 +++++----- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index cd90507e..7c7341ef 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -23,8 +23,8 @@ jobs: shell: bash archive_ext: tar sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future -- - - name: MacOS 12 Apple Clang - os: macos-12 + - name: MacOS 13 Apple Clang + os: macos-13 simple_name: macos compiler: clang++ comp: clang @@ -52,24 +52,20 @@ jobs: - x86-64-vnni256 - x86-64-vnni512 exclude: - - binaries: x86-32 - config: { os: macos-12 } - - binaries: x86-32 - config: { os: windows-2022} - - binaries: x86-64-avx2 - config: { os: macos-12 } - - binaries: x86-64-bmi2 - config: { os: macos-12 } - - binaries: x86-64-avxvnni - config: { os: macos-12 } - binaries: x86-64-avxvnni config: { ubuntu-20.04 } + - binaries: x86-32 + config: { os: windows-2022} + - binaries: x86-32 + config: { os: macos-13 } + - binaries: x86-64-avxvnni + config: { os: macos-13 } - binaries: x86-64-avx512 - config: { os: macos-12 } + config: { os: macos-13 } - binaries: x86-64-vnni256 - config: { os: macos-12 } + config: { os: macos-13 } - binaries: x86-64-vnni512 - config: { os: macos-12 } + config: { os: macos-13 } defaults: run: working-directory: src @@ -85,6 +81,10 @@ jobs: sudo apt update sudo apt install g++-multilib g++-11-multilib + - name: Download required macOS packages + if: runner.os == 'macOS' + run: brew install coreutils + - name: Install fixed GCC on Linux if: runner.os == 'Linux' uses: egor-tensin/setup-gcc@v1 @@ -118,6 +118,14 @@ jobs: - name: Check compiler run: $COMPILER -v + - name: Show g++ cpu info + if: runner.os != 'macOS' + run: g++ -Q -march=native --help=target + + - name: Show clang++ cpu info + if: runner.os == 'macOS' + run: clang++ -E - -march=native -### + - name: Test help target run: make help diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 41f61dab..90e01537 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -21,13 +21,13 @@ jobs: compiler: clang++ comp: clang shell: bash - - name: MacOS 12 Apple Clang - os: macos-12 + - name: MacOS 13 Apple Clang + os: macos-13 compiler: clang++ comp: clang shell: bash - - name: MacOS 12 GCC 11 - os: macos-12 + - name: MacOS 13 GCC 11 + os: macos-13 compiler: g++-11 comp: gcc shell: bash diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index c2ed7a4b..cb6c4c59 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -38,14 +38,14 @@ jobs: comp: ndk run_armv7_tests: true shell: bash - - name: MacOS 12 Apple Clang - os: macos-12 + - name: MacOS 13 Apple Clang + os: macos-13 compiler: clang++ comp: clang run_64bit_tests: true shell: bash - - name: MacOS 12 GCC 11 - os: macos-12 + - name: MacOS 13 GCC 11 + os: macos-13 compiler: g++-11 comp: gcc run_64bit_tests: true @@ -177,23 +177,23 @@ jobs: # x86-64 tests - - name: Test debug x86-64-sse41-popcnt build + - name: Test debug x86-64-avx2 build if: matrix.config.run_64bit_tests run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean - make -j2 ARCH=x86-64-sse41-popcnt optimize=no debug=yes build + make -j2 ARCH=x86-64-avx2 optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-64-bmi2 build - if: matrix.config.run_64bit_tests && runner.os != 'macOS' + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-bmi2 build ../tests/signature.sh $benchref - name: Test x86-64-avx2 build - if: matrix.config.run_64bit_tests && runner.os != 'macOS' + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-avx2 build @@ -279,6 +279,6 @@ jobs: if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-sse41-popcnt build + make -j2 ARCH=x86-64-avx2 build ../tests/perft.sh ../tests/reprosearch.sh From 6a31f54d3cacf8c4dc57f301e0e3aa40d7aec435 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Mon, 17 Jul 2023 18:02:22 +0200 Subject: [PATCH 345/678] remove evalType from bench no longer used closes https://github.com/official-stockfish/Stockfish/pull/4694 No functional change --- AUTHORS | 1 + src/benchmark.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 79289394..462ae5e8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -180,6 +180,7 @@ renouve Reuven Peleg (R-Peleg) Richard Lloyd (Richard-Lloyd) rn5f107s2 +Robert Nürnberg (robertnurnberg) Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) Ronald de Man (syzygy1, syzygy) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index baa90140..c41092a9 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -121,7 +121,6 @@ vector setup_bench(const Position& current, istream& is) { string limit = (is >> token) ? token : "13"; string fenFile = (is >> token) ? token : "default"; string limitType = (is >> token) ? token : "depth"; - string evalType = (is >> token) ? token : "mixed"; go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; From 42d28424bc64246e141e9f06a2a518592272f8fd Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Mon, 17 Jul 2023 21:41:03 +0200 Subject: [PATCH 346/678] Removes a few Bitboards and functions No longer used closes https://github.com/official-stockfish/Stockfish/pull/4695 No functional change --- src/bitboard.h | 74 -------------------------------------------------- src/position.h | 30 -------------------- 2 files changed, 104 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index d21d390b..bbed9177 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -32,9 +32,6 @@ std::string pretty(Bitboard b); } // namespace Stockfish::Bitboards -constexpr Bitboard AllSquares = ~Bitboard(0); -constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; - constexpr Bitboard FileABB = 0x0101010101010101ULL; constexpr Bitboard FileBBB = FileABB << 1; constexpr Bitboard FileCBB = FileABB << 2; @@ -53,17 +50,6 @@ constexpr Bitboard Rank6BB = Rank1BB << (8 * 5); constexpr Bitboard Rank7BB = Rank1BB << (8 * 6); constexpr Bitboard Rank8BB = Rank1BB << (8 * 7); -constexpr Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; -constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; -constexpr Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; -constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); - -constexpr Bitboard KingFlank[FILE_NB] = { - QueenSide ^ FileDBB, QueenSide, QueenSide, - CenterFiles, CenterFiles, - KingSide, KingSide, KingSide ^ FileEBB -}; - extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; @@ -124,11 +110,6 @@ constexpr bool more_than_one(Bitboard b) { } -constexpr bool opposite_colors(Square s1, Square s2) { - return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1; -} - - /// rank_bb() and file_bb() return a bitboard representing all the squares on /// the given file or rank. @@ -177,25 +158,6 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { return PawnAttacks[c][s]; } - -/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the -/// given color from the squares in the given bitboard. - -template -constexpr Bitboard pawn_double_attacks_bb(Bitboard b) { - return C == WHITE ? shift(b) & shift(b) - : shift(b) & shift(b); -} - - -/// adjacent_files_bb() returns a bitboard representing all the squares on the -/// adjacent files of a given square. - -constexpr Bitboard adjacent_files_bb(Square s) { - return shift(file_bb(s)) | shift(file_bb(s)); -} - - /// line_bb() returns a bitboard representing an entire line (from board edge /// to board edge) that intersects the two given squares. If the given squares /// are not on a same file/rank/diagonal, the function returns 0. For instance, @@ -234,32 +196,6 @@ constexpr Bitboard forward_ranks_bb(Color c, Square s) { : ~Rank8BB >> 8 * relative_rank(BLACK, s); } - -/// forward_file_bb() returns a bitboard representing all the squares along the -/// line in front of the given one, from the point of view of the given color. - -constexpr Bitboard forward_file_bb(Color c, Square s) { - return forward_ranks_bb(c, s) & file_bb(s); -} - - -/// pawn_attack_span() returns a bitboard representing all the squares that can -/// be attacked by a pawn of the given color when it moves along its file, starting -/// from the given square. - -constexpr Bitboard pawn_attack_span(Color c, Square s) { - return forward_ranks_bb(c, s) & adjacent_files_bb(s); -} - - -/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of -/// the given color and on the given square is a passed pawn. - -constexpr Bitboard passed_pawn_span(Color c, Square s) { - return pawn_attack_span(c, s) | forward_file_bb(c, s); -} - - /// aligned() returns true if the squares s1, s2 and s3 are aligned either on a /// straight or on a diagonal line. @@ -277,8 +213,6 @@ template<> inline int distance(Square x, Square y) { return std::abs(rank_ template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); } - /// attacks_bb(Square) returns the pseudo attacks of the give piece type /// assuming an empty board. @@ -430,14 +364,6 @@ inline Square pop_lsb(Bitboard& b) { return s; } - -/// frontmost_sq() returns the most advanced square for the given color, -/// requires a non-zero bitboard. -inline Square frontmost_sq(Color c, Bitboard b) { - assert(b); - return c == WHITE ? msb(b) : lsb(b); -} - } // namespace Stockfish #endif // #ifndef BITBOARD_H_INCLUDED diff --git a/src/position.h b/src/position.h index e3917ede..dc4c5837 100644 --- a/src/position.h +++ b/src/position.h @@ -100,7 +100,6 @@ public: template int count(Color c) const; template int count() const; template Square square(Color c) const; - bool is_on_semiopen_file(Color c, Square s) const; // Castling CastlingRights castling_rights(Color c) const; @@ -129,11 +128,6 @@ public: Piece moved_piece(Move m) const; Piece captured_piece() const; - // Piece specific - bool pawn_passed(Color c, Square s) const; - bool opposite_bishops() const; - int pawns_on_same_color_squares(Color c, Square s) const; - // Doing and undoing moves void do_move(Move m, StateInfo& newSt); void do_move(Move m, StateInfo& newSt, bool givesCheck); @@ -149,7 +143,6 @@ public: Key key() const; Key key_after(Move m) const; Key material_key() const; - Key pawn_key() const; // Other properties of the position Color side_to_move() const; @@ -160,7 +153,6 @@ public: bool has_game_cycle(int ply) const; bool has_repeated() const; int rule50_count() const; - Score psq_score() const; Value psq_eg_stm() const; Value non_pawn_material(Color c) const; Value non_pawn_material() const; @@ -258,10 +250,6 @@ inline Square Position::ep_square() const { return st->epSquare; } -inline bool Position::is_on_semiopen_file(Color c, Square s) const { - return !(pieces(c, PAWN) & file_bb(s)); -} - inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; } @@ -318,14 +306,6 @@ inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } -inline bool Position::pawn_passed(Color c, Square s) const { - return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); -} - -inline int Position::pawns_on_same_color_squares(Color c, Square s) const { - return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); -} - inline Key Position::key() const { return adjust_key50(st->key); } @@ -341,10 +321,6 @@ inline Key Position::material_key() const { return st->materialKey; } -inline Score Position::psq_score() const { - return psq; -} - inline Value Position::psq_eg_stm() const { return (sideToMove == WHITE ? 1 : -1) * eg_value(psq); } @@ -365,12 +341,6 @@ inline int Position::rule50_count() const { return st->rule50; } -inline bool Position::opposite_bishops() const { - return count(WHITE) == 1 - && count(BLACK) == 1 - && opposite_colors(square(WHITE), square(BLACK)); -} - inline bool Position::is_chess960() const { return chess960; } From 6abd0bd9fbd8aff7dad653d33ab344d3f47f0042 Mon Sep 17 00:00:00 2001 From: Jorge <46056498+jorgectf@users.noreply.github.com> Date: Mon, 3 Jul 2023 09:17:24 +0200 Subject: [PATCH 347/678] Add CodeQL workflow add CI tooling to detect security bugs. closes https://github.com/official-stockfish/Stockfish/pull/4659 No functional change --- .github/workflows/codeql.yml | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..863f219c --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,53 @@ +name: "CodeQL" + +on: + push: + branches: [ 'master' ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ 'master' ] + schedule: + - cron: '17 18 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Build + working-directory: src + run: make -j build ARCH=x86-64-modern + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From 3fe0d5c53389b3d548cf1fbbba3f6c978e9f02ae Mon Sep 17 00:00:00 2001 From: Cody Ho Date: Tue, 18 Jul 2023 13:03:26 -0700 Subject: [PATCH 348/678] Unused code cleanup closes https://github.com/official-stockfish/Stockfish/pull/4696 No functional change --- AUTHORS | 1 + src/bitboard.h | 10 ---------- src/misc.h | 8 -------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/AUTHORS b/AUTHORS index 462ae5e8..b345ff0b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,6 +45,7 @@ candirufish Chess13234 Chris Cain (ceebo) clefrks +Cody Ho (aesrentai) Dale Weiler (graphitemaster) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) diff --git a/src/bitboard.h b/src/bitboard.h index bbed9177..244dc034 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -186,16 +186,6 @@ inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } - -/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in -/// front of the given one, from the point of view of the given color. For instance, -/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. - -constexpr Bitboard forward_ranks_bb(Color c, Square s) { - return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s) - : ~Rank8BB >> 8 * relative_rank(BLACK, s); -} - /// aligned() returns true if the squares s1, s2 and s3 are aligned either on a /// straight or on a diagonal line. diff --git a/src/misc.h b/src/misc.h index 69d470c2..0005fc0f 100644 --- a/src/misc.h +++ b/src/misc.h @@ -55,14 +55,6 @@ inline TimePoint now() { (std::chrono::steady_clock::now().time_since_epoch()).count(); } -template -struct HashTable { - Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } - -private: - std::vector table = std::vector(Size); // Allocate on the heap -}; - enum SyncCout { IO_LOCK, IO_UNLOCK }; std::ostream& operator<<(std::ostream&, SyncCout); From 1444837887e5982a2f1f343312e9080248ed9ca8 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 18 Jul 2023 11:50:34 -0700 Subject: [PATCH 349/678] Remove inline assembly closes https://github.com/official-stockfish/Stockfish/pull/4698 No functional change --- src/nnue/layers/simd.h | 118 ----------------------------------------- 1 file changed, 118 deletions(-) diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 22c51980..fae31a62 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -38,21 +38,6 @@ # include #endif -// The inline asm is only safe for GCC, where it is necessary to get good codegen. -// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101693 -// Clang does fine without it. -// Play around here: https://godbolt.org/z/7EWqrYq51 -#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) -#define USE_INLINE_ASM -#endif - -// Use either the AVX512 or AVX-VNNI version of the VNNI instructions. -#if defined(USE_AVXVNNI) -#define VNNI_PREFIX "%{vex%} " -#else -#define VNNI_PREFIX "" -#endif - namespace Stockfish::Simd { #if defined (USE_AVX512) @@ -117,29 +102,11 @@ namespace Stockfish::Simd { __m512i b) { # if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - "vpdpbusd %[b], %[a], %[acc]\n\t" - : [acc]"+v"(acc) - : [a]"v"(a), [b]"vm"(b) - ); -# else acc = _mm512_dpbusd_epi32(acc, a, b); -# endif # else -# if defined (USE_INLINE_ASM) - __m512i tmp = _mm512_maddubs_epi16(a, b); - asm( - "vpmaddwd %[tmp], %[ones], %[tmp]\n\t" - "vpaddd %[acc], %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm512_set1_epi16(1)) - ); -# else __m512i product0 = _mm512_maddubs_epi16(a, b); product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); acc = _mm512_add_epi32(acc, product0); -# endif # endif } @@ -149,36 +116,14 @@ namespace Stockfish::Simd { __m512i a1, __m512i b1) { # if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - "vpdpbusd %[b0], %[a0], %[acc]\n\t" - "vpdpbusd %[b1], %[a1], %[acc]\n\t" - : [acc]"+&v"(acc) - : [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1) - ); -# else acc = _mm512_dpbusd_epi32(acc, a0, b0); acc = _mm512_dpbusd_epi32(acc, a1, b1); -# endif # else -# if defined (USE_INLINE_ASM) - __m512i tmp0 = _mm512_maddubs_epi16(a0, b0); - __m512i tmp1 = _mm512_maddubs_epi16(a1, b1); - asm( - "vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t" - "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t" - "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t" - "vpaddd %[acc], %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm512_set1_epi16(1)) - ); -# else __m512i product0 = _mm512_maddubs_epi16(a0, b0); __m512i product1 = _mm512_maddubs_epi16(a1, b1); product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); -# endif # endif } @@ -214,29 +159,11 @@ namespace Stockfish::Simd { __m256i b) { # if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - VNNI_PREFIX "vpdpbusd %[b], %[a], %[acc]\n\t" - : [acc]"+v"(acc) - : [a]"v"(a), [b]"vm"(b) - ); -# else acc = _mm256_dpbusd_epi32(acc, a, b); -# endif # else -# if defined (USE_INLINE_ASM) - __m256i tmp = _mm256_maddubs_epi16(a, b); - asm( - "vpmaddwd %[tmp], %[ones], %[tmp]\n\t" - "vpaddd %[acc], %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm256_set1_epi16(1)) - ); -# else __m256i product0 = _mm256_maddubs_epi16(a, b); product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); acc = _mm256_add_epi32(acc, product0); -# endif # endif } @@ -246,36 +173,14 @@ namespace Stockfish::Simd { __m256i a1, __m256i b1) { # if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - VNNI_PREFIX "vpdpbusd %[b0], %[a0], %[acc]\n\t" - VNNI_PREFIX "vpdpbusd %[b1], %[a1], %[acc]\n\t" - : [acc]"+&v"(acc) - : [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1) - ); -# else acc = _mm256_dpbusd_epi32(acc, a0, b0); acc = _mm256_dpbusd_epi32(acc, a1, b1); -# endif # else -# if defined (USE_INLINE_ASM) - __m256i tmp0 = _mm256_maddubs_epi16(a0, b0); - __m256i tmp1 = _mm256_maddubs_epi16(a1, b1); - asm( - "vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t" - "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t" - "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t" - "vpaddd %[acc], %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm256_set1_epi16(1)) - ); -# else __m256i product0 = _mm256_maddubs_epi16(a0, b0); __m256i product1 = _mm256_maddubs_epi16(a1, b1); product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); -# endif # endif } @@ -304,19 +209,9 @@ namespace Stockfish::Simd { __m128i a, __m128i b) { -# if defined (USE_INLINE_ASM) - __m128i tmp = _mm_maddubs_epi16(a, b); - asm( - "pmaddwd %[ones], %[tmp]\n\t" - "paddd %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm_set1_epi16(1)) - ); -# else __m128i product0 = _mm_maddubs_epi16(a, b); product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); acc = _mm_add_epi32(acc, product0); -# endif } [[maybe_unused]] static void m128_add_dpbusd_epi32x2( @@ -324,24 +219,11 @@ namespace Stockfish::Simd { __m128i a0, __m128i b0, __m128i a1, __m128i b1) { -# if defined (USE_INLINE_ASM) - __m128i tmp0 = _mm_maddubs_epi16(a0, b0); - __m128i tmp1 = _mm_maddubs_epi16(a1, b1); - asm( - "pmaddwd %[ones], %[tmp0]\n\t" - "pmaddwd %[ones], %[tmp1]\n\t" - "paddd %[tmp1], %[tmp0]\n\t" - "paddd %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm_set1_epi16(1)) - ); -# else __m128i product0 = _mm_maddubs_epi16(a0, b0); __m128i product1 = _mm_maddubs_epi16(a1, b1); product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); -# endif } #endif From 5ea1cbc778508a9a7b720becaf22dd96a4472826 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 19 Jul 2023 11:58:02 +0300 Subject: [PATCH 350/678] Do more futility pruning for cutNodes that are not in TT This is somewhat similar to IIR for cutnodes but instead of reducing depth for cutnodes that don't have tt move we reduce margin multiplier in futility pruning for cutnodes that are not in TT. Passed STC: https://tests.stockfishchess.org/tests/view/64b244b90cdec37b95734c5b LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 75552 W: 19404 L: 19029 D: 37119 Ptnml(0-2): 233, 8806, 19378, 9071, 288 Passed LTC: https://tests.stockfishchess.org/tests/view/64b3ae5a0cdec37b95736516 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 104988 W: 27152 L: 26697 D: 51139 Ptnml(0-2): 41, 11259, 29446, 11700, 48 closes https://github.com/official-stockfish/Stockfish/pull/4700 bench 1727577 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 61c75d7d..8fdaca7c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -63,8 +63,8 @@ namespace { enum NodeType { NonPV, PV, Root }; // Futility margin - Value futility_margin(Depth d, bool improving) { - return Value(140 * (d - improving)); + Value futility_margin(Depth d, bool noTtCutNode, bool improving) { + return Value((140 - 40 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup @@ -767,7 +767,7 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 9 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 306 >= beta + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; From 4b2979760f3862700c6a0b8d3ab0f6a6e0a638c0 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Jul 2023 09:41:55 +0200 Subject: [PATCH 351/678] Check clock more often This patch changes the frequency with which the time is checked, changing frequency from every 1024 counted nodes to every 512 counted nodes. The master value was tuned for the old classical eval, the patch takes the roughly 2x slowdown in nps with SFNNUEv7 into account. This could reduce a bit the losses on time on fishtest, but they are probably unrelated. passed STC: https://tests.stockfishchess.org/tests/view/64bb8ae5dc56e1650abb1b11 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 76576 W: 19677 L: 19501 D: 37398 Ptnml(0-2): 274, 8592, 20396, 8736, 290 closes https://github.com/official-stockfish/Stockfish/pull/4704 No functional change --- src/search.cpp | 4 ++-- src/timeman.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8fdaca7c..db9a5a8d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1828,7 +1828,7 @@ void MainThread::check_time() { return; // When using nodes, ensure checking rate is not lower than 0.1% of nodes - callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024; + callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512; static TimePoint lastInfoTime = now(); @@ -1845,7 +1845,7 @@ void MainThread::check_time() { if (ponder) return; - if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit)) + if ( (Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) || (Limits.movetime && elapsed >= Limits.movetime) || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) Threads.stop = true; diff --git a/src/timeman.cpp b/src/timeman.cpp index 061de018..169c7821 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -100,7 +100,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // Never use more than 80% of the available time for this move optimumTime = TimePoint(optScale * timeLeft); - maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)); + maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; if (Options["Ponder"]) optimumTime += optimumTime / 4; From 78e3d2ad78f1835d627ec40b9228e8b7dbb676ef Mon Sep 17 00:00:00 2001 From: windfishballad Date: Sat, 22 Jul 2023 20:35:40 -0400 Subject: [PATCH 352/678] Simplify some qsearch conditions Use the assert which ensures that beta == alpha+1 at PVNodes to simplify a little bit the conditions further down in the code. passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 56160 W: 14370 L: 14173 D: 27617 Ptnml(0-2): 210, 6192, 15076, 6395, 207 https://tests.stockfishchess.org/tests/view/64bc769cdc56e1650abb2e26 closes https://tests.stockfishchess.org/tests/view/64bc769cdc56e1650abb2e26 No functional change --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index db9a5a8d..616d1133 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1496,7 +1496,7 @@ moves_loop: // When in check, search starts here return bestValue; } - if (PvNode && bestValue > alpha) + if (bestValue > alpha) alpha = bestValue; futilityBase = bestValue + 200; @@ -1608,7 +1608,7 @@ moves_loop: // When in check, search starts here if (PvNode) // Update pv even in fail-high case update_pv(ss->pv, move, (ss+1)->pv); - if (PvNode && value < beta) // Update alpha here! + if (value < beta) // Update alpha here! alpha = value; else break; // Fail high From 76e1e8fd39a08c0586259a76c880c3267cb85f62 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 20 Jul 2023 22:12:15 +0200 Subject: [PATCH 353/678] Simplify TT cutoff Remove the exact bound condition from TT depth check. STC: https://tests.stockfishchess.org/tests/view/64b30b320cdec37b957359e9 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 332928 W: 84895 L: 85003 D: 163030 Ptnml(0-2): 1242, 39200, 85604, 39260, 1158 LTC: https://tests.stockfishchess.org/tests/view/64b74e2adc56e1650abac0b6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 92946 W: 23628 L: 23482 D: 45836 Ptnml(0-2): 38, 10033, 26192, 10165, 45 closes https://github.com/official-stockfish/Stockfish/pull/4702 Bench: 1601764 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 616d1133..c2d35796 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -616,7 +616,7 @@ namespace { // At non-PV nodes we check for an early TT cutoff if ( !PvNode && !excludedMove - && tte->depth() > depth - (tte->bound() == BOUND_EXACT) + && tte->depth() > depth && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { From 027713c4b4738446818aedfbfb480e962b624e31 Mon Sep 17 00:00:00 2001 From: Stephen Touset Date: Tue, 25 Jul 2023 00:06:14 +0200 Subject: [PATCH 354/678] Remove Zobrist::noPawns Zobrist::noPawns is no longer used. closes https://github.com/official-stockfish/Stockfish/pull/4344 no functional change --- AUTHORS | 1 + src/position.cpp | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index b345ff0b..2f323d64 100644 --- a/AUTHORS +++ b/AUTHORS @@ -205,6 +205,7 @@ Stefano Cardanobile (Stefano80) Stefano Di Martino (StefanoD) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) +Stephen Touset (stouset) Syine Mineta (MinetaS) Thanar2 thaspel diff --git a/src/position.cpp b/src/position.cpp index 31cdbc06..16181e96 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -42,7 +42,7 @@ namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; Key enpassant[FILE_NB]; Key castling[CASTLING_RIGHT_NB]; - Key side, noPawns; + Key side; } namespace { @@ -125,7 +125,6 @@ void Position::init() { Zobrist::castling[cr] = rng.rand(); Zobrist::side = rng.rand(); - Zobrist::noPawns = rng.rand(); // Prepare the cuckoo tables std::memset(cuckoo, 0, sizeof(cuckoo)); From 2667316ffcf1b3396e42be3d5cb6bcbdcc98c216 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 25 Jul 2023 13:55:29 +0300 Subject: [PATCH 355/678] Simplify one multicut extension Simplify away the ttValue <= alpha extension in the multicut block. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 318336 W: 81307 L: 81398 D: 155631 Ptnml(0-2): 1088, 37291, 82469, 37264, 1056 https://tests.stockfishchess.org/tests/view/64b8589fdc56e1650abad61d Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 89388 W: 22925 L: 22775 D: 43688 Ptnml(0-2): 34, 9635, 25210, 9777, 38 https://tests.stockfishchess.org/tests/view/64bc41d0dc56e1650abb29cb closes https://github.com/official-stockfish/Stockfish/pull/4709 bench: 1604592 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c2d35796..96b29d1e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1104,10 +1104,6 @@ moves_loop: // When in check, search starts here // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; - - // If the eval of ttMove is less than alpha, we reduce it (negative extension) (~1 Elo) - else if (ttValue <= alpha) - extension = -1; } // Check extensions (~1 Elo) From cb22520a9c7e1d716a56f08390aa638a23a597b2 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 24 Jul 2023 19:02:49 -0700 Subject: [PATCH 356/678] Remove unused return type from propagate() Also make two get_weight_index() static methods constexpr, for consistency with the other static get_hash_value() method right above. Tested for speed by user Torom (thanks). closes https://github.com/official-stockfish/Stockfish/pull/4708 No functional change --- src/nnue/layers/affine_transform.h | 8 +++----- src/nnue/layers/affine_transform_sparse_input.h | 9 +++------ src/nnue/layers/clipped_relu.h | 4 +--- src/nnue/layers/sqr_clipped_relu.h | 4 +--- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index b0169306..c936a83e 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -171,7 +171,7 @@ namespace Stockfish::Eval::NNUE::Layers { return hashValue; } - static IndexType get_weight_index_scrambled(IndexType i) + static constexpr IndexType get_weight_index_scrambled(IndexType i) { return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + @@ -179,7 +179,7 @@ namespace Stockfish::Eval::NNUE::Layers { i % 4; } - static IndexType get_weight_index(IndexType i) + static constexpr IndexType get_weight_index(IndexType i) { #if defined (USE_SSSE3) return get_weight_index_scrambled(i); @@ -207,7 +207,7 @@ namespace Stockfish::Eval::NNUE::Layers { return !stream.fail(); } // Forward propagation - const OutputType* propagate( + void propagate( const InputType* input, OutputType* output) const { #if defined (USE_AVX512) @@ -291,8 +291,6 @@ namespace Stockfish::Eval::NNUE::Layers { PaddedInputDimensions, OutputDimensions>(output, weights, biases, input); #endif - - return output; } private: diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index a5bea08e..134b7d13 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -102,7 +102,6 @@ namespace Stockfish::Eval::NNUE::Layers { template class AffineTransformSparseInput { public: - // Input/output type // Input/output type using InputType = std::uint8_t; using OutputType = std::int32_t; @@ -135,7 +134,7 @@ namespace Stockfish::Eval::NNUE::Layers { return hashValue; } - static IndexType get_weight_index_scrambled(IndexType i) + static constexpr IndexType get_weight_index_scrambled(IndexType i) { return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + @@ -143,7 +142,7 @@ namespace Stockfish::Eval::NNUE::Layers { i % ChunkSize; } - static IndexType get_weight_index(IndexType i) + static constexpr IndexType get_weight_index(IndexType i) { #if defined (USE_SSSE3) return get_weight_index_scrambled(i); @@ -171,7 +170,7 @@ namespace Stockfish::Eval::NNUE::Layers { return !stream.fail(); } // Forward propagation - const OutputType* propagate( + void propagate( const InputType* input, OutputType* output) const { #if defined (USE_SSSE3) @@ -230,8 +229,6 @@ namespace Stockfish::Eval::NNUE::Layers { PaddedInputDimensions, OutputDimensions>(output, weights, biases, input); #endif - - return output; } private: diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 51e562da..d5aa6fbf 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -59,7 +59,7 @@ namespace Stockfish::Eval::NNUE::Layers { } // Forward propagation - const OutputType* propagate( + void propagate( const InputType* input, OutputType* output) const { #if defined(USE_AVX2) @@ -170,8 +170,6 @@ namespace Stockfish::Eval::NNUE::Layers { output[i] = static_cast( std::max(0, std::min(127, input[i] >> WeightScaleBits))); } - - return output; } }; diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 3fbb243c..69bd5147 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -59,7 +59,7 @@ namespace Stockfish::Eval::NNUE::Layers { } // Forward propagation - const OutputType* propagate( + void propagate( const InputType* input, OutputType* output) const { #if defined(USE_SSE2) @@ -110,8 +110,6 @@ namespace Stockfish::Eval::NNUE::Layers { // needs to be accounted for in the trainer std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128))); } - - return output; } }; From f84eb1f3ef9dc4078368b849f8deb55982882390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Fri, 28 Jul 2023 23:38:37 +0200 Subject: [PATCH 357/678] Improve some comments - clarify the examples for the bench command - typo in search.cpp closes https://github.com/official-stockfish/Stockfish/pull/4710 No functional change --- src/benchmark.cpp | 15 +++++++-------- src/nnue/evaluate_nnue.cpp | 6 +++--- src/search.cpp | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index c41092a9..e340ebcd 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -100,15 +100,14 @@ namespace Stockfish { /// setup_bench() builds a list of UCI commands to be run by bench. There /// are five parameters: TT size in MB, number of search threads that /// should be used, the limit value spent for each position, a file name -/// where to look for positions in FEN format, the type of the limit: -/// depth, perft, nodes and movetime (in millisecs), and evaluation type -/// mixed (default), classical, NNUE. +/// where to look for positions in FEN format, and the type of the limit: +/// depth, perft, nodes and movetime (in milliseconds). Examples: /// -/// bench -> search default positions up to depth 13 -/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB) -/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec -/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each -/// bench 16 1 5 default perft -> run a perft 5 on default positions +/// bench : search default positions up to depth 13 +/// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB) +/// bench 64 1 100000 default nodes : search default positions for 100K nodes each +/// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec +/// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" vector setup_bench(const Position& current, istream& is) { diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index d90f59a2..cff1d024 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -82,6 +82,7 @@ namespace Stockfish::Eval::NNUE { } // namespace Detail + // Initialize the evaluation function parameters static void initialize() { @@ -187,7 +188,6 @@ namespace Stockfish::Eval::NNUE { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. - constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) @@ -249,8 +249,9 @@ namespace Stockfish::Eval::NNUE { } - // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals. + // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals static void format_cp_aligned_dot(Value v, std::stringstream &stream) { + const double pawns = std::abs(0.01 * UCI::to_cp(v)); stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') @@ -263,7 +264,6 @@ namespace Stockfish::Eval::NNUE { // trace() returns a string with the value of each piece on a board, // and a table for (PSQT, Layers) values bucket by bucket. - std::string trace(Position& pos) { std::stringstream ss; diff --git a/src/search.cpp b/src/search.cpp index 96b29d1e..45758031 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -745,8 +745,8 @@ namespace { } // Set up the improving flag, which is true if current static evaluation is - // bigger than the previous static evaluation at our turn (if we were in - // check at our previous move we look at static evaluaion at move prior to it + // bigger than the previous static evaluation at our turn (if we were in + // check at our previous move we look at static evaluation at move prior to it // and if we were in check at move prior to it flag is set to true) and is // false otherwise. The improving flag is used in various pruning heuristics. improving = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval > (ss-2)->staticEval From 65ece7d985291cc787d6c804a33f1dd82b75736d Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Wed, 26 Jul 2023 14:31:16 +0200 Subject: [PATCH 358/678] Malus during move ordering for putting pieces en prise The original idea is the reverse of a previous patch [1] which added bonuses in our move picker to moves escaping threats. In this patch, in addition to bonuses for evading threats, we apply penalties to moves moving to threatened squares. Further tweaks of that basic idea resulted in this specific version which further increases the penalty of moves moving to squares threatend depending on the piece threatening it. So for example a queen moving to a square attacked by a pawn would receive a larger penalty than a queen moving to square attacked by a rook. [1]: https://github.com/official-stockfish/Stockfish/commit/08e0f52b77edb929989c68c49e954b9bc5d7d67e -------- Passed STC: https://tests.stockfishchess.org/tests/live_elo/64c11269dc56e1650abb935d LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 95552 W: 24654 L: 24250 D: 46648 Ptnml(0-2): 322, 11098, 24562, 11442, 352 Passed LTC: https://tests.stockfishchess.org/tests/live_elo/64c2004ddc56e1650abba8b3 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 190230 W: 48806 L: 48178 D: 93246 Ptnml(0-2): 90, 20439, 53453, 21019, 114 ------- closes https://github.com/official-stockfish/Stockfish/pull/4711 Bench: 1350831 --- src/movepick.cpp | 50 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 6fbcb2c3..40508103 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -123,21 +123,45 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = (7 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; else if constexpr (Type == QUIETS) - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)] - + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] - + (threatenedPieces & from_sq(m) ? - (type_of(pos.moved_piece(m)) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000 - : type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000 - : !(to_sq(m) & threatenedByPawn) ? 15000 - : 0) - : 0) - + bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384; + { + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pos.moved_piece(m)); + Square from = from_sq(m); + Square to = to_sq(m); + + // histories + m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value += 2 * (*continuationHistory[0])[pc][to]; + m.value += (*continuationHistory[1])[pc][to]; + m.value += (*continuationHistory[3])[pc][to]; + m.value += (*continuationHistory[5])[pc][to]; + + // bonus for checks + m.value += bool(pos.check_squares(pt) & to) * 16384; + + // bonus for escaping from capture + m.value += threatenedPieces & from ? + (pt == QUEEN && !(to & threatenedByRook) ? 50000 + : pt == ROOK && !(to & threatenedByMinor) ? 25000 + : !(to & threatenedByPawn) ? 15000 + : 0 ) + : 0 ; + + // malus for putting piece en prise + m.value -= !(threatenedPieces & from) ? + (pt == QUEEN ? bool(to & threatenedByRook) * 50000 + + bool(to & threatenedByMinor) * 10000 + + bool(to & threatenedByPawn) * 20000 + : pt == ROOK ? bool(to & threatenedByMinor) * 25000 + + bool(to & threatenedByPawn) * 10000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0 ) + : 0 ; + } + else // Type == EVASIONS { if (pos.capture_stage(m)) From 002a50457ce1975bf049b7bac904f23d0eab4d3b Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 29 Jul 2023 14:34:58 +0000 Subject: [PATCH 359/678] Identify NEON_DOTPROD in compiler_info() closes https://github.com/official-stockfish/Stockfish/pull/4712 No functional change --- src/misc.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index f1554060..29ef757e 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -280,7 +280,9 @@ std::string compiler_info() { #if defined(USE_MMX) compiler += " MMX"; #endif - #if defined(USE_NEON) + #if defined(USE_NEON_DOTPROD) + compiler += " NEON_DOTPROD"; + #elif defined(USE_NEON) compiler += " NEON"; #endif From 4c43e1e27ce990735fb0226e35248fc82ea6a519 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Mon, 31 Jul 2023 13:41:28 +0200 Subject: [PATCH 360/678] Add new CPU archs in CI Tests workflow Add CPU archs: armv8-dotprod, riscv64 and ppc64le. The last two archs are built using QEMU multiarch docker container. References: https://docs.docker.com/build/building/multi-platform/ https://github.com/docker/setup-buildx-action https://github.com/docker/setup-qemu-action https://github.com/tonistiigi/binfmt https://stackoverflow.com/questions/72444103/what-does-running-the-multiarch-qemu-user-static-does-before-building-a-containe closes https://github.com/official-stockfish/Stockfish/pull/4718 No functional change --- .github/workflows/stockfish_test.yml | 63 +++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index cb6c4c59..307d3a02 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -38,6 +38,22 @@ jobs: comp: ndk run_armv7_tests: true shell: bash + - name: Linux GCC riscv64 + os: ubuntu-22.04 + compiler: g++ + comp: gcc + run_riscv64_tests: true + base_image: 'riscv64/alpine:edge' + platform: linux/riscv64 + shell: bash + - name: Linux GCC ppc64 + os: ubuntu-22.04 + compiler: g++ + comp: gcc + run_ppc64_tests: true + base_image: 'ppc64le/alpine:latest' + platform: linux/ppc64le + shell: bash - name: MacOS 13 Apple Clang os: macos-13 compiler: clang++ @@ -87,7 +103,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt update - sudo apt install expect valgrind g++-multilib qemu-user + sudo apt install expect valgrind g++-multilib qemu-user-static - name: Install NDK if: runner.os == 'Linux' @@ -103,6 +119,24 @@ jobs: echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV fi + - name: Set up QEMU + if: matrix.config.base_image + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + if: matrix.config.base_image + uses: docker/setup-buildx-action@v2 + + - name: Build Docker container + if: matrix.config.base_image + run: | + docker buildx build --load -t sf_builder - << EOF + FROM ${{ matrix.config.base_image }} + WORKDIR /app + RUN apk update && apk add make g++ + CMD sh make_sf.sh + EOF + - name: Download required macOS packages if: runner.os == 'macOS' run: brew install coreutils @@ -253,6 +287,15 @@ jobs: make -j2 ARCH=armv8 build ../tests/signature.sh $benchref + - name: Test armv8-dotprod build + if: matrix.config.run_armv8_tests + run: | + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + make clean + make -j2 ARCH=armv8-dotprod build + ../tests/signature.sh $benchref + # armv7 tests - name: Test armv7 build @@ -273,6 +316,24 @@ jobs: make -j2 ARCH=armv7-neon build ../tests/signature.sh $benchref + # riscv64 tests + + - name: Test riscv64 build + if: matrix.config.run_riscv64_tests + run: | + echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > make_sf.sh + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder + ../tests/signature.sh $benchref + + # ppc64 tests + + - name: Test ppc64 build + if: matrix.config.run_ppc64_tests + run: | + echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > make_sf.sh + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder + ../tests/signature.sh $benchref + # Other tests - name: Check perft and search reproducibility From a6d9a302b867a76c3df5b658de6206e77b649a4d Mon Sep 17 00:00:00 2001 From: AndrovT <31534597+AndrovT@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:43:37 +0200 Subject: [PATCH 361/678] Implement AffineTransformSparseInput for armv8 Implements AffineTransformSparseInput layer for the NNUE evaluation for the armv8 and armv8-dotprod architectures. We measured some nice speed improvements via 10 runs of our benchmark: armv8, Cortex-X1 : 18.5% speed-up armv8, Cortex-A76 : 13.2% speed-up armv8-dotprod, Cortex-X1 : 27.1% speed-up armv8-dotprod, Cortex-A76 : 12.1% speed-up armv8, Cortex-A72, Raspberry Pi 4 : 8.2% speed-up (thanks Torom!) closes https://github.com/official-stockfish/Stockfish/pull/4719 No functional change --- .../layers/affine_transform_sparse_input.h | 100 ++++++++++++------ src/nnue/layers/simd.h | 18 +++- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 134b7d13..63cbaf45 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -35,7 +35,7 @@ namespace Stockfish::Eval::NNUE::Layers { -#if defined(USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ std::array, 256> v{}; for (unsigned i = 0; i < 256; ++i) @@ -50,19 +50,37 @@ namespace Stockfish::Eval::NNUE::Layers { // Find indices of nonzero numbers in an int32_t array template void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { -#if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) -#elif defined (USE_AVX2) - using vec_t = __m256i; - #if defined(USE_VNNI) && !defined(USE_AVXVNNI) - #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) - #else - #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) +#if defined (USE_SSSE3) + #if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) + #elif defined (USE_AVX2) + using vec_t = __m256i; + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif + #elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) #endif -#elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) + using vec128_t = __m128i; + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #define vec128_load(a) _mm_load_si128(a) + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) +#elif defined (USE_NEON) + using vec_t = int32x4_t; + static const std::uint32_t Mask[4] = {1, 2, 4, 8}; + #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) + using vec128_t = int16x8_t; + #define vec128_zero vdupq_n_u16(0) + #define vec128_set_16(a) vdupq_n_u16(a) + #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) + #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) + #define vec128_add(a, b) vaddq_u16(a, b) #endif constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) @@ -73,8 +91,8 @@ namespace Stockfish::Eval::NNUE::Layers { const auto inputVector = reinterpret_cast(input); IndexType count = 0; - __m128i base = _mm_setzero_si128(); - const __m128i increment = _mm_set1_epi16(8); + vec128_t base = vec128_zero; + const vec128_t increment = vec128_set_16(8); for (IndexType i = 0; i < NumChunks; ++i) { // bitmask of nonzero values in this chunk @@ -87,15 +105,20 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType j = 0; j < OutputsPerChunk; ++j) { const auto lookup = (nnz >> (j * 8)) & 0xFF; - const auto offsets = _mm_loadu_si128(reinterpret_cast(&lookup_indices[lookup])); - _mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets)); + const auto offsets = vec128_load(reinterpret_cast(&lookup_indices[lookup])); + vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); count += popcount(lookup); - base = _mm_add_epi16(base, increment); + base = vec128_add(base, increment); } } count_out = count; } # undef vec_nnz +# undef vec128_zero +# undef vec128_set_16 +# undef vec128_load +# undef vec128_storeu +# undef vec128_add #endif // Sparse input implementation @@ -117,7 +140,7 @@ namespace Stockfish::Eval::NNUE::Layers { static constexpr IndexType PaddedOutputDimensions = ceil_to_multiple(OutputDimensions, MaxSimdWidth); -#if defined (USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) static constexpr IndexType ChunkSize = 4; #else static constexpr IndexType ChunkSize = 1; @@ -144,7 +167,7 @@ namespace Stockfish::Eval::NNUE::Layers { static constexpr IndexType get_weight_index(IndexType i) { -#if defined (USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) return get_weight_index_scrambled(i); #else return i; @@ -173,24 +196,34 @@ namespace Stockfish::Eval::NNUE::Layers { void propagate( const InputType* input, OutputType* output) const { -#if defined (USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) #if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_setzero _mm512_setzero_si512 + using invec_t = __m512i; + using outvec_t = __m512i; #define vec_set_32 _mm512_set1_epi32 #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 #elif defined (USE_AVX2) - using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 + using invec_t = __m256i; + using outvec_t = __m256i; #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 #elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 + using invec_t = __m128i; + using outvec_t = __m128i; #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 +#elif defined (USE_NEON_DOTPROD) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 +#elif defined (USE_NEON) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 #endif - static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; @@ -202,24 +235,23 @@ namespace Stockfish::Eval::NNUE::Layers { // Find indices of nonzero 32bit blocks find_nnz(input32, nnz, count); - const vec_t* biasvec = reinterpret_cast(biases); - vec_t acc[NumRegs]; + const outvec_t* biasvec = reinterpret_cast(biases); + outvec_t acc[NumRegs]; for (IndexType k = 0; k < NumRegs; ++k) acc[k] = biasvec[k]; for (IndexType j = 0; j < count; ++j) { const auto i = nnz[j]; - const vec_t in = vec_set_32(input32[i]); - const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + const invec_t in = vec_set_32(input32[i]); + const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); for (IndexType k = 0; k < NumRegs; ++k) vec_add_dpbusd_32(acc[k], in, col[k]); } - vec_t* outptr = reinterpret_cast(output); + outvec_t* outptr = reinterpret_cast(output); for (IndexType k = 0; k < NumRegs; ++k) outptr[k] = acc[k]; -# undef vec_setzero # undef vec_set_32 # undef vec_add_dpbusd_32 #else diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index fae31a62..638e3994 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -239,6 +239,12 @@ namespace Stockfish::Simd { acc = vdotq_s32(acc, a1, b1); } + [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32( + int32x4_t& acc, + int8x16_t a, int8x16_t b) { + + acc = vdotq_s32(acc, a, b); + } #endif #if defined (USE_NEON) @@ -277,9 +283,19 @@ namespace Stockfish::Simd { product = vmlal_s8(product, a1, b1); acc = vpadalq_s16(acc, product); } - #endif +#if USE_NEON >= 8 + [[maybe_unused]] static void neon_m128_add_dpbusd_epi32( + int32x4_t& acc, + int8x16_t a, int8x16_t b) { + + int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); + int16x8_t product1 = vmull_high_s8(a, b); + int16x8_t sum = vpaddq_s16(product0, product1); + acc = vpadalq_s16(acc, sum); + } +#endif } #endif // STOCKFISH_SIMD_H_INCLUDED From 0ad9b51deaaa1f2a8273ed064fbf6425cfbbe4f2 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 24 Jul 2023 01:22:21 -0400 Subject: [PATCH 362/678] Remove classical psqt Based on vondele's deletepsqt branch: https://github.com/vondele/Stockfish/commit/369f5b051 This huge simplification uses a weighted material differences instead of the positional piece square tables (psqt) in the semi-classical complexity calculation. Tuned weights using spsa at 45+0.45 with: int pawnMult = 100; int knightMult = 325; int bishopMult = 350; int rookMult = 500; int queenMult = 900; TUNE(SetRange(0, 200), pawnMult); TUNE(SetRange(0, 650), knightMult); TUNE(SetRange(0, 700), bishopMult); TUNE(SetRange(200, 800), rookMult); TUNE(SetRange(600, 1200), queenMult); The values obtained via this tuning session were for a model where the psqt replacement formula was always from the point of view of White, even if the side to move was Black. We re-used the same values for an implementation with a psqt replacement from the point of view of the side to move, testing the result both on our standard book on positions with a strong White bias, and an alternate book with positions with a strong Black bias. We note that with the patch the last use of the venerable "Score" type disappears in Stockfish codebase (the Score type was used in classical evaluation to get a tampered eval interpolating values smoothly from the early midgame stage to the endgame stage). We leave it to another commit to clean all occurrences of Score in the code and the comments. ------- Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142542 W: 36264 L: 36168 D: 70110 Ptnml(0-2): 76, 15578, 39856, 15696, 65 https://tests.stockfishchess.org/tests/view/64c8cb495b17f7c21c0cf9f8 Passed non-regression LTC (with a book with Black bias): https://tests.stockfishchess.org/tests/view/64c8f9295b17f7c21c0cfdaf LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 494814 W: 125565 L: 125827 D: 243422 Ptnml(0-2): 244, 53926, 139346, 53630, 261 ------ closes https://github.com/official-stockfish/Stockfish/pull/4713 Bench: 1655985 --- src/Makefile | 2 +- src/evaluate.cpp | 9 +++- src/main.cpp | 2 - src/position.h | 10 ---- src/psqt.cpp | 131 ----------------------------------------------- src/psqt.h | 38 -------------- 6 files changed, 8 insertions(+), 184 deletions(-) delete mode 100644 src/psqt.cpp delete mode 100644 src/psqt.h diff --git a/src/Makefile b/src/Makefile index f66d84d5..8811d15e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -53,7 +53,7 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench ### Source and object files SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ - misc.cpp movegen.cpp movepick.cpp position.cpp psqt.cpp \ + misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 7f0ea4bc..c37dd98a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -143,7 +143,6 @@ Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); Value v; - Value psq = pos.psq_eg_stm(); int nnueComplexity; int npm = pos.non_pawn_material() / 64; @@ -153,8 +152,14 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + int material = 67 * (pos.count(stm) - pos.count(~stm)) + + 395 * (pos.count(stm) - pos.count(~stm)) + + 288 * (pos.count(stm) - pos.count(~stm)) + + 630 * (pos.count(stm) - pos.count(~stm)) + + 857 * (pos.count(stm) - pos.count(~stm)); + // Blend optimism with nnue complexity and (semi)classical complexity - optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; + optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; v = ( nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm + pos.count())) / 1024; diff --git a/src/main.cpp b/src/main.cpp index 593408f6..c854ac0c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,6 @@ #include "bitboard.h" #include "position.h" -#include "psqt.h" #include "search.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -36,7 +35,6 @@ int main(int argc, char* argv[]) { CommandLine::init(argc, argv); UCI::init(Options); Tune::init(); - PSQT::init(); Bitboards::init(); Position::init(); Threads.set(size_t(Options["Threads"])); diff --git a/src/position.h b/src/position.h index dc4c5837..393c1ac9 100644 --- a/src/position.h +++ b/src/position.h @@ -26,7 +26,6 @@ #include "bitboard.h" #include "evaluate.h" -#include "psqt.h" #include "types.h" #include "nnue/nnue_accumulator.h" @@ -153,7 +152,6 @@ public: bool has_game_cycle(int ply) const; bool has_repeated() const; int rule50_count() const; - Value psq_eg_stm() const; Value non_pawn_material(Color c) const; Value non_pawn_material() const; @@ -192,7 +190,6 @@ private: StateInfo* st; int gamePly; Color sideToMove; - Score psq; bool chess960; }; @@ -321,10 +318,6 @@ inline Key Position::material_key() const { return st->materialKey; } -inline Value Position::psq_eg_stm() const { - return (sideToMove == WHITE ? 1 : -1) * eg_value(psq); -} - inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } @@ -374,7 +367,6 @@ inline void Position::put_piece(Piece pc, Square s) { byColorBB[color_of(pc)] |= s; pieceCount[pc]++; pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; - psq += PSQT::psq[pc][s]; } inline void Position::remove_piece(Square s) { @@ -386,7 +378,6 @@ inline void Position::remove_piece(Square s) { board[s] = NO_PIECE; pieceCount[pc]--; pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; - psq -= PSQT::psq[pc][s]; } inline void Position::move_piece(Square from, Square to) { @@ -398,7 +389,6 @@ inline void Position::move_piece(Square from, Square to) { byColorBB[color_of(pc)] ^= fromTo; board[from] = NO_PIECE; board[to] = pc; - psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; } inline void Position::do_move(Move m, StateInfo& newSt) { diff --git a/src/psqt.cpp b/src/psqt.cpp deleted file mode 100644 index d3ebb20d..00000000 --- a/src/psqt.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - - -#include "psqt.h" - -#include - -#include "bitboard.h" -#include "types.h" - -namespace Stockfish { - -namespace -{ - -auto constexpr S = make_score; - -// 'Bonus' contains Piece-Square parameters. -// Scores are explicit for files A to D, implicitly mirrored for E to H. -constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { - { }, - { }, - { // Knight - { S(-175, -96), S(-92,-65), S(-74,-49), S(-73,-21) }, - { S( -77, -67), S(-41,-54), S(-27,-18), S(-15, 8) }, - { S( -61, -40), S(-17,-27), S( 6, -8), S( 12, 29) }, - { S( -35, -35), S( 8, -2), S( 40, 13), S( 49, 28) }, - { S( -34, -45), S( 13,-16), S( 44, 9), S( 51, 39) }, - { S( -9, -51), S( 22,-44), S( 58,-16), S( 53, 17) }, - { S( -67, -69), S(-27,-50), S( 4,-51), S( 37, 12) }, - { S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) } - }, - { // Bishop - { S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) }, - { S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) }, - { S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) }, - { S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) }, - { S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) }, - { S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) }, - { S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) }, - { S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) } - }, - { // Rook - { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) }, - { S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) }, - { S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) }, - { S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) }, - { S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) }, - { S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) }, - { S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) }, - { S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) } - }, - { // Queen - { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) }, - { S(-3,-54), S( 5,-31), S( 8,-22), S(12, -4) }, - { S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) }, - { S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) }, - { S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) }, - { S(-4,-38), S(10,-18), S( 6,-11), S( 8, 1) }, - { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) }, - { S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) } - }, - { // King - { S(271, 1), S(327, 45), S(271, 85), S(198, 76) }, - { S(278, 53), S(303,100), S(234,133), S(179,135) }, - { S(195, 88), S(258,130), S(169,169), S(120,175) }, - { S(164,103), S(190,156), S(138,172), S( 98,172) }, - { S(154, 96), S(179,166), S(105,199), S( 70,199) }, - { S(123, 92), S(145,172), S( 81,184), S( 31,191) }, - { S( 88, 47), S(120,121), S( 65,116), S( 33,131) }, - { S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) } - } -}; - -constexpr Score PBonus[RANK_NB][FILE_NB] = - { // Pawn (asymmetric distribution) - { }, - { S( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) }, - { S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) }, - { S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) }, - { S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) }, - { S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) }, - { S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) } - }; - -} // namespace - - -namespace PSQT -{ - -Score psq[PIECE_NB][SQUARE_NB]; - -// PSQT::init() initializes piece-square tables: the white halves of the tables are -// copied from Bonus[] and PBonus[], adding the piece value, then the black halves of -// the tables are initialized by flipping and changing the sign of the white scores. -void init() { - - for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING}) - { - Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); - - for (Square s = SQ_A1; s <= SQ_H8; ++s) - { - File f = File(edge_distance(file_of(s))); - psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] - : Bonus[pc][rank_of(s)][f]); - psq[~pc][flip_rank(s)] = -psq[pc][s]; - } - } -} - -} // namespace PSQT - -} // namespace Stockfish diff --git a/src/psqt.h b/src/psqt.h deleted file mode 100644 index 9630f446..00000000 --- a/src/psqt.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - - -#ifndef PSQT_H_INCLUDED -#define PSQT_H_INCLUDED - - -#include "types.h" - - -namespace Stockfish::PSQT -{ - -extern Score psq[PIECE_NB][SQUARE_NB]; - -// Fill psqt array from a set of internally linked parameters -void init(); - -} // namespace Stockfish::PSQT - - -#endif // PSQT_H_INCLUDED From a26f8d37e108c103ada129e619a17597c7e50046 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 5 Aug 2023 14:21:08 +0300 Subject: [PATCH 363/678] Tweak formula for pruning moves losing material Simplify the "Prune moves with negative SEE" formula, by removing one multiplication and subtraction operation. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 214272 W: 54596 L: 54572 D: 105104 Ptnml(0-2): 741, 25160, 55320, 25164, 751 https://tests.stockfishchess.org/tests/view/64c430d1dc56e1650abbdbf6 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 238380 W: 60600 L: 60601 D: 117179 Ptnml(0-2): 132, 26069, 66791, 26064, 134 https://tests.stockfishchess.org/tests/view/64c81f155b17f7c21c0cee2b closes https://github.com/official-stockfish/Stockfish/pull/4721 bench: 1655337 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 45758031..dc439ed0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1037,7 +1037,7 @@ 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, Value(-27 * lmrDepth * lmrDepth - 16 * lmrDepth))) + if (!pos.see_ge(move, Value(-31 * lmrDepth * lmrDepth))) continue; } } From 5c2111cc30b283aa5b7e1cc1c1e9d7c52e1e910b Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 7 Aug 2023 02:32:38 +0300 Subject: [PATCH 364/678] Adjust futility pruning base in qsearch Current master used value from transposition table there if it existed, this patch uses minimum between this tt value and the static eval instead (this thus is closer to the main search function, which uses the static eval). Passed STC: https://tests.stockfishchess.org/tests/view/64cd57285b17f7c21c0d6a8c LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 252544 W: 64671 L: 64039 D: 123834 Ptnml(0-2): 839, 29207, 65575, 29785, 866 Passed LTC: https://tests.stockfishchess.org/tests/view/64cf6c915b17f7c21c0d9fcb LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 60150 W: 15374 L: 15012 D: 29764 Ptnml(0-2): 24, 6321, 17024, 6681, 25 closes https://github.com/official-stockfish/Stockfish/pull/4725 Bench: 1573024 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index dc439ed0..24f54c32 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1495,7 +1495,7 @@ moves_loop: // When in check, search starts here if (bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 200; + futilityBase = std::min(ss->staticEval, bestValue) + 200; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, From e64b817e0a2335824ef1f1f7ba9f5cd4310994e1 Mon Sep 17 00:00:00 2001 From: Cody Ho Date: Sun, 6 Aug 2023 14:21:22 -0700 Subject: [PATCH 365/678] Remove all references to Score type Score is obsolete with the removal of psqt. No functional change. Signed-off-by: Cody Ho closes https://github.com/official-stockfish/Stockfish/pull/4724 --- src/tune.cpp | 13 ------------- src/tune.h | 6 ++---- src/types.h | 55 ---------------------------------------------------- 3 files changed, 2 insertions(+), 72 deletions(-) diff --git a/src/tune.cpp b/src/tune.cpp index 41f6664d..ccfc33c5 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -92,19 +92,6 @@ template<> void Tune::Entry::read_option() { value = Value(int(Options[name])); } -template<> void Tune::Entry::init_option() { - make_option("m" + name, mg_value(value), range); - make_option("e" + name, eg_value(value), range); -} - -template<> void Tune::Entry::read_option() { - if (Options.count("m" + name)) - value = make_score(int(Options["m" + name]), eg_value(value)); - - if (Options.count("e" + name)) - value = make_score(mg_value(value), int(Options["e" + name])); -} - // Instead of a variable here we have a PostUpdate function: just call it template<> void Tune::Entry::init_option() {} template<> void Tune::Entry::read_option() { value(); } diff --git a/src/tune.h b/src/tune.h index 440d950a..bdbee14e 100644 --- a/src/tune.h +++ b/src/tune.h @@ -51,18 +51,17 @@ struct SetRange { /// qualifiers from the variables you want to tune and flag them for tuning, so /// if you have: /// -/// const Score myScore = S(10, 15); /// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; /// /// If you have a my_post_update() function to run after values have been updated, /// and a my_range() function to set custom Option's min-max values, then you just /// remove the 'const' qualifiers and write somewhere below in the file: /// -/// TUNE(SetRange(my_range), myScore, myValue, my_post_update); +/// TUNE(SetRange(my_range), myValue, my_post_update); /// /// You can also set the range directly, and restore the default at the end /// -/// TUNE(SetRange(-100, 100), myScore, SetDefaultRange); +/// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); /// /// In case update function is slow and you have many parameters, you can add: /// @@ -98,7 +97,6 @@ class Tune { static_assert( std::is_same::value || std::is_same::value - || std::is_same::value || std::is_same::value, "Parameter type not supported!"); Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} diff --git a/src/types.h b/src/types.h index 06b0a059..b0c11778 100644 --- a/src/types.h +++ b/src/types.h @@ -154,8 +154,6 @@ enum CastlingRights { }; enum Phase { - PHASE_ENDGAME, - PHASE_MIDGAME = 128, MG = 0, EG = 1, PHASE_NB = 2 }; @@ -194,8 +192,6 @@ enum Value : int { BishopValueMg = 825, BishopValueEg = 915, RookValueMg = 1276, RookValueEg = 1380, QueenValueMg = 2538, QueenValueEg = 2682, - - MidgameLimit = 15258, EndgameLimit = 3915 }; enum PieceType { @@ -281,29 +277,6 @@ struct DirtyPiece { Square to[3]; }; -/// Score enum stores a middlegame and an endgame value in a single integer (enum). -/// The least significant 16 bits are used to store the middlegame value and the -/// upper 16 bits are used to store the endgame value. We have to take care to -/// avoid left-shifting a signed int to avoid undefined behavior. -enum Score : int { SCORE_ZERO }; - -constexpr Score make_score(int mg, int eg) { - return Score((int)((unsigned int)eg << 16) + mg); -} - -/// Extracting the signed lower and upper 16 bits is not so trivial because -/// according to the standard a simple cast to short is implementation defined -/// and so is a right shift of a signed integer. -inline Value eg_value(Score s) { - union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; - return Value(eg.s); -} - -inline Value mg_value(Score s) { - union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; - return Value(mg.s); -} - #define ENABLE_BASE_OPERATORS_ON(T) \ constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ @@ -333,8 +306,6 @@ ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) ENABLE_INCR_OPERATORS_ON(Rank) -ENABLE_BASE_OPERATORS_ON(Score) - #undef ENABLE_FULL_OPERATORS_ON #undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON @@ -345,32 +316,6 @@ constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d inline Square& operator+=(Square& s, Direction d) { return s = s + d; } inline Square& operator-=(Square& s, Direction d) { return s = s - d; } -/// Only declared but not defined. We don't want to multiply two scores due to -/// a very high risk of overflow. So user should explicitly convert to integer. -Score operator*(Score, Score) = delete; - -/// Division of a Score must be handled separately for each term -inline Score operator/(Score s, int i) { - return make_score(mg_value(s) / i, eg_value(s) / i); -} - -/// Multiplication of a Score by an integer. We check for overflow in debug mode. -inline Score operator*(Score s, int i) { - - Score result = Score(int(s) * i); - - assert(eg_value(result) == (i * eg_value(s))); - assert(mg_value(result) == (i * mg_value(s))); - assert((i == 0) || (result / i) == s); - - return result; -} - -/// Multiplication of a Score by a boolean -inline Score operator*(Score s, bool b) { - return b ? s : SCORE_ZERO; -} - constexpr Color operator~(Color c) { return Color(c ^ BLACK); // Toggle color } From 0d2ddb81ef44211e7bf40369dc8fc52160d0ee18 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Mon, 7 Aug 2023 13:11:09 +0200 Subject: [PATCH 366/678] Fix Makefile for incorrect nnue file If an incorrect network file is present at the start of the compilation stage, the Makefile script now correctly removes it before trying to download a clean version. closes https://github.com/official-stockfish/Stockfish/pull/4726 No functional change --- src/Makefile | 83 +++++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/src/Makefile b/src/Makefile index 8811d15e..c7e059ea 100644 --- a/src/Makefile +++ b/src/Makefile @@ -869,42 +869,6 @@ install: clean: objclean profileclean @rm -f .depend *~ core -# evaluation network (nnue) -net: - $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) - @echo "Default net: $(nnuenet)" - $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) - $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) - $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) - @if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ - fi - $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) - @if [ "x$(shasum_command)" = "x" ]; then \ - echo "shasum / sha256sum not found, skipping net validation"; \ - fi - @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ - if test -f "$(nnuenet)"; then \ - echo "$(nnuenet) available."; \ - else \ - if [ "x$(curl_or_wget)" != "x" ]; then \ - echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ - else \ - echo "No net found and download not possible"; exit 1;\ - fi; \ - fi; \ - if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Removing failed download"; rm -f $(nnuenet); \ - else \ - echo "Network validated"; break; \ - fi; \ - fi; \ - done - @if ! test -f "$(nnuenet)"; then \ - echo "Failed to download $(nnuenet)."; \ - fi - # clean binaries and objects objclean: @rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o @@ -919,6 +883,53 @@ profileclean: @rm -f stockfish.res @rm -f ./-lstdc++.res +# set up shell variables for the net stuff +netvariables: + $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) + $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) + $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) + $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) + $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) + +# evaluation network (nnue) +net: netvariables + @echo "Default net: $(nnuenet)" + @if [ "x$(curl_or_wget)" = "x" ]; then \ + echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ + fi + @if [ "x$(shasum_command)" = "x" ]; then \ + echo "shasum / sha256sum not found, skipping net validation"; \ + elif test -f "$(nnuenet)"; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing invalid network"; rm -f $(nnuenet); \ + fi; \ + fi; + @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ + if test -f "$(nnuenet)"; then \ + echo "$(nnuenet) available : OK"; break; \ + else \ + if [ "x$(curl_or_wget)" != "x" ]; then \ + echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ + else \ + echo "No net found and download not possible"; exit 1;\ + fi; \ + fi; \ + if [ "x$(shasum_command)" != "x" ]; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing failed download"; rm -f $(nnuenet); \ + fi; \ + fi; \ + done + @if ! test -f "$(nnuenet)"; then \ + echo "Failed to download $(nnuenet)."; \ + fi; + @if [ "x$(shasum_command)" != "x" ]; then \ + if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Network validated"; break; \ + fi; \ + fi; \ + +# default target default: help From 8192945870967fb9c8801247d0b040b2bc657443 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 9 Aug 2023 15:34:53 +0200 Subject: [PATCH 367/678] Improve testing coverage, remove unused code a) Add further tests to CI to cover most features. This uncovered a potential race in case setoption was sent between two searches. As the UCI protocol requires this sent to be went the engine is not searching, setoption now ensures that this is the case. b) Remove some unused code closes https://github.com/official-stockfish/Stockfish/pull/4730 No functional change --- src/nnue/layers/simd.h | 55 ------------------------------------------ src/syzygy/tbprobe.h | 23 ------------------ src/types.h | 1 - src/uci.cpp | 2 ++ tests/instrumented.sh | 52 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 52 insertions(+), 81 deletions(-) diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 638e3994..f478cd78 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -79,23 +79,6 @@ namespace Stockfish::Simd { return _mm512_add_epi32(sum0123a, sum0123b); } - [[maybe_unused]] static __m128i m512_haddx4( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3, - __m128i bias) { - - __m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3); - - __m256i sum256lo = _mm512_castsi512_si256(sum); - __m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1); - - sum256lo = _mm256_add_epi32(sum256lo, sum256hi); - - __m128i sum128lo = _mm256_castsi256_si128(sum256lo); - __m128i sum128hi = _mm256_extracti128_si256(sum256lo, 1); - - return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias); - } - [[maybe_unused]] static void m512_add_dpbusd_epi32( __m512i& acc, __m512i a, @@ -138,21 +121,6 @@ namespace Stockfish::Simd { return _mm_cvtsi128_si32(sum128) + bias; } - [[maybe_unused]] static __m128i m256_haddx4( - __m256i sum0, __m256i sum1, __m256i sum2, __m256i sum3, - __m128i bias) { - - sum0 = _mm256_hadd_epi32(sum0, sum1); - sum2 = _mm256_hadd_epi32(sum2, sum3); - - sum0 = _mm256_hadd_epi32(sum0, sum2); - - __m128i sum128lo = _mm256_castsi256_si128(sum0); - __m128i sum128hi = _mm256_extracti128_si256(sum0, 1); - - return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias); - } - [[maybe_unused]] static void m256_add_dpbusd_epi32( __m256i& acc, __m256i a, @@ -194,16 +162,6 @@ namespace Stockfish::Simd { return _mm_cvtsi128_si32(sum) + bias; } - [[maybe_unused]] static __m128i m128_haddx4( - __m128i sum0, __m128i sum1, __m128i sum2, __m128i sum3, - __m128i bias) { - - sum0 = _mm_hadd_epi32(sum0, sum1); - sum2 = _mm_hadd_epi32(sum2, sum3); - sum0 = _mm_hadd_epi32(sum0, sum2); - return _mm_add_epi32(sum0, bias); - } - [[maybe_unused]] static void m128_add_dpbusd_epi32( __m128i& acc, __m128i a, @@ -261,19 +219,6 @@ namespace Stockfish::Simd { return neon_m128_reduce_add_epi32(sum) + bias; } - [[maybe_unused]] static int32x4_t neon_m128_haddx4( - int32x4_t sum0, int32x4_t sum1, int32x4_t sum2, int32x4_t sum3, - int32x4_t bias) { - - int32x4_t hsums { - neon_m128_reduce_add_epi32(sum0), - neon_m128_reduce_add_epi32(sum1), - neon_m128_reduce_add_epi32(sum2), - neon_m128_reduce_add_epi32(sum3) - }; - return vaddq_s32(hsums, bias); - } - [[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2( int32x4_t& acc, int8x8_t a0, int8x8_t b0, diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 159c6865..fe994f68 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -19,8 +19,6 @@ #ifndef TBPROBE_H #define TBPROBE_H -#include - #include "../search.h" namespace Stockfish::Tablebases { @@ -50,27 +48,6 @@ bool root_probe(Position& pos, Search::RootMoves& rootMoves); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); -inline std::ostream& operator<<(std::ostream& os, const WDLScore v) { - - os << (v == WDLLoss ? "Loss" : - v == WDLBlessedLoss ? "Blessed loss" : - v == WDLDraw ? "Draw" : - v == WDLCursedWin ? "Cursed win" : - v == WDLWin ? "Win" : "None"); - - return os; -} - -inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { - - os << (v == FAIL ? "Failed" : - v == OK ? "Success" : - v == CHANGE_STM ? "Probed opponent side" : - v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None"); - - return os; -} - } // namespace Stockfish::Tablebases #endif diff --git a/src/types.h b/src/types.h index b0c11778..5d783776 100644 --- a/src/types.h +++ b/src/types.h @@ -300,7 +300,6 @@ inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } ENABLE_FULL_OPERATORS_ON(Value) ENABLE_FULL_OPERATORS_ON(Direction) -ENABLE_INCR_OPERATORS_ON(Piece) ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) diff --git a/src/uci.cpp b/src/uci.cpp index f893bd9c..ffe5e057 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -98,6 +98,8 @@ namespace { void setoption(istringstream& is) { + Threads.main()->wait_for_search_finished(); + string token, name, value; is >> token; // Consume the "name" token diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 1b37c7a8..637d19f9 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -64,14 +64,32 @@ EOF ;; esac +cat << EOF > bench_tmp.epd +Rn6/1rbq1bk1/2p2n1p/2Bp1p2/3Pp1pP/1N2P1P1/2Q1NPB1/6K1 w - - 2 26 +rnbqkb1r/ppp1pp2/5n1p/3p2p1/P2PP3/5P2/1PP3PP/RNBQKBNR w KQkq - 0 3 +3qnrk1/4bp1p/1p2p1pP/p2bN3/1P1P1B2/P2BQ3/5PP1/4R1K1 w - - 9 28 +r4rk1/1b2ppbp/pq4pn/2pp1PB1/1p2P3/1P1P1NN1/1PP3PP/R2Q1RK1 w - - 0 13 +EOF + # simple command line testing for args in "eval" \ "go nodes 1000" \ "go depth 10" \ + "go perft 4" \ "go movetime 1000" \ "go wtime 8000 btime 8000 winc 500 binc 500" \ + "go wtime 1000 btime 1000 winc 0 binc 0" \ + "go wtime 1000 btime 1000 winc 0 binc 0" \ + "go wtime 1000 btime 1000 winc 0 binc 0 movestogo 5" \ + "go movetime 200" \ + "go nodes 20000 searchmoves e2e4 d2d4" \ "bench 128 $threads 8 default depth" \ - "export_net verify.nnue" + "bench 128 $threads 3 bench_tmp.epd depth" \ + "export_net verify.nnue" \ + "d" \ + "compiler" \ + "license" \ + "uci" do echo "$prefix $exeprefix ./stockfish $args $postfix" @@ -92,6 +110,7 @@ cat << EOF > game.exp send "uci\n" expect "uciok" + # send "setoption name Debug Log File value debug.log\n" send "setoption name Threads value $threads\n" send "ucinewgame\n" @@ -107,6 +126,28 @@ cat << EOF > game.exp send "go depth 10\n" expect "bestmove" + send "setoption name UCI_ShowWDL value true\n" + send "position startpos\n" + send "flip\n" + send "go depth 5\n" + expect "bestmove" + + send "setoption name Skill Level value 10\n" + send "position startpos\n" + send "go depth 5\n" + expect "bestmove" + + send "setoption name Clear Hash\n" + + send "setoption name EvalFile value verify.nnue\n" + send "position startpos\n" + send "go depth 5\n" + expect "bestmove" + + send "setoption name MultiPV value 4\n" + send "position startpos\n" + send "go depth 5\n" + send "quit\n" expect eof @@ -128,6 +169,13 @@ cat << EOF > syzygy.exp send "setoption name SyzygyPath value ../tests/syzygy/\n" expect "info string Found 35 tablebases" {} timeout {exit 1} send "bench 128 1 8 default depth\n" + send "ucinewgame\n" + send "position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\n" + send "go depth 5\n" + expect "bestmove" + send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\n" + send "go depth 5\n" + expect "bestmove" send "quit\n" expect eof @@ -146,6 +194,6 @@ do done -rm -f tsan.supp +rm -f tsan.supp bench_tmp.epd echo "instrumented testing OK" From 4be94f41a6119f6d463e13adc6aaf5e02383da63 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 13 Aug 2023 10:59:35 +0200 Subject: [PATCH 368/678] Update sanitizer CI to ubuntu 22.04 might fix the tsan errors closes https://github.com/official-stockfish/Stockfish/pull/4745 No functional change --- .github/workflows/stockfish_sanitizers.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index 305b8557..228742b3 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -12,8 +12,8 @@ jobs: strategy: matrix: config: - - name: Ubuntu 20.04 GCC - os: ubuntu-20.04 + - name: Ubuntu 22.04 GCC + os: ubuntu-22.04 compiler: g++ comp: gcc shell: bash From d97a02ea2b9328e666aff7a906820c9ec65ab381 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 13 Aug 2023 11:03:28 +0300 Subject: [PATCH 369/678] Give extra bonus to main history for moves that caused a fail low. #4744 Current master gives this type of bonuses to continuation history, this patch also gives them to main history. Passed STC: https://tests.stockfishchess.org/tests/view/64d4802a5b17f7c21c0e27b3 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 480768 W: 122767 L: 121798 D: 236203 Ptnml(0-2): 1563, 56190, 123834, 57309, 1488 Passed LTC: https://tests.stockfishchess.org/tests/view/64d7e4c05b17f7c21c0e7456 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 32052 W: 8329 L: 8022 D: 15701 Ptnml(0-2): 19, 3335, 9015, 3634, 23 closes https://github.com/official-stockfish/Stockfish/pull/4744 Bench: 1711793 --- src/search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/search.cpp b/src/search.cpp index 24f54c32..ce9ed950 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1373,6 +1373,7 @@ moves_loop: // When in check, search starts here { int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 113 * depth) + ((ss-1)->moveCount > 12); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus / 2; } if (PvNode) From 222f3ea55bab2414c4c260391ffd14dabc1684df Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:06:31 +0800 Subject: [PATCH 370/678] Simplify a depth condition As the negative extension term has sensitive scaling, it would make more sense to allow more negative extension also at lower depth, and not just a region between low and high depth. Passed STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 124096 W: 31611 L: 31485 D: 61000 Ptnml(0-2): 422, 14437, 32205, 14561, 423 https://tests.stockfishchess.org/tests/view/64d205d75b17f7c21c0dea82 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 387882 W: 97840 L: 97993 D: 192049 Ptnml(0-2): 198, 42004, 109668, 41895, 176 https://tests.stockfishchess.org/tests/view/64d333f85b17f7c21c0e06c6 closes https://github.com/official-stockfish/Stockfish/pull/4743 Bench: 1542357 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ce9ed950..03d72b95 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1099,7 +1099,7 @@ moves_loop: // When in check, search starts here // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) else if (cutNode) - extension = depth > 8 && depth < 17 ? -3 : -1; + extension = depth < 17 ? -3 : -1; // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) From b7b7a3f3fa786449832bd84d501c1183290f3e3a Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:19:40 +0300 Subject: [PATCH 371/678] Detect repetitions before they happen in qsearch Passed STC: https://tests.stockfishchess.org/tests/view/64d495995b17f7c21c0e29ed LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 340288 W: 86664 L: 85910 D: 167714 Ptnml(0-2): 1030, 38855, 89697, 39455, 1107 Passed LTC: https://tests.stockfishchess.org/tests/view/64d5e1085b17f7c21c0e4ab5 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 193230 W: 49235 L: 48606 D: 95389 Ptnml(0-2): 98, 20432, 54921, 21071, 93 closes https://github.com/official-stockfish/Stockfish/pull/4742 Bench: 1579576 --- src/search.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 03d72b95..44e13dae 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1410,6 +1410,18 @@ moves_loop: // When in check, search starts here assert(PvNode || (alpha == beta - 1)); assert(depth <= 0); + // Check if we have an upcoming move that draws by repetition, or + // if the opponent had an alternative move earlier to this position. + if ( depth < 0 + && pos.rule50_count() >= 3 + && alpha < VALUE_DRAW + && pos.has_game_cycle(ss->ply)) + { + alpha = value_draw(pos.this_thread()); + if (alpha >= beta) + return alpha; + } + Move pv[MAX_PLY+1]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); From 84e97a38a3966a38add0f3b07bd011fa707ec5be Mon Sep 17 00:00:00 2001 From: Gabrik <> Date: Fri, 11 Aug 2023 23:54:48 +0200 Subject: [PATCH 372/678] Remove the unused enum ScaleFactor closes https://github.com/official-stockfish/Stockfish/pull/4740 No functional change --- AUTHORS | 1 + src/types.h | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index 2f323d64..5622ca8c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -74,6 +74,7 @@ fanon Fauzi Akram Dabat (FauziAkram) Felix Wittmann gamander +Gabriele Lombardo (gabe) Gary Heckman (gheckman) George Sobala (gsobala) gguliash diff --git a/src/types.h b/src/types.h index 5d783776..637e1675 100644 --- a/src/types.h +++ b/src/types.h @@ -157,13 +157,6 @@ enum Phase { MG = 0, EG = 1, PHASE_NB = 2 }; -enum ScaleFactor { - SCALE_FACTOR_DRAW = 0, - SCALE_FACTOR_NORMAL = 64, - SCALE_FACTOR_MAX = 128, - SCALE_FACTOR_NONE = 255 -}; - enum Bound { BOUND_NONE, BOUND_UPPER, From 796d9df6438b416a63364c7cf5edbc9be5434101 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Fri, 11 Aug 2023 16:57:26 +0200 Subject: [PATCH 373/678] Check compiler for docker builds in CI closes https://github.com/official-stockfish/Stockfish/pull/4739 No functional change --- .github/workflows/stockfish_test.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 307d3a02..72f0c22e 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -134,7 +134,7 @@ jobs: FROM ${{ matrix.config.base_image }} WORKDIR /app RUN apk update && apk add make g++ - CMD sh make_sf.sh + CMD ["sh", "script.sh"] EOF - name: Download required macOS packages @@ -160,10 +160,15 @@ jobs: - name: Check compiler run: | - if [ $COMP == ndk ]; then - export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + if [ -z "${{ matrix.config.base_image }}" ]; then + if [ $COMP == ndk ]; then + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + fi + $COMPILER -v + else + echo "$COMPILER -v" > script.sh + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder fi - $COMPILER -v - name: Test help target run: make help @@ -321,7 +326,7 @@ jobs: - name: Test riscv64 build if: matrix.config.run_riscv64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > make_sf.sh + echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder ../tests/signature.sh $benchref @@ -330,7 +335,7 @@ jobs: - name: Test ppc64 build if: matrix.config.run_ppc64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > make_sf.sh + echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder ../tests/signature.sh $benchref From c02ee70927bcb90240f40d8e580e30dc622d5ce9 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 10 Aug 2023 18:44:58 +0300 Subject: [PATCH 374/678] Simplify prior countermove bonus Swapping a multiplication operation between two terms with a simple constant Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 60512 W: 15424 L: 15231 D: 29857 Ptnml(0-2): 200, 6985, 15712, 7140, 219 https://tests.stockfishchess.org/tests/view/64d2addf5b17f7c21c0dfae6 Passed LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 108456 W: 27545 L: 27414 D: 53497 Ptnml(0-2): 63, 11629, 30698, 11790, 48 https://tests.stockfishchess.org/tests/view/64d3ab6e5b17f7c21c0e1188 closes https://github.com/official-stockfish/Stockfish/pull/4738 Bench: 1636213 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 44e13dae..e51e2f4d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1371,7 +1371,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 113 * depth) + ((ss-1)->moveCount > 12); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 800) + ((ss-1)->moveCount > 12); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus / 2; } From 495852fecdd9ce0fe0c1e9c0518f1bc01ccfa239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicolet?= Date: Thu, 10 Aug 2023 06:31:48 +0200 Subject: [PATCH 375/678] Simplify SEE pruning for captures It seems that the current search is smart enough to allow us to remove (again) the block of code that checks for discovered attacks after the first pruning condition for captures. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 278848 W: 70856 L: 70903 D: 137089 Ptnml(0-2): 960, 32829, 71894, 32780, 961 https://tests.stockfishchess.org/tests/view/64d0af095b17f7c21c0dc440 LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 100704 W: 25564 L: 25425 D: 49715 Ptnml(0-2): 56, 10858, 28381, 11005, 52 https://tests.stockfishchess.org/tests/view/64d293e85b17f7c21c0df844 closes https://github.com/official-stockfish/Stockfish/pull/4736 Bench: 1470572 --- src/search.cpp | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e51e2f4d..970b0f47 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -989,28 +989,9 @@ moves_loop: // When in check, search starts here + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; - Bitboard occupied; - // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-205) * depth)) - { - if (depth < 2 - capture) - continue; - // Don't prune the move if opponent Queen/Rook is under discovered attack after the exchanges - // Don't prune the move if opponent King is under discovered attack after or during the exchanges - Bitboard leftEnemies = (pos.pieces(~us, KING, QUEEN, ROOK)) & occupied; - Bitboard attacks = 0; - occupied |= to_sq(move); - while (leftEnemies && !attacks) - { - Square sq = pop_lsb(leftEnemies); - attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // Don't consider pieces that were already threatened/hanging before SEE exchanges - if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) - attacks = 0; - } - if (!attacks) - continue; - } + // SEE based pruning for captures and checks (~11 Elo) + if (!pos.see_ge(move, Value(-205) * depth)) + continue; } else { From 3322349c1a3dbe2f4c42f84141745c4d94efde2e Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Mon, 7 Aug 2023 22:27:12 +0300 Subject: [PATCH 376/678] Simplify pieceValue to one phase. Simplifies the usage of pieceValues to mg values with the exception of pawnValues, After the removal of PSQT. passed STC: https://tests.stockfishchess.org/tests/view/64d147845b17f7c21c0dd86c LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 197248 W: 50168 L: 50125 D: 96955 Ptnml(0-2): 651, 23029, 51222, 23070, 652 passed LTC: https://tests.stockfishchess.org/tests/view/64d212de5b17f7c21c0debbb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 181170 W: 45949 L: 45893 D: 89328 Ptnml(0-2): 84, 19656, 51052, 19706, 87 closes https://github.com/official-stockfish/Stockfish/pull/4734 Bench: 1494401 --- src/movepick.cpp | 4 ++-- src/position.cpp | 20 ++++++++++---------- src/search.cpp | 6 +++--- src/syzygy/tbprobe.cpp | 4 ++-- src/types.h | 22 +++++++--------------- 5 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 40508103..9d5805a7 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -122,7 +122,7 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) - m.value = (7 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) + m.value = (7 * int(PieceValue[pos.piece_on(to_sq(m))]) + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; else if constexpr (Type == QUIETS) @@ -165,7 +165,7 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + (1 << 28); else diff --git a/src/position.cpp b/src/position.cpp index 16181e96..675dec99 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -347,7 +347,7 @@ void Position::set_state() const { st->key ^= Zobrist::psq[pc][s]; if (type_of(pc) != KING && type_of(pc) != PAWN) - st->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; + st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; } if (st->epSquare != SQ_NONE) @@ -742,7 +742,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } } else - st->nonPawnMaterial[them] -= PieceValue[MG][captured]; + st->nonPawnMaterial[them] -= PieceValue[captured]; dp.dirty_num = 2; // 1 piece moved, 1 piece captured dp.piece[1] = captured; @@ -822,7 +822,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ^ Zobrist::psq[pc][pieceCount[pc]]; // Update material - st->nonPawnMaterial[us] += PieceValue[MG][promotion]; + st->nonPawnMaterial[us] += PieceValue[promotion]; } // Reset rule 50 draw counter @@ -1048,11 +1048,11 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { Square from = from_sq(m), to = to_sq(m); - int swap = PieceValue[MG][piece_on(to)] - threshold; + int swap = PieceValue[piece_on(to)] - threshold; if (swap < 0) return false; - swap = PieceValue[MG][piece_on(from)] - swap; + swap = PieceValue[piece_on(from)] - swap; if (swap <= 0) return true; @@ -1089,7 +1089,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { if ((bb = stmAttackers & pieces(PAWN))) { occupied ^= least_significant_square_bb(bb); - if ((swap = PawnValueMg - swap) < res) + if ((swap = PawnValue - swap) < res) break; attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); @@ -1098,14 +1098,14 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { else if ((bb = stmAttackers & pieces(KNIGHT))) { occupied ^= least_significant_square_bb(bb); - if ((swap = KnightValueMg - swap) < res) + if ((swap = KnightValue - swap) < res) break; } else if ((bb = stmAttackers & pieces(BISHOP))) { occupied ^= least_significant_square_bb(bb); - if ((swap = BishopValueMg - swap) < res) + if ((swap = BishopValue - swap) < res) break; attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); @@ -1114,7 +1114,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { else if ((bb = stmAttackers & pieces(ROOK))) { occupied ^= least_significant_square_bb(bb); - if ((swap = RookValueMg - swap) < res) + if ((swap = RookValue - swap) < res) break; attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); @@ -1123,7 +1123,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { else if ((bb = stmAttackers & pieces(QUEEN))) { occupied ^= least_significant_square_bb(bb); - if ((swap = QueenValueMg - swap) < res) + if ((swap = QueenValue - swap) < res) break; attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) diff --git a/src/search.cpp b/src/search.cpp index 970b0f47..697c8cfe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -985,7 +985,7 @@ moves_loop: // When in check, search starts here if ( !givesCheck && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; @@ -1535,7 +1535,7 @@ moves_loop: // When in check, search starts here if (moveCount > 2) continue; - futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; + futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; if (futilityValue <= alpha) { @@ -1783,7 +1783,7 @@ moves_loop: // When in check, search starts here // RootMoves are already sorted by score in descending order Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg); + int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue); int maxScore = -VALUE_INFINITE; double weakness = 120 - 2 * level; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 9cb0bfdb..56cc016a 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1573,9 +1573,9 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // 1 cp to cursed wins and let it grow to 49 cp as the positions gets // closer to a real win. m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 - : r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValueEg)) / 200) + : r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200) : r == 0 ? VALUE_DRAW - : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValueEg)) / 200) + : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200) : -VALUE_MATE + MAX_PLY + 1; } diff --git a/src/types.h b/src/types.h index 637e1675..34dc42e1 100644 --- a/src/types.h +++ b/src/types.h @@ -153,10 +153,6 @@ enum CastlingRights { CASTLING_RIGHT_NB = 16 }; -enum Phase { - MG = 0, EG = 1, PHASE_NB = 2 -}; - enum Bound { BOUND_NONE, BOUND_UPPER, @@ -180,11 +176,11 @@ enum Value : int { // In the code, we make the assumption that these values // are such that non_pawn_material() can be used to uniquely // identify the material on the board. - PawnValueMg = 126, PawnValueEg = 208, - KnightValueMg = 781, KnightValueEg = 854, - BishopValueMg = 825, BishopValueEg = 915, - RookValueMg = 1276, RookValueEg = 1380, - QueenValueMg = 2538, QueenValueEg = 2682, + PawnValue = 208, + KnightValue = 781, + BishopValue = 825, + RookValue = 1276, + QueenValue = 2538, }; enum PieceType { @@ -200,12 +196,8 @@ enum Piece { PIECE_NB = 16 }; -constexpr Value PieceValue[PHASE_NB][PIECE_NB] = { - { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO }, - { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO } -}; +constexpr Value PieceValue[PIECE_NB] = { VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO }; using Depth = int; From 9b80897657bde99cfb6568d8bd3386c3999f22c4 Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 9 Aug 2023 11:48:33 -0700 Subject: [PATCH 377/678] Simplify material difference in evaluate STC: https://tests.stockfishchess.org/tests/view/64d166235b17f7c21c0ddc15 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 100032 W: 25698 L: 25547 D: 48787 Ptnml(0-2): 308, 11748, 25771, 11863, 326 LTC: https://tests.stockfishchess.org/tests/view/64d28c085b17f7c21c0df775 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 123870 W: 31463 L: 31348 D: 61059 Ptnml(0-2): 63, 13487, 34719, 13604, 62 Besides rebasing I replaced PawnValueMg w/ 126 explicitly to decouple from https://tests.stockfishchess.org/tests/view/64d212de5b17f7c21c0debbb by @peregrineshahin which also passed. #4734 closes https://github.com/official-stockfish/Stockfish/pull/4731 Bench: 1447866 --- src/evaluate.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c37dd98a..72899068 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -152,11 +152,8 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - int material = 67 * (pos.count(stm) - pos.count(~stm)) - + 395 * (pos.count(stm) - pos.count(~stm)) - + 288 * (pos.count(stm) - pos.count(~stm)) - + 630 * (pos.count(stm) - pos.count(~stm)) - + 857 * (pos.count(stm) - pos.count(~stm)); + int material = pos.non_pawn_material(stm) - pos.non_pawn_material(~stm) + + 126 * (pos.count(stm) - pos.count(~stm)); // Blend optimism with nnue complexity and (semi)classical complexity optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; From a77a8448ffe3736e44a0125eece5d87abf082a60 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 13 Aug 2023 17:14:38 +0200 Subject: [PATCH 378/678] Add CONTRIBUTING.md closes https://github.com/official-stockfish/Stockfish/pull/4741 No functional change --- .github/CONTRIBUTING.md | 85 +++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 2 files changed, 87 insertions(+) create mode 100644 .github/CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..0dff8a9d --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,85 @@ +# Contributing to Stockfish + +Welcome to the Stockfish project! We are excited that you are interested in +contributing. This document outlines the guidelines and steps to follow when +making contributions to Stockfish. + +## Table of Contents + +- [Building Stockfish](#building-stockfish) +- [Making Contributions](#making-contributions) + - [Reporting Issues](#reporting-issues) + - [Submitting Pull Requests](#submitting-pull-requests) +- [Code Style](#code-style) +- [Community and Communication](#community-and-communication) +- [License](#license) + +## Building Stockfish + +In case you do not have a C++ compiler installed, you can follow the +instructions from our wiki. + +- [Linux][linux-compiling-link] +- [Windows][windows-compiling-link] +- [macOS][macos-compiling-link] + +## Making Contributions + +### Reporting Issues + +If you find a bug, please open an issue on the +[issue tracker][issue-tracker-link]. Be sure to include relevant information +like your operating system, build environment, and a detailed description of the +problem. + +_Please note that Stockfish's development is not focused on adding new features. +Thus any issue regarding missing features will potentially be closed without +further discussion._ + +### Submitting Pull Requests + +- Functional changes need to be tested on fishtest. See + [Creating my First Test][creating-my-first-test] for more details. + The accompanying pull request should include a link to the test results and + the new bench. + +- Non-functional changes (e.g. refactoring, code style, documentation) do not + need to be tested on fishtest, unless they might impact performance. + +- Provide a clear and concise description of the changes in the pull request + description. + +_First time contributors should add their name to [AUTHORS](../AUTHORS)._ + +_Stockfish's development is not focused on adding new features. Thus any pull +request introducing new features will potentially be closed without further +discussion._ + +## Code Style + +We do not have a strict code style. But it is best to stick to the existing +style of the file you are editing. + +## Community and Communication + +- Join the [Stockfish discord][discord-link] to discuss ideas, issues, and + development. +- Participate in the [Stockfish GitHub discussions][discussions-link] for + broader conversations. + +## License + +By contributing to Stockfish, you agree that your contributions will be licensed +under the GNU General Public License v3.0. See [Copying.txt][copying-link] for +more details. + +Thank you for contributing to Stockfish and helping us make it even better! + +[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt +[discord-link]: https://discord.gg/GWDRS3kU6R +[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new +[creating-my-first-test]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test#create-your-test +[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues +[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux +[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#windows +[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos diff --git a/README.md b/README.md index e0e3da39..249bff1c 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ Detailed compilation instructions for all platforms can be found in our ## Contributing +__See [Contributing Guide](./.github/CONTRIBUTING.md).__ + ### Donating hardware Improving Stockfish requires a massive amount of testing. You can donate your From 3e5a817fd243bf395474c88b3bf351e57e56a119 Mon Sep 17 00:00:00 2001 From: SzilBalazs Date: Sun, 13 Aug 2023 17:52:08 +0200 Subject: [PATCH 379/678] Fix dead link to compression algorithm in tbprobe closes https://github.com/official-stockfish/Stockfish/pull/4746 No functional change --- AUTHORS | 1 + src/syzygy/tbprobe.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 5622ca8c..d20278e1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,7 @@ Aram Tumanian (atumanian) Arjun Temurnikar Artem Solopiy (EntityFX) Auguste Pop +Balazs Szilagyi Balint Pfliegel Ben Chaney (Chaneybenjamini) Ben Koshy (BKSpurgeon) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 56cc016a..838453b6 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1023,7 +1023,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // frequent adjacent pair of symbols in the source message by a new symbol, // reevaluating the frequencies of all of the symbol pairs with respect to // the extended alphabet, and then repeating the process. - // See http://www.larsson.dogma.net/dcc99.pdf + // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf std::vector visited(d->symlen.size()); for (Sym sym = 0; sym < d->symlen.size(); ++sym) From fe0dca12f1207a3a1ecca678d7d13bc533fd5332 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sun, 13 Aug 2023 22:02:17 +0800 Subject: [PATCH 380/678] Simplify PvNode Reduction Remove the depth condition for PvNode reduction. Simplification STC: https://tests.stockfishchess.org/tests/view/64d308fa5b17f7c21c0e0303 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 38976 W: 10106 L: 9889 D: 18981 Ptnml(0-2): 129, 4479, 10040, 4726, 114 Simplification LTC: https://tests.stockfishchess.org/tests/view/64d457db5b17f7c21c0e236f LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 156402 W: 39727 L: 39645 D: 77030 Ptnml(0-2): 71, 17143, 43696, 17215, 76 closes https://github.com/official-stockfish/Stockfish/pull/4747 Bench: 1493904 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 697c8cfe..c7e6fff0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1136,9 +1136,9 @@ moves_loop: // When in check, search starts here if (ttCapture) r++; - // Decrease reduction for PvNodes based on depth (~2 Elo) + // Decrease reduction for PvNodes (~2 Elo) if (PvNode) - r -= 1 + (depth < 6); + r--; // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) From 46756996e7884c665da18f357208c2344a0f9374 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 14 Aug 2023 13:49:41 +0200 Subject: [PATCH 381/678] Add -funroll-loops to CXXFLAGS Optimize profiling data accuracy by enabling -funroll-loops during the profile generation phase, in addition to its default activation by -fprofile-use. This seems to produce a slightly faster binary, for most compilers. make -j profile-build ARCH=x86-64-avx2 sf_base = 1392875 +/- 5905 (95%) sf_test = 1402332 +/- 7303 (95%) diff = 9457 +/- 4413 (95%) speedup = 0.67896% +/- 0.317% (95%) STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 34784 W: 8970 L: 8665 D: 17149 Ptnml(0-2): 115, 3730, 9405, 4019, 123 https://tests.stockfishchess.org/tests/view/64d944815b17f7c21c0e92e1 closes https://github.com/official-stockfish/Stockfish/pull/4750 No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index c7e059ea..0a3f8329 100644 --- a/src/Makefile +++ b/src/Makefile @@ -562,7 +562,7 @@ endif ### 3.3 Optimization ifeq ($(optimize),yes) - CXXFLAGS += -O3 + CXXFLAGS += -O3 -funroll-loops ifeq ($(comp),gcc) ifeq ($(OS), Android) From a9a0dbbcd0749b4e6255c7e9a17f19cffedaa531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Tue, 22 Aug 2023 10:39:03 +0200 Subject: [PATCH 382/678] Fix some 'possible loss of data' warnings Patch by Maxim Masiutin closes https://github.com/official-stockfish/Stockfish/pull/4440 No functional change --- src/misc.cpp | 6 +++--- src/syzygy/tbprobe.cpp | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 29ef757e..922fad96 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -375,7 +375,7 @@ void dbg_print() { for (int i = 0; i < MaxDebugSlots; ++i) if ((n = stdev[i][0])) { - double r = sqrtl(E(stdev[i][2]) - sqr(E(stdev[i][1]))); + double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1]))); std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl; @@ -385,8 +385,8 @@ void dbg_print() { if ((n = correl[i][0])) { double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3])) - / ( sqrtl(E(correl[i][2]) - sqr(E(correl[i][1]))) - * sqrtl(E(correl[i][4]) - sqr(E(correl[i][3])))); + / ( sqrt(E(correl[i][2]) - sqr(E(correl[i][1]))) + * sqrt(E(correl[i][4]) - sqr(E(correl[i][3])))); std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::endl; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 838453b6..ba727825 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -995,13 +995,19 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { d->lowestSym = (Sym*)data; d->base64.resize(d->maxSymLen - d->minSymLen + 1); + // See https://en.wikipedia.org/wiki/Huffman_coding // The canonical code is ordered such that longer symbols (in terms of // the number of bits of their Huffman code) have lower numeric value, // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. - // See https://en.wikipedia.org/wiki/Huffman_coding - for (int i = d->base64.size() - 2; i >= 0; --i) { + + // Implementation note: we first cast the unsigned size_t "base64.size()" + // to a signed int "base64_size" variable and then we are able to subtract 2, + // avoiding unsigned overflow warnings. + + int base64_size = static_cast(d->base64.size()); + for (int i = base64_size - 2; i >= 0; --i) { d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) - number(&d->lowestSym[i + 1])) / 2; @@ -1012,10 +1018,10 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // than d->base64[i+1] and given the above assert condition, we ensure that // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. - for (size_t i = 0; i < d->base64.size(); ++i) + for (int i = 0; i < base64_size; ++i) d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits - data += d->base64.size() * sizeof(Sym); + data += base64_size * sizeof(Sym); d->symlen.resize(number(data)); data += sizeof(uint16_t); d->btree = (LR*)data; From 9abef246a9ce7c17a21c2cc0d609dc61ddc5be67 Mon Sep 17 00:00:00 2001 From: Matthies Date: Wed, 16 Aug 2023 11:11:27 +0200 Subject: [PATCH 383/678] Allow compilation on Raspi (for ARMv8) Current master fails to compile for ARMv8 on Raspi cause gcc (version 10.2.1) does not like to cast between signed and unsigned vector types. This patch fixes it by using unsigned vector pointer for ARM to avoid implicite cast. closes https://github.com/official-stockfish/Stockfish/pull/4752 No functional change --- src/nnue/layers/affine_transform_sparse_input.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 63cbaf45..2cd77e49 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -72,10 +72,10 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) #elif defined (USE_NEON) - using vec_t = int32x4_t; + using vec_t = uint32x4_t; static const std::uint32_t Mask[4] = {1, 2, 4, 8}; #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) - using vec128_t = int16x8_t; + using vec128_t = uint16x8_t; #define vec128_zero vdupq_n_u16(0) #define vec128_set_16(a) vdupq_n_u16(a) #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) From fe7353f7027e2d49b7ffb60b01d88ce0d3b04fff Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 21 Aug 2023 22:45:26 +0200 Subject: [PATCH 384/678] Update links to fishtest Fishtest has moved to https://github.com/official-stockfish/fishtest/ closes https://github.com/official-stockfish/Stockfish/pull/4758 No functional change --- .github/CONTRIBUTING.md | 15 ++++++++------- AUTHORS | 2 +- README.md | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0dff8a9d..7667a942 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -75,11 +75,12 @@ more details. Thank you for contributing to Stockfish and helping us make it even better! -[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt -[discord-link]: https://discord.gg/GWDRS3kU6R -[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new -[creating-my-first-test]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test#create-your-test -[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues -[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux + +[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt +[discord-link]: https://discord.gg/GWDRS3kU6R +[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new +[creating-my-first-test]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test#create-your-test +[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues +[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux [windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#windows -[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos +[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos diff --git a/AUTHORS b/AUTHORS index d20278e1..9314f5cb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -230,4 +230,4 @@ zz4032 # Additionally, we acknowledge the authors and maintainers of fishtest, # an amazing and essential framework for Stockfish development! # -# https://github.com/glinscott/fishtest/blob/master/AUTHORS +# https://github.com/official-stockfish/fishtest/blob/master/AUTHORS diff --git a/README.md b/README.md index 249bff1c..4d63b71e 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ also be made available under GPL v3. [issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml [discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new [fishtest-link]: https://tests.stockfishchess.org/tests -[guideline-link]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test +[guideline-link]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test [license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt [programming-link]: https://www.chessprogramming.org/Main_Page [programmingsf-link]: https://www.chessprogramming.org/Stockfish @@ -155,7 +155,7 @@ also be made available under GPL v3. [wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage [wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source [wiki-commands-link]: https://github.com/official-stockfish/Stockfish/wiki/Commands -[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker +[worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker [build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github [commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge From 4c5919fa95d543b1cd5d0403f3a89e10a2bdd10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Tue, 22 Aug 2023 10:00:03 +0200 Subject: [PATCH 385/678] Fix some tabs in Makefile Avoid mixing spaces and tabs for indentation in Makefile closes https://github.com/official-stockfish/Stockfish/pull/4759 No functional change --- src/Makefile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Makefile b/src/Makefile index 0a3f8329..d6443845 100644 --- a/src/Makefile +++ b/src/Makefile @@ -895,33 +895,33 @@ netvariables: net: netvariables @echo "Default net: $(nnuenet)" @if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ + echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ fi @if [ "x$(shasum_command)" = "x" ]; then \ - echo "shasum / sha256sum not found, skipping net validation"; \ - elif test -f "$(nnuenet)"; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Removing invalid network"; rm -f $(nnuenet); \ - fi; \ + echo "shasum / sha256sum not found, skipping net validation"; \ + elif test -f "$(nnuenet)"; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing invalid network"; rm -f $(nnuenet); \ + fi; \ fi; @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ if test -f "$(nnuenet)"; then \ - echo "$(nnuenet) available : OK"; break; \ + echo "$(nnuenet) available : OK"; break; \ else \ - if [ "x$(curl_or_wget)" != "x" ]; then \ + if [ "x$(curl_or_wget)" != "x" ]; then \ echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ else \ echo "No net found and download not possible"; exit 1;\ - fi; \ + fi; \ fi; \ if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ echo "Removing failed download"; rm -f $(nnuenet); \ - fi; \ + fi; \ fi; \ done @if ! test -f "$(nnuenet)"; then \ - echo "Failed to download $(nnuenet)."; \ + echo "Failed to download $(nnuenet)."; \ fi; @if [ "x$(shasum_command)" != "x" ]; then \ if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ From c6f62363a657263a567a0cc9bae09f3c4016156d Mon Sep 17 00:00:00 2001 From: Gian-Carlo Pascutto Date: Mon, 14 Aug 2023 17:30:10 +0200 Subject: [PATCH 386/678] Simplify Square Clipped ReLU code. Squared numbers are never negative, so barring any wraparound there is no need to clamp to 0. From reading the code, there's no obvious way to get wraparound, so the entire operation can be simplified away. Updated original truncated code comments to be sensible. Verified by running ./stockfish bench 128 1 24 and by the following test: STC: https://tests.stockfishchess.org/tests/view/64da4db95b17f7c21c0eabe7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60224 W: 15425 L: 15236 D: 29563 Ptnml(0-2): 195, 6576, 16382, 6763, 196 closes https://github.com/official-stockfish/Stockfish/pull/4751 No functional change --- src/nnue/layers/sqr_clipped_relu.h | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 69bd5147..5c1b9e6c 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -65,12 +65,6 @@ namespace Stockfish::Eval::NNUE::Layers { #if defined(USE_SSE2) constexpr IndexType NumChunks = InputDimensions / 16; - #ifdef USE_SSE41 - const __m128i Zero = _mm_setzero_si128(); - #else - const __m128i k0x80s = _mm_set1_epi8(-128); - #endif - static_assert(WeightScaleBits == 6); const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); @@ -82,21 +76,13 @@ namespace Stockfish::Eval::NNUE::Layers { _mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])); - // Not sure if + // We shift by WeightScaleBits * 2 = 12 and divide by 128 + // which is an additional shift-right of 7, meaning 19 in total. + // MulHi strips the lower 16 bits so we need to shift out 3 more to match. words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - - _mm_store_si128(&out[i], - - #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, Zero) - #else - _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) - #endif - - ); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); } constexpr IndexType Start = NumChunks * 16; @@ -108,7 +94,7 @@ namespace Stockfish::Eval::NNUE::Layers { output[i] = static_cast( // really should be /127 but we need to make it fast // needs to be accounted for in the trainer - std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128))); + std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128)); } } }; From 030b87182a7fff98b1724b857d6d40cda5d90b9f Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sat, 12 Aug 2023 14:56:23 +0800 Subject: [PATCH 387/678] Do more full window searches Remove the value < beta condition for doing full window searches. As an added bonus the condition for full-window search is now much more similar to other fail-soft engines. Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 244608 W: 62286 L: 62294 D: 120028 Ptnml(0-2): 758, 28772, 63214, 28840, 720 https://tests.stockfishchess.org/tests/view/64d72d365b17f7c21c0e6675 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 311460 W: 78909 L: 78985 D: 153566 Ptnml(0-2): 129, 33959, 87656, 33831, 155 https://tests.stockfishchess.org/tests/view/64dca2265b17f7c21c0ee06c closes https://github.com/official-stockfish/Stockfish/pull/4755 Bench: 1624221 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c7e6fff0..d911593c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1211,10 +1211,9 @@ moves_loop: // When in check, search starts here value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); } - // For PV nodes only, do a full PV search on the first move or after a fail - // high (in the latter case search only if value < beta), otherwise let the - // parent node fail low with value <= alpha and try another move. - if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta)))) + // For PV nodes only, do a full PV search on the first move or after a fail high, + // otherwise let the parent node fail low with value <= alpha and try another move. + if (PvNode && (moveCount == 1 || value > alpha)) { (ss+1)->pv = pv; (ss+1)->pv[0] = MOVE_NONE; From 440feecb4da2f051da6dafb70b4eb6cf443ccc1e Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Sun, 20 Aug 2023 01:15:22 +0300 Subject: [PATCH 388/678] Reduce repetitions branches Increase reduction on retrying a move we just retreated that falls in a repetition: if current move can be the same move from previous previous turn then we retreated that move on the previous turn, this patch increases reduction if retrying that move results in a repetition. How to continue from there? Maybe we some variants of this idea could bring Elo too (only testing the destination square, or triangulations, etc.) Passed STC: https://tests.stockfishchess.org/tests/view/64e1aede883cbb7cbd9ad976 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 424000 W: 108675 L: 107809 D: 207516 Ptnml(0-2): 1296, 47350, 113896, 48108, 1350 Passed LTC: https://tests.stockfishchess.org/tests/view/64e32d629970091252666872 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 89682 W: 22976 L: 22569 D: 44137 Ptnml(0-2): 39, 8843, 26675, 9240, 44 closes https://github.com/official-stockfish/Stockfish/pull/4757 bench: 1574347 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index d911593c..d9b41cb3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1143,6 +1143,11 @@ moves_loop: // When in check, search starts here // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) r--; + + // Increase reduction on repetition (~1 Elo) + if ( move == (ss-4)->currentMove + && pos.has_repeated()) + r += 2; // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss+1)->cutoffCnt > 3) From 4c4cb185aaaa0b3175ca35ab6473f17e9ec64055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Wed, 23 Aug 2023 07:50:36 +0200 Subject: [PATCH 389/678] Play turbulent when defending, simpler when attacking This patch decays a little the evaluation (up to a few percent) for positions which have a large complexity measure (material imbalance, positional compensations, etc). This may have nice consequences on the playing style, as it modifies the search differently for attack and defense, both effects being desirable: - to see the effect on positions when Stockfish is defending, let us suppose for instance that the side to move is Stockfish and the nnue evaluation on the principal variation is -100 : this patch will decay positions with an evaluation of -103 (say) to the same level, provided they have huge material imbalance or huge positional compensation. In other words, chaotic positions with an evaluation of -103 are now comparable in our search tree to stable positions with an evaluation of -100, and chaotic positions with an evaluation of -102 are now preferred to stable positions with an evaluation of -100. - the effect on positions when Stockfish is attacking is the opposite. Let us suppose for instance that the side to move is Stockfish and the nnue evaluation on the principal variation is +100 : this patch will decay the evaluation to +97 if the positions on the principal variation have huge material imbalance or huge positional compensation. In other words, stable positions with an evaluation of +97 are now comparable in our search tree to chaotic positions with an evaluation of +100, and stable positions with an evaluation of +98 are now preferred to chaotic positions with an evaluation of +100. So the effect of this small change of evaluation on the playing style is that Stockfish should now play a little bit more turbulent when defending, and choose slightly simpler lines when attacking. passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 268448 W: 68713 L: 68055 D: 131680 Ptnml(0-2): 856, 31514, 68943, 31938, 973 https://tests.stockfishchess.org/tests/view/64e252bb99700912526653ed passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 141060 W: 36066 L: 35537 D: 69457 Ptnml(0-2): 71, 15179, 39522, 15666, 92 https://tests.stockfishchess.org/tests/view/64e4447a9009777747553725 closes https://github.com/official-stockfish/Stockfish/pull/4762 Bench: 1426295 --- src/evaluate.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 72899068..54216b97 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -155,8 +155,9 @@ Value Eval::evaluate(const Position& pos) { int material = pos.non_pawn_material(stm) - pos.non_pawn_material(~stm) + 126 * (pos.count(stm) - pos.count(~stm)); - // Blend optimism with nnue complexity and (semi)classical complexity + // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; + nnue -= nnue * (nnueComplexity + abs(material - nnue)) / 32768; v = ( nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm + pos.count())) / 1024; From 8cd5cbf6939d76b33a744f1379a6f84a4ac3a6cb Mon Sep 17 00:00:00 2001 From: ttruscott Date: Fri, 25 Aug 2023 15:47:52 -0400 Subject: [PATCH 390/678] Omit two unneeded tests These redundant tests were intended as a speed-up, but they do not seem to provide any speed anymore. STC: https://tests.stockfishchess.org/tests/view/64e9079c85e3e95030fd8259 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 134688 W: 34338 L: 34226 D: 66124 Ptnml(0-2): 426, 15122, 36124, 15258, 414 closes https://github.com/official-stockfish/Stockfish/pull/4767 No functional change --- src/search.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d9b41cb3..18e4aa56 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -518,7 +518,6 @@ namespace { // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( !rootNode - && pos.rule50_count() >= 3 && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { @@ -1398,7 +1397,6 @@ moves_loop: // When in check, search starts here // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( depth < 0 - && pos.rule50_count() >= 3 && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { From 3c0e86a91e48baea273306e45fb6cf13a59373cf Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 23 Aug 2023 19:36:55 +0200 Subject: [PATCH 391/678] Cleanup includes Reorder a few includes, include "position.h" where it was previously missing and apply include-what-you-use suggestions. Also make the order of the includes consistent, in the following way: 1. Related header (for .cpp files) 2. A blank line 3. C/C++ headers 4. A blank line 5. All other header files closes https://github.com/official-stockfish/Stockfish/pull/4763 fixes https://github.com/official-stockfish/Stockfish/issues/4707 No functional change --- src/benchmark.cpp | 2 +- src/bitboard.cpp | 4 +++- src/bitboard.h | 5 +++++ src/evaluate.cpp | 17 +++++++++-------- src/evaluate.h | 4 +--- src/main.cpp | 7 +++++-- src/misc.cpp | 11 ++++++----- src/misc.h | 8 +++----- src/movegen.cpp | 7 +++++-- src/movegen.h | 1 + src/movepick.cpp | 9 +++++++-- src/movepick.h | 5 ++++- src/nnue/evaluate_nnue.cpp | 15 ++++++++++----- src/nnue/evaluate_nnue.h | 13 ++++++++++++- src/nnue/features/half_ka_v2_hm.cpp | 3 +++ src/nnue/features/half_ka_v2_hm.h | 6 ++++-- src/nnue/layers/affine_transform.h | 4 ++-- .../layers/affine_transform_sparse_input.h | 6 ++++-- src/nnue/layers/clipped_relu.h | 4 ++++ src/nnue/layers/sqr_clipped_relu.h | 4 ++++ src/nnue/nnue_accumulator.h | 3 +++ src/nnue/nnue_architecture.h | 12 +++++------- src/nnue/nnue_common.h | 6 +++++- src/nnue/nnue_feature_transformer.h | 15 +++++++++++---- src/position.cpp | 15 +++++++++++---- src/position.h | 7 +++---- src/search.cpp | 19 ++++++++++++++----- src/search.h | 1 + src/syzygy/tbprobe.cpp | 19 ++++++++++++------- src/syzygy/tbprobe.h | 6 ++++++ src/thread.cpp | 16 ++++++++++++---- src/thread.h | 4 +++- src/timeman.cpp | 4 ++-- src/timeman.h | 3 +++ src/tt.cpp | 9 ++++++--- src/tt.h | 3 +++ src/tune.cpp | 11 ++++++++--- src/tune.h | 3 +++ src/types.h | 3 --- src/uci.cpp | 17 ++++++++++++----- src/uci.h | 2 ++ src/ucioption.cpp | 9 ++++++++- 42 files changed, 226 insertions(+), 96 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index e340ebcd..f3401c61 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -18,9 +18,9 @@ #include "benchmark.h" +#include #include #include -#include #include #include "position.h" diff --git a/src/bitboard.cpp b/src/bitboard.cpp index fd5c3c22..bed2b3ee 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -16,10 +16,12 @@ along with this program. If not, see . */ +#include "bitboard.h" + #include #include +#include -#include "bitboard.h" #include "misc.h" namespace Stockfish { diff --git a/src/bitboard.h b/src/bitboard.h index 244dc034..f9175333 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -19,6 +19,11 @@ #ifndef BITBOARD_H_INCLUDED #define BITBOARD_H_INCLUDED +#include +#include +#include +#include +#include #include #include "types.h" diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 54216b97..25a65455 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -16,23 +16,24 @@ along with this program. If not, see . */ +#include "evaluate.h" + #include #include +#include #include #include -#include #include -#include +#include #include -#include "bitboard.h" -#include "evaluate.h" -#include "misc.h" -#include "thread.h" -#include "timeman.h" -#include "uci.h" #include "incbin/incbin.h" +#include "misc.h" #include "nnue/evaluate_nnue.h" +#include "position.h" +#include "thread.h" +#include "types.h" +#include "uci.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). diff --git a/src/evaluate.h b/src/evaluate.h index 586e3b81..a222da73 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -20,13 +20,11 @@ #define EVALUATE_H_INCLUDED #include -#include - -#include "types.h" namespace Stockfish { class Position; +enum Value : int; namespace Eval { diff --git a/src/main.cpp b/src/main.cpp index c854ac0c..eee149fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,14 +16,17 @@ along with this program. If not, see . */ +#include #include #include "bitboard.h" +#include "evaluate.h" +#include "misc.h" #include "position.h" #include "search.h" -#include "syzygy/tbprobe.h" #include "thread.h" -#include "tt.h" +#include "tune.h" +#include "types.h" #include "uci.h" using namespace Stockfish; diff --git a/src/misc.cpp b/src/misc.cpp index 922fad96..42083e0a 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -16,6 +16,8 @@ along with this program. If not, see . */ +#include "misc.h" + #ifdef _WIN32 #if _WIN32_WINNT < 0x0601 #undef _WIN32_WINNT @@ -44,17 +46,19 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES } #endif +#include #include #include #include #include #include +#include #include #include -#include + +#include "types.h" #if defined(__linux__) && !defined(__ANDROID__) -#include #include #endif @@ -63,9 +67,6 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES #include #endif -#include "misc.h" -#include "thread.h" - using namespace std; namespace Stockfish { diff --git a/src/misc.h b/src/misc.h index 0005fc0f..aed677b5 100644 --- a/src/misc.h +++ b/src/misc.h @@ -21,12 +21,10 @@ #include #include -#include -#include -#include +#include #include - -#include "types.h" +#include +#include #define stringify2(x) #x #define stringify(x) stringify2(x) diff --git a/src/movegen.cpp b/src/movegen.cpp index 6b28a52e..f0733c73 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -16,9 +16,12 @@ along with this program. If not, see . */ -#include - #include "movegen.h" + +#include +#include + +#include "bitboard.h" #include "position.h" namespace Stockfish { diff --git a/src/movegen.h b/src/movegen.h index b8df3e65..6449de25 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -20,6 +20,7 @@ #define MOVEGEN_H_INCLUDED #include +#include #include "types.h" diff --git a/src/movepick.cpp b/src/movepick.cpp index 9d5805a7..d4f8ab09 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -16,10 +16,15 @@ along with this program. If not, see . */ +#include "movepick.h" + +#include #include +#include +#include #include "bitboard.h" -#include "movepick.h" +#include "position.h" namespace Stockfish { @@ -161,7 +166,7 @@ void MovePicker::score() { : 0 ) : 0 ; } - + else // Type == EVASIONS { if (pos.capture_stage(m)) diff --git a/src/movepick.h b/src/movepick.h index 0b44557f..5243f89c 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -20,14 +20,17 @@ #define MOVEPICK_H_INCLUDED #include +#include +#include +#include #include #include #include "movegen.h" -#include "position.h" #include "types.h" namespace Stockfish { +class Position; /// StatsEntry stores the stat table value. It is usually a number but could /// be a move or even a nested history. We use a class instead of naked value diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index cff1d024..456f2edf 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -18,19 +18,24 @@ // Code for calculating NNUE evaluation function +#include "evaluate_nnue.h" + +#include +#include +#include #include #include #include -#include #include #include #include "../evaluate.h" +#include "../misc.h" #include "../position.h" -#include "../uci.h" #include "../types.h" - -#include "evaluate_nnue.h" +#include "../uci.h" +#include "nnue_accumulator.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { @@ -251,7 +256,7 @@ namespace Stockfish::Eval::NNUE { // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals static void format_cp_aligned_dot(Value v, std::stringstream &stream) { - + const double pawns = std::abs(0.01 * UCI::to_cp(v)); stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index b84bed8b..8faec6cc 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -21,9 +21,20 @@ #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED #define NNUE_EVALUATE_NNUE_H_INCLUDED +#include +#include +#include +#include +#include + +#include "../misc.h" +#include "nnue_architecture.h" #include "nnue_feature_transformer.h" -#include +namespace Stockfish { + class Position; + enum Value : int; +} namespace Stockfish::Eval::NNUE { diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 19ebb15f..016934b8 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -20,7 +20,10 @@ #include "half_ka_v2_hm.h" +#include "../../bitboard.h" #include "../../position.h" +#include "../../types.h" +#include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Features { diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 78063c36..9da1cc05 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -21,13 +21,15 @@ #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED #define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED -#include "../nnue_common.h" +#include -#include "../../evaluate.h" #include "../../misc.h" +#include "../../types.h" +#include "../nnue_common.h" namespace Stockfish { struct StateInfo; + class Position; } namespace Stockfish::Eval::NNUE::Features { diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index c936a83e..e9d0beac 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -21,9 +21,9 @@ #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED #define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#include #include -#include -#include + #include "../nnue_common.h" #include "simd.h" diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 2cd77e49..c9894f5d 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -21,10 +21,12 @@ #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED #define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED -#include #include #include -#include +#include +#include + +#include "../../bitboard.h" #include "../nnue_common.h" #include "affine_transform.h" #include "simd.h" diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index d5aa6fbf..2856bfb0 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -21,6 +21,10 @@ #ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED #define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#include +#include +#include + #include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Layers { diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 5c1b9e6c..503b283b 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -21,6 +21,10 @@ #ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED #define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#include +#include +#include + #include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Layers { diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 8eba4497..03fc3bd5 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -21,7 +21,10 @@ #ifndef NNUE_ACCUMULATOR_H_INCLUDED #define NNUE_ACCUMULATOR_H_INCLUDED +#include + #include "nnue_architecture.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 65319b14..b50c52df 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -21,18 +21,16 @@ #ifndef NNUE_ARCHITECTURE_H_INCLUDED #define NNUE_ARCHITECTURE_H_INCLUDED -#include - -#include "nnue_common.h" +#include +#include +#include #include "features/half_ka_v2_hm.h" - -#include "layers/affine_transform_sparse_input.h" #include "layers/affine_transform.h" +#include "layers/affine_transform_sparse_input.h" #include "layers/clipped_relu.h" #include "layers/sqr_clipped_relu.h" - -#include "../misc.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index e8ed2bc6..a42a86c9 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -21,10 +21,14 @@ #ifndef NNUE_COMMON_H_INCLUDED #define NNUE_COMMON_H_INCLUDED +#include +#include +#include #include #include +#include -#include "../misc.h" // for IsLittleEndian +#include "../misc.h" #if defined(USE_AVX2) #include diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 7571f398..0af0ed96 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -21,11 +21,18 @@ #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED #define NNUE_FEATURE_TRANSFORMER_H_INCLUDED -#include "nnue_common.h" -#include "nnue_architecture.h" +#include +#include +#include +#include +#include +#include -#include // std::memset() -#include // std::pair +#include "../position.h" +#include "../types.h" +#include "nnue_accumulator.h" +#include "nnue_architecture.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { diff --git a/src/position.cpp b/src/position.cpp index 675dec99..0f15727d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -16,22 +16,29 @@ along with this program. If not, see . */ +#include "position.h" + #include +#include #include -#include // For offsetof() -#include // For std::memset, std::memcmp +#include +#include +#include +#include #include +#include #include #include +#include #include "bitboard.h" #include "misc.h" #include "movegen.h" -#include "position.h" +#include "nnue/nnue_common.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" #include "uci.h" -#include "syzygy/tbprobe.h" using std::string; diff --git a/src/position.h b/src/position.h index 393c1ac9..f0546af3 100644 --- a/src/position.h +++ b/src/position.h @@ -21,14 +21,13 @@ #include #include -#include // For std::unique_ptr +#include +#include #include #include "bitboard.h" -#include "evaluate.h" -#include "types.h" - #include "nnue/nnue_accumulator.h" +#include "types.h" namespace Stockfish { diff --git a/src/search.cpp b/src/search.cpp index 18e4aa56..76d0545a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -16,25 +16,34 @@ along with this program. If not, see . */ +#include "search.h" + #include +#include +#include #include #include -#include // For std::memset +#include +#include +#include #include #include +#include +#include +#include "bitboard.h" #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" +#include "nnue/evaluate_nnue.h" +#include "nnue/nnue_common.h" #include "position.h" -#include "search.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "timeman.h" #include "tt.h" #include "uci.h" -#include "syzygy/tbprobe.h" -#include "nnue/evaluate_nnue.h" namespace Stockfish { @@ -1142,7 +1151,7 @@ moves_loop: // When in check, search starts here // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) r--; - + // Increase reduction on repetition (~1 Elo) if ( move == (ss-4)->currentMove && pos.has_repeated()) diff --git a/src/search.h b/src/search.h index 806e4be6..c6dbffce 100644 --- a/src/search.h +++ b/src/search.h @@ -19,6 +19,7 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED +#include #include #include "misc.h" diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index ba727825..d1b32d24 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -16,33 +16,38 @@ along with this program. If not, see . */ +#include "tbprobe.h" + +#include #include #include +#include #include -#include // For std::memset and std::memcpy +#include +#include #include #include +#include #include -#include #include #include #include #include +#include +#include #include "../bitboard.h" +#include "../misc.h" #include "../movegen.h" #include "../position.h" #include "../search.h" #include "../types.h" #include "../uci.h" -#include "tbprobe.h" - #ifndef _WIN32 #include -#include #include -#include +#include #else #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX @@ -1002,7 +1007,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. - // Implementation note: we first cast the unsigned size_t "base64.size()" + // Implementation note: we first cast the unsigned size_t "base64.size()" // to a signed int "base64_size" variable and then we are able to subtract 2, // avoiding unsigned overflow warnings. diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index fe994f68..b2ba35ff 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -19,8 +19,14 @@ #ifndef TBPROBE_H #define TBPROBE_H +#include + #include "../search.h" +namespace Stockfish { +class Position; +} + namespace Stockfish::Tablebases { enum WDLScore { diff --git a/src/thread.cpp b/src/thread.cpp index c680393e..9cf85310 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -16,15 +16,23 @@ along with this program. If not, see . */ -#include +#include "thread.h" -#include // For std::count +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" #include "movegen.h" #include "search.h" -#include "thread.h" -#include "uci.h" #include "syzygy/tbprobe.h" #include "tt.h" +#include "uci.h" namespace Stockfish { diff --git a/src/thread.h b/src/thread.h index aa9db2f3..a421af9e 100644 --- a/src/thread.h +++ b/src/thread.h @@ -21,14 +21,16 @@ #include #include +#include +#include #include -#include #include #include "movepick.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" +#include "types.h" namespace Stockfish { diff --git a/src/timeman.cpp b/src/timeman.cpp index 169c7821..5e57f8f9 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -16,12 +16,12 @@ along with this program. If not, see . */ +#include "timeman.h" + #include -#include #include #include "search.h" -#include "timeman.h" #include "uci.h" namespace Stockfish { diff --git a/src/timeman.h b/src/timeman.h index 3462b823..9ad6bdcc 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -19,9 +19,12 @@ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED +#include + #include "misc.h" #include "search.h" #include "thread.h" +#include "types.h" namespace Stockfish { diff --git a/src/tt.cpp b/src/tt.cpp index 3339c993..1582121f 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -16,14 +16,17 @@ along with this program. If not, see . */ -#include // For std::memset +#include "tt.h" + +#include +#include +#include #include #include +#include -#include "bitboard.h" #include "misc.h" #include "thread.h" -#include "tt.h" #include "uci.h" namespace Stockfish { diff --git a/src/tt.h b/src/tt.h index 3e335b44..df962faa 100644 --- a/src/tt.h +++ b/src/tt.h @@ -19,6 +19,9 @@ #ifndef TT_H_INCLUDED #define TT_H_INCLUDED +#include +#include + #include "misc.h" #include "types.h" diff --git a/src/tune.cpp b/src/tune.cpp index ccfc33c5..97baeb78 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -16,14 +16,20 @@ along with this program. If not, see . */ +#include "tune.h" + #include #include +#include #include +#include -#include "types.h" -#include "misc.h" #include "uci.h" +namespace Stockfish { +enum Value : int; +} + using std::string; namespace Stockfish { @@ -108,7 +114,6 @@ template<> void Tune::Entry::read_option() { value(); } // // Then paste the output below, as the function body -#include namespace Stockfish { diff --git a/src/tune.h b/src/tune.h index bdbee14e..3e94f7ef 100644 --- a/src/tune.h +++ b/src/tune.h @@ -19,12 +19,15 @@ #ifndef TUNE_H_INCLUDED #define TUNE_H_INCLUDED +#include #include #include #include +#include #include namespace Stockfish { +enum Value : int; using Range = std::pair; // Option's min-max values using RangeFun = Range (int); diff --git a/src/types.h b/src/types.h index 34dc42e1..bb319c2b 100644 --- a/src/types.h +++ b/src/types.h @@ -37,10 +37,7 @@ /// | only in 64-bit mode and requires hardware with pext support. #include -#include #include -#include -#include #if defined(_MSC_VER) // Disable some silly and noisy warning from MSVC compiler diff --git a/src/uci.cpp b/src/uci.cpp index ffe5e057..2a35a40f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -16,23 +16,30 @@ along with this program. If not, see . */ +#include "uci.h" + +#include #include +#include #include +#include +#include +#include #include +#include +#include #include #include +#include #include "benchmark.h" #include "evaluate.h" +#include "misc.h" #include "movegen.h" +#include "nnue/evaluate_nnue.h" #include "position.h" #include "search.h" #include "thread.h" -#include "timeman.h" -#include "tt.h" -#include "uci.h" -#include "syzygy/tbprobe.h" -#include "nnue/evaluate_nnue.h" using namespace std; diff --git a/src/uci.h b/src/uci.h index 2e40c912..7ca97d5c 100644 --- a/src/uci.h +++ b/src/uci.h @@ -19,6 +19,8 @@ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED +#include +#include #include #include diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 27f436d3..8d2c5c09 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -18,16 +18,23 @@ #include #include +#include +#include +#include +#include +#include #include #include +#include #include "evaluate.h" #include "misc.h" #include "search.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" +#include "types.h" #include "uci.h" -#include "syzygy/tbprobe.h" using std::string; From 4f7fe255c7ac9cf705350cabfc231d87a3e75018 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 26 Aug 2023 09:49:04 +0200 Subject: [PATCH 392/678] Simplify README The UCI protocol is rather technical and has little value in our README. Instead it should be explained in our wiki. "Contributing" is moved above "Compiling Stockfish" to make it more prominent. Also move the CONTRIBUTING.md into the root directory and include it in the distributed artifacts/releases. closes https://github.com/official-stockfish/Stockfish/pull/4766 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 1 + .github/workflows/stockfish_binaries.yml | 1 + .github/CONTRIBUTING.md => CONTRIBUTING.md | 0 README.md | 56 ++++++++------------ 4 files changed, 24 insertions(+), 34 deletions(-) rename .github/CONTRIBUTING.md => CONTRIBUTING.md (100%) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index dfe4e2a2..4d7f3d55 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -128,6 +128,7 @@ jobs: cp AUTHORS stockfish/ cp CITATION.cff stockfish/ cp README.md stockfish/ + cp CONTRIBUTING.md stockfish/ tar -cvf stockfish-android-$BINARY.tar stockfish - name: Upload binaries diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 7c7341ef..fadfbcfc 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -161,6 +161,7 @@ jobs: cp AUTHORS stockfish/ cp CITATION.cff stockfish/ cp README.md stockfish/ + cp CONTRIBUTING.md stockfish/ - name: Create tar if: runner.os != 'Windows' diff --git a/.github/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 100% rename from .github/CONTRIBUTING.md rename to CONTRIBUTING.md diff --git a/README.md b/README.md index 4d63b71e..52b123cb 100644 --- a/README.md +++ b/README.md @@ -59,40 +59,9 @@ This distribution of Stockfish consists of the following files: * a file with the .nnue extension, storing the neural network for the NNUE evaluation. Binary distributions will have this file embedded. -## The UCI protocol - -The [Universal Chess Interface][uci-link] (UCI) is a standard text-based protocol -used to communicate with a chess engine and is the recommended way to do so for -typical graphical user interfaces (GUI) or chess tools. Stockfish implements the -majority of its options. - -Developers can see the default values for the UCI options available in Stockfish -by typing `./stockfish uci` in a terminal, but most users should typically use a -chess GUI to interact with Stockfish. - -For more information on UCI or debug commands, see our [documentation][wiki-commands-link]. - -## Compiling Stockfish - -Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions, -big-endian machines such as Power PC, and other platforms. - -On Unix-like systems, it should be easy to compile Stockfish directly from the -source code with the included Makefile in the folder `src`. In general, it is -recommended to run `make help` to see a list of make targets with corresponding -descriptions. An example suitable for most Intel and AMD chips: - -``` -cd src -make -j profile-build ARCH=x86-64-avx2 -``` - -Detailed compilation instructions for all platforms can be found in our -[documentation][wiki-compile-link]. - ## Contributing -__See [Contributing Guide](./.github/CONTRIBUTING.md).__ +__See [Contributing Guide](CONTRIBUTING.md).__ ### Donating hardware @@ -116,6 +85,25 @@ Discussions about Stockfish take place these days mainly in the Stockfish [Discord server][discord-link]. This is also the best place to ask questions about the codebase and how to improve it. +## Compiling Stockfish + +Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions, +big-endian machines such as Power PC, and other platforms. + +On Unix-like systems, it should be easy to compile Stockfish directly from the +source code with the included Makefile in the folder `src`. In general, it is +recommended to run `make help` to see a list of make targets with corresponding +descriptions. An example suitable for most Intel and AMD chips: + +``` +cd src +make -j profile-build ARCH=x86-64-avx2 +``` + +Detailed compilation instructions for all platforms can be found in our +[documentation][wiki-compile-link]. Our wiki also has information about +the [UCI commands][wiki-uci-link] supported by Stockfish. + ## Terms of use Stockfish is free and distributed under the @@ -152,9 +140,9 @@ also be made available under GPL v3. [website-link]: https://stockfishchess.org [website-blog-link]: https://stockfishchess.org/blog/ [wiki-link]: https://github.com/official-stockfish/Stockfish/wiki -[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage [wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source -[wiki-commands-link]: https://github.com/official-stockfish/Stockfish/wiki/Commands +[wiki-uci-link]: https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands +[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage [worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker [build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github From 1f7ff8406d323e634a2aa1e1264042340707cdd9 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Thu, 17 Aug 2023 14:31:05 +0200 Subject: [PATCH 393/678] Simplify slider_blocker calculation Now that classical evaluation was removed, we can adapt this method to the needs of set_check_info. STC: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 298176 W: 75802 L: 75868 D: 146506 Ptnml(0-2): 908, 33608, 80192, 33402, 978 https://tests.stockfishchess.org/tests/view/64e70b899009777747557b43 closes https://github.com/official-stockfish/Stockfish/pull/4753 no functional change --- src/position.cpp | 34 +++++++++++++++------------------- src/position.h | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 0f15727d..12067743 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -321,8 +321,8 @@ void Position::set_castling_right(Color c, Square rfrom) { void Position::set_check_info() const { - st->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), st->pinners[BLACK]); - st->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), st->pinners[WHITE]); + update_slider_blockers(WHITE); + update_slider_blockers(BLACK); Square ksq = square(~sideToMove); @@ -443,37 +443,33 @@ string Position::fen() const { return ss.str(); } +/// update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], +/// which store respectively the pieces preventing king of color c from being in check +/// and the slider pieces of color ~c pinning pieces of color c to the king. +void Position::update_slider_blockers(Color c) const { -/// Position::slider_blockers() returns a bitboard of all the pieces (both colors) -/// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a -/// slider if removing that piece from the board would result in a position where -/// square 's' is attacked. For example, a king-attack blocking piece can be either -/// a pinned or a discovered check piece, according if its color is the opposite -/// or the same of the color of the slider. + Square ksq = square(c); -Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const { - - Bitboard blockers = 0; - pinners = 0; + st->blockersForKing[c] = 0; + st->pinners[~c] = 0; // Snipers are sliders that attack 's' when a piece and other snipers are removed - Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK)) - | (attacks_bb(s) & pieces(QUEEN, BISHOP))) & sliders; + Bitboard snipers = ( (attacks_bb< ROOK>(ksq) & pieces(QUEEN, ROOK)) + | (attacks_bb(ksq) & pieces(QUEEN, BISHOP))) & pieces(~c); Bitboard occupancy = pieces() ^ snipers; while (snipers) { Square sniperSq = pop_lsb(snipers); - Bitboard b = between_bb(s, sniperSq) & occupancy; + Bitboard b = between_bb(ksq, sniperSq) & occupancy; if (b && !more_than_one(b)) { - blockers |= b; - if (b & pieces(color_of(piece_on(s)))) - pinners |= sniperSq; + st->blockersForKing[c] |= b; + if (b & pieces(c)) + st->pinners[~c] |= sniperSq; } } - return blockers; } diff --git a/src/position.h b/src/position.h index f0546af3..ca7c3ace 100644 --- a/src/position.h +++ b/src/position.h @@ -114,7 +114,7 @@ public: // Attacks to/from a given square Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Bitboard occupied) const; - Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; + void update_slider_blockers(Color c) const; template Bitboard attacks_by(Color c) const; // Properties of moves From adf29b3fd69cdca035d1aa6675e01acafbf4d07f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 25 Aug 2023 15:42:44 +0300 Subject: [PATCH 394/678] Rename one variable To enhance code clarity and prevent potential confusion with the 'r' variable assigned to reduction later in the code, this pull request renames it to 'reductionScale' when we use the same name in the reduction() function. Using distinct variable names for separate functions improves code readability and maintainability. closes https://github.com/official-stockfish/Stockfish/pull/4765 No functional change --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 76d0545a..eefe5a3b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -80,8 +80,9 @@ namespace { int Reductions[MAX_MOVES]; // [depth or moveNumber] Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { - int r = Reductions[d] * Reductions[mn]; - return (r + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 + (!i && r > 936); + int reductionScale = Reductions[d] * Reductions[mn]; + return (reductionScale + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 + + (!i && reductionScale > 936); } constexpr int futility_move_count(bool improving, Depth depth) { From b25d68f6ee2d016cc0c14b076e79e6c44fdaea2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sat, 2 Sep 2023 08:39:16 +0200 Subject: [PATCH 395/678] Introduce simple_eval() for lazy evaluations This patch implements the pure materialistic evaluation called simple_eval() to gain a speed-up during Stockfish search. We use the so-called lazy evaluation trick: replace the accurate but slow NNUE network evaluation by the super-fast simple_eval() if the position seems to be already won (high material advantage). To guard against some of the most obvious blunders introduced by this idea, this patch uses the following features which will raise the lazy evaluation threshold in some situations: - avoid lazy evals on shuffling branches in the search tree - avoid lazy evals if the position at root already has a material imbalance - avoid lazy evals if the search value at root is already winning/losing. Moreover, we add a small random noise to the simple_eval() term. This idea (stochastic mobility in the minimax tree) was worth about 200 Elo in the pure simple_eval() player on Lichess. Overall, the current implementation in this patch evaluates about 2% of the leaves in the search tree lazily. -------------------------------------------- STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 60352 W: 15585 L: 15234 D: 29533 Ptnml(0-2): 216, 6906, 15578, 7263, 213 https://tests.stockfishchess.org/tests/view/64f1d9bcbd9967ffae366209 LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 35106 W: 8990 L: 8678 D: 17438 Ptnml(0-2): 14, 3668, 9887, 3960, 24 https://tests.stockfishchess.org/tests/view/64f25204f5b0c54e3f04c0e7 verification run at VLTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 74362 W: 19088 L: 18716 D: 36558 Ptnml(0-2): 6, 7226, 22348, 7592, 9 https://tests.stockfishchess.org/tests/view/64f2ecdbf5b0c54e3f04d3ae All three tests above were run with adjudication off, we also verified that there was no regression on matetracker (thanks Disservin!). ---------------------------------------------- closes https://github.com/official-stockfish/Stockfish/pull/4771 Bench: 1393714 --- src/evaluate.cpp | 52 +++++++++++++++++++++++++++++++++--------------- src/evaluate.h | 4 ++++ src/thread.cpp | 2 ++ src/thread.h | 1 + 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 25a65455..46ebbb49 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -136,35 +136,54 @@ namespace Eval { } } -/// evaluate() is the evaluator for the outer world. It returns a static -/// evaluation of the position from the point of view of the side to move. + +/// simple_eval() returns a static, purely materialistic evaluation of the position +/// from the point of view of the given color. It can be divided by PawnValue to get +/// an approximation of the material advantage on the board in terms of pawns. + +Value Eval::simple_eval(const Position& pos, Color c) { + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); +} + + +/// evaluate() is the evaluator for the outer world. It returns a static evaluation +/// of the position from the point of view of the side to move. Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); Value v; + Color stm = pos.side_to_move(); + int shuffling = pos.rule50_count(); + int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); - int nnueComplexity; - int npm = pos.non_pawn_material() / 64; + bool lazy = abs(simpleEval) >= RookValue + KnightValue + + 16 * shuffling * shuffling + + abs(pos.this_thread()->bestValue) + + abs(pos.this_thread()->rootSimpleEval); - Color stm = pos.side_to_move(); - Value optimism = pos.this_thread()->optimism[stm]; + if (lazy) + v = Value(simpleEval); + else + { + int nnueComplexity; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + Value optimism = pos.this_thread()->optimism[stm]; - int material = pos.non_pawn_material(stm) - pos.non_pawn_material(~stm) - + 126 * (pos.count(stm) - pos.count(~stm)); + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768; - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; - nnue -= nnue * (nnueComplexity + abs(material - nnue)) / 32768; - - v = ( nnue * (915 + npm + 9 * pos.count()) - + optimism * (154 + npm + pos.count())) / 1024; + int npm = pos.non_pawn_material() / 64; + v = ( nnue * (915 + npm + 9 * pos.count()) + + optimism * (154 + npm + pos.count())) / 1024; + } // Damp down the evaluation linearly when shuffling - v = v * (200 - pos.rule50_count()) / 214; + v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); @@ -184,6 +203,7 @@ std::string Eval::trace(Position& pos) { // Reset any global variable used in eval pos.this_thread()->bestValue = VALUE_ZERO; + pos.this_thread()->rootSimpleEval = VALUE_ZERO; pos.this_thread()->optimism[WHITE] = VALUE_ZERO; pos.this_thread()->optimism[BLACK] = VALUE_ZERO; diff --git a/src/evaluate.h b/src/evaluate.h index a222da73..7f4feedf 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -21,6 +21,8 @@ #include +#include "types.h" + namespace Stockfish { class Position; @@ -29,6 +31,8 @@ enum Value : int; namespace Eval { std::string trace(Position& pos); + + Value simple_eval(const Position& pos, Color c); Value evaluate(const Position& pos); extern std::string currentEvalFileName; diff --git a/src/thread.cpp b/src/thread.cpp index 9cf85310..60f760ed 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -27,6 +27,7 @@ #include #include +#include "evaluate.h" #include "misc.h" #include "movegen.h" #include "search.h" @@ -212,6 +213,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, th->rootMoves = rootMoves; th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); th->rootState = setupStates->back(); + th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); } main()->start_searching(); diff --git a/src/thread.h b/src/thread.h index a421af9e..8d0adcf0 100644 --- a/src/thread.h +++ b/src/thread.h @@ -67,6 +67,7 @@ public: Search::RootMoves rootMoves; Depth rootDepth, completedDepth; Value rootDelta; + Value rootSimpleEval; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; CapturePieceToHistory captureHistory; From a8b4fd16716e74a9819e798fc09e5926e003013e Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Mon, 4 Sep 2023 22:01:20 +0200 Subject: [PATCH 396/678] Avoid "using namespace std" This is a cleanup PR that prepares the automatic checking of missing or superfluous #include directives via the include-what-you-use (IWYU) tool on the CI. Unfortunately, IWYU proposes additional includes for "namespace std" although we don't need them. To avoid the problem, the commit removes all "using namespace std" statements from the code and directly uses the std:: prefix instead. Alternatively, we could add specific usings (e.g. "using std::string") foreach used type. Also, a mix of both approaches would be possible. I decided for the prefix approach because most of the files were already using the std:: prefixes despite the "using namespace std". closes https://github.com/official-stockfish/Stockfish/pull/4772 No functional change --- src/benchmark.cpp | 30 +++++++++++------------- src/evaluate.cpp | 30 +++++++++++------------- src/misc.cpp | 48 ++++++++++++++++++------------------- src/uci.cpp | 60 +++++++++++++++++++++++------------------------ 4 files changed, 80 insertions(+), 88 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index f3401c61..8e28184a 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -25,11 +25,9 @@ #include "position.h" -using namespace std; - namespace { -const vector Defaults = { +const std::vector Defaults = { "setoption name UCI_Chess960 value false", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", @@ -109,17 +107,17 @@ namespace Stockfish { /// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec /// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" -vector setup_bench(const Position& current, istream& is) { +std::vector setup_bench(const Position& current, std::istream& is) { - vector fens, list; - string go, token; + std::vector fens, list; + std::string go, token; // Assign default values to missing arguments - string ttSize = (is >> token) ? token : "16"; - string threads = (is >> token) ? token : "1"; - string limit = (is >> token) ? token : "13"; - string fenFile = (is >> token) ? token : "default"; - string limitType = (is >> token) ? token : "depth"; + std::string ttSize = (is >> token) ? token : "16"; + std::string threads = (is >> token) ? token : "1"; + std::string limit = (is >> token) ? token : "13"; + std::string fenFile = (is >> token) ? token : "default"; + std::string limitType = (is >> token) ? token : "depth"; go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; @@ -131,12 +129,12 @@ vector setup_bench(const Position& current, istream& is) { else { - string fen; - ifstream file(fenFile); + std::string fen; + std::ifstream file(fenFile); if (!file.is_open()) { - cerr << "Unable to open file " << fenFile << endl; + std::cerr << "Unable to open file " << fenFile << std::endl; exit(EXIT_FAILURE); } @@ -151,8 +149,8 @@ vector setup_bench(const Position& current, istream& is) { list.emplace_back("setoption name Hash value " + ttSize); list.emplace_back("ucinewgame"); - for (const string& fen : fens) - if (fen.find("setoption") != string::npos) + for (const std::string& fen : fens) + if (fen.find("setoption") != std::string::npos) list.emplace_back(fen); else { diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 46ebbb49..9ca0e456 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -51,13 +51,11 @@ #endif -using namespace std; - namespace Stockfish { namespace Eval { - string currentEvalFileName = "None"; + std::string currentEvalFileName = "None"; /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -69,22 +67,22 @@ namespace Eval { void NNUE::init() { - string eval_file = string(Options["EvalFile"]); + std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; #if defined(DEFAULT_NNUE_DIRECTORY) - vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; + std::vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; #else - vector dirs = { "" , "" , CommandLine::binaryDirectory }; + std::vector dirs = { "" , "" , CommandLine::binaryDirectory }; #endif - for (const string& directory : dirs) + for (const std::string& directory : dirs) if (currentEvalFileName != eval_file) { if (directory != "") { - ifstream stream(directory + eval_file, ios::binary); + std::ifstream stream(directory + eval_file, std::ios::binary); if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } @@ -92,7 +90,7 @@ namespace Eval { if (directory == "" && eval_file == EvalFileDefaultName) { // C++ way to prepare a buffer for a memory stream - class MemoryBuffer : public basic_streambuf { + class MemoryBuffer : public std::basic_streambuf { public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } }; @@ -100,7 +98,7 @@ namespace Eval { size_t(gEmbeddedNNUESize)); (void) gEmbeddedNNUEEnd; // Silence warning on unused variable - istream stream(&buffer); + std::istream stream(&buffer); if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } @@ -110,18 +108,18 @@ namespace Eval { /// NNUE::verify() verifies that the last net used was loaded successfully void NNUE::verify() { - string eval_file = string(Options["EvalFile"]); + std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; if (currentEvalFileName != eval_file) { - string msg1 = "Network evaluation parameters compatible with the engine must be available."; - string msg2 = "The network file " + eval_file + " was not loaded successfully."; - string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); - string msg5 = "The engine will be terminated now."; + std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); + std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; sync_cout << "info string ERROR: " << msg2 << sync_endl; diff --git a/src/misc.cpp b/src/misc.cpp index 42083e0a..a72a1c13 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -67,14 +67,12 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES #include #endif -using namespace std; - namespace Stockfish { namespace { /// Version number or dev. -constexpr string_view version = "dev"; +constexpr std::string_view version = "dev"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -82,16 +80,16 @@ constexpr string_view version = "dev"; /// usual I/O functionality, all without changing a single line of code! /// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 -struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout +struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout - Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} + Tie(std::streambuf* b, std::streambuf* l) : buf(b), logBuf(l) {} int sync() override { return logBuf->pubsync(), buf->pubsync(); } int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } int underflow() override { return buf->sgetc(); } int uflow() override { return log(buf->sbumpc(), ">> "); } - streambuf *buf, *logBuf; + std::streambuf *buf, *logBuf; int log(int c, const char* prefix) { @@ -106,10 +104,10 @@ struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout class Logger { - Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {} + Logger() : in(std::cin.rdbuf(), file.rdbuf()), out(std::cout.rdbuf(), file.rdbuf()) {} ~Logger() { start(""); } - ofstream file; + std::ofstream file; Tie in, out; public: @@ -119,23 +117,23 @@ public: if (l.file.is_open()) { - cout.rdbuf(l.out.buf); - cin.rdbuf(l.in.buf); + std::cout.rdbuf(l.out.buf); + std::cin.rdbuf(l.in.buf); l.file.close(); } if (!fname.empty()) { - l.file.open(fname, ifstream::out); + l.file.open(fname, std::ifstream::out); if (!l.file.is_open()) { - cerr << "Unable to open debug log file " << fname << endl; + std::cerr << "Unable to open debug log file " << fname << std::endl; exit(EXIT_FAILURE); } - cin.rdbuf(&l.in); - cout.rdbuf(&l.out); + std::cin.rdbuf(&l.in); + std::cout.rdbuf(&l.out); } } }; @@ -153,9 +151,9 @@ public: /// For releases (non dev builds) we only include the version number: /// Stockfish version -string engine_info(bool to_uci) { - stringstream ss; - ss << "Stockfish " << version << setfill('0'); +std::string engine_info(bool to_uci) { + std::stringstream ss; + ss << "Stockfish " << version << std::setfill('0'); if constexpr (version == "dev") { @@ -163,12 +161,12 @@ string engine_info(bool to_uci) { #ifdef GIT_DATE ss << stringify(GIT_DATE); #else - constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); - string month, day, year; - stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" + constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + std::string month, day, year; + std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" date >> month >> day >> year; - ss << year << setw(2) << setfill('0') << (1 + months.find(month) / 4) << setw(2) << setfill('0') << day; + ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) << std::setw(2) << std::setfill('0') << day; #endif ss << "-"; @@ -741,12 +739,12 @@ void bindThisThread(size_t idx) { namespace CommandLine { -string argv0; // path+name of the executable binary, as given by argv[0] -string binaryDirectory; // path of the executable directory -string workingDirectory; // path of the working directory +std::string argv0; // path+name of the executable binary, as given by argv[0] +std::string binaryDirectory; // path of the executable directory +std::string workingDirectory; // path of the working directory void init([[maybe_unused]] int argc, char* argv[]) { - string pathSeparator; + std::string pathSeparator; // extract the path+name of the executable binary argv0 = argv[0]; diff --git a/src/uci.cpp b/src/uci.cpp index 2a35a40f..f3e436ef 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -41,8 +41,6 @@ #include "search.h" #include "thread.h" -using namespace std; - namespace Stockfish { namespace { @@ -56,10 +54,10 @@ namespace { // the initial position ("startpos") and then makes the moves given in the following // move list ("moves"). - void position(Position& pos, istringstream& is, StateListPtr& states) { + void position(Position& pos, std::istringstream& is, StateListPtr& states) { Move m; - string token, fen; + std::string token, fen; is >> token; @@ -103,11 +101,11 @@ namespace { // setoption() is called when the engine receives the "setoption" UCI command. // The function updates the UCI option ("name") to the given value ("value"). - void setoption(istringstream& is) { + void setoption(std::istringstream& is) { Threads.main()->wait_for_search_finished(); - string token, name, value; + std::string token, name, value; is >> token; // Consume the "name" token @@ -130,10 +128,10 @@ namespace { // sets the thinking time and other parameters from the input string, then starts // with a search. - void go(Position& pos, istringstream& is, StateListPtr& states) { + void go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits; - string token; + std::string token; bool ponderMode = false; limits.startTime = now(); // The search starts as early as possible @@ -164,24 +162,24 @@ namespace { // Firstly, a list of UCI commands is set up according to the bench // parameters, then it is run one by one, printing a summary at the end. - void bench(Position& pos, istream& args, StateListPtr& states) { + void bench(Position& pos, std::istream& args, StateListPtr& states) { - string token; + std::string token; uint64_t num, nodes = 0, cnt = 1; - vector list = setup_bench(pos, args); - num = count_if(list.begin(), list.end(), [](const string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + std::vector list = setup_bench(pos, args); + num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); TimePoint elapsed = now(); for (const auto& cmd : list) { - istringstream is(cmd); - is >> skipws >> token; + std::istringstream is(cmd); + is >> std::skipws >> token; if (token == "go" || token == "eval") { - cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl; + std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << std::endl; if (token == "go") { go(pos, is, states); @@ -200,10 +198,10 @@ namespace { dbg_print(); - cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes - << "\nNodes/second : " << 1000 * nodes / elapsed << endl; + std::cerr << "\n===========================" + << "\nTotal time (ms) : " << elapsed + << "\nNodes searched : " << nodes + << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; } // The win rate model returns the probability of winning (in per mille units) given an @@ -244,7 +242,7 @@ namespace { void UCI::loop(int argc, char* argv[]) { Position pos; - string token, cmd; + std::string token, cmd; StateListPtr states(new std::deque(1)); pos.set(StartFEN, false, &states->back(), Threads.main()); @@ -253,13 +251,13 @@ void UCI::loop(int argc, char* argv[]) { cmd += std::string(argv[i]) + " "; do { - if (argc == 1 && !getline(cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + if (argc == 1 && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication cmd = "quit"; - istringstream is(cmd); + std::istringstream is(cmd); token.clear(); // Avoid a stale if getline() returns nothing or a blank line - is >> skipws >> token; + is >> std::skipws >> token; if ( token == "quit" || token == "stop") @@ -294,7 +292,7 @@ void UCI::loop(int argc, char* argv[]) { { std::optional filename; std::string f; - if (is >> skipws >> f) + if (is >> std::skipws >> f) filename = f; Eval::NNUE::save_eval(filename); } @@ -325,11 +323,11 @@ int UCI::to_cp(Value v) { /// mate Mate in 'y' moves (not plies). If the engine is getting mated, /// uses negative values for 'y'. -string UCI::value(Value v) { +std::string UCI::value(Value v) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - stringstream ss; + std::stringstream ss; if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) ss << "cp " << UCI::to_cp(v); @@ -348,9 +346,9 @@ string UCI::value(Value v) { /// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation /// and a game ply based on the data gathered for fishtest LTC games. -string UCI::wdl(Value v, int ply) { +std::string UCI::wdl(Value v, int ply) { - stringstream ss; + std::stringstream ss; int wdl_w = win_rate_model( v, ply); int wdl_l = win_rate_model(-v, ply); @@ -373,7 +371,7 @@ std::string UCI::square(Square s) { /// standard chess mode and in e1h1 notation it is printed in Chess960 mode. /// Internally, all castling moves are always encoded as 'king captures rook'. -string UCI::move(Move m, bool chess960) { +std::string UCI::move(Move m, bool chess960) { if (m == MOVE_NONE) return "(none)"; @@ -387,7 +385,7 @@ string UCI::move(Move m, bool chess960) { if (type_of(m) == CASTLING && !chess960) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - string move = UCI::square(from) + UCI::square(to); + std::string move = UCI::square(from) + UCI::square(to); if (type_of(m) == PROMOTION) move += " pnbrqk"[promotion_type(m)]; @@ -399,7 +397,7 @@ string UCI::move(Move m, bool chess960) { /// UCI::to_move() converts a string representing a move in coordinate notation /// (g1f3, a7a8q) to the corresponding legal Move, if any. -Move UCI::to_move(const Position& pos, string& str) { +Move UCI::to_move(const Position& pos, std::string& str) { if (str.length() == 5) str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased From 1461d861c8240e29df690f1e34dc50eee37ae1b5 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Mon, 4 Sep 2023 13:53:30 +0200 Subject: [PATCH 397/678] Prevent usage of AVX-512 for the last layer. Add more static checks regarding the SIMD width match. STC: https://tests.stockfishchess.org/tests/view/64f5c568a9bc5a78c669e70e LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 125216 W: 31756 L: 31636 D: 61824 Ptnml(0-2): 327, 13993, 33848, 14113, 327 Fixes a bug introduced in 2f2f45f, where with AVX-512 the weights and input to the last layer were being read out of bounds. Now AVX-512 is only used for the layers it can be used for. Additional static assertions have been added to prevent more errors like this in the future. closes https://github.com/official-stockfish/Stockfish/pull/4773 No functional change --- src/nnue/layers/affine_transform.h | 50 ++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index e9d0beac..61cdb781 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -210,6 +210,11 @@ namespace Stockfish::Eval::NNUE::Layers { void propagate( const InputType* input, OutputType* output) const { +#if defined (USE_SSSE3) + + if constexpr (OutputDimensions > 1) + { + #if defined (USE_AVX512) using vec_t = __m512i; #define vec_setzero _mm512_setzero_si512 @@ -233,15 +238,10 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_hadd Simd::m128_hadd #endif -#if defined (USE_SSSE3) - const auto inputVector = reinterpret_cast(input); + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); - static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + static_assert(OutputDimensions % OutputSimdWidth == 0); - static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1); - - if constexpr (OutputDimensions % OutputSimdWidth == 0) - { constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; @@ -264,10 +264,41 @@ namespace Stockfish::Eval::NNUE::Layers { vec_t* outptr = reinterpret_cast(output); for (IndexType k = 0; k < NumRegs; ++k) outptr[k] = acc[k]; + +# undef vec_setzero +# undef vec_set_32 +# undef vec_add_dpbusd_32 +# undef vec_add_dpbusd_32x2 +# undef vec_hadd + } else if constexpr (OutputDimensions == 1) { - constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; + +// We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements. +#if defined (USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 + #define vec_hadd Simd::m256_hadd +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 + #define vec_hadd Simd::m128_hadd +#endif + + const auto inputVector = reinterpret_cast(input); + + static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); + + static_assert(PaddedInputDimensions % InputSimdWidth == 0); + + constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; vec_t sum0 = vec_setzero(); const auto row0 = reinterpret_cast(&weights[0]); @@ -277,13 +308,14 @@ namespace Stockfish::Eval::NNUE::Layers { vec_add_dpbusd_32(sum0, in, row0[j]); } output[0] = vec_hadd(sum0, biases[0]); - } # undef vec_setzero # undef vec_set_32 # undef vec_add_dpbusd_32 # undef vec_add_dpbusd_32x2 # undef vec_hadd + + } #else // Use old implementation for the other architectures. affine_transform_non_ssse3< From 46a5cedc11bbad4a35f36aec660f270680008f37 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 10 Sep 2023 12:15:06 +0200 Subject: [PATCH 398/678] Cleanup git checkout actions We now fetch only the current commit for jobs that don't need the git history. For the Prerelease job, we don't checkout the code at all. closes https://github.com/official-stockfish/Stockfish/pull/4779 No functional change --- .github/workflows/stockfish.yml | 4 ---- .github/workflows/stockfish_compile_test.yml | 2 -- .github/workflows/stockfish_sanitizers.yml | 2 -- 3 files changed, 8 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 99c4259a..8ea1837d 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -16,10 +16,6 @@ jobs: if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - # returns null if no pre-release exists - name: Get Commit SHA of Latest Pre-release run: | diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 90e01537..808fcb55 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -52,8 +52,6 @@ jobs: shell: ${{ matrix.config.shell }} steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Setup msys and install required packages if: runner.os == 'Windows' diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index 228742b3..b137f50e 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -36,8 +36,6 @@ jobs: shell: ${{ matrix.config.shell }} steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Download required linux packages run: | From 6d85f43e26cb8632337e67cea5ef88bab78121f3 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:49:18 +0800 Subject: [PATCH 399/678] Simplify cutnode depth condition With this patch, the depth condition for the cutnodes reduction is loosened from tte->depth() >= depth + 3 to just tte->depth() >= depth. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 101152 W: 25830 L: 25682 D: 49640 Ptnml(0-2): 312, 11788, 26258, 11876, 342 https://tests.stockfishchess.org/tests/view/64fd15635dab775b5359eaa6 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82542 W: 20980 L: 20824 D: 40738 Ptnml(0-2): 42, 8795, 23440, 8953, 41 https://tests.stockfishchess.org/tests/view/64fda3545dab775b5359fbf1 closes https://github.com/official-stockfish/Stockfish/pull/4780 Bench: 1479029 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index eefe5a3b..a745d3bf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1131,7 +1131,7 @@ moves_loop: // When in check, search starts here // Decrease further on cutNodes. (~1 Elo) if ( ss->ttPv && !likelyFailLow) - r -= cutNode && tte->depth() >= depth + 3 ? 3 : 2; + r -= cutNode && tte->depth() >= depth ? 3 : 2; // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss-1)->moveCount > 8) From ef2282961602f47a9c0c11adc2c0da7af39dab0f Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 11 Sep 2023 15:37:18 +0300 Subject: [PATCH 400/678] Do more futility pruning in qsearch This patch introduces a third futility pruning heuristic in qsearch. The idea is that the static exchange evaluation is much worse than the difference between futility base and alpha. Thus we can assume that the probability of the move being good enough to beat alpha is low so it can be pruned. Passed STC: https://tests.stockfishchess.org/tests/view/64fc982a5dab775b5359dc83 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 36576 W: 9484 L: 9170 D: 17922 Ptnml(0-2): 121, 4119, 9495, 4431, 122 Passed LTC: https://tests.stockfishchess.org/tests/view/64fcc7935dab775b5359e1a9 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 135408 W: 34556 L: 34041 D: 66811 Ptnml(0-2): 56, 14462, 38165, 14953, 68 closes https://github.com/official-stockfish/Stockfish/pull/4781 Bench: 1330793 --- src/search.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index a745d3bf..beb1cb54 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1549,17 +1549,29 @@ moves_loop: // When in check, search starts here futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; + // If static eval + value of piece we are going to capture is much lower + // than alpha we can prune this move if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); continue; } + // If static eval is much lower than alpha and move is not winning material + // we can prune this move if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; } + + // If static exchange evaluation is much worse than what is needed to not + // fall below alpha we can prune this move + if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) + { + bestValue = alpha; + continue; + } } // We prune after the second quiet check evasion move, where being 'in check' is From 3d1b067d853d6e8cc22cf18c1abb4cd9833dd38f Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 9 Sep 2023 10:24:57 -0400 Subject: [PATCH 401/678] Update default net to nn-1ee1aba5ed4c.nnue Created by retraining the master net on a dataset composed by: - adding Leela data from T60 jul-dec 2020, T77 nov 2021, T80 jun-jul 2023 - deduplicating and unminimizing parts of the dataset before interleaving Trained initially with max epoch 800, then increased near the end of training twice. First to 960, then 1200. After training, post-processing involved: - greedy permuting L1 weights with https://github.com/official-stockfish/Stockfish/pull/4620 - greedy 2- and 3- cycle permuting with https://github.com/official-stockfish/Stockfish/pull/4640 python3 easy_train.py \ --experiment-name 2048-retrain-S6-sk28 \ --training-dataset /data/S6.binpack \ --early-fen-skipping 28 \ --start-from-engine-test-net True \ --max_epoch 1200 \ --lr 4.375e-4 \ --gamma 0.995 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --tui False \ --seed $RANDOM \ --gpus 0 In the list of datasets below, periods in the filename represent the sequence of steps applied to arrive at the particular binpack. For example: test77-dec2021-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack 1. test77 dec2021 data rescored with 16 TB of syzygy tablebases during data conversion 2. filtered with csv_filter_v6_dd.py - v6 filtering and deduplication in one step 3. minimized with the original mar2023 implementation of `minimize_binpack` in the tools branch 4. unminimized by removing all positions with score == 32002 (`VALUE_NONE`) Binpacks were: - filtered with: https://github.com/linrock/nnue-data - unminimized with: https://github.com/linrock/Stockfish/tree/tools-unminify - deduplicated with: https://github.com/linrock/Stockfish/tree/tools-dd DATASETS=( leela96-filt-v2.min.unminimized.binpack dfrc99-16tb7p-eval-filt-v2.min.unminimized.binpack # most of the 0dd1cebea57 v6-dd dataset (without test80-jul2022) # https://github.com/official-stockfish/Stockfish/pull/4606 test60-novdec2021-12tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test77-dec2021-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test78-jantomay2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test78-juntosep2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test79-apr2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test79-may2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-jun2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-aug2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-sep2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-oct2022-16tb7p.filter-v6-dd.min.binpack test80-nov2022-16tb7p.filter-v6-dd.min.binpack test80-jan2023-3of3-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-feb2023-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack # older Leela data, recently converted test60-octnovdec2020-2tb7p.min.unminimized.binpack test60-julaugsep2020-2tb7p.min.binpack test77-nov2021-2tb7p.min.dd.binpack # newer Leela data test80-mar2023-2tb7p.min.unminimized.binpack test80-apr2023-2tb7p.filter-v6-sk16.min.unminimized.binpack test80-may2023-2tb7p.min.dd.binpack test80-jun2023-2tb7p.min.binpack test80-jul2023-2tb7p.binpack ) python3 interleave_binpacks.py ${DATASETS[@]} /data/S6.binpack Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch1059 : 2.7 +/- 1.6 Passed STC: https://tests.stockfishchess.org/tests/view/64fc8d705dab775b5359db42 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 168352 W: 43216 L: 42704 D: 82432 Ptnml(0-2): 599, 19672, 43134, 20160, 611 Passed LTC: https://tests.stockfishchess.org/tests/view/64fd44a75dab775b5359f065 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 154194 W: 39436 L: 38881 D: 75877 Ptnml(0-2): 78, 16577, 43238, 17120, 84 closes https://github.com/official-stockfish/Stockfish/pull/4782 Bench: 1603079 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 7f4feedf..8ac24dae 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -40,7 +40,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-c38c3d8d3920.nnue" + #define EvalFileDefaultName "nn-1ee1aba5ed4c.nnue" namespace NNUE { From b9319c4fa4f42438f484d144be9a1306765cf998 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Thu, 31 Aug 2023 21:56:34 +0200 Subject: [PATCH 402/678] Cleanup code after dropping ICC support in favor of ICX The commit removes all uses of ICC's __INTEL_COMPILER macro and other references to ICC. It also adds ICX info to the compiler command and fixes two typos in Makefile's help output. closes https://github.com/official-stockfish/Stockfish/pull/4769 No functional change --- src/Makefile | 4 ++-- src/bitboard.h | 4 ++-- src/misc.cpp | 29 +++++++++++------------------ src/types.h | 19 ++++++++++--------- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/Makefile b/src/Makefile index d6443845..f5a420b7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -804,8 +804,8 @@ help: @echo "" @echo "Supported compilers:" @echo "" - @echo "gcc > Gnu compiler (default)" - @echo "mingw > Gnu compiler with MinGW under Windows" + @echo "gcc > GNU compiler (default)" + @echo "mingw > GNU compiler with MinGW under Windows" @echo "clang > LLVM Clang compiler" @echo "icx > Intel oneAPI DPC++/C++ Compiler" @echo "ndk > Google NDK to cross-compile for Android" diff --git a/src/bitboard.h b/src/bitboard.h index f9175333..c05b6e3f 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -262,7 +262,7 @@ inline int popcount(Bitboard b) { 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]]; -#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) +#elif defined(_MSC_VER) return (int)_mm_popcnt_u64(b); @@ -276,7 +276,7 @@ inline int popcount(Bitboard b) { /// lsb() and msb() return the least/most significant bit in a non-zero bitboard -#if defined(__GNUC__) // GCC, Clang, ICC +#if defined(__GNUC__) // GCC, Clang, ICX inline Square lsb(Bitboard b) { assert(b); diff --git a/src/misc.cpp b/src/misc.cpp index a72a1c13..83ea8e10 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -193,22 +193,21 @@ std::string compiler_info() { /// Predefined macros hell: /// -/// __GNUC__ Compiler is gcc, Clang or Intel on Linux -/// __INTEL_COMPILER Compiler is Intel -/// _MSC_VER Compiler is MSVC or Intel on Windows -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +/// __GNUC__ Compiler is GCC, Clang or ICX +/// __clang__ Compiler is Clang or ICX +/// __INTEL_LLVM_COMPILER Compiler is ICX +/// _MSC_VER Compiler is MSVC +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit std::string compiler = "\nCompiled by "; - #ifdef __clang__ + #if defined(__INTEL_LLVM_COMPILER) + compiler += "ICX "; + compiler += stringify(__INTEL_LLVM_COMPILER); + #elif defined(__clang__) compiler += "clang++ "; compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); - #elif __INTEL_COMPILER - compiler += "Intel compiler "; - compiler += "(version "; - compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE); - compiler += ")"; #elif _MSC_VER compiler += "MSVC "; compiler += "(version "; @@ -425,13 +424,7 @@ void prefetch(void*) {} void prefetch(void* addr) { -# if defined(__INTEL_COMPILER) - // This hack prevents prefetches from being optimized away by - // Intel compiler. Both MSVC and gcc seem not be affected by this. - __asm__ (""); -# endif - -# if defined(__INTEL_COMPILER) || defined(_MSC_VER) +# if defined(_MSC_VER) _mm_prefetch((char*)addr, _MM_HINT_T0); # else __builtin_prefetch(addr); diff --git a/src/types.h b/src/types.h index bb319c2b..f81d30fe 100644 --- a/src/types.h +++ b/src/types.h @@ -48,11 +48,12 @@ /// Predefined macros hell: /// -/// __GNUC__ Compiler is gcc, Clang or Intel on Linux -/// __INTEL_COMPILER Compiler is Intel -/// _MSC_VER Compiler is MSVC or Intel on Windows -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +/// __GNUC__ Compiler is GCC, Clang or ICX +/// __clang__ Compiler is Clang or ICX +/// __INTEL_LLVM_COMPILER Compiler is ICX +/// _MSC_VER Compiler is MSVC +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit #if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) #define ALIGNAS_ON_STACK_VARIABLES_BROKEN @@ -65,12 +66,12 @@ # define IS_64BIT #endif -#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) -# include // Intel and Microsoft header for _mm_popcnt_u64() +#if defined(USE_POPCNT) && defined(_MSC_VER) +# include // Microsoft header for _mm_popcnt_u64() #endif -#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) -# include // Intel and Microsoft header for _mm_prefetch() +#if !defined(NO_PREFETCH) && defined(_MSC_VER) +# include // Microsoft header for _mm_prefetch() #endif #if defined(USE_PEXT) From 3f7fb5ac1d58e1c90db063053e9f913b9df79994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Mon, 11 Sep 2023 23:19:06 +0200 Subject: [PATCH 403/678] Reformat some comments Also include the bench to make Continuation Integration happy on Github. Bench: 1603079 --- src/search.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index beb1cb54..4b403c49 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -928,7 +928,8 @@ moves_loop: // When in check, search starts here moveCountPruning = singularQuietLMR = false; // Indicate PvNodes that will probably fail low if the node was searched - // at a depth equal to or greater than the current depth, and the result of this search was a fail low. + // at a depth equal to or greater than the current depth, and the result + // of this search was a fail low. bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) @@ -1039,10 +1040,10 @@ moves_loop: // When in check, search starts here // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the - // result is lower than ttValue minus a margin, then we will extend the ttMove. - // 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 + // a reduced search on all the other moves but the ttMove and if the result + // is lower than ttValue minus a margin, then we will extend the ttMove. Note + // that 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 this type of time controls. if ( !rootNode && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) @@ -1076,10 +1077,10 @@ moves_loop: // When in check, search starts here } // Multi-cut pruning - // Our ttMove is assumed to fail high, and now we failed high also on a reduced - // search without the ttMove. So we assume this expected Cut-node is not singular, - // that multiple moves fail high, and we can prune the whole subtree by returning - // a softbound. + // Our ttMove is assumed to fail high, and now we failed high also on a + // reduced search without the ttMove. So we assume this expected cut-node + // is not singular, that multiple moves fail high, and we can prune the + // whole subtree by returning a softbound. else if (singularBeta >= beta) return singularBeta; @@ -1126,8 +1127,7 @@ moves_loop: // When in check, search starts here // Step 16. Make the move pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV - // and node is not likely to fail low. (~3 Elo) + // Decrease reduction if position is or has been on the PV and not likely to fail low. (~3 Elo) // Decrease further on cutNodes. (~1 Elo) if ( ss->ttPv && !likelyFailLow) @@ -1162,6 +1162,7 @@ moves_loop: // When in check, search starts here if ((ss+1)->cutoffCnt > 3) r++; + // Decrease reduction for first generated move (ttMove) else if (move == ttMove) r--; From 0e32287af470dee230a30d9f513682c3ce798668 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Thu, 14 Sep 2023 14:15:43 +0300 Subject: [PATCH 404/678] Reorder some lines Now that qsearch has its own repetition detection we can flip the order of lines and remove the guard of depth < 0 which is not needed after reordering (i.e. it was there to prevent checking repetition again at depth ==0). Passed STC: https://tests.stockfishchess.org/tests/view/6502ecbb2cd016da89abc3fb LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 69536 W: 17668 L: 17490 D: 34378 Ptnml(0-2): 190, 7652, 18929, 7784, 213 Passed LTC: https://tests.stockfishchess.org/tests/view/6505ce9072620bc881ea9086 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 52116 W: 13294 L: 13113 D: 25709 Ptnml(0-2): 26, 5176, 15471, 5361, 24 closes https://github.com/official-stockfish/Stockfish/pull/4791 No functional change --- src/search.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4b403c49..cae91018 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -525,6 +525,10 @@ namespace { constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; + // Dive into quiescence search when the depth reaches zero + if (depth <= 0) + return qsearch(pos, ss, alpha, beta); + // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( !rootNode @@ -536,10 +540,6 @@ namespace { return alpha; } - // Dive into quiescence search when the depth reaches zero - if (depth <= 0) - return qsearch(pos, ss, alpha, beta); - assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(0 < depth && depth < MAX_PLY); @@ -1407,8 +1407,7 @@ moves_loop: // When in check, search starts here // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. - if ( depth < 0 - && alpha < VALUE_DRAW + if ( alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(pos.this_thread()); From 97f706ecc11459c8d0aa1901134d12fba00b4b15 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 12 Sep 2023 12:23:24 -0700 Subject: [PATCH 405/678] Sparse impl of affine_transform_non_ssse3() deal with the general case About a 8.6% speedup (for general arch) Results for 200 tests for each version: Base Test Diff Mean 141741 153998 -12257 StDev 2990 3042 3742 p-value: 0.999 speedup: 0.086 closes https://github.com/official-stockfish/Stockfish/pull/4786 No functional change --- src/nnue/layers/affine_transform.h | 20 ++++++++++++++------ src/nnue/layers/clipped_relu.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 6 +++--- src/nnue/nnue_feature_transformer.h | 6 +++--- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 61cdb781..af85c817 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -45,6 +45,7 @@ namespace Stockfish::Eval::NNUE::Layers { template static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) { +# if defined(USE_SSE2) || defined(USE_MMX) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) # if defined(USE_SSE2) // At least a multiple of 16, with SSE2. constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; @@ -129,18 +130,25 @@ namespace Stockfish::Eval::NNUE::Layers { } output[i] = sum[0] + sum[1] + sum[2] + sum[3]; -# else - std::int32_t sum = biases[i]; - for (IndexType j = 0; j < InputDimensions; ++j) { - sum += weights[offset + j] * input[j]; - } - output[i] = sum; # endif } # if defined(USE_MMX) _mm_empty(); # endif + +# else + std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); + + // Traverse weights in transpose order to take advantage of input sparsity + for (IndexType i = 0; i < InputDimensions; ++i) + if (input[i]) { + const std::int8_t* w = &weights[i]; + const int in = input[i]; + for (IndexType j = 0; j < OutputDimensions; ++j) + output[j] += w[j * PaddedInputDimensions] * in; + } +# endif } #endif diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 2856bfb0..aab824b3 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -172,7 +172,7 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( - std::max(0, std::min(127, input[i] >> WeightScaleBits))); + std::clamp(input[i] >> WeightScaleBits, 0, 127)); } } }; diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 503b283b..a3d2059b 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -96,9 +96,9 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( - // really should be /127 but we need to make it fast - // needs to be accounted for in the trainer - std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128)); + // Really should be /127 but we need to make it fast so we right shift + // by an extra 7 bits instead. Needs to be accounted for in the trainer. + std::min(127ll, ((long long)input[i] * input[i]) >> (2 * WeightScaleBits + 7))); } } }; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 0af0ed96..56442bac 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -327,9 +327,9 @@ namespace Stockfish::Eval::NNUE { for (IndexType j = 0; j < HalfDimensions / 2; ++j) { BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - sum0 = std::max(0, std::min(127, sum0)); - sum1 = std::max(0, std::min(127, sum1)); - output[offset + j] = static_cast(sum0 * sum1 / 128); + sum0 = std::clamp(sum0, 0, 127); + sum1 = std::clamp(sum1, 0, 127); + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 128); } #endif From 708319a433951ee5d5d74e0bf1cda218c14dd18e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 26 Aug 2023 11:38:16 +0200 Subject: [PATCH 406/678] Enable a default native ARCH uses a posix compatible script to find the native arch. (based on ppigazzini's https://github.com/ppigazzini/stockfish-downloader ) use that native arch by default, no changes if ARCH is specified explicitly. SF can now be compiled in an optimal way simply using make -j profile-build closes https://github.com/official-stockfish/Stockfish/pull/4777 No functional change --- scripts/get_native_properties.sh | 121 +++++++++++++++++++++++++++++++ src/Makefile | 18 ++--- 2 files changed, 130 insertions(+), 9 deletions(-) create mode 100755 scripts/get_native_properties.sh diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh new file mode 100755 index 00000000..cffb0ce2 --- /dev/null +++ b/scripts/get_native_properties.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +# +# Returns properties of the native system. +# best architecture as supported by the CPU +# filename of the best binary uploaded as an artifact during CI +# + +# Check if all the given flags are present in the CPU flags list +check_flags() { + for flag; do + printf '%s\n' "$flags" | grep -q -w "$flag" || return 1 + done +} + +# Set the CPU flags list +get_flags() { + flags="$(awk '/^flags[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*/, ""); line=$0} END{print line}' /proc/cpuinfo) $(awk '/^Features[ \t]*:/{gsub(/^Features[ \t]*:[ \t]*/, ""); line=$0} END{print line}' /proc/cpuinfo)" + # remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 + flags=$(printf '%s' "$flags" | sed "s/[_.]//g") +} + +# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid +check_znver_1_2() { + vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo) + cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo) + [ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true +} + +# Set the file CPU x86_64 architecture +set_arch_x86_64() { + if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then + true_arch='x86-64-vnni256' + elif check_flags 'avx512f' 'avx512bw'; then + true_arch='x86-64-avx512' + elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then + true_arch='x86-64-bmi2' + elif check_flags 'avx2'; then + true_arch='x86-64-avx2' + elif check_flags 'sse41' && check_flags 'popcnt'; then + true_arch='x86-64-sse41-popcnt' + else + true_arch='x86-64' + fi +} + +# Check the system type +uname_s=$(uname -s) +uname_m=$(uname -m) +case $uname_s in + 'Darwin') # Mac OSX system + case $uname_m in + 'arm64') + true_arch='apple-silicon' + file_arch='x86-64-sse41-popcnt' # Supported by Rosetta 2 + ;; + 'x86_64') + flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | sed "s/[_.]//g") + set_arch_x86_64 + if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then + file_arch='x86-64-bmi2' + fi + ;; + esac + file_os='macos' + file_ext='tar' + ;; + 'Linux') # Linux system + get_flags + case $uname_m in + 'x86_64') + file_os='ubuntu' + check_znver_1_2 + set_arch_x86_64 + ;; + 'i686') + file_os='ubuntu' + true_arch='x86-32' + ;; + 'aarch64') + file_os='android' + true_arch='armv8' + if check_flags 'dotprod'; then + true_arch="$true_arch-dotprod" + fi + ;; + 'armv7'*) + file_os='android' + true_arch='armv7' + if check_flags 'neon'; then + true_arch="$true_arch-neon" + fi + ;; + *) # Unsupported machine type, exit with error + printf 'Unsupported machine type: %s\n' "$uname_m" + exit 1 + ;; + esac + file_ext='tar' + ;; + 'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows system with POSIX compatibility layer + get_flags + check_znver_1_2 + set_arch_x86_64 + file_os='windows' + file_ext='zip' + ;; + *) + # Unknown system type, exit with error + printf 'Unsupported system type: %s\n' "$uname_s" + exit 1 + ;; +esac + +if [ -z "$file_arch" ]; then + file_arch=$true_arch +fi + +file_name="stockfish-$file_os-$file_arch.$file_ext" + +printf '%s %s\n' "$true_arch" "$file_name" diff --git a/src/Makefile b/src/Makefile index f5a420b7..bf483f8c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -104,9 +104,13 @@ VPATH = syzygy:nnue:nnue/features ### 2.1. General and architecture defaults ifeq ($(ARCH),) - ARCH = x86-64-avx2 - help_skip_sanity = yes + ARCH = native endif + +ifeq ($(ARCH), native) + override ARCH = $(shell ../scripts/get_native_properties.sh | cut -d " " -f 1) +endif + # explicitly check for the list of supported architectures (as listed with make help), # the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true` ifeq ($(ARCH), $(filter $(ARCH), \ @@ -757,12 +761,11 @@ endif ### Section 4. Public Targets ### ========================================================================== - help: @echo "" @echo "To compile stockfish, type: " @echo "" - @echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]" + @echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]" @echo "" @echo "Supported targets:" @echo "" @@ -776,6 +779,7 @@ help: @echo "" @echo "Supported archs:" @echo "" + @echo "native > select the best architecture for the host processor (default)" @echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" @echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" @echo "x86-64-avx512 > x86 64-bit with avx512 support" @@ -822,11 +826,7 @@ help: @echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" @echo "make -j build ARCH=x86-64-ssse3 COMP=clang" @echo "" - @echo "-------------------------------" -ifeq ($(SUPPORTED_ARCH)$(help_skip_sanity), true) - @echo "The selected architecture $(ARCH) will enable the following configuration: " - @$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity -else +ifneq ($(SUPPORTED_ARCH), true) @echo "Specify a supported architecture with the ARCH option for more details" @echo "" endif From e594aa74290cf37881432f268befde9ad3f3c498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Thu, 14 Sep 2023 11:04:36 +0200 Subject: [PATCH 407/678] Export makefile ARCH in binary Example of the `./stockfish compiler` command: Compiled by : g++ (GNUC) 10.3.0 on Apple Compilation architecture : x86-64-bmi2 Compilation settings : 64bit BMI2 AVX2 SSE41 SSSE3 SSE2 POPCNT Compiler __VERSION__ macro : 10.3.0 closes https://github.com/official-stockfish/Stockfish/pull/4789 no functional change --- src/Makefile | 5 +++++ src/misc.cpp | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Makefile b/src/Makefile index bf483f8c..e7c06389 100644 --- a/src/Makefile +++ b/src/Makefile @@ -715,6 +715,11 @@ ifneq ($(GIT_DATE), ) CXXFLAGS += -DGIT_DATE=$(GIT_DATE) endif +### 3.7.3 Try to include architecture +ifneq ($(ARCH), ) + CXXFLAGS += -DARCH=$(ARCH) +endif + ### 3.8 Link Time Optimization ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. diff --git a/src/misc.cpp b/src/misc.cpp index 83ea8e10..aecc4d23 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -200,7 +200,7 @@ std::string compiler_info() { /// _WIN32 Building on Windows (any) /// _WIN64 Building on Windows 64 bit - std::string compiler = "\nCompiled by "; + std::string compiler = "\nCompiled by : "; #if defined(__INTEL_LLVM_COMPILER) compiler += "ICX "; @@ -253,8 +253,15 @@ std::string compiler_info() { compiler += " on unknown system"; #endif - compiler += "\nCompilation settings include: "; - compiler += (Is64Bit ? " 64bit" : " 32bit"); + compiler += "\nCompilation architecture : "; + #if defined(ARCH) + compiler += stringify(ARCH); + #else + compiler += "(undefined architecture)"; + #endif + + compiler += "\nCompilation settings : "; + compiler += (Is64Bit ? "64bit" : "32bit"); #if defined(USE_VNNI) compiler += " VNNI"; #endif @@ -288,12 +295,13 @@ std::string compiler_info() { compiler += " DEBUG"; #endif - compiler += "\n__VERSION__ macro expands to: "; + compiler += "\nCompiler __VERSION__ macro : "; #ifdef __VERSION__ compiler += __VERSION__; #else compiler += "(undefined macro)"; #endif + compiler += "\n"; return compiler; From 952740b36ca46961a64457767f58dfbe71ae1ead Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 12 Sep 2023 00:03:04 +0200 Subject: [PATCH 408/678] Let CI check C++ includes The commit adds a CI workflow that uses the included-what-you-use (IWYU) tool to check for missing or superfluous includes in .cpp files and their corresponding .h files. This means that some .h files (especially in the nnue folder) are not checked yet. The CI setup looks like this: - We build IWYU from source to include some yet unreleased fixes. This IWYU version targets LLVM 17. Thus, we get the latest release candidate of LLVM 17 from LLVM's nightly packages. - The Makefile now has an analyze target that just build the object files (without linking) - The CI uses the analyze target with the IWYU tool as compiler to analyze the compiled .cpp file and its corresponding .h file. - If IWYU suggests a change the build fails (-Xiwyu --error). - To avoid false positives we use LLVM's libc++ as standard library - We have a custom mappings file that adds some mappings that are missing in IWYU's default mappings We also had to add one IWYU pragma to prevent a false positive in movegen.h. https://github.com/official-stockfish/Stockfish/pull/4783 No functional change --- .github/workflows/libcxx17.imp | 21 ++++++++++ .github/workflows/stockfish.yml | 2 + .github/workflows/stockfish_analyzers.yml | 47 +++++++++++++++++++++++ src/Makefile | 7 +++- src/evaluate.h | 1 - src/movegen.h | 2 +- 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/libcxx17.imp create mode 100644 .github/workflows/stockfish_analyzers.yml diff --git a/.github/workflows/libcxx17.imp b/.github/workflows/libcxx17.imp new file mode 100644 index 00000000..7bdcf5bc --- /dev/null +++ b/.github/workflows/libcxx17.imp @@ -0,0 +1,21 @@ +[ + # Mappings for libcxx's internal headers + { include: [ "<__fwd/fstream.h>", private, "", public ] }, + { include: [ "<__fwd/ios.h>", private, "", public ] }, + { include: [ "<__fwd/istream.h>", private, "", public ] }, + { include: [ "<__fwd/ostream.h>", private, "", public ] }, + { include: [ "<__fwd/sstream.h>", private, "", public ] }, + { include: [ "<__fwd/streambuf.h>", private, "", public ] }, + { include: [ "<__fwd/string_view.h>", private, "", public ] }, + + # Mappings for includes between public headers + { include: [ "", public, "", public ] }, + { include: [ "", public, "", public ] }, + { include: [ "", public, "", public ] }, + { include: [ "", public, "", public ] }, + { include: [ "", public, "", public ] }, + + # Missing mappings in include-what-you-use's libcxx.imp + { include: ["@<__condition_variable/.*>", private, "", public ] }, + { include: ["@<__mutex/.*>", private, "", public ] }, +] diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 8ea1837d..1ed4b92d 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -33,6 +33,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + Analyzers: + uses: ./.github/workflows/stockfish_analyzers.yml Sanitizers: uses: ./.github/workflows/stockfish_sanitizers.yml Tests: diff --git a/.github/workflows/stockfish_analyzers.yml b/.github/workflows/stockfish_analyzers.yml new file mode 100644 index 00000000..5f985cc8 --- /dev/null +++ b/.github/workflows/stockfish_analyzers.yml @@ -0,0 +1,47 @@ +name: Stockfish +on: + workflow_call: +jobs: + Analyzers: + name: Check includes + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: Stockfish/src + shell: bash + steps: + - name: Checkout Stockfish + uses: actions/checkout@v3 + with: + path: Stockfish + + - name: Checkout include-what-you-use + uses: actions/checkout@v3 + with: + repository: include-what-you-use/include-what-you-use + ref: f25caa280dc3277c4086ec345ad279a2463fea0f + path: include-what-you-use + + - name: Download required linux packages + run: | + sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main' + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo apt update + sudo apt install -y libclang-17-dev clang-17 libc++-17-dev + + - name: Set up include-what-you-use + run: | + mkdir build && cd build + cmake -G "Unix Makefiles" -DCMAKE_PREFIX_PATH="/usr/lib/llvm-17" .. + sudo make install + working-directory: include-what-you-use + + - name: Check include-what-you-use + run: include-what-you-use --version + + - name: Check includes + run: > + make analyze + COMP=clang + CXX=include-what-you-use + CXXFLAGS="-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/workflows/libcxx17.imp' -Xiwyu --error" diff --git a/src/Makefile b/src/Makefile index e7c06389..1b03bbc2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -837,12 +837,15 @@ ifneq ($(SUPPORTED_ARCH), true) endif -.PHONY: help build profile-build strip install clean net objclean profileclean \ - config-sanity \ +.PHONY: help analyze build profile-build strip install clean net \ + objclean profileclean config-sanity \ icx-profile-use icx-profile-make \ gcc-profile-use gcc-profile-make \ clang-profile-use clang-profile-make FORCE +analyze: net config-sanity objclean + $(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS) + build: net config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all diff --git a/src/evaluate.h b/src/evaluate.h index 8ac24dae..fd1b0de1 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -26,7 +26,6 @@ namespace Stockfish { class Position; -enum Value : int; namespace Eval { diff --git a/src/movegen.h b/src/movegen.h index 6449de25..b15f1230 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -19,7 +19,7 @@ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED -#include +#include // IWYU pragma: keep #include #include "types.h" From fce4cc1829f25fd52c5dd637ab54d867eec065fb Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:06:12 +0800 Subject: [PATCH 409/678] Make casting styles consistent Make casting styles consistent with the rest of the code. closes https://github.com/official-stockfish/Stockfish/pull/4793 No functional change --- src/bitboard.h | 2 +- src/misc.cpp | 24 ++++++++-------- src/misc.h | 8 +++--- src/movegen.cpp | 2 +- src/nnue/evaluate_nnue.cpp | 6 ++-- src/nnue/layers/affine_transform.h | 2 +- .../layers/affine_transform_sparse_input.h | 2 +- src/search.cpp | 6 ++-- src/syzygy/tbprobe.cpp | 28 +++++++++---------- src/tt.cpp | 20 ++++++------- src/tt.h | 12 ++++---- 11 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index c05b6e3f..dee73b4b 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -264,7 +264,7 @@ inline int popcount(Bitboard b) { #elif defined(_MSC_VER) - return (int)_mm_popcnt_u64(b); + return int(_mm_popcnt_u64(b)); #else // Assumed gcc or compatible compiler diff --git a/src/misc.cpp b/src/misc.cpp index aecc4d23..98e346a6 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -85,7 +85,7 @@ struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and Tie(std::streambuf* b, std::streambuf* l) : buf(b), logBuf(l) {} int sync() override { return logBuf->pubsync(), buf->pubsync(); } - int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } + int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); } int underflow() override { return buf->sgetc(); } int uflow() override { return log(buf->sbumpc(), ">> "); } @@ -98,7 +98,7 @@ struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and if (last == '\n') logBuf->sputn(prefix, 3); - return last = logBuf->sputc((char)c); + return last = logBuf->sputc(char(c)); } }; @@ -215,9 +215,9 @@ std::string compiler_info() { compiler += ")"; #elif defined(__e2k__) && defined(__LCC__) #define dot_ver2(n) \ - compiler += (char)'.'; \ - compiler += (char)('0' + (n) / 10); \ - compiler += (char)('0' + (n) % 10); + compiler += char('.'); \ + compiler += char('0' + (n) / 10); \ + compiler += char('0' + (n) % 10); compiler += "MCST LCC "; compiler += "(version "; @@ -498,13 +498,13 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize if (!hAdvapi32) hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - auto fun6 = (fun6_t)(void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken"); + auto fun6 = fun6_t((void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken")); if (!fun6) return nullptr; - auto fun7 = (fun7_t)(void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"); + auto fun7 = fun7_t((void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); if (!fun7) return nullptr; - auto fun8 = (fun8_t)(void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"); + auto fun8 = fun8_t((void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); if (!fun8) return nullptr; @@ -699,10 +699,10 @@ void bindThisThread(size_t idx) { // Early exit if the needed API are not available at runtime HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); - auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"); - auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2"); - auto fun5 = (fun5_t)(void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount"); + auto fun2 = fun2_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx")); + auto fun3 = fun3_t((void(*)())GetProcAddress(k32, "SetThreadGroupAffinity")); + auto fun4 = fun4_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2")); + auto fun5 = fun5_t((void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount")); if (!fun2 || !fun3) return; diff --git a/src/misc.h b/src/misc.h index aed677b5..c0387f7c 100644 --- a/src/misc.h +++ b/src/misc.h @@ -133,13 +133,13 @@ public: inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #if defined(__GNUC__) && defined(IS_64BIT) __extension__ using uint128 = unsigned __int128; - return ((uint128)a * (uint128)b) >> 64; + return (uint128(a) * uint128(b)) >> 64; #else - uint64_t aL = (uint32_t)a, aH = a >> 32; - uint64_t bL = (uint32_t)b, bH = b >> 32; + uint64_t aL = uint32_t(a), aH = a >> 32; + uint64_t bL = uint32_t(b), bH = b >> 32; uint64_t c1 = (aL * bL) >> 32; uint64_t c2 = aH * bL + c1; - uint64_t c3 = aL * bH + (uint32_t)c2; + uint64_t c3 = aL * bH + uint32_t(c2); return aH * bH + (c2 >> 32) + (c3 >> 32); #endif } diff --git a/src/movegen.cpp b/src/movegen.cpp index f0733c73..c6a8dbb8 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -246,7 +246,7 @@ template ExtMove* generate(const Position& pos, ExtMove* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate()"); - assert((Type == EVASIONS) == (bool)pos.checkers()); + assert((Type == EVASIONS) == bool(pos.checkers())); Color us = pos.side_to_move(); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 456f2edf..e1fa3b81 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -139,7 +139,7 @@ namespace Stockfish::Eval::NNUE { if (!Detail::write_parameters(stream, *featureTransformer)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) if (!Detail::write_parameters(stream, *(network[i]))) return false; - return (bool)stream; + return bool(stream); } void hint_common_parent_position(const Position& pos) { @@ -281,8 +281,8 @@ namespace Stockfish::Eval::NNUE { // A lambda to output one box of the board auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - const int x = ((int)file) * 8; - const int y = (7 - (int)rank) * 3; + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; for (int i = 1; i < 8; ++i) board[y][x+i] = board[y+3][x+i] = '-'; for (int i = 1; i < 3; ++i) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index af85c817..42839bb5 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -310,7 +310,7 @@ namespace Stockfish::Eval::NNUE::Layers { vec_t sum0 = vec_setzero(); const auto row0 = reinterpret_cast(&weights[0]); - for (int j = 0; j < (int)NumChunks; ++j) + for (int j = 0; j < int(NumChunks); ++j) { const vec_t in = inputVector[j]; vec_add_dpbusd_32(sum0, in, row0[j]); diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index c9894f5d..1dc42109 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -102,7 +102,7 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType j = 0; j < InputsPerChunk; ++j) { const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; - nnz |= (unsigned)vec_nnz(inputChunk) << (j * InputSimdWidth); + nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); } for (IndexType j = 0; j < OutputsPerChunk; ++j) { diff --git a/src/search.cpp b/src/search.cpp index cae91018..936aa0db 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -316,7 +316,7 @@ void Thread::search() { // When playing with strength handicap enable MultiPV search that we will // use behind-the-scenes to retrieve a set of possible moves. if (skill.enabled()) - multiPV = std::max(multiPV, (size_t)4); + multiPV = std::max(multiPV, size_t(4)); multiPV = std::min(multiPV, rootMoves.size()); @@ -1861,7 +1861,7 @@ void MainThread::check_time() { if ( (Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) || (Limits.movetime && elapsed >= Limits.movetime) - || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) + || (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))) Threads.stop = true; } @@ -1875,7 +1875,7 @@ string UCI::pv(const Position& pos, Depth depth) { TimePoint elapsed = Time.elapsed() + 1; const RootMoves& rootMoves = pos.this_thread()->rootMoves; size_t pvIdx = pos.this_thread()->pvIdx; - size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); + size_t multiPV = std::min(size_t(Options["MultiPV"]), rootMoves.size()); uint64_t nodesSearched = Threads.nodes_searched(); uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index d1b32d24..13d271fc 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -114,7 +114,7 @@ template T number(void* addr) { T v; - if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) + if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare) std::memcpy(&v, addr, sizeof(T)); else v = *((T*)addr); @@ -263,7 +263,7 @@ public: exit(EXIT_FAILURE); } - *mapping = (uint64_t)mmap; + *mapping = uint64_t(mmap); *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); if (!*baseAddress) @@ -429,7 +429,7 @@ class TBTables { std::deque> dtzTable; void insert(Key key, TBTable* wdl, TBTable* dtz) { - uint32_t homeBucket = (uint32_t)key & (Size - 1); + uint32_t homeBucket = uint32_t(key) & (Size - 1); Entry entry{ key, wdl, dtz }; // Ensure last element is empty to avoid overflow when looking up @@ -442,7 +442,7 @@ class TBTables { // Robin Hood hashing: If we've probed for longer than this element, // insert here and search for a new spot for the other element instead. - uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1); + uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1); if (otherHomeBucket > homeBucket) { std::swap(entry, hashTable[bucket]); key = otherKey; @@ -456,7 +456,7 @@ class TBTables { public: template TBTable* get(Key key) { - for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) { + for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)]; ; ++entry) { if (entry->key == key || !entry->get()) return entry->get(); } @@ -489,7 +489,7 @@ void TBTables::add(const std::vector& pieces) { file.close(); - MaxCardinality = std::max((int)pieces.size(), MaxCardinality); + MaxCardinality = std::max(int(pieces.size()), MaxCardinality); wdlTable.emplace_back(code); dtzTable.emplace_back(wdlTable.back()); @@ -560,7 +560,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { offset -= d->blockLength[block++] + 1; // Finally, we find the start address of our block of canonical Huffman symbols - uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock)); + uint32_t* ptr = (uint32_t*)(d->data + (uint64_t(block) * d->sizeofBlock)); // Read the first 64 bits in our block, this is a (truncated) sequence of // unknown number of symbols of unknown length but we know the first one @@ -600,7 +600,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { if (buf64Size <= 32) { // Refill the buffer buf64Size += 32; - buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size); + buf64 |= uint64_t(number(ptr++)) << (64 - buf64Size); } } @@ -1054,22 +1054,22 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { auto flags = e.get(0, f)->flags; if (flags & TBFlag::Mapped) { if (flags & TBFlag::Wide) { - data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table + data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x - e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1); + e.get(0, f)->map_idx[i] = uint16_t((uint16_t*)data - (uint16_t*)e.map + 1); data += 2 * number(data) + 2; } } else { for (int i = 0; i < 4; ++i) { - e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1); + e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1); data += *data + 1; } } } } - return data += (uintptr_t)data & 1; // Word alignment + return data += uintptr_t(data) & 1; // Word alignment } // Populate entry's PairsData records with data from the just memory mapped file. @@ -1110,7 +1110,7 @@ void set(T& e, uint8_t* data) { set_groups(e, e.get(i, f), order[i], f); } - data += (uintptr_t)data & 1; // Word alignment + data += uintptr_t(data) & 1; // Word alignment for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) @@ -1132,7 +1132,7 @@ void set(T& e, uint8_t* data) { for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) { - data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment + data = (uint8_t*)((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment (d = e.get(i, f))->data = data; data += d->blocksNum * d->sizeofBlock; } diff --git a/src/tt.cpp b/src/tt.cpp index 1582121f..adcfe628 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -39,22 +39,22 @@ TranspositionTable TT; // Our global transposition table void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { // Preserve any existing move for the same position - if (m || (uint16_t)k != key16) - move16 = (uint16_t)m; + if (m || uint16_t(k) != key16) + move16 = uint16_t(m); // Overwrite less valuable entries (cheapest checks first) if ( b == BOUND_EXACT - || (uint16_t)k != key16 + || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) { assert(d > DEPTH_OFFSET); assert(d < 256 + DEPTH_OFFSET); - key16 = (uint16_t)k; - depth8 = (uint8_t)(d - DEPTH_OFFSET); - genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b); - value16 = (int16_t)v; - eval16 = (int16_t)ev; + key16 = uint16_t(k); + depth8 = uint8_t(d - DEPTH_OFFSET); + genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b); + value16 = int16_t(v); + eval16 = int16_t(ev); } } @@ -123,14 +123,14 @@ void TranspositionTable::clear() { TTEntry* TranspositionTable::probe(const Key key, bool& found) const { TTEntry* const tte = first_entry(key); - const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster + const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16 || !tte[i].depth8) { tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh - return found = (bool)tte[i].depth8, &tte[i]; + return found = bool(tte[i].depth8), &tte[i]; } // Find an entry to be replaced according to the replacement strategy diff --git a/src/tt.h b/src/tt.h index df962faa..c11cf085 100644 --- a/src/tt.h +++ b/src/tt.h @@ -40,12 +40,12 @@ namespace Stockfish { struct TTEntry { - Move move() const { return (Move )move16; } - Value value() const { return (Value)value16; } - Value eval() const { return (Value)eval16; } - Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; } - bool is_pv() const { return (bool)(genBound8 & 0x4); } - Bound bound() const { return (Bound)(genBound8 & 0x3); } + Move move() const { return Move (move16); } + Value value() const { return Value(value16); } + Value eval() const { return Value(eval16); } + Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } + bool is_pv() const { return bool (genBound8 & 0x4); } + Bound bound() const { return Bound(genBound8 & 0x3); } void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); private: From 95fe2b9a9d33811a7fcad1cdfea79c54e8fdb074 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 21 Sep 2023 19:26:11 -0700 Subject: [PATCH 410/678] Reduce SIMD register count from 32 to 16 in the case of avx512 and vnni512 archs. Up to 17% speedup, depending on the compiler, e.g. ``` AMD pro 7840u (zen4 phoenix apu 4nm) bash bench_parallel.sh ./stockfish_avx512_gcc13 ./stockfish_avx512_pr_gcc13 20 10 sf_base = 1077737 +/- 8446 (95%) sf_test = 1264268 +/- 8543 (95%) diff = 186531 +/- 4280 (95%) speedup = 17.308% +/- 0.397% (95%) ``` Prior to this patch, it appears gcc spills registers. closes https://github.com/official-stockfish/Stockfish/pull/4796 No functional change --- src/nnue/nnue_feature_transformer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 56442bac..902918b2 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -69,7 +69,7 @@ namespace Stockfish::Eval::NNUE { #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 32 + #define NumRegistersSIMD 16 #define MaxChunkSize 64 #elif USE_AVX2 From 154b8d3ecb19d0b3fa9ec11cc3a1e666dfe0d2ce Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Tue, 19 Sep 2023 09:08:58 +0200 Subject: [PATCH 411/678] Remove VALUE_KNOWN_WIN. After removing classic evaluation VALUE_KNOWN_WIN is not anymore returned explicit evaluation. So remove and replace it with VALUE_TB_WIN_IN_MAX_PLY. Measurement on my big bench (bench 16 1 16 pos1000.fen) verifies that at least with current net the calculated evaluation lies always in the open interval (-VALUE_KNOWN_WIN, VALUE_KNOWN_WIN). So i consider this a non-functional change. But to be safe i tested this also at LTC as requested by Stephane Nicolet. STC: https://tests.stockfishchess.org/tests/view/64f9db40eaf01be8259a6ed5 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 455296 W: 115981 L: 116217 D: 223098 Ptnml(0-2): 1415, 50835, 123420, 50527, 1451 LTC: https://tests.stockfishchess.org/tests/view/650bfd867ca0d3f7bbf25feb LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 35826 W: 9170 L: 8973 D: 17683 Ptnml(0-2): 12, 3523, 10645, 3722, 11 closes https://github.com/official-stockfish/Stockfish/pull/4792 Bench: 1603079 --- src/search.cpp | 10 +++++----- src/types.h | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 936aa0db..3e19000a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -778,7 +778,7 @@ namespace { && depth < 9 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta - && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 24923) // smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) @@ -908,8 +908,8 @@ moves_loop: // When in check, search starts here && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta - && abs(ttValue) <= VALUE_KNOWN_WIN - && abs(beta) <= VALUE_KNOWN_WIN) + && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY + && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1050,7 +1050,7 @@ moves_loop: // When in check, search starts here && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ - && abs(ttValue) < VALUE_KNOWN_WIN + && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { @@ -1541,7 +1541,7 @@ moves_loop: // When in check, search starts here // Futility pruning and moveCount pruning (~10 Elo) if ( !givesCheck && to_sq(move) != prevSq - && futilityBase > -VALUE_KNOWN_WIN + && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY && type_of(move) != PROMOTION) { if (moveCount > 2) diff --git a/src/types.h b/src/types.h index f81d30fe..340c47a5 100644 --- a/src/types.h +++ b/src/types.h @@ -161,7 +161,6 @@ enum Bound { enum Value : int { VALUE_ZERO = 0, VALUE_DRAW = 0, - VALUE_KNOWN_WIN = 10000, VALUE_MATE = 32000, VALUE_INFINITE = 32001, VALUE_NONE = 32002, From 70ba9de85cddc5460b1ec53e0a99bee271e26ece Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 12 May 2023 18:07:20 -0400 Subject: [PATCH 412/678] Update NNUE architecture to SFNNv8: L1-2560 nn-ac1dbea57aa3.nnue Creating this net involved: - a 6-stage training process from scratch. The datasets used in stages 1-5 were fully minimized. - permuting L1 weights with https://github.com/official-stockfish/nnue-pytorch/pull/254 A strong epoch after each training stage was chosen for the next. The 6 stages were: ``` 1. 400 epochs, lambda 1.0, default LR and gamma UHOx2-wIsRight-multinet-dfrc-n5000 (135G) nodes5000pv2_UHO.binpack data_pv-2_diff-100_nodes-5000.binpack wrongIsRight_nodes5000pv2.binpack multinet_pv-2_diff-100_nodes-5000.binpack dfrc_n5000.binpack 2. 800 epochs, end-lambda 0.75, LR 4.375e-4, gamma 0.995, skip 12 LeelaFarseer-T78juntoaugT79marT80dec.binpack (141G) T60T70wIsRightFarseerT60T74T75T76.binpack test78-junjulaug2022-16tb7p.no-db.min.binpack test79-mar2022-16tb7p.no-db.min.binpack test80-dec2022-16tb7p.no-db.min.binpack 3. 800 epochs, end-lambda 0.725, LR 4.375e-4, gamma 0.995, skip 20 leela93-v1-dfrc99-v2-T78juntosepT80jan-v6dd-T78janfebT79aprT80aprmay.min.binpack leela93-filt-v1.min.binpack dfrc99-16tb7p-filt-v2.min.binpack test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.binpack test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.binpack test78-janfeb2022-16tb7p.min.binpack test79-apr2022-16tb7p.min.binpack test80-apr2022-16tb7p.min.binpack test80-may2022-16tb7p.min.binpack 4. 800 epochs, end-lambda 0.7, LR 4.375e-4, gamma 0.995, skip 24 leela96-dfrc99-v2-T78juntosepT79mayT80junsepnovjan-v6dd-T80mar23-v6-T60novdecT77decT78aprmayT79aprT80may23.min.binpack leela96-filt-v2.min.binpack dfrc99-16tb7p-filt-v2.min.binpack test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.binpack test79-may2022-16tb7p.filter-v6-dd.min.binpack test80-jun2022-16tb7p.filter-v6-dd.min.binpack test80-sep2022-16tb7p.filter-v6-dd.min.binpack test80-nov2022-16tb7p.filter-v6-dd.min.binpack test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.binpack test80-mar2023-2tb7p.v6-sk16.min.binpack test60-novdec2021-16tb7p.min.binpack test77-dec2021-16tb7p.min.binpack test78-aprmay2022-16tb7p.min.binpack test79-apr2022-16tb7p.min.binpack test80-may2023-2tb7p.min.binpack 5. 960 epochs, end-lambda 0.7, LR 4.375e-4, gamma 0.995, skip 28 Increased max-epoch to 960 near the end of the first 800 epochs 5af11540bbfe dataset: https://github.com/official-stockfish/Stockfish/pull/4635 6. 1000 epochs, end-lambda 0.7, LR 4.375e-4, gamma 0.995, skip 28 Increased max-epoch to 1000 near the end of the first 800 epochs 1ee1aba5ed dataset: https://github.com/official-stockfish/Stockfish/pull/4782 ``` L1 weights permuted with: ```bash python3 serialize.py $nnue $nnue_permuted \ --features=HalfKAv2_hm \ --ft_optimize \ --ft_optimize_data=/data/fishpack32.binpack \ --ft_optimize_count=10000 ``` Speed measurements from 100 bench runs at depth 13 with profile-build x86-64-avx2: ``` sf_base = 1329051 +/- 2224 (95%) sf_test = 1163344 +/- 2992 (95%) diff = -165706 +/- 4913 (95%) speedup = -12.46807% +/- 0.370% (95%) ``` Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move (vs. L1-2048 nn-1ee1aba5ed4c.nnue) ep959 : 16.2 +/- 2.3 Failed 10+0.1 STC: https://tests.stockfishchess.org/tests/view/6501beee2cd016da89abab21 LLR: -2.92 (-2.94,2.94) <0.00,2.00> Total: 13184 W: 3285 L: 3535 D: 6364 Ptnml(0-2): 85, 1662, 3334, 1440, 71 Failed 180+1.8 VLTC: https://tests.stockfishchess.org/tests/view/6505cf9a72620bc881ea908e LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 64248 W: 16224 L: 16374 D: 31650 Ptnml(0-2): 26, 6788, 18640, 6650, 20 Passed 60+0.6 th 8 VLTC SMP (STC bounds): https://tests.stockfishchess.org/tests/view/65084a4618698b74c2e541dc LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 90630 W: 23372 L: 23033 D: 44225 Ptnml(0-2): 13, 8490, 27968, 8833, 11 Passed 60+0.6 th 8 VLTC SMP: https://tests.stockfishchess.org/tests/view/6501d45d2cd016da89abacdb LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 137804 W: 35764 L: 35276 D: 66764 Ptnml(0-2): 31, 13006, 42326, 13522, 17 closes https://github.com/official-stockfish/Stockfish/pull/4795 bench 1246812 --- src/evaluate.h | 2 +- src/nnue/nnue_architecture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index fd1b0de1..acf9edd2 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1ee1aba5ed4c.nnue" + #define EvalFileDefaultName "nn-ac1dbea57aa3.nnue" namespace NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index b50c52df..2a7f064b 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -38,7 +38,7 @@ namespace Stockfish::Eval::NNUE { using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 2048; +constexpr IndexType TransformedFeatureDimensions = 2560; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; From 22cdb6c1ea1f5ca429333bcbe26706c8b4dd38d7 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 23 Sep 2023 23:26:29 +0200 Subject: [PATCH 413/678] Explicitly invoke shell in some cases the permission on the script might be incorrect (zip downloads?). Explicitly invoke the shell closes https://github.com/official-stockfish/Stockfish/pull/4803 No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 1b03bbc2..95f0fe9a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -108,7 +108,7 @@ ifeq ($(ARCH),) endif ifeq ($(ARCH), native) - override ARCH = $(shell ../scripts/get_native_properties.sh | cut -d " " -f 1) + override ARCH = $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1) endif # explicitly check for the list of supported architectures (as listed with make help), From ce99b4b2ef74d09499f35f09bc33102d203791cd Mon Sep 17 00:00:00 2001 From: Jasper Shovelton Date: Fri, 29 Sep 2023 22:02:24 +0200 Subject: [PATCH 414/678] Increment minor section number from 3.7.1 to 3.8.1. Pext has nothing to do with git commit sha/date, so separate the two sub-sections. closes https://github.com/official-stockfish/Stockfish/pull/4785 No functional change --- AUTHORS | 1 + src/Makefile | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9314f5cb..b1e82806 100644 --- a/AUTHORS +++ b/AUTHORS @@ -98,6 +98,7 @@ Jake Senne (w1wwwwww) Jan Ondruš (hxim) Jared Kish (Kurtbusch, kurt22i) Jarrod Torriero (DU-jdto) +Jasper Shovelton (Beanie496) Jean-Francois Romang (jromang) Jean Gauthier (OuaisBla) Jekaa diff --git a/src/Makefile b/src/Makefile index 95f0fe9a..a59303ac 100644 --- a/src/Makefile +++ b/src/Makefile @@ -703,24 +703,24 @@ ifeq ($(pext),yes) endif endif -### 3.7.1 Try to include git commit sha for versioning +### 3.8.1 Try to include git commit sha for versioning GIT_SHA = $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) ifneq ($(GIT_SHA), ) CXXFLAGS += -DGIT_SHA=$(GIT_SHA) endif -### 3.7.2 Try to include git commit date for versioning +### 3.8.2 Try to include git commit date for versioning GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) ifneq ($(GIT_DATE), ) CXXFLAGS += -DGIT_DATE=$(GIT_DATE) endif -### 3.7.3 Try to include architecture +### 3.8.3 Try to include architecture ifneq ($(ARCH), ) CXXFLAGS += -DARCH=$(ARCH) endif -### 3.8 Link Time Optimization +### 3.9 Link Time Optimization ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. ifeq ($(optimize),yes) @@ -755,7 +755,7 @@ ifeq ($(debug), no) endif endif -### 3.9 Android 5 can only run position independent executables. Note that this +### 3.10 Android 5 can only run position independent executables. Note that this ### breaks Android 4.0 and earlier. ifeq ($(OS), Android) CXXFLAGS += -fPIE From 9739ed7a97c153c3223b608b24717edbf2dfd7bc Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 23 Sep 2023 13:24:09 +0300 Subject: [PATCH 415/678] Simplify pawn count in evaluation This simplifies the evaluation by removing the unnecessary pawn count term when combining nnue and optimism values. Passed STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 61472 W: 15748 L: 15554 D: 30170 Ptnml(0-2): 191, 7123, 15933, 7279, 210 https://tests.stockfishchess.org/tests/view/650c34cf7ca0d3f7bbf264ff Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 81264 W: 20657 L: 20500 D: 40107 Ptnml(0-2): 30, 8713, 22997, 8854, 38 https://tests.stockfishchess.org/tests/view/650cc30efb151d43ae6d5987 closes https://github.com/official-stockfish/Stockfish/pull/4800 Bench: 1530568 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9ca0e456..208e3ed5 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -177,7 +177,7 @@ Value Eval::evaluate(const Position& pos) { int npm = pos.non_pawn_material() / 64; v = ( nnue * (915 + npm + 9 * pos.count()) - + optimism * (154 + npm + pos.count())) / 1024; + + optimism * (154 + npm )) / 1024; } // Damp down the evaluation linearly when shuffling From 243f7b264a81c2981cec2818b47d609d9d3ca119 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 29 Sep 2023 22:16:57 +0200 Subject: [PATCH 416/678] Improve grammar of comments closes https://github.com/official-stockfish/Stockfish/pull/4801 No functional change --- src/bitboard.h | 2 +- src/movegen.h | 5 +++-- src/movepick.cpp | 8 ++++---- src/position.cpp | 17 +++++++++-------- src/search.cpp | 25 +++++++++++++------------ src/types.h | 4 ++-- src/uci.cpp | 6 +++--- 7 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index dee73b4b..eb2f949d 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -209,7 +209,7 @@ template<> inline int distance(Square x, Square y) { return SquareDistan inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -/// attacks_bb(Square) returns the pseudo attacks of the give piece type +/// attacks_bb(Square) returns the pseudo attacks of the given piece type /// assuming an empty board. template diff --git a/src/movegen.h b/src/movegen.h index b15f1230..5eee2f1a 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -56,8 +56,9 @@ inline bool operator<(const ExtMove& f, const ExtMove& s) { template ExtMove* generate(const Position& pos, ExtMove* moveList); -/// The MoveList struct is a simple wrapper around generate(). It sometimes comes -/// in handy to use this class instead of the low level generate() function. +/// The MoveList struct wraps the generate() function and returns a convenient +/// list of moves. Using MoveList is sometimes preferable to directly calling +/// the lower level generate() function. template struct MoveList { diff --git a/src/movepick.cpp b/src/movepick.cpp index d4f8ab09..eea1d49e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -55,11 +55,11 @@ namespace { } // namespace -/// Constructors of the MovePicker class. As arguments we pass information -/// to help it to return the (presumably) good moves first, to decide which +/// Constructors of the MovePicker class. As arguments, we pass information +/// to help it return the (presumably) good moves first, to decide which /// moves to return (in the quiescence search, for instance, we only want to -/// search captures, promotions, and some checks) and how important good move -/// ordering is at the current node. +/// search captures, promotions, and some checks) and how important a good +/// move ordering is at the current node. /// MovePicker constructor for the main search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, diff --git a/src/position.cpp b/src/position.cpp index 12067743..67dafd8d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -102,8 +102,9 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { } -// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition" -// situations. Description of the algorithm in the following paper: +// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions +// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes to +// allow fast detection of recurring positions. For details see: // http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf // First and second hash functions for indexing the cuckoo tables @@ -549,7 +550,7 @@ bool Position::legal(Move m) const { /// Position::pseudo_legal() takes a random move and tests whether the move is -/// pseudo legal. It is used to validate moves from TT that can be corrupted +/// pseudo-legal. It is used to validate moves from TT that can be corrupted /// due to SMP concurrent access or hash position key aliasing. bool Position::pseudo_legal(const Move m) const { @@ -565,7 +566,7 @@ bool Position::pseudo_legal(const Move m) const { return checkers() ? MoveList< EVASIONS>(*this).contains(m) : MoveList(*this).contains(m); - // Is not a promotion, so promotion piece must be empty + // Is not a promotion, so the promotion piece must be empty assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); // If the 'from' square is not occupied by a piece belonging to the side to @@ -603,7 +604,7 @@ bool Position::pseudo_legal(const Move m) const { { if (type_of(pc) != KING) { - // Double check? In this case a king move is required + // Double check? In this case, a king move is required if (more_than_one(checkers())) return false; @@ -611,7 +612,7 @@ bool Position::pseudo_legal(const Move m) const { if (!(between_bb(square(us), lsb(checkers())) & to)) return false; } - // In case of king moves under check we have to remove king so as to catch + // In case of king moves under check we have to remove the king so as to catch // invalid moves like b1a1 when opposite queen is on c1. else if (attackers_to(to, pieces() ^ from) & pieces(~us)) return false; @@ -1134,7 +1135,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { } else // KING - // If we "capture" with the king but opponent still has attackers, + // If we "capture" with the king but the opponent still has attackers, // reverse the result. return (attackers & ~pieces(stm)) ? res ^ 1 : res; } @@ -1265,7 +1266,7 @@ void Position::flip() { /// Position::pos_is_ok() performs some consistency checks for the -/// position object and raises an asserts if something wrong is detected. +/// position object and raise an assert if something wrong is detected. /// This is meant to be helpful when debugging. bool Position::pos_is_ok() const { diff --git a/src/search.cpp b/src/search.cpp index 3e19000a..9949e2ae 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -100,10 +100,12 @@ namespace { return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); } - // Skill structure is used to implement strength limit. If we have an uci_elo then - // we convert it to a suitable fractional skill level using anchoring to CCRL Elo - // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for a match (TC 60+0.6) - // results spanning a wide range of k values. + // Skill structure is used to implement strength limit. + // If we have a UCI_Elo, we convert it to an appropriate skill level, anchored to the Stash engine. + // This method is based on a fit of the Elo results for games played between the master at various + // skill levels and various versions of the Stash engine, all ranked at CCRL. + // Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately + // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c20acedbfe17d618c3c384b339ec struct Skill { Skill(int skill_level, int uci_elo) { if (uci_elo) @@ -272,10 +274,9 @@ void MainThread::search() { void Thread::search() { - // To allow access to (ss-7) up to (ss+2), the stack must be oversized. - // The former is needed to allow update_continuation_histories(ss-1, ...), - // which accesses its argument at ss-6, also near the root. - // The latter is needed for statScore and killer initialization. + // Allocate stack with extra size to allow access from (ss-7) to (ss+2) + // (ss-7) is needed for update_continuation_histories(ss-1, ...) which accesses (ss-6) + // (ss+2) is needed for initialization of statScore and killers Stack stack[MAX_PLY+10], *ss = stack+7; Move pv[MAX_PLY+1]; Value alpha, beta, delta; @@ -362,7 +363,7 @@ void Thread::search() { alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); - // Adjust optimism based on root move's previousScore + // Adjust optimism based on root move's previousScore (~4 Elo) int opt = 109 * prev / (std::abs(prev) + 141); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; @@ -721,7 +722,7 @@ namespace { } else if (excludedMove) { - // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 Elo) + // 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); eval = ss->staticEval; } @@ -762,7 +763,7 @@ namespace { : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval > (ss-4)->staticEval : true; - // Step 7. Razoring (~1 Elo). + // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. if (eval < alpha - 456 - 252 * depth * depth) @@ -772,7 +773,7 @@ namespace { return value; } - // Step 8. Futility pruning: child node (~40 Elo). + // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 9 diff --git a/src/types.h b/src/types.h index 340c47a5..f682e764 100644 --- a/src/types.h +++ b/src/types.h @@ -40,7 +40,7 @@ #include #if defined(_MSC_VER) -// Disable some silly and noisy warning from MSVC compiler +// Disable some silly and noisy warnings from MSVC compiler #pragma warning(disable: 4127) // Conditional expression is constant #pragma warning(disable: 4146) // Unary minus operator applied to unsigned type #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' @@ -405,7 +405,7 @@ constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -/// Based on a congruential pseudo random number generator +/// Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } diff --git a/src/uci.cpp b/src/uci.cpp index f3e436ef..f62bb8bf 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -159,7 +159,7 @@ namespace { // bench() is called when the engine receives the "bench" command. - // Firstly, a list of UCI commands is set up according to the bench + // First, a list of UCI commands is set up according to the bench // parameters, then it is run one by one, printing a summary at the end. void bench(Position& pos, std::istream& args, StateListPtr& states) { @@ -226,14 +226,14 @@ namespace { // Transform the eval to centipawns with limited range double x = std::clamp(double(v), -4000.0, 4000.0); - // Return the win rate in per mille units rounded to the nearest value + // Return the win rate in per mille units, rounded to the nearest integer return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); } } // namespace -/// UCI::loop() waits for a command from the stdin, parses it and then calls the appropriate +/// UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate /// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a /// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, /// like running 'bench', the function returns immediately after the command is executed. From 31d0b7fe932458d6661f4d4c2ce88502086616c5 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 24 Sep 2023 18:09:52 -0700 Subject: [PATCH 417/678] Remove unused see_ge() code closes https://github.com/official-stockfish/Stockfish/pull/4805 No functional change --- src/position.cpp | 20 +++++++------------- src/position.h | 1 - 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 67dafd8d..a2b377af 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1042,7 +1042,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { +bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); @@ -1061,7 +1061,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1092,43 +1092,43 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { - occupied ^= least_significant_square_bb(bb); if ((swap = PawnValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { - occupied ^= least_significant_square_bb(bb); if ((swap = KnightValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { - occupied ^= least_significant_square_bb(bb); if ((swap = BishopValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { - occupied ^= least_significant_square_bb(bb); if ((swap = RookValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { - occupied ^= least_significant_square_bb(bb); if ((swap = QueenValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); @@ -1143,12 +1143,6 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return bool(res); } -bool Position::see_ge(Move m, Value threshold) const { - Bitboard occupied; - return see_ge(m, occupied, threshold); -} - - /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. diff --git a/src/position.h b/src/position.h index ca7c3ace..aae4db94 100644 --- a/src/position.h +++ b/src/position.h @@ -135,7 +135,6 @@ public: // Static Exchange Evaluation bool see_ge(Move m, Value threshold = VALUE_ZERO) const; - bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; From 4f0fecad8a0f5258114f63f0ac0c905a54d65219 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Mon, 25 Sep 2023 12:24:48 +0200 Subject: [PATCH 418/678] Use C++17 variable templates for type traits The C++17 variable templates are slightly more readable and allow us to remove the typename keyword in a few cases. closes https://github.com/official-stockfish/Stockfish/pull/4806 No functional change --- src/movepick.h | 4 ++-- src/nnue/nnue_common.h | 4 ++-- src/syzygy/tbprobe.cpp | 4 ++-- src/tune.h | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 5243f89c..dd9de0b2 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include // IWYU pragma: keep #include "movegen.h" #include "types.h" @@ -70,7 +70,7 @@ struct Stats : public std::array, Size> void fill(const T& v) { // For standard-layout 'this' points to first struct member - assert(std::is_standard_layout::value); + assert(std::is_standard_layout_v); using entry = StatsEntry; entry* p = reinterpret_cast(this); diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index a42a86c9..e159c5dc 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -103,7 +103,7 @@ namespace Stockfish::Eval::NNUE { else { std::uint8_t u[sizeof(IntType)]; - typename std::make_unsigned::type v = 0; + std::make_unsigned_t v = 0; stream.read(reinterpret_cast(u), sizeof(IntType)); for (std::size_t i = 0; i < sizeof(IntType); ++i) @@ -128,7 +128,7 @@ namespace Stockfish::Eval::NNUE { else { std::uint8_t u[sizeof(IntType)]; - typename std::make_unsigned::type v = value; + std::make_unsigned_t v = value; std::size_t i = 0; // if constexpr to silence the warning about shift by 8 diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 13d271fc..ffe29ce1 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -102,7 +102,7 @@ constexpr Value WDL_to_value[] = { template inline void swap_endian(T& x) { - static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned"); + static_assert(std::is_unsigned_v, "Argument of swap_endian not unsigned"); uint8_t tmp, *c = (uint8_t*)&x; for (int i = 0; i < Half; ++i) @@ -332,7 +332,7 @@ struct PairsData { // first access, when the corresponding file is memory mapped. template struct TBTable { - using Ret = typename std::conditional::type; + using Ret = std::conditional_t; static constexpr int Sides = Type == WDL ? 2 : 1; diff --git a/src/tune.h b/src/tune.h index 3e94f7ef..dde03b32 100644 --- a/src/tune.h +++ b/src/tune.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include // IWYU pragma: keep #include #include @@ -96,11 +96,11 @@ class Tune { template struct Entry : public EntryBase { - static_assert(!std::is_const::value, "Parameter cannot be const!"); + static_assert(!std::is_const_v, "Parameter cannot be const!"); - static_assert( std::is_same::value - || std::is_same::value - || std::is_same::value, "Parameter type not supported!"); + static_assert( std::is_same_v + || std::is_same_v + || std::is_same_v, "Parameter type not supported!"); Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} void operator=(const Entry&) = delete; // Because 'value' is a reference From 660da1ca7b4c2c03dce03d14ef3496d9fb4aead2 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Mon, 25 Sep 2023 13:18:05 +0800 Subject: [PATCH 419/678] Skip moves-loop pruning in qsearch if we have only pawns At first my idea was only to cover movecount and futility pruning, but @peregrineshahin suggested to test it on all moves-loop pruning and it worked. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 167968 W: 42970 L: 42480 D: 82518 Ptnml(0-2): 444, 18324, 46002, 18726, 488 https://tests.stockfishchess.org/tests/view/6511181a55b420c569d0d54c Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 40794 W: 10496 L: 10182 D: 20116 Ptnml(0-2): 12, 4021, 12025, 4319, 20 https://tests.stockfishchess.org/tests/view/6512ccc4b3e74811c8aee86c closes https://github.com/official-stockfish/Stockfish/pull/4809 Bench: 1338472 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9949e2ae..97b70b8f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1427,6 +1427,7 @@ moves_loop: // When in check, search starts here Value bestValue, value, ttValue, futilityValue, futilityBase; bool pvHit, givesCheck, capture; int moveCount; + Color us = pos.side_to_move(); // Step 1. Initialize node if (PvNode) @@ -1537,7 +1538,7 @@ moves_loop: // When in check, search starts here moveCount++; // Step 6. Pruning. - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) if ( !givesCheck From afe7f4d9b0c5e1a1aa224484d2cd9e04c7f099b9 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 27 Sep 2023 20:58:28 -0400 Subject: [PATCH 420/678] Update default net to nn-0000000000a0.nnue This is a later epoch from the same experiment that led to the previous master net. In training stage 6, max-epoch was raised to 1,200 near the end of the first 1,000 epochs. For more details, see https://github.com/official-stockfish/Stockfish/pull/4795 Local elo at 25k nodes per move (vs. L1-2048 nn-1ee1aba5ed4c.nnue) ep1079 : 15.6 +/- 1.2 Passed STC: https://tests.stockfishchess.org/tests/view/651503b3b3e74811c8af1e2a LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 29408 W: 7607 L: 7304 D: 14497 Ptnml(0-2): 97, 3277, 7650, 3586, 94 Passed LTC: https://tests.stockfishchess.org/tests/view/651585ceb3e74811c8af2a5f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 73164 W: 18828 L: 18440 D: 35896 Ptnml(0-2): 30, 7749, 20644, 8121, 38 closes https://github.com/official-stockfish/Stockfish/pull/4810 Bench: 1453057 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index acf9edd2..26f2fc4f 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-ac1dbea57aa3.nnue" + #define EvalFileDefaultName "nn-0000000000a0.nnue" namespace NNUE { From 8a912951de6d4bff78d3ff5258213a0c7e6f494e Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 24 Sep 2023 15:15:50 -0700 Subject: [PATCH 421/678] Remove handcrafted MMX code too small a benefit to maintain this old target closes https://github.com/official-stockfish/Stockfish/pull/4804 No functional change --- src/Makefile | 5 ++-- src/misc.cpp | 3 --- src/nnue/layers/affine_transform.h | 32 +---------------------- src/nnue/layers/clipped_relu.h | 18 ------------- src/nnue/layers/simd.h | 3 --- src/nnue/nnue_common.h | 6 ----- src/nnue/nnue_feature_transformer.h | 40 ----------------------------- 7 files changed, 3 insertions(+), 104 deletions(-) diff --git a/src/Makefile b/src/Makefile index a59303ac..5b43c35f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -674,7 +674,6 @@ ifeq ($(sse2),yes) endif ifeq ($(mmx),yes) - CXXFLAGS += -DUSE_MMX ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mmmx endif @@ -794,11 +793,11 @@ help: @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" @echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt" @echo "x86-64-ssse3 > x86 64-bit with ssse3 support" - @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support" + @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 compile and popcnt support" @echo "x86-64 > x86 64-bit generic (with sse2 support)" @echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support" @echo "x86-32-sse2 > x86 32-bit with sse2 support" - @echo "x86-32 > x86 32-bit generic (with mmx and sse support)" + @echo "x86-32 > x86 32-bit generic (with mmx compile support)" @echo "ppc-64 > PPC 64-bit" @echo "ppc-32 > PPC 32-bit" @echo "armv7 > ARMv7 32-bit" diff --git a/src/misc.cpp b/src/misc.cpp index 98e346a6..2f6ffd28 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -282,9 +282,6 @@ std::string compiler_info() { compiler += " SSE2"; #endif compiler += (HasPopCnt ? " POPCNT" : ""); - #if defined(USE_MMX) - compiler += " MMX"; - #endif #if defined(USE_NEON_DOTPROD) compiler += " NEON_DOTPROD"; #elif defined(USE_NEON) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 42839bb5..fc65c343 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -45,18 +45,13 @@ namespace Stockfish::Eval::NNUE::Layers { template static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) { -# if defined(USE_SSE2) || defined(USE_MMX) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) +# if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) # if defined(USE_SSE2) // At least a multiple of 16, with SSE2. constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; const __m128i Zeros = _mm_setzero_si128(); const auto inputVector = reinterpret_cast(input); -# elif defined(USE_MMX) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 8; - const __m64 Zeros = _mm_setzero_si64(); - const auto inputVector = reinterpret_cast(input); - # elif defined(USE_NEON_DOTPROD) constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; const auto inputVector = reinterpret_cast(input); @@ -92,26 +87,6 @@ namespace Stockfish::Eval::NNUE::Layers { sum = _mm_add_epi32(sum, sum_second_32); output[i] = _mm_cvtsi128_si32(sum); -# elif defined(USE_MMX) - __m64 sumLo = _mm_cvtsi32_si64(biases[i]); - __m64 sumHi = Zeros; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - __m64 row_j = row[j]; - __m64 input_j = inputVector[j]; - __m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8); - __m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8); - __m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros); - __m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros); - __m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo); - __m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi); - sumLo = _mm_add_pi32(sumLo, productLo); - sumHi = _mm_add_pi32(sumHi, productHi); - } - __m64 sum = _mm_add_pi32(sumLo, sumHi); - sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum)); - output[i] = _mm_cvtsi64_si32(sum); - # elif defined(USE_NEON_DOTPROD) int32x4_t sum = {biases[i]}; const auto row = reinterpret_cast(&weights[offset]); @@ -132,11 +107,6 @@ namespace Stockfish::Eval::NNUE::Layers { # endif } - -# if defined(USE_MMX) - _mm_empty(); -# endif - # else std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index aab824b3..48cd6c69 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -135,24 +135,6 @@ namespace Stockfish::Eval::NNUE::Layers { } constexpr IndexType Start = NumChunks * SimdWidth; - #elif defined(USE_MMX) - constexpr IndexType NumChunks = InputDimensions / SimdWidth; - const __m64 k0x80s = _mm_set1_pi8(-128); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m64*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m64 words0 = _mm_srai_pi16( - _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]), - WeightScaleBits); - const __m64 words1 = _mm_srai_pi16( - _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]), - WeightScaleBits); - const __m64 packedbytes = _mm_packs_pi16(words0, words1); - out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); - } - _mm_empty(); - constexpr IndexType Start = NumChunks * SimdWidth; - #elif defined(USE_NEON) constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); const int8x8_t Zero = {0}; diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index f478cd78..349217ed 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -31,9 +31,6 @@ #elif defined(USE_SSE2) # include -#elif defined(USE_MMX) -# include - #elif defined(USE_NEON) # include #endif diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index e159c5dc..779f4e75 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -42,9 +42,6 @@ #elif defined(USE_SSE2) #include -#elif defined(USE_MMX) -#include - #elif defined(USE_NEON) #include #endif @@ -71,9 +68,6 @@ namespace Stockfish::Eval::NNUE { #elif defined(USE_SSE2) constexpr std::size_t SimdWidth = 16; - #elif defined(USE_MMX) - constexpr std::size_t SimdWidth = 8; - #elif defined(USE_NEON) constexpr std::size_t SimdWidth = 16; #endif diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 902918b2..77a175f5 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -117,34 +117,6 @@ namespace Stockfish::Eval::NNUE { #define NumRegistersSIMD (Is64Bit ? 16 : 8) #define MaxChunkSize 16 - #elif USE_MMX - using vec_t = __m64; - using psqt_vec_t = __m64; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) _mm_add_pi16(a,b) - #define vec_sub_16(a,b) _mm_sub_pi16(a,b) - #define vec_mul_16(a,b) _mm_mullo_pi16(a,b) - #define vec_zero() _mm_setzero_si64() - #define vec_set_16(a) _mm_set1_pi16(a) - inline vec_t vec_max_16(vec_t a,vec_t b){ - vec_t comparison = _mm_cmpgt_pi16(a,b); - return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b)); - } - inline vec_t vec_min_16(vec_t a,vec_t b){ - vec_t comparison = _mm_cmpgt_pi16(a,b); - return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a)); - } - #define vec_msb_pack_16(a,b) _mm_packs_pi16(_mm_srli_pi16(a,7),_mm_srli_pi16(b,7)) - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) _mm_add_pi32(a,b) - #define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b) - #define vec_zero_psqt() _mm_setzero_si64() - #define vec_cleanup() _mm_empty() - #define NumRegistersSIMD 8 - #define MaxChunkSize 8 - #elif USE_NEON using vec_t = int16x8_t; using psqt_vec_t = int32x4_t; @@ -335,10 +307,6 @@ namespace Stockfish::Eval::NNUE { #endif } -#if defined(vec_cleanup) - vec_cleanup(); -#endif - return psqt; } // end of function transform() @@ -529,10 +497,6 @@ namespace Stockfish::Eval::NNUE { } } #endif - - #if defined(USE_MMX) - _mm_empty(); - #endif } template @@ -613,10 +577,6 @@ namespace Stockfish::Eval::NNUE { accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } #endif - - #if defined(USE_MMX) - _mm_empty(); - #endif } template From f1ce1cd4751a098b7ee09e304fa6397d08fe8d7f Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 30 Sep 2023 08:11:14 +0200 Subject: [PATCH 422/678] Update links in license matches https://www.gnu.org/licenses/gpl-3.0.txt closes https://github.com/official-stockfish/Stockfish/pull/4813 No functional change --- Copying.txt | 1348 +++++++++++++++++++++++++-------------------------- 1 file changed, 674 insertions(+), 674 deletions(-) diff --git a/Copying.txt b/Copying.txt index 818433ec..f288702d 100644 --- a/Copying.txt +++ b/Copying.txt @@ -1,674 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 040dfedb3457ca6971d98c754362cde4dc767aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sun, 1 Oct 2023 20:19:49 +0200 Subject: [PATCH 423/678] Remove one test in the move loop Simplification passed STC test: https://tests.stockfishchess.org/tests/view/6519fc91cff46e538ee014f6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 191264 W: 48550 L: 48501 D: 94213 Ptnml(0-2): 576, 21529, 51392, 21540, 595 closes #4815 Non functional change --- src/search.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 97b70b8f..55084788 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -945,16 +945,15 @@ moves_loop: // When in check, search starts here if (move == excludedMove) continue; - // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. As a consequence, any illegal move is also skipped. In MultiPV - // mode we also skip PV moves that have been already searched and those - // of lower "TB rank" if we are in a TB root position. - if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, - thisThread->rootMoves.begin() + thisThread->pvLast, move)) + // Check for legality + if (!pos.legal(move)) continue; - // Check for legality - if (!rootNode && !pos.legal(move)) + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. In MultiPV mode we also skip PV moves that have been already + // searched and those of lower "TB rank" if we are in a TB root position. + if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, + thisThread->rootMoves.begin() + thisThread->pvLast, move)) continue; ss->moveCount = ++moveCount; From c17a657b045d4dc720c8c36558fe649a1c3f4a05 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 30 Sep 2023 23:12:02 -0700 Subject: [PATCH 424/678] Optimize the most common update accumalator cases w/o tiling In the most common case where we only update a single state it's faster to not use temporary accumulation registers and tiling. (Also includes a couple of small cleanups.) passed STC https://tests.stockfishchess.org/tests/view/651918e3cff46e538ee0023b LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 34944 W: 8989 L: 8687 D: 17268 Ptnml(0-2): 88, 3743, 9512, 4037, 92 A simpler version https://tests.stockfishchess.org/tests/view/65190dfacff46e538ee00155 also passed but this version is stronger still https://tests.stockfishchess.org/tests/view/6519b95fcff46e538ee00fa2 closes https://github.com/official-stockfish/Stockfish/pull/4816 No functional change --- src/misc.h | 1 + src/nnue/nnue_feature_transformer.h | 180 +++++++++++++++++++--------- 2 files changed, 122 insertions(+), 59 deletions(-) diff --git a/src/misc.h b/src/misc.h index c0387f7c..52595fb9 100644 --- a/src/misc.h +++ b/src/misc.h @@ -87,6 +87,7 @@ public: void push_back(const T& value) { values_[size_++] = value; } const T* begin() const { return values_; } const T* end() const { return values_ + size_; } + const T& operator[](int index) const { return values_[index]; } private: T values_[MaxSize]; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 77a175f5..25f686da 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -370,13 +370,13 @@ namespace Stockfish::Eval::NNUE { while (states_to_update[i] == nullptr) --i; - StateInfo *st2 = states_to_update[i]; + StateInfo* st2 = states_to_update[i]; for (; i >= 0; --i) { states_to_update[i]->accumulator.computed[Perspective] = true; - StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; for (; st2 != end_state; st2 = st2->previous) FeatureSet::append_changed_indices( @@ -388,78 +388,140 @@ namespace Stockfish::Eval::NNUE { // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + + if ( states_to_update[1] == nullptr + && (removed[0].size() == 1 || removed[0].size() == 2) + && added[0].size() == 1) { - // Load accumulator - auto accTile = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTile[k]); + assert(states_to_update[0]); - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) + auto accTileIn = reinterpret_cast( + &st->accumulator.accumulation[Perspective][0]); + auto accTileOut = reinterpret_cast( + &states_to_update[0]->accumulator.accumulation[Perspective][0]); + + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); + + if (removed[0].size() == 1) { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) + accTileOut[k] = vec_add_16(vec_sub_16(accTileIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); + + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) + accTileOut[k] = vec_sub_16( + vec_add_16(accTileIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); } - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } + auto accTilePsqtIn = reinterpret_cast( + &st->accumulator.psqtAccumulation[Perspective][0]); + auto accTilePsqtOut = reinterpret_cast( + &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]); - // Store accumulator - accTile = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTile[k], acc[k]); - } + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; + auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); + const IndexType offsetPsqtA = PSQTBuckets * added[0][0]; + auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); + + if (removed[0].size() == 1) + { + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) + accTilePsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32( + accTilePsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]); + } + else + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1]; + auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); + + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) + accTilePsqtOut[k] = vec_sub_psqt_32( + vec_add_psqt_32(accTilePsqtIn[k], columnPsqtA[k]), + vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); + } } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + else { - // Load accumulator - auto accTilePsqt = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_load_psqt(&accTilePsqt[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + // Load accumulator + auto accTileIn = reinterpret_cast( + &st->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTileIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); + } } - // Difference calculation for the activated features - for (const auto index : added[i]) + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + // Load accumulator + auto accTilePsqtIn = reinterpret_cast( + &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } + psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); - // Store accumulator - accTilePsqt = reinterpret_cast( - &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + // Store accumulator + auto accTilePsqtOut = reinterpret_cast( + &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqtOut[k], psqt[k]); + } + } } - #else for (IndexType i = 0; states_to_update[i]; ++i) { From 008d59512ac38e1e4a2f7880fe4e07b902845bb0 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 30 Sep 2023 06:57:39 +0200 Subject: [PATCH 425/678] Simplify collection of bad moves for history updates. 1. collect only the first 32 moves searched and ignore the rest. So late bad moves get no further negative history updates. 2. collect now for quiet moves also at most 32 bad moves STC: https://tests.stockfishchess.org/tests/view/6517b3aeb3e74811c8af5651 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 51168 W: 13013 L: 12810 D: 25345 Ptnml(0-2): 120, 6006, 13186, 6095, 177 LTC: https://tests.stockfishchess.org/tests/view/651adafecff46e538ee02734 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 109866 W: 27786 L: 27656 D: 54424 Ptnml(0-2): 52, 11816, 31069, 11942, 54 closes https://github.com/official-stockfish/Stockfish/pull/4818 Bench: 1338617 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 55084788..f49c23ae 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -546,7 +546,7 @@ namespace { assert(0 < depth && depth < MAX_PLY); assert(!(PvNode && cutNode)); - Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; + Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[32]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); @@ -1328,12 +1328,12 @@ moves_loop: // When in check, search starts here // If the move is worse than some previously searched move, remember it, to update its stats later - if (move != bestMove) + if (move != bestMove && moveCount <= 32) { - if (capture && captureCount < 32) + if (capture) capturesSearched[captureCount++] = move; - else if (!capture && quietCount < 64) + else quietsSearched[quietCount++] = move; } } From 25d444ed60e3873c02a70525776b145f03833103 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:46:03 +0200 Subject: [PATCH 426/678] Razor more if ss+1 cutoffCnt > 3 STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 221760 W: 56726 L: 56144 D: 108890 Ptnml(0-2): 655, 25453, 58123, 25953, 696 https://tests.stockfishchess.org/tests/view/651d34dbcff46e538ee05d91 LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 130326 W: 33188 L: 32681 D: 64457 Ptnml(0-2): 69, 13949, 36620, 14456, 69 https://tests.stockfishchess.org/tests/view/651f844eac577114367273d5 closes https://github.com/official-stockfish/Stockfish/pull/4822 bench: 1291708 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f49c23ae..a5b5c101 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -766,7 +766,8 @@ namespace { // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 456 - 252 * depth * depth) + // Adjust razor margin according to cutoffCnt. (~1 Elo) + if (eval < alpha - 456 - (252 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) From f7fbc6880efae2ec9d97d6f1d65e2ad00547e32c Mon Sep 17 00:00:00 2001 From: gabe Date: Fri, 6 Oct 2023 22:59:22 +0200 Subject: [PATCH 427/678] Avoid recomputing moveCountPruning In search, when moveCountPruning becomes true, it can never turn false again. Passed STC https://tests.stockfishchess.org/tests/view/652075ceac57711436728aac LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 136448 W: 34923 L: 34472 D: 67053 Ptnml(0-2): 420, 15094, 36767, 15501, 442 closes https://github.com/official-stockfish/Stockfish/pull/4823 Non functional change --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a5b5c101..69d8decf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -984,7 +984,8 @@ moves_loop: // When in check, search starts here && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - moveCountPruning = moveCount >= futility_move_count(improving, depth); + if (!moveCountPruning) + moveCountPruning = moveCount >= futility_move_count(improving, depth); // Reduced depth of the next LMR search int lmrDepth = newDepth - r; From 7a4de96159f76f2465d474d76e08a1c8ca3383b8 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Sat, 7 Oct 2023 23:25:34 +0200 Subject: [PATCH 428/678] Skip futility pruning if ttMove has bad history Passed STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 52416 W: 13465 L: 13128 D: 25823 Ptnml(0-2): 128, 6024, 13604, 6287, 165 https://tests.stockfishchess.org/tests/view/651fadd4ac577114367277bf Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 87348 W: 22234 L: 21818 D: 43296 Ptnml(0-2): 38, 9240, 24698, 9664, 34 https://tests.stockfishchess.org/tests/view/65201932ac57711436728218 closes https://github.com/official-stockfish/Stockfish/pull/4825 bench: 1246560 --- AUTHORS | 1 + src/search.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index b1e82806..d7b64b62 100644 --- a/AUTHORS +++ b/AUTHORS @@ -210,6 +210,7 @@ Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Stephen Touset (stouset) Syine Mineta (MinetaS) +Taras Vuk (TarasVuk) Thanar2 thaspel theo77186 diff --git a/src/search.cpp b/src/search.cpp index 69d8decf..85106513 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -780,7 +780,10 @@ namespace { && depth < 9 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta - && eval < 24923) // smaller than TB wins + && eval < 24923 // smaller than TB wins + && !( !ttCapture + && ttMove + && thisThread->mainHistory[us][from_to(ttMove)] < 989)) return eval; // Step 9. Null move search with verification search (~35 Elo) From 002636362e175134c6d0d53b332b527ec4a12db0 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Tue, 10 Oct 2023 17:43:36 +0200 Subject: [PATCH 429/678] Search parameters tune at 180+1.8 Passed VLTC: https://tests.stockfishchess.org/tests/view/65200c58ac577114367280bc LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 146180 W: 37407 L: 36988 D: 71785 Ptnml(0-2): 21, 14474, 43675, 14905, 15 Passed VLTC SMP: https://tests.stockfishchess.org/tests/view/652403da3125598fc7eb4b6d LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 57580 W: 15061 L: 14739 D: 27780 Ptnml(0-2): 2, 5001, 18460, 5327, 0 closes https://github.com/official-stockfish/Stockfish/pull/4826 Bench: 1099336 --- src/search.cpp | 78 +++++++++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 85106513..71332e50 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -73,7 +73,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return Value((140 - 40 * noTtCutNode) * (d - improving)); + return Value((126 - 42 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup @@ -81,8 +81,8 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 - + (!i && reductionScale > 936); + return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024 + + (!i && reductionScale > 791); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -92,7 +92,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(336 * d - 547, 1561); + return std::min(334 * d - 531, 1538); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -174,7 +174,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.57 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -359,12 +359,12 @@ void Thread::search() { // Reset aspiration window starting size Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 15799; + delta = Value(10) + int(prev) * prev / 17470; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore (~4 Elo) - int opt = 109 * prev / (std::abs(prev) + 141); + int opt = 113 * prev / (std::abs(prev) + 109); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; @@ -750,7 +750,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1817, 1817); + int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1812, 1812); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -767,7 +767,7 @@ namespace { // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 456 - (252 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 492 - (257 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -778,9 +778,9 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 9 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 321 >= beta && eval >= beta - && eval < 24923 // smaller than TB wins + && eval < 29462 // smaller than TB wins && !( !ttCapture && ttMove && thisThread->mainHistory[us][from_to(ttMove)] < 989)) @@ -789,10 +789,10 @@ namespace { // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 17329 + && (ss-1)->statScore < 17257 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 21 * depth + 258 + && ss->staticEval >= beta - 24 * depth + 281 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly @@ -801,7 +801,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 173, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -850,7 +850,7 @@ namespace { && !ttMove) depth -= 2; - probCutBeta = beta + 168 - 61 * improving; + probCutBeta = beta + 168 - 70 * improving; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -906,7 +906,7 @@ namespace { moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 413; + probCutBeta = beta + 416; if ( ss->inCheck && !PvNode && ttCapture @@ -1000,12 +1000,12 @@ moves_loop: // When in check, search starts here if ( !givesCheck && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] + && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, Value(-205) * depth)) + if (!pos.see_ge(move, Value(-185) * depth)) continue; } else @@ -1016,24 +1016,24 @@ moves_loop: // When in check, search starts here // Continuation history based pruning (~2 Elo) if ( lmrDepth < 6 - && history < -3832 * depth) + && history < -3232 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7011; + lmrDepth += history / 5793; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck - && lmrDepth < 12 - && ss->staticEval + 112 + 138 * lmrDepth <= alpha) + && lmrDepth < 13 + && ss->staticEval + 115 + 122 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-31 * lmrDepth * lmrDepth))) + if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth))) continue; } } @@ -1051,7 +1051,7 @@ moves_loop: // When in check, search starts here // scaling. Their values are optimized to time controls of 180+1.8 and longer // so changing them requires tests at this type of time controls. if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ @@ -1059,7 +1059,7 @@ moves_loop: // When in check, search starts here && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (82 + 65 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; @@ -1073,11 +1073,11 @@ moves_loop: // When in check, search starts here // Avoid search explosion by limiting the number of double extensions if ( !PvNode - && value < singularBeta - 21 + && value < singularBeta - 18 && ss->doubleExtensions <= 11) { extension = 2; - depth += depth < 13; + depth += depth < 15; } } @@ -1095,7 +1095,7 @@ moves_loop: // When in check, search starts here // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) else if (cutNode) - extension = depth < 17 ? -3 : -1; + extension = depth < 19 ? -2 : -1; // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) @@ -1111,7 +1111,7 @@ moves_loop: // When in check, search starts here else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5168) + && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) extension = 1; } @@ -1139,7 +1139,7 @@ moves_loop: // When in check, search starts here r -= cutNode && tte->depth() >= depth ? 3 : 2; // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss-1)->moveCount > 8) + if ((ss-1)->moveCount > 7) r--; // Increase reduction for cut nodes (~3 Elo) @@ -1175,10 +1175,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4006; + - 3848; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (11124 + 4740 * (depth > 5 && depth < 22)); + r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1202,8 +1202,8 @@ 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 + 64 + 11 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 711 && ss->doubleExtensions <= 6; + const bool doDeeperSearch = value > (bestValue + 51 + 10 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; @@ -1321,8 +1321,8 @@ moves_loop: // When in check, search starts here // Reduce other moves if we have found at least one score improvement (~2 Elo) if ( depth > 2 && depth < 12 - && beta < 14362 - && value > -12393) + && beta < 13828 + && value > -11369) depth -= 2; assert(depth > 0); @@ -1371,7 +1371,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 800) + ((ss-1)->moveCount > 12); + int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653) + ((ss-1)->moveCount > 11); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus / 2; } @@ -1593,7 +1593,7 @@ moves_loop: // When in check, search starts here continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-95))) + if (!pos.see_ge(move, Value(-90))) continue; } @@ -1726,7 +1726,7 @@ moves_loop: // When in check, search starts here if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 145 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 38e830af4bfa6c9e9c11279a8e6a60b6ca4ec2cd Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 14 Oct 2023 17:41:41 +0300 Subject: [PATCH 430/678] Use more continuation histories. This patch allows stats updates and movepicker bonuses for continuation history 3 plies deep - so counter counter move. Updates and movepicker usage are done with 1/4 multiplier compared to other histories. Passed STC: https://tests.stockfishchess.org/tests/view/6528f28d3125598fc7ebb5a3 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 161344 W: 41369 L: 40868 D: 79107 Ptnml(0-2): 535, 18720, 41679, 19185, 553 Passed LTC: https://tests.stockfishchess.org/tests/view/652a397a3125598fc7ebd1d6 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 48564 W: 12556 L: 12215 D: 23793 Ptnml(0-2): 25, 5149, 13595, 5486, 27 closes https://github.com/official-stockfish/Stockfish/pull/4827 bench 1327410 --- src/movepick.cpp | 1 + src/search.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index eea1d49e..bc3fcf7e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -141,6 +141,7 @@ void MovePicker::score() { m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; + m.value += (*continuationHistory[2])[pc][to] / 4; m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; diff --git a/src/search.cpp b/src/search.cpp index 71332e50..a1834ab9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -918,7 +918,7 @@ moves_loop: // When in check, search starts here return probCutBeta; const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - nullptr , (ss-4)->continuationHistory, + (ss-3)->continuationHistory, (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; @@ -1511,7 +1511,7 @@ moves_loop: // When in check, search starts here } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - nullptr , (ss-4)->continuationHistory, + (ss-3)->continuationHistory, (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; // Initialize a MovePicker object for the current position, and prepare @@ -1768,13 +1768,13 @@ moves_loop: // When in check, search starts here void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - for (int i : {1, 2, 4, 6}) + for (int i : {1, 2, 3, 4, 6}) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (is_ok((ss-i)->currentMove)) - (*(ss-i)->continuationHistory)[pc][to] << bonus; + (*(ss-i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); } } From a4fedd8152e717a37ec8b8ddda0658364c1f5ee4 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Thu, 27 Jul 2023 06:21:54 +0300 Subject: [PATCH 431/678] Fix greater than TB scores in null move pruning. This patch is a simplification and a fix to dealing with null moves scores that returns proven mates or TB scores by preventing 'null move pruning' if the nullvalue is in that range. Current solution downgrades nullValues on the non-PV node but the value can be used in a transposed PV-node to the same position afterwards (Triangulation), the later is prone to propagate a wrong score (96.05) to root that will not be refuted unless we search further. Score of (96.05) can be obtained be two methods, maxim static-eval returned on Pv update (mostly qSearch) this downgrade (clamp) in NMP and theoretically can happen with or without TBs but the second scenario is more dangerous than the first. This fixes the reproducible case in very common scenarios with TBs as shown in the debugging at discord. Fixes: #4699 Passed STC: https://tests.stockfishchess.org/tests/view/64c1eca8dc56e1650abba6f9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 670048 W: 171132 L: 171600 D: 327316 Ptnml(0-2): 2134, 75687, 179820, 75279, 2104 Passed LTC: https://tests.stockfishchess.org/tests/view/64c5e130dc56e1650abc0438 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 92868 W: 23642 L: 23499 D: 45727 Ptnml(0-2): 52, 9509, 27171, 9648, 54 closes https://github.com/official-stockfish/Stockfish/pull/4715 Bench: 1327410 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a1834ab9..1b019a08 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -812,11 +812,9 @@ namespace { pos.undo_null_move(); - if (nullValue >= beta) + // Do not return unproven mate or TB scores + if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { - // Do not return unproven mate or TB scores - nullValue = std::min(nullValue, VALUE_TB_WIN_IN_MAX_PLY-1); - if (thisThread->nmpMinPly || depth < 14) return nullValue; From fe53a18f7a149f7e6d1a9dde8a7478692ef82997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sun, 1 Oct 2023 19:18:05 +0200 Subject: [PATCH 432/678] Reformat some comments and conditions closes https://github.com/official-stockfish/Stockfish/pull/4814 No functional change --- src/movegen.cpp | 5 +-- src/position.cpp | 18 +++++----- src/search.cpp | 88 ++++++++++++++++++++++++------------------------ 3 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index c6a8dbb8..cda43b3a 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -236,9 +236,10 @@ namespace { /// Generates all pseudo-legal captures plus queen promotions /// Generates all pseudo-legal non-captures and underpromotions -/// Generates all pseudo-legal check evasions when the side to move is in check -/// Generates all pseudo-legal non-captures giving check, except castling and promotions +/// Generates all pseudo-legal check evasions /// Generates all pseudo-legal captures and non-captures +/// Generates all pseudo-legal non-captures giving check, +/// except castling and promotions /// /// Returns a pointer to the end of the move list. diff --git a/src/position.cpp b/src/position.cpp index a2b377af..0d7d9571 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -102,9 +102,9 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { } -// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions -// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes to -// allow fast detection of recurring positions. For details see: +// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions +// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes +// to allow fast detection of recurring positions. For details see: // http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf // First and second hash functions for indexing the cuckoo tables @@ -188,9 +188,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th 4) En passant target square (in algebraic notation). If there's no en passant target square, this is "-". If a pawn has just made a 2-square move, this - is the position "behind" the pawn. Following X-FEN standard, this is recorded only - if there is a pawn in position to make an en passant capture, and if there really - is a pawn that might have advanced two squares. + is the position "behind" the pawn. Following X-FEN standard, this is recorded + only if there is a pawn in position to make an en passant capture, and if + there really is a pawn that might have advanced two squares. 5) Halfmove clock. This is the number of halfmoves since the last pawn advance or capture. This is used to determine if a draw can be claimed under the @@ -587,8 +587,8 @@ bool Position::pseudo_legal(const Move m) const { return false; if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !( (from + 2 * pawn_push(us) == to) // Not a double push + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !( (from + 2 * pawn_push(us) == to) // Not a double push && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) @@ -959,7 +959,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ // Remove both pieces first since squares could overlap in Chess960 remove_piece(Do ? from : to); remove_piece(Do ? rfrom : rto); - board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us + board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // remove_piece does not do this for us put_piece(make_piece(us, KING), Do ? to : from); put_piece(make_piece(us, ROOK), Do ? rto : rfrom); } diff --git a/src/search.cpp b/src/search.cpp index 1b019a08..2d4a3f3d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -100,12 +100,12 @@ namespace { return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); } - // Skill structure is used to implement strength limit. - // If we have a UCI_Elo, we convert it to an appropriate skill level, anchored to the Stash engine. - // This method is based on a fit of the Elo results for games played between the master at various - // skill levels and various versions of the Stash engine, all ranked at CCRL. + // Skill structure is used to implement strength limit. If we have a UCI_Elo, + // we convert it to an appropriate skill level, anchored to the Stash engine. + // This method is based on a fit of the Elo results for games played between + // Stockfish at various skill levels and various versions of the Stash engine. // Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately - // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c20acedbfe17d618c3c384b339ec + // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 struct Skill { Skill(int skill_level, int uci_elo) { if (uci_elo) @@ -274,9 +274,9 @@ void MainThread::search() { void Thread::search() { - // Allocate stack with extra size to allow access from (ss-7) to (ss+2) - // (ss-7) is needed for update_continuation_histories(ss-1, ...) which accesses (ss-6) - // (ss+2) is needed for initialization of statScore and killers + // Allocate stack with extra size to allow access from (ss-7) to (ss+2): + // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6), + // (ss+2) is needed for initialization of statScore and killers. Stack stack[MAX_PLY+10], *ss = stack+7; Move pv[MAX_PLY+1]; Value alpha, beta, delta; @@ -478,8 +478,7 @@ void Thread::search() { double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; - // Cap used time in case of a single legal move for a better viewer experience in tournaments - // yielding correct scores and sufficiently fast moves. + // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); @@ -574,7 +573,8 @@ namespace { static_cast(thisThread)->check_time(); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) - if (PvNode && thisThread->selDepth < ss->ply + 1) + if ( PvNode + && thisThread->selDepth < ss->ply + 1) thisThread->selDepth = ss->ply + 1; if (!rootNode) @@ -640,7 +640,9 @@ namespace { update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) - if (prevSq != SQ_NONE && (ss-1)->moveCount <= 2 && !priorCapture) + if ( prevSq != SQ_NONE + && (ss-1)->moveCount <= 2 + && !priorCapture) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); } // Penalty for a quiet ttMove that fails low (~1 Elo) @@ -748,7 +750,9 @@ namespace { } // Use static evaluation difference to improve quiet move ordering (~4 Elo) - if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) + if ( is_ok((ss-1)->currentMove) + && !(ss-1)->inCheck + && !priorCapture) { int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1812, 1812); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; @@ -979,7 +983,8 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); - // Step 14. Pruning at shallow depth (~120 Elo). Depth conditions are important for mate finding. + // Step 14. Pruning at shallow depth (~120 Elo). + // Depth conditions are important for mate finding. if ( !rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) @@ -1052,7 +1057,6 @@ moves_loop: // When in check, search starts here && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search - /* && ttValue != VALUE_NONE Already implicit in the next condition */ && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) @@ -1130,8 +1134,7 @@ moves_loop: // When in check, search starts here // Step 16. Make the move pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV and not likely to fail low. (~3 Elo) - // Decrease further on cutNodes. (~1 Elo) + // Decrease reduction if position is or has been on the PV (~4 Elo) if ( ss->ttPv && !likelyFailLow) r -= cutNode && tte->depth() >= depth ? 3 : 2; @@ -1196,10 +1199,11 @@ moves_loop: // When in check, search starts here value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); // Do a full-depth search when reduced LMR search fails high - if (value > alpha && d < newDepth) + if ( value > alpha + && d < newDepth) { // Adjust full-depth search based on LMR results - if the result - // was good enough search deeper, if it was bad enough search shallower + // was good enough search deeper, if it was bad enough search shallower. const bool doDeeperSearch = value > (bestValue + 51 + 10 * (newDepth - d)); const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; @@ -1219,19 +1223,22 @@ moves_loop: // When in check, search starts here } } - // Step 18. Full-depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. + // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { // Increase reduction for cut nodes and not ttMove (~1 Elo) - if (!ttMove && cutNode) + if ( !ttMove + && cutNode) r += 2; + // Note that if expected reduction is high, we reduce search depth by 1 here value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, // otherwise let the parent node fail low with value <= alpha and try another move. - if (PvNode && (moveCount == 1 || value > alpha)) + if ( PvNode + && (moveCount == 1 || value > alpha)) { (ss+1)->pv = pv; (ss+1)->pv[0] = MOVE_NONE; @@ -1329,8 +1336,8 @@ moves_loop: // When in check, search starts here } } - - // If the move is worse than some previously searched move, remember it, to update its stats later + // If the move is worse than some previously searched move, + // remember it, to update its stats later. if (move != bestMove && moveCount <= 32) { if (capture) @@ -1341,14 +1348,6 @@ moves_loop: // When in check, search starts here } } - // The following condition would detect a stop only after move loop has been - // completed. But in this case, bestValue is valid because we have fully - // searched our subtree, and we can anyhow save the result in TT. - /* - if (Threads.stop) - return VALUE_DRAW; - */ - // Step 21. Check for mate and stalemate // All legal moves have been searched and if there are no legal moves, it // must be a mate or a stalemate. If we are in a singular extension search then @@ -1494,7 +1493,6 @@ moves_loop: // When in check, search starts here // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { - // Save gathered info in transposition table if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval); @@ -1539,8 +1537,9 @@ moves_loop: // When in check, search starts here moveCount++; - // Step 6. Pruning. - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) + // Step 6. Pruning + if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY + && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) if ( !givesCheck @@ -1554,7 +1553,7 @@ moves_loop: // When in check, search starts here futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; // If static eval + value of piece we are going to capture is much lower - // than alpha we can prune this move + // than alpha we can prune this move. if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); @@ -1562,15 +1561,16 @@ moves_loop: // When in check, search starts here } // If static eval is much lower than alpha and move is not winning material - // we can prune this move - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + // we can prune this move. + if ( futilityBase <= alpha + && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; } // If static exchange evaluation is much worse than what is needed to not - // fall below alpha we can prune this move + // fall below alpha we can prune this move. if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) { bestValue = alpha; @@ -1655,8 +1655,8 @@ moves_loop: // When in check, search starts here } - // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to - // "plies to mate from the current position". Standard scores are unchanged. + // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" + // to "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. Value value_to_tt(Value v, int ply) { @@ -1670,9 +1670,9 @@ moves_loop: // When in check, search starts here // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score // from the transposition table (which refers to the plies to mate/be mated from - // current position) to "plies to mate/be mated (TB win/loss) from the root". However, - // for mate scores, to avoid potentially false mate scores related to the 50 moves rule - // and the graph history interaction, we return an optimal TB score instead. + // current position) to "plies to mate/be mated (TB win/loss) from the root". + // However, to avoid potentially false mate scores related to the 50 moves rule + // and the graph history interaction problem, we return an optimal TB score instead. Value value_from_tt(Value v, int ply, int r50c) { From edb4ab924f09abd7c6836c7017365dceccd76b80 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 4 Oct 2023 18:14:40 +0300 Subject: [PATCH 433/678] Standardize Comments use double slashes (//) only for comments. closes #4820 No functional change. --- src/benchmark.cpp | 22 ++++---- src/bitboard.cpp | 12 ++--- src/bitboard.h | 66 +++++++++++------------ src/evaluate.cpp | 34 ++++++------ src/misc.cpp | 88 +++++++++++++++---------------- src/misc.h | 44 ++++++++-------- src/movegen.cpp | 18 +++---- src/movegen.h | 6 +-- src/movepick.cpp | 34 ++++++------ src/movepick.h | 60 ++++++++++----------- src/nnue/evaluate_nnue.cpp | 2 +- src/position.cpp | 104 ++++++++++++++++++------------------- src/position.h | 26 +++++----- src/search.cpp | 30 +++++------ src/search.h | 16 +++--- src/syzygy/tbprobe.cpp | 95 +++++++++++++++++---------------- src/thread.cpp | 40 +++++++------- src/thread.h | 16 +++--- src/thread_win32_osx.h | 10 ++-- src/timeman.cpp | 6 +-- src/timeman.h | 4 +- src/tt.cpp | 28 +++++----- src/tt.h | 30 +++++------ src/tune.h | 51 +++++++++--------- src/types.h | 74 +++++++++++++------------- src/uci.cpp | 42 +++++++-------- src/uci.h | 6 +-- src/ucioption.cpp | 20 +++---- 28 files changed, 491 insertions(+), 493 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 8e28184a..d67e37f6 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -95,17 +95,17 @@ const std::vector Defaults = { namespace Stockfish { -/// setup_bench() builds a list of UCI commands to be run by bench. There -/// are five parameters: TT size in MB, number of search threads that -/// should be used, the limit value spent for each position, a file name -/// where to look for positions in FEN format, and the type of the limit: -/// depth, perft, nodes and movetime (in milliseconds). Examples: -/// -/// bench : search default positions up to depth 13 -/// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB) -/// bench 64 1 100000 default nodes : search default positions for 100K nodes each -/// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec -/// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" +// setup_bench() builds a list of UCI commands to be run by bench. There +// are five parameters: TT size in MB, number of search threads that +// should be used, the limit value spent for each position, a file name +// where to look for positions in FEN format, and the type of the limit: +// depth, perft, nodes and movetime (in milliseconds). Examples: +// +// bench : search default positions up to depth 13 +// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB) +// bench 64 1 100000 default nodes : search default positions for 100K nodes each +// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec +// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" std::vector setup_bench(const Position& current, std::istream& is) { diff --git a/src/bitboard.cpp b/src/bitboard.cpp index bed2b3ee..89eeee61 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -46,8 +46,8 @@ namespace { } -/// safe_destination() returns the bitboard of target square for the given step -/// from the given square. If the step is off the board, returns empty bitboard. +// safe_destination() returns the bitboard of target square for the given step +// from the given square. If the step is off the board, returns empty bitboard. inline Bitboard safe_destination(Square s, int step) { Square to = Square(s + step); @@ -55,8 +55,8 @@ inline Bitboard safe_destination(Square s, int step) { } -/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable -/// to be printed to standard output. Useful for debugging. +// Bitboards::pretty() returns an ASCII representation of a bitboard suitable +// to be printed to standard output. Useful for debugging. std::string Bitboards::pretty(Bitboard b) { @@ -75,8 +75,8 @@ std::string Bitboards::pretty(Bitboard b) { } -/// Bitboards::init() initializes various bitboard tables. It is called at -/// startup and relies on global objects to be already zero-initialized. +// Bitboards::init() initializes various bitboard tables. It is called at +// startup and relies on global objects to be already zero-initialized. void Bitboards::init() { diff --git a/src/bitboard.h b/src/bitboard.h index eb2f949d..0908c957 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -64,7 +64,7 @@ extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; -/// Magic holds all magic bitboards relevant data for a single square +// Magic holds all magic bitboards relevant data for a single square struct Magic { Bitboard mask; Bitboard magic; @@ -95,8 +95,8 @@ inline Bitboard square_bb(Square s) { } -/// Overloads of bitwise operators between a Bitboard and a Square for testing -/// whether a given bit is set in a bitboard, and for setting and clearing bits. +// Overloads of bitwise operators between a Bitboard and a Square for testing +// whether a given bit is set in a bitboard, and for setting and clearing bits. inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); } inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); } @@ -115,8 +115,8 @@ constexpr bool more_than_one(Bitboard b) { } -/// rank_bb() and file_bb() return a bitboard representing all the squares on -/// the given file or rank. +// rank_bb() and file_bb() return a bitboard representing all the squares on +// the given file or rank. constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); @@ -135,7 +135,7 @@ constexpr Bitboard file_bb(Square s) { } -/// shift() moves a bitboard one or two steps as specified by the direction D +// shift() moves a bitboard one or two steps as specified by the direction D template constexpr Bitboard shift(Bitboard b) { @@ -148,8 +148,8 @@ constexpr Bitboard shift(Bitboard b) { } -/// pawn_attacks_bb() returns the squares attacked by pawns of the given color -/// from the squares in the given bitboard. +// pawn_attacks_bb() returns the squares attacked by pawns of the given color +// from the squares in the given bitboard. template constexpr Bitboard pawn_attacks_bb(Bitboard b) { @@ -163,10 +163,10 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { return PawnAttacks[c][s]; } -/// line_bb() returns a bitboard representing an entire line (from board edge -/// to board edge) that intersects the two given squares. If the given squares -/// are not on a same file/rank/diagonal, the function returns 0. For instance, -/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. +// line_bb() returns a bitboard representing an entire line (from board edge +// to board edge) that intersects the two given squares. If the given squares +// are not on a same file/rank/diagonal, the function returns 0. For instance, +// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. inline Bitboard line_bb(Square s1, Square s2) { @@ -176,13 +176,13 @@ inline Bitboard line_bb(Square s1, Square s2) { } -/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open -/// segment between the squares s1 and s2 (excluding s1 but including s2). If the -/// given squares are not on a same file/rank/diagonal, it returns s2. For instance, -/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but -/// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick -/// allows to generate non-king evasion moves faster: the defending piece must either -/// interpose itself to cover the check or capture the checking piece. +// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open +// segment between the squares s1 and s2 (excluding s1 but including s2). If the +// given squares are not on a same file/rank/diagonal, it returns s2. For instance, +// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but +// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick +// allows to generate non-king evasion moves faster: the defending piece must either +// interpose itself to cover the check or capture the checking piece. inline Bitboard between_bb(Square s1, Square s2) { @@ -191,16 +191,16 @@ inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } -/// aligned() returns true if the squares s1, s2 and s3 are aligned either on a -/// straight or on a diagonal line. +// aligned() returns true if the squares s1, s2 and s3 are aligned either on a +// straight or on a diagonal line. inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } -/// distance() functions return the distance between x and y, defined as the -/// number of steps for a king in x to reach y. +// distance() functions return the distance between x and y, defined as the +// number of steps for a king in x to reach y. template inline int distance(Square x, Square y); template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } @@ -209,8 +209,8 @@ template<> inline int distance(Square x, Square y) { return SquareDistan inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -/// attacks_bb(Square) returns the pseudo attacks of the given piece type -/// assuming an empty board. +// attacks_bb(Square) returns the pseudo attacks of the given piece type +// assuming an empty board. template inline Bitboard attacks_bb(Square s) { @@ -221,9 +221,9 @@ inline Bitboard attacks_bb(Square s) { } -/// attacks_bb(Square, Bitboard) returns the attacks by the given piece -/// assuming the board is occupied according to the passed Bitboard. -/// Sliding piece attacks do not continue passed an occupied square. +// attacks_bb(Square, Bitboard) returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. template inline Bitboard attacks_bb(Square s, Bitboard occupied) { @@ -253,7 +253,7 @@ inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { } -/// popcount() counts the number of non-zero bits in a bitboard +// popcount() counts the number of non-zero bits in a bitboard inline int popcount(Bitboard b) { @@ -274,7 +274,7 @@ inline int popcount(Bitboard b) { } -/// lsb() and msb() return the least/most significant bit in a non-zero bitboard +// lsb() and msb() return the least/most significant bit in a non-zero bitboard #if defined(__GNUC__) // GCC, Clang, ICX @@ -342,15 +342,15 @@ inline Square msb(Bitboard b) { #endif -/// least_significant_square_bb() returns the bitboard of the least significant -/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). +// least_significant_square_bb() returns the bitboard of the least significant +// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). inline Bitboard least_significant_square_bb(Bitboard b) { assert(b); return b & -b; } -/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard +// pop_lsb() finds and clears the least significant bit in a non-zero bitboard inline Square pop_lsb(Bitboard& b) { assert(b); diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 208e3ed5..3eb7ee85 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -57,13 +57,13 @@ namespace Eval { std::string currentEvalFileName = "None"; - /// NNUE::init() tries to load a NNUE network at startup time, or when the engine - /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" - /// The name of the NNUE network is always retrieved from the EvalFile option. - /// We search the given network in three locations: internally (the default - /// network may be embedded in the binary), in the active working directory and - /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY - /// variable to have the engine search in a special directory in their distro. + // NNUE::init() tries to load a NNUE network at startup time, or when the engine + // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" + // The name of the NNUE network is always retrieved from the EvalFile option. + // We search the given network in three locations: internally (the default + // network may be embedded in the binary), in the active working directory and + // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY + // variable to have the engine search in a special directory in their distro. void NNUE::init() { @@ -105,7 +105,7 @@ namespace Eval { } } - /// NNUE::verify() verifies that the last net used was loaded successfully + // NNUE::verify() verifies that the last net used was loaded successfully void NNUE::verify() { std::string eval_file = std::string(Options["EvalFile"]); @@ -135,9 +135,9 @@ namespace Eval { } -/// simple_eval() returns a static, purely materialistic evaluation of the position -/// from the point of view of the given color. It can be divided by PawnValue to get -/// an approximation of the material advantage on the board in terms of pawns. +// simple_eval() returns a static, purely materialistic evaluation of the position +// from the point of view of the given color. It can be divided by PawnValue to get +// an approximation of the material advantage on the board in terms of pawns. Value Eval::simple_eval(const Position& pos, Color c) { return PawnValue * (pos.count(c) - pos.count(~c)) @@ -145,8 +145,8 @@ Value Eval::simple_eval(const Position& pos, Color c) { } -/// evaluate() is the evaluator for the outer world. It returns a static evaluation -/// of the position from the point of view of the side to move. +// evaluate() is the evaluator for the outer world. It returns a static evaluation +// of the position from the point of view of the side to move. Value Eval::evaluate(const Position& pos) { @@ -189,10 +189,10 @@ Value Eval::evaluate(const Position& pos) { return v; } -/// trace() is like evaluate(), but instead of returning a value, it returns -/// a string (suitable for outputting to stdout) that contains the detailed -/// descriptions and values of each evaluation term. Useful for debugging. -/// Trace scores are from white's point of view +// trace() is like evaluate(), but instead of returning a value, it returns +// a string (suitable for outputting to stdout) that contains the detailed +// descriptions and values of each evaluation term. Useful for debugging. +// Trace scores are from white's point of view std::string Eval::trace(Position& pos) { diff --git a/src/misc.cpp b/src/misc.cpp index 2f6ffd28..5abdaf07 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -71,14 +71,14 @@ namespace Stockfish { namespace { -/// Version number or dev. +// Version number or dev. constexpr std::string_view version = "dev"; -/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and -/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We -/// can toggle the logging of std::cout and std:cin at runtime whilst preserving -/// usual I/O functionality, all without changing a single line of code! -/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 +// Our fancy logging facility. The trick here is to replace cin.rdbuf() and +// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We +// can toggle the logging of std::cout and std:cin at runtime whilst preserving +// usual I/O functionality, all without changing a single line of code! +// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout @@ -141,15 +141,15 @@ public: } // namespace -/// engine_info() returns the full name of the current Stockfish version. -/// For local dev compiles we try to append the commit sha and commit date -/// from git if that fails only the local compilation date is set and "nogit" is specified: -/// Stockfish dev-YYYYMMDD-SHA -/// or -/// Stockfish dev-YYYYMMDD-nogit -/// -/// For releases (non dev builds) we only include the version number: -/// Stockfish version +// engine_info() returns the full name of the current Stockfish version. +// For local dev compiles we try to append the commit sha and commit date +// from git if that fails only the local compilation date is set and "nogit" is specified: +// Stockfish dev-YYYYMMDD-SHA +// or +// Stockfish dev-YYYYMMDD-nogit +// +// For releases (non-dev builds) we only include the version number: +// Stockfish version std::string engine_info(bool to_uci) { std::stringstream ss; @@ -185,20 +185,20 @@ std::string engine_info(bool to_uci) { } -/// compiler_info() returns a string trying to describe the compiler we use +// compiler_info() returns a string trying to describe the compiler we use std::string compiler_info() { #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) -/// Predefined macros hell: -/// -/// __GNUC__ Compiler is GCC, Clang or ICX -/// __clang__ Compiler is Clang or ICX -/// __INTEL_LLVM_COMPILER Compiler is ICX -/// _MSC_VER Compiler is MSVC -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +// Predefined macros hell: +// +// __GNUC__ Compiler is GCC, Clang or ICX +// __clang__ Compiler is Clang or ICX +// __INTEL_LLVM_COMPILER Compiler is ICX +// _MSC_VER Compiler is MSVC +// _WIN32 Building on Windows (any) +// _WIN64 Building on Windows 64 bit std::string compiler = "\nCompiled by : "; @@ -305,7 +305,7 @@ std::string compiler_info() { } -/// Debug functions used mainly to collect run-time statistics +// Debug functions used mainly to collect run-time statistics constexpr int MaxDebugSlots = 32; namespace { @@ -397,8 +397,8 @@ void dbg_print() { } -/// Used to serialize access to std::cout to avoid multiple threads writing at -/// the same time. +// Used to serialize access to std::cout to avoid multiple threads writing at +// the same time. std::ostream& operator<<(std::ostream& os, SyncCout sc) { @@ -414,13 +414,13 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) { } -/// Trampoline helper to avoid moving Logger to misc.h +// Trampoline helper to avoid moving Logger to misc.h void start_logger(const std::string& fname) { Logger::start(fname); } -/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking -/// function that doesn't stall the CPU waiting for data to be loaded from memory, -/// which can be quite slow. +// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking +// function that doesn't stall the CPU waiting for data to be loaded from memory, +// which can be quite slow. #ifdef NO_PREFETCH void prefetch(void*) {} @@ -439,9 +439,9 @@ void prefetch(void* addr) { #endif -/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation -/// does not guarantee the availability of aligned_alloc(). Memory allocated with -/// std_aligned_alloc() must be freed with std_aligned_free(). +// std_aligned_alloc() is our wrapper for systems where the c++17 implementation +// does not guarantee the availability of aligned_alloc(). Memory allocated with +// std_aligned_alloc() must be freed with std_aligned_free(). void* std_aligned_alloc(size_t alignment, size_t size) { @@ -470,7 +470,7 @@ void std_aligned_free(void* ptr) { #endif } -/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. +// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. #if defined(_WIN32) @@ -550,7 +550,7 @@ void* aligned_large_pages_alloc(size_t allocSize) { // Try to allocate large pages void* mem = aligned_large_pages_alloc_windows(allocSize); - // Fall back to regular, page aligned, allocation if necessary + // Fall back to regular, page-aligned, allocation if necessary if (!mem) mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); @@ -579,7 +579,7 @@ void* aligned_large_pages_alloc(size_t allocSize) { #endif -/// aligned_large_pages_free() will free the previously allocated ttmem +// aligned_large_pages_free() will free the previously allocated ttmem #if defined(_WIN32) @@ -612,9 +612,9 @@ void bindThisThread(size_t) {} #else -/// best_node() retrieves logical processor information using Windows specific -/// API and returns the best node id for the thread with index idx. Original -/// code from Texel by Peter Österlund. +// best_node() retrieves logical processor information using Windows specific +// API and returns the best node id for the thread with index idx. Original +// code from Texel by Peter Österlund. static int best_node(size_t idx) { @@ -666,8 +666,8 @@ static int best_node(size_t idx) { std::vector groups; - // Run as many threads as possible on the same node until core limit is - // reached, then move on filling the next node. + // Run as many threads as possible on the same node until the core limit is + // reached, then move on to filling the next node. for (int n = 0; n < nodes; n++) for (int i = 0; i < cores / nodes; i++) groups.push_back(n); @@ -684,7 +684,7 @@ static int best_node(size_t idx) { } -/// bindThisThread() set the group affinity of the current thread +// bindThisThread() sets the group affinity of the current thread void bindThisThread(size_t idx) { @@ -751,7 +751,7 @@ void init([[maybe_unused]] int argc, char* argv[]) { pathSeparator = "\\"; #ifdef _MSC_VER // Under windows argv[0] may not have the extension. Also _get_pgmptr() had - // issues in some windows 10 versions, so check returned values carefully. + // issues in some Windows 10 versions, so check returned values carefully. char* pgmptr = nullptr; if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) argv0 = pgmptr; diff --git a/src/misc.h b/src/misc.h index 52595fb9..60602048 100644 --- a/src/misc.h +++ b/src/misc.h @@ -74,7 +74,7 @@ T* align_ptr_up(T* ptr) } -// IsLittleEndian : true if and only if the binary is compiled on a little endian machine +// IsLittleEndian : 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); @@ -95,20 +95,20 @@ private: }; -/// xorshift64star Pseudo-Random Number Generator -/// This class is based on original code written and dedicated -/// to the public domain by Sebastiano Vigna (2014). -/// It has the following characteristics: -/// -/// - Outputs 64-bit numbers -/// - Passes Dieharder and SmallCrush test batteries -/// - Does not require warm-up, no zeroland to escape -/// - Internal state is a single 64-bit integer -/// - Period is 2^64 - 1 -/// - Speed: 1.60 ns/call (Core i7 @3.40GHz) -/// -/// For further analysis see -/// +// xorshift64star Pseudo-Random Number Generator +// This class is based on original code written and dedicated +// to the public domain by Sebastiano Vigna (2014). +// It has the following characteristics: +// +// - Outputs 64-bit numbers +// - Passes Dieharder and SmallCrush test batteries +// - Does not require warm-up, no zeroland to escape +// - Internal state is a single 64-bit integer +// - Period is 2^64 - 1 +// - Speed: 1.60 ns/call (Core i7 @3.40GHz) +// +// For further analysis see +// class PRNG { @@ -125,8 +125,8 @@ public: template T rand() { return T(rand64()); } - /// Special generator used to fast init magic numbers. - /// Output values only have 1/8th of their bits set on average. + // Special generator used to fast init magic numbers. + // Output values only have 1/8th of their bits set on average. template T sparse_rand() { return T(rand64() & rand64() & rand64()); } }; @@ -145,11 +145,11 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #endif } -/// Under Windows it is not possible for a process to run on more than one -/// logical processor group. This usually means to be limited to use max 64 -/// cores. To overcome this, some special platform specific API should be -/// called to set group affinity for each thread. Original code from Texel by -/// Peter Österlund. +// Under Windows it is not possible for a process to run on more than one +// logical processor group. This usually means being limited to using max 64 +// cores. To overcome this, some special platform-specific API should be +// called to set group affinity for each thread. Original code from Texel by +// Peter Österlund. namespace WinProcGroup { void bindThisThread(size_t idx); diff --git a/src/movegen.cpp b/src/movegen.cpp index cda43b3a..82ad6061 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -234,14 +234,14 @@ namespace { } // namespace -/// Generates all pseudo-legal captures plus queen promotions -/// Generates all pseudo-legal non-captures and underpromotions -/// Generates all pseudo-legal check evasions -/// Generates all pseudo-legal captures and non-captures -/// Generates all pseudo-legal non-captures giving check, -/// except castling and promotions -/// -/// Returns a pointer to the end of the move list. +// Generates all pseudo-legal captures plus queen promotions +// Generates all pseudo-legal non-captures and underpromotions +// Generates all pseudo-legal check evasions +// Generates all pseudo-legal captures and non-captures +// Generates all pseudo-legal non-captures giving check, +// except castling and promotions +// +// Returns a pointer to the end of the move list. template ExtMove* generate(const Position& pos, ExtMove* moveList) { @@ -263,7 +263,7 @@ template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); -/// generate generates all the legal moves in the given position +// generate generates all the legal moves in the given position template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { diff --git a/src/movegen.h b/src/movegen.h index 5eee2f1a..e913a13e 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -56,9 +56,9 @@ inline bool operator<(const ExtMove& f, const ExtMove& s) { template ExtMove* generate(const Position& pos, ExtMove* moveList); -/// The MoveList struct wraps the generate() function and returns a convenient -/// list of moves. Using MoveList is sometimes preferable to directly calling -/// the lower level generate() function. +// The MoveList struct wraps the generate() function and returns a convenient +// list of moves. Using MoveList is sometimes preferable to directly calling +// the lower level generate() function. template struct MoveList { diff --git a/src/movepick.cpp b/src/movepick.cpp index bc3fcf7e..5bb0fd6c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -55,13 +55,13 @@ namespace { } // namespace -/// Constructors of the MovePicker class. As arguments, we pass information -/// to help it return the (presumably) good moves first, to decide which -/// moves to return (in the quiescence search, for instance, we only want to -/// search captures, promotions, and some checks) and how important a good -/// move ordering is at the current node. +// Constructors of the MovePicker class. As arguments, we pass information +// to help it return the (presumably) good moves first, to decide which +// moves to return (in the quiescence search, for instance, we only want to +// search captures, promotions, and some checks) and how important a good +// move ordering is at the current node. -/// MovePicker constructor for the main search +// MovePicker constructor for the main search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, @@ -76,7 +76,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist !(ttm && pos.pseudo_legal(ttm)); } -/// MovePicker constructor for quiescence search +// MovePicker constructor for quiescence search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, @@ -90,8 +90,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist && pos.pseudo_legal(ttm)); } -/// MovePicker constructor for ProbCut: we generate captures with SEE greater -/// than or equal to the given threshold. +// MovePicker constructor for ProbCut: we generate captures with SEE greater +// than or equal to the given threshold. MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) { @@ -102,9 +102,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece && pos.see_ge(ttm, threshold)); } -/// MovePicker::score() assigns a numerical value to each move in a list, used -/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring -/// captures with a good history. Quiets moves are ordered using the history tables. +// MovePicker::score() assigns a numerical value to each move in a list, used +// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring +// captures with a good history. Quiets moves are ordered using the history tables. template void MovePicker::score() { @@ -180,8 +180,8 @@ void MovePicker::score() { } } -/// MovePicker::select() returns the next move satisfying a predicate function. -/// It never returns the TT move. +// MovePicker::select() returns the next move satisfying a predicate function. +// It never returns the TT move. template Move MovePicker::select(Pred filter) { @@ -198,9 +198,9 @@ Move MovePicker::select(Pred filter) { return MOVE_NONE; } -/// MovePicker::next_move() is the most important method of the MovePicker class. It -/// returns a new pseudo-legal move every time it is called until there are no more -/// moves left, picking the move with the highest score from a list of generated moves. +// MovePicker::next_move() is the most important method of the MovePicker class. It +// returns a new pseudo-legal move every time it is called until there are no more +// moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { top: diff --git a/src/movepick.h b/src/movepick.h index dd9de0b2..652ef161 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -32,10 +32,10 @@ namespace Stockfish { class Position; -/// StatsEntry stores the stat table value. It is usually a number but could -/// be a move or even a nested history. We use a class instead of naked value -/// to directly call history update operator<<() on the entry so to use stats -/// tables at caller sites as simple multi-dim arrays. +// StatsEntry stores the stat table value. It is usually a number but could +// be a move or even a nested history. We use a class instead of a naked value +// to directly call history update operator<<() on the entry so to use stats +// tables at caller sites as simple multi-dim arrays. template class StatsEntry { @@ -57,11 +57,11 @@ public: } }; -/// Stats is a generic N-dimensional array used to store various statistics. -/// The first template parameter T is the base type of the array, the second -/// template parameter D limits the range of updates in [-D, D] when we update -/// values with the << operator, while the last parameters (Size and Sizes) -/// encode the dimensions of the array. +// Stats is a generic N-dimensional array used to store various statistics. +// The first template parameter T is the base type of the array, and the second +// template parameter D limits the range of updates in [-D, D] when we update +// values with the << operator, while the last parameters (Size and Sizes) +// encode the dimensions of the array. template struct Stats : public std::array, Size> { @@ -69,7 +69,7 @@ struct Stats : public std::array, Size> void fill(const T& v) { - // For standard-layout 'this' points to first struct member + // For standard-layout 'this' points to the first struct member assert(std::is_standard_layout_v); using entry = StatsEntry; @@ -81,40 +81,40 @@ struct Stats : public std::array, Size> template struct Stats : public std::array, Size> {}; -/// In stats table, D=0 means that the template parameter is not used +// In stats table, D=0 means that the template parameter is not used enum StatsParams { NOT_USED = 0 }; enum StatsType { NoCaptures, Captures }; -/// ButterflyHistory records how often quiet moves have been successful or -/// unsuccessful during the current search, and is used for reduction and move -/// ordering decisions. It uses 2 tables (one for each color) indexed by -/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards -/// (~11 elo) +// ButterflyHistory records how often quiet moves have been successful or +// unsuccessful during the current search, and is used for reduction and move +// ordering decisions. It uses 2 tables (one for each color) indexed by +// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards +// (~11 elo) using ButterflyHistory = Stats; -/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous -/// move, see www.chessprogramming.org/Countermove_Heuristic +// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous +// move, see www.chessprogramming.org/Countermove_Heuristic using CounterMoveHistory = Stats; -/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] +// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; -/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] +// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] using PieceToHistory = Stats; -/// ContinuationHistory is the combined history of a given pair of moves, usually -/// the current one given a previous one. The nested history table is based on -/// PieceToHistory instead of ButterflyBoards. -/// (~63 elo) +// ContinuationHistory is the combined history of a given pair of moves, usually +// the current one given a previous one. The nested history table is based on +// PieceToHistory instead of ButterflyBoards. +// (~63 elo) using ContinuationHistory = Stats; -/// MovePicker class is used to pick one pseudo-legal move at a time from the -/// current position. The most important method is next_move(), which returns a -/// new pseudo-legal move each time it is called, until there are no moves left, -/// when MOVE_NONE is returned. In order to improve the efficiency of the -/// alpha-beta algorithm, MovePicker attempts to return the moves which are most -/// likely to get a cut-off first. +// MovePicker class is used to pick one pseudo-legal move at a time from the +// current position. The most important method is next_move(), which returns a +// new pseudo-legal move each time it is called, until there are no moves left, +// when MOVE_NONE is returned. In order to improve the efficiency of the +// alpha-beta algorithm, MovePicker attempts to return the moves which are most +// likely to get a cut-off first. class MovePicker { enum PickType { Next, Best }; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index e1fa3b81..1f821cf9 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -375,7 +375,7 @@ namespace Stockfish::Eval::NNUE { return write_parameters(stream); } - /// Save eval, to a file given by its name + // Save eval, to a file given by its name bool save_eval(const std::optional& filename) { std::string actualFilename; diff --git a/src/position.cpp b/src/position.cpp index 0d7d9571..ada371eb 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -61,7 +61,7 @@ constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING } // namespace -/// operator<<(Position) returns an ASCII representation of the position +// operator<<(Position) returns an ASCII representation of the position std::ostream& operator<<(std::ostream& os, const Position& pos) { @@ -116,7 +116,7 @@ Key cuckoo[8192]; Move cuckooMove[8192]; -/// Position::init() initializes at startup the various arrays used to compute hash keys +// Position::init() initializes at startup the various arrays used to compute hash keys void Position::init() { @@ -160,9 +160,9 @@ void Position::init() { } -/// Position::set() initializes the position object with the given FEN string. -/// This function is not very robust - make sure that input FENs are correct, -/// this is assumed to be the responsibility of the GUI. +// Position::set() initializes the position object with the given FEN string. +// This function is not very robust - make sure that input FENs are correct, +// this is assumed to be the responsibility of the GUI. Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { /* @@ -297,8 +297,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th } -/// Position::set_castling_right() is a helper function used to set castling -/// rights given the corresponding color and the rook starting square. +// Position::set_castling_right() is a helper function used to set castling +// rights given the corresponding color and the rook starting square. void Position::set_castling_right(Color c, Square rfrom) { @@ -318,7 +318,7 @@ void Position::set_castling_right(Color c, Square rfrom) { } -/// Position::set_check_info() sets king attacks to detect if a move gives check +// Position::set_check_info() sets king attacks to detect if a move gives check void Position::set_check_info() const { @@ -336,9 +336,9 @@ void Position::set_check_info() const { } -/// Position::set_state() computes the hash keys of the position, and other -/// data that once computed is updated incrementally as moves are made. -/// The function is only used when a new position is set up +// Position::set_state() computes the hash keys of the position, and other +// data that once computed is updated incrementally as moves are made. +// The function is only used when a new position is set up void Position::set_state() const { @@ -372,9 +372,9 @@ void Position::set_state() const { } -/// Position::set() is an overload to initialize the position object with -/// the given endgame code string like "KBPKN". It is mainly a helper to -/// get the material key out of an endgame code. +// Position::set() is an overload to initialize the position object with +// the given endgame code string like "KBPKN". It is mainly a helper to +// get the material key out of an endgame code. Position& Position::set(const string& code, Color c, StateInfo* si) { @@ -395,8 +395,8 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { } -/// Position::fen() returns a FEN representation of the position. In case of -/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. +// Position::fen() returns a FEN representation of the position. In case of +// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. string Position::fen() const { @@ -444,9 +444,9 @@ string Position::fen() const { return ss.str(); } -/// update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], -/// which store respectively the pieces preventing king of color c from being in check -/// and the slider pieces of color ~c pinning pieces of color c to the king. +// update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], +// which store respectively the pieces preventing king of color c from being in check +// and the slider pieces of color ~c pinning pieces of color c to the king. void Position::update_slider_blockers(Color c) const { Square ksq = square(c); @@ -474,8 +474,8 @@ void Position::update_slider_blockers(Color c) const { } -/// Position::attackers_to() computes a bitboard of all pieces which attack a -/// given square. Slider attacks use the occupied bitboard to indicate occupancy. +// Position::attackers_to() computes a bitboard of all pieces which attack a +// given square. Slider attacks use the occupied bitboard to indicate occupancy. Bitboard Position::attackers_to(Square s, Bitboard occupied) const { @@ -488,7 +488,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { } -/// Position::legal() tests whether a pseudo-legal move is legal +// Position::legal() tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { @@ -532,7 +532,7 @@ bool Position::legal(Move m) const { if (attackers_to(s) & pieces(~us)) return false; - // In case of Chess960, verify if the Rook blocks some checks + // In case of Chess960, verify if the Rook blocks some checks. // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. return !chess960 || !(blockers_for_king(us) & to_sq(m)); } @@ -549,9 +549,9 @@ bool Position::legal(Move m) const { } -/// Position::pseudo_legal() takes a random move and tests whether the move is -/// pseudo-legal. It is used to validate moves from TT that can be corrupted -/// due to SMP concurrent access or hash position key aliasing. +// Position::pseudo_legal() takes a random move and tests whether the move is +// pseudo-legal. It is used to validate moves from TT that can be corrupted +// due to SMP concurrent access or hash position key aliasing. bool Position::pseudo_legal(const Move m) const { @@ -622,7 +622,7 @@ bool Position::pseudo_legal(const Move m) const { } -/// Position::gives_check() tests whether a pseudo-legal move gives a check +// Position::gives_check() tests whether a pseudo-legal move gives a check bool Position::gives_check(Move m) const { @@ -672,9 +672,9 @@ bool Position::gives_check(Move m) const { } -/// Position::do_move() makes a move, and saves all information necessary -/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal -/// moves should be filtered out before this function is called. +// Position::do_move() makes a move, and saves all information necessary +// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +// moves should be filtered out before this function is called. void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { @@ -870,8 +870,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } -/// Position::undo_move() unmakes a move. When it returns, the position should -/// be restored to exactly the same state as before the move was made. +// Position::undo_move() unmakes a move. When it returns, the position should +// be restored to exactly the same state as before the move was made. void Position::undo_move(Move m) { @@ -934,8 +934,8 @@ void Position::undo_move(Move m) { } -/// Position::do_castling() is a helper used to do/undo a castling move. This -/// is a bit tricky in Chess960 where from/to squares can overlap. +// Position::do_castling() is a helper used to do/undo a castling move. This +// is a bit tricky in Chess960 where from/to squares can overlap. template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { @@ -965,8 +965,8 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ } -/// Position::do_null_move() is used to do a "null move": it flips -/// the side to move without executing any move on the board. +// Position::do_null_move() is used to do a "null move": it flips +// the side to move without executing any move on the board. void Position::do_null_move(StateInfo& newSt) { @@ -1005,7 +1005,7 @@ void Position::do_null_move(StateInfo& newSt) { } -/// Position::undo_null_move() must be used to undo a "null move" +// Position::undo_null_move() must be used to undo a "null move" void Position::undo_null_move() { @@ -1016,9 +1016,9 @@ void Position::undo_null_move() { } -/// Position::key_after() computes the new hash key after the given move. Needed -/// for speculative prefetch. It doesn't recognize special moves like castling, -/// en passant and promotions. +// Position::key_after() computes the new hash key after the given move. Needed +// for speculative prefetch. It doesn't recognize special moves like castling, +// en passant and promotions. Key Position::key_after(Move m) const { @@ -1038,9 +1038,9 @@ Key Position::key_after(Move m) const { } -/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the -/// SEE value of move is greater or equal to the given threshold. We'll use an -/// algorithm similar to alpha-beta pruning with a null window. +// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the +// SEE value of move is greater or equal to the given threshold. We'll use an +// algorithm similar to alpha-beta pruning with a null window. bool Position::see_ge(Move m, Value threshold) const { @@ -1143,8 +1143,8 @@ bool Position::see_ge(Move m, Value threshold) const { return bool(res); } -/// Position::is_draw() tests whether the position is drawn by 50-move rule -/// or by repetition. It does not detect stalemates. +// Position::is_draw() tests whether the position is drawn by 50-move rule +// or by repetition. It does not detect stalemates. bool Position::is_draw(int ply) const { @@ -1175,8 +1175,8 @@ bool Position::has_repeated() const { } -/// Position::has_game_cycle() tests if the position has a move which draws by repetition, -/// or an earlier position has a move that directly reaches the current position. +// Position::has_game_cycle() tests if the position has a move which draws by repetition, +// or an earlier position has a move that directly reaches the current position. bool Position::has_game_cycle(int ply) const { @@ -1224,8 +1224,8 @@ bool Position::has_game_cycle(int ply) const { } -/// Position::flip() flips position with the white and black sides reversed. This -/// is only useful for debugging e.g. for finding evaluation symmetry bugs. +// Position::flip() flips position with the white and black sides reversed. This +// is only useful for debugging e.g. for finding evaluation symmetry bugs. void Position::flip() { @@ -1259,9 +1259,9 @@ void Position::flip() { } -/// Position::pos_is_ok() performs some consistency checks for the -/// position object and raise an assert if something wrong is detected. -/// This is meant to be helpful when debugging. +// Position::pos_is_ok() performs some consistency checks for the +// position object and raise an assert if something wrong is detected. +// This is meant to be helpful when debugging. bool Position::pos_is_ok() const { diff --git a/src/position.h b/src/position.h index aae4db94..23fd5bf5 100644 --- a/src/position.h +++ b/src/position.h @@ -31,9 +31,9 @@ namespace Stockfish { -/// StateInfo struct stores information needed to restore a Position object to -/// its previous state when we retract a move. Whenever a move is made on the -/// board (by calling Position::do_move), a StateInfo object must be passed. +// StateInfo struct stores information needed to restore a Position object to +// its previous state when we retract a move. Whenever a move is made on the +// board (by calling Position::do_move), a StateInfo object must be passed. struct StateInfo { @@ -61,17 +61,17 @@ struct StateInfo { }; -/// A list to keep track of the position states along the setup moves (from the -/// start position to the position just before the search starts). Needed by -/// 'draw by repetition' detection. Use a std::deque because pointers to -/// elements are not invalidated upon list resizing. +// A list to keep track of the position states along the setup moves (from the +// start position to the position just before the search starts). Needed by +// 'draw by repetition' detection. Use a std::deque because pointers to +// elements are not invalidated upon list resizing. using StateListPtr = std::unique_ptr>; -/// Position class stores information regarding the board representation as -/// pieces, side to move, hash keys, castling info, etc. Important methods are -/// do_move() and undo_move(), used by the search to update node info when -/// traversing the search tree. +// Position class stores information regarding the board representation as +// pieces, side to move, hash keys, castling info, etc. Important methods are +// do_move() and undo_move(), used by the search to update node info when +// traversing the search tree. class Thread; class Position { @@ -342,8 +342,8 @@ inline bool Position::capture(Move m) const { || type_of(m) == EN_PASSANT; } -// returns true if a move is generated from the capture stage -// having also queen promotions covered, i.e. consistency with the capture stage move generation +// Returns true if a move is generated from the capture stage, having also +// queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { assert(is_ok(m)); diff --git a/src/search.cpp b/src/search.cpp index 2d4a3f3d..16c6b0f3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -169,7 +169,7 @@ namespace { } // namespace -/// Search::init() is called at startup to initialize various lookup tables +// Search::init() is called at startup to initialize various lookup tables void Search::init() { @@ -178,7 +178,7 @@ void Search::init() { } -/// Search::clear() resets search state to its initial value +// Search::clear() resets search state to its initial value void Search::clear() { @@ -191,8 +191,8 @@ void Search::clear() { } -/// MainThread::search() is started when the program receives the UCI 'go' -/// command. It searches from the root position and outputs the "bestmove". +// MainThread::search() is started when the program receives the UCI 'go' +// command. It searches from the root position and outputs the "bestmove". void MainThread::search() { @@ -268,9 +268,9 @@ void MainThread::search() { } -/// Thread::search() is the main iterative deepening loop. It calls search() -/// repeatedly with increasing depth until the allocated thinking time has been -/// consumed, the user stops the search, or the maximum search depth is reached. +// Thread::search() is the main iterative deepening loop. It calls search() +// repeatedly with increasing depth until the allocated thinking time has been +// consumed, the user stops the search, or the maximum search depth is reached. void Thread::search() { @@ -1837,8 +1837,8 @@ moves_loop: // When in check, search starts here } // namespace -/// MainThread::check_time() is used to print debug info and, more importantly, -/// to detect when we are out of available time and thus stop the search. +// MainThread::check_time() is used to print debug info and, more importantly, +// to detect when we are out of available time and thus stop the search. void MainThread::check_time() { @@ -1870,8 +1870,8 @@ void MainThread::check_time() { } -/// UCI::pv() formats PV information according to the UCI protocol. UCI requires -/// that all (if any) unsearched PV lines are sent using a previous search score. +// UCI::pv() formats PV information according to the UCI protocol. UCI requires +// that all (if any) unsearched PV lines are sent using a previous search score. string UCI::pv(const Position& pos, Depth depth) { @@ -1929,10 +1929,10 @@ string UCI::pv(const Position& pos, Depth depth) { } -/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move -/// before exiting the search, for instance, in case we stop the search during a -/// fail high at root. We try hard to have a ponder move to return to the GUI, -/// otherwise in case of 'ponder on' we have nothing to think about. +// RootMove::extract_ponder_from_tt() is called in case we have no ponder move +// before exiting the search, for instance, in case we stop the search during a +// fail high at root. We try hard to have a ponder move to return to the GUI, +// otherwise in case of 'ponder on' we have nothing to think about. bool RootMove::extract_ponder_from_tt(Position& pos) { diff --git a/src/search.h b/src/search.h index c6dbffce..c434ba75 100644 --- a/src/search.h +++ b/src/search.h @@ -33,9 +33,9 @@ class Position; namespace Search { -/// Stack struct keeps track of the information we need to remember from nodes -/// shallower and deeper in the tree during the search. Each search thread has -/// its own array of Stack objects, indexed by the current ply. +// Stack struct keeps track of the information we need to remember from nodes +// shallower and deeper in the tree during the search. Each search thread has +// its own array of Stack objects, indexed by the current ply. struct Stack { Move* pv; @@ -55,9 +55,9 @@ struct Stack { }; -/// RootMove struct is used for moves at the root of the tree. For each root move -/// we store a score and a PV (really a refutation in the case of moves which -/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. +// RootMove struct is used for moves at the root of the tree. For each root move +// we store a score and a PV (really a refutation in the case of moves which +// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. struct RootMove { @@ -84,8 +84,8 @@ struct RootMove { using RootMoves = std::vector; -/// LimitsType struct stores information sent by GUI about available time to -/// search the current move, maximum depth/time, or if we are in analysis mode. +// LimitsType struct stores information sent by GUI about available time to +// search the current move, maximum depth/time, or if we are in analysis mode. struct LimitsType { diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index ffe29ce1..4114db60 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -139,7 +139,7 @@ template int sign_of(T val) { return (T(0) < val) - (val < T(0)); } -// Numbers in little endian used by sparseIndex[] to point into blockLength[] +// Numbers in little-endian used by sparseIndex[] to point into blockLength[] struct SparseEntry { char block[4]; // Number of block char offset[2]; // Offset within the block @@ -153,7 +153,7 @@ struct LR { enum Side { Left, Right }; uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 - // bits is the right-hand symbol. If symbol has length 1, + // bits is the right-hand symbol. If the symbol has length 1, // then the left-hand symbol is the stored value. template Sym get() { @@ -301,9 +301,9 @@ public: std::string TBFile::Paths; -// struct PairsData contains low level indexing information to access TB data. -// There are 8, 4 or 2 PairsData records for each TBTable, according to type of -// table and if positions have pawns or not. It is populated at first access. +// struct PairsData contains low-level indexing information to access TB data. +// There are 8, 4, or 2 PairsData records for each TBTable, according to the type +// of table and if positions have pawns or not. It is populated at first access. struct PairsData { uint8_t flags; // Table flags, see enum TBFlag uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols @@ -379,7 +379,7 @@ TBTable::TBTable(const std::string& code) : TBTable() { hasUniquePieces = true; // Set the leading color. In case both sides have pawns the leading color - // is the side with less pawns because this leads to better compression. + // is the side with fewer pawns because this leads to better compression. bool c = !pos.count(BLACK) || ( pos.count(WHITE) && pos.count(BLACK) >= pos.count(WHITE)); @@ -404,7 +404,7 @@ TBTable::TBTable(const TBTable& wdl) : TBTable() { } // class TBTables creates and keeps ownership of the TBTable objects, one for -// each TB file found. It supports a fast, hash based, table lookup. Populated +// each TB file found. It supports a fast, hash-based, table lookup. Populated // at init time, accessed at probe time. class TBTables { @@ -511,9 +511,9 @@ void TBTables::add(const std::vector& pieces) { // mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so // in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. // The generator picks the size that leads to the smallest table. The "book" of symbols and -// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file +// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file // will have one table for wtm and one for btm, a TB file with pawns will have tables per -// file a,b,c,d also in this case one set for wtm and one for btm. +// file a,b,c,d also, in this case, one set for wtm and one for btm. int decompress_pairs(PairsData* d, uint64_t idx) { // Special case where all table positions store the same value @@ -541,7 +541,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { uint32_t block = number(&d->sparseIndex[k].block); int offset = number(&d->sparseIndex[k].offset); - // Now compute the difference idx - I(k). From definition of k we know that + // Now compute the difference idx - I(k). From the definition of k, we know that // // idx = k * d->span + idx % d->span (2) // @@ -551,7 +551,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // Sum the above to offset to find the offset corresponding to our idx offset += diff; - // Move to previous/next block, until we reach the correct block that contains idx, + // Move to the previous/next block, until we reach the correct block that contains idx, // that is when 0 <= offset <= d->blockLength[block] while (offset < 0) offset += d->blockLength[--block] + 1; @@ -564,7 +564,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // Read the first 64 bits in our block, this is a (truncated) sequence of // unknown number of symbols of unknown length but we know the first one - // is at the beginning of this 64 bits sequence. + // is at the beginning of this 64-bit sequence. uint64_t buf64 = number(ptr); ptr += 2; int buf64Size = 64; Sym sym; @@ -587,8 +587,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // Now add the value of the lowest symbol of length len to get our symbol sym += number(&d->lowestSym[len]); - // If our offset is within the number of values represented by symbol sym - // we are done... + // If our offset is within the number of values represented by symbol sym, + // we are done. if (offset < d->symlen[sym] + 1) break; @@ -604,7 +604,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { } } - // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols. + // Now we have our symbol that expands into d->symlen[sym] + 1 symbols. // We binary-search for our value recursively expanding into the left and // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 // that will store the value we need. @@ -614,7 +614,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then - // we know that, for instance the ten-th value (offset = 10) will be on + // we know that, for instance, the tenth value (offset = 10) will be on // the left side because in Recursive Pairing child symbols are adjacent. if (offset < d->symlen[left] + 1) sym = left; @@ -639,7 +639,7 @@ bool check_dtz_stm(TBTable* entry, int stm, File f) { // DTZ scores are sorted by frequency of occurrence and then assigned the // values 0, 1, 2, ... in order of decreasing frequency. This is done for each // of the four WDLScore values. The mapping information necessary to reconstruct -// the original values is stored in the TB file and read during map[] init. +// the original values are stored in the TB file and read during map[] init. WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(value - 2); } int map_score(TBTable* entry, File f, int value, WDLScore wdl) { @@ -658,7 +658,7 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { } // DTZ tables store distance to zero in number of moves or plies. We - // want to return plies, so we have convert to plies when needed. + // want to return plies, so we have to convert to plies when needed. if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin @@ -669,7 +669,7 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { } // Compute a unique index out of a position and use it to probe the TB file. To -// encode k pieces of same type and color, first sort the pieces by square in +// encode k pieces of the same type and color, first sort the pieces by square in // ascending order s1 <= s2 <= ... <= sk then compute the unique index as: // // idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] @@ -687,13 +687,13 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. // If both sides have the same pieces keys are equal. In this case TB tables - // only store the 'white to move' case, so if the position to lookup has black + // only stores the 'white to move' case, so if the position to lookup has black // to move, we need to switch the color and flip the squares before to lookup. bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); - // TB files are calculated for white as stronger side. For instance we have - // KRvK, not KvKR. A position where stronger side is white will have its - // material key == entry->key, otherwise we have to switch the color and + // TB files are calculated for white as the stronger side. For instance, we + // have KRvK, not KvKR. A position where the stronger side is white will have + // its material key == entry->key, otherwise we have to switch the color and // flip the squares before to lookup. bool blackStronger = (pos.material_key() != entry->key); @@ -816,7 +816,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be // swapped and still get the same position.) // - // In case we have at least 3 unique pieces (included kings) we encode them + // In case we have at least 3 unique pieces (including kings) we encode them // together. if (entry->hasUniquePieces) { @@ -861,7 +861,7 @@ encode_remaining: idx *= d->groupIdx[0]; Square* groupSq = squares + d->groupLen[0]; - // Encode remaining pawns then pieces according to square, in ascending order + // Encode remaining pawns and then pieces according to square, in ascending order bool remainingPawns = entry->hasPawns && entry->pawnCount[1]; while (d->groupLen[++next]) @@ -870,7 +870,7 @@ encode_remaining: uint64_t n = 0; // Map down a square if "comes later" than a square in the previous - // groups (similar to what done earlier for leading group pieces). + // groups (similar to what was done earlier for leading group pieces). for (int i = 0; i < d->groupLen[next]; ++i) { auto f = [&](Square s) { return groupSq[i] > s; }; @@ -888,7 +888,7 @@ encode_remaining: } // Group together pieces that will be encoded together. The general rule is that -// a group contains pieces of same type and color. The exception is the leading +// a group contains pieces of the same type and color. The exception is the leading // group that, in case of positions without pawns, can be formed by 3 different // pieces (default) or by the king pair when there is not a unique piece apart // from the kings. When there are pawns, pawns are always first in pieces[]. @@ -953,7 +953,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) { // In Recursive Pairing each symbol represents a pair of children symbols. So // read d->btree[] symbols data and expand each one in his left and right child -// symbol until reaching the leafs that represent the symbol value. +// symbol until reaching the leaves that represent the symbol value. uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { visited[s] = true; // We can set it now because tree is acyclic @@ -1002,7 +1002,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // See https://en.wikipedia.org/wiki/Huffman_coding // The canonical code is ordered such that longer symbols (in terms of - // the number of bits of their Huffman code) have lower numeric value, + // the number of bits of their Huffman code) have a lower numeric value, // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. @@ -1072,7 +1072,7 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { return data += uintptr_t(data) & 1; // Word alignment } -// Populate entry's PairsData records with data from the just memory mapped file. +// Populate entry's PairsData records with data from the just memory-mapped file. // Called at first access. template void set(T& e, uint8_t* data) { @@ -1138,9 +1138,9 @@ void set(T& e, uint8_t* data) { } } -// If the TB file corresponding to the given position is already memory mapped -// then return its base address, otherwise try to memory map and init it. Called -// at every probe, memory map and init only at first access. Function is thread +// If the TB file corresponding to the given position is already memory-mapped +// then return its base address, otherwise, try to memory map and init it. Called +// at every probe, memory map, and init only at first access. Function is thread // safe and can be called concurrently. template void* mapped(TBTable& e, const Position& pos) { @@ -1191,7 +1191,7 @@ Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) } // For a position where the side to move has a winning capture it is not necessary -// to store a winning value so the generator treats such positions as "don't cares" +// to store a winning value so the generator treats such positions as "don't care" // and tries to assign to it a value that improves the compression ratio. Similarly, // if the side to move has a drawing capture, then the position is at least drawn. // If the position is won, then the TB needs to store a win value. But if the @@ -1200,7 +1200,7 @@ Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) // their results and must probe the position itself. The "best" result of these // probes is the correct result for the position. // DTZ tables do not store values when a following move is a zeroing winning move -// (winning capture or winning pawn move). Also DTZ store wrong values for positions +// (winning capture or winning pawn move). Also, DTZ store wrong values for positions // where the best move is an ep-move (even if losing). So in all these cases set // the state to ZEROING_BEST_MOVE. template @@ -1268,9 +1268,9 @@ WDLScore search(Position& pos, ProbeState* result) { } // namespace -/// Tablebases::init() is called at startup and after every change to -/// "SyzygyPath" UCI option to (re)create the various tables. It is not thread -/// safe, nor it needs to be. +// Tablebases::init() is called at startup and after every change to +// "SyzygyPath" UCI option to (re)create the various tables. It is not thread +// safe, nor it needs to be. void Tablebases::init(const std::string& paths) { TBTables.clear(); @@ -1302,7 +1302,7 @@ void Tablebases::init(const std::string& paths) { // MapKK[] encodes all the 462 possible legal positions of two kings where // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 - // diagonal, the other one shall not to be above the a1-h8 diagonal. + // diagonal, the other one shall not be above the a1-h8 diagonal. std::vector> bothOnDiagonal; code = 0; for (int idx = 0; idx < 10; idx++) @@ -1323,7 +1323,7 @@ void Tablebases::init(const std::string& paths) { MapKK[idx][s2] = code++; } - // Legal positions with both kings on diagonal are encoded as last ones + // Legal positions with both kings on a diagonal are encoded as last ones for (auto p : bothOnDiagonal) MapKK[p.first][p.second] = code++; @@ -1338,8 +1338,8 @@ void Tablebases::init(const std::string& paths) { // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible // available squares when the leading one is in 's'. Moreover the pawn with - // highest MapPawns[] is the leading pawn, the one nearest the edge and, - // among pawns with same file, the one with lowest rank. + // highest MapPawns[] is the leading pawn, the one nearest the edge, and + // among pawns with the same file, the one with the lowest rank. int availableSquares = 47; // Available squares when lead pawn is in a2 // Init the tables for the encoding of leading pawns group: with 7-men TB we @@ -1463,7 +1463,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws return 0; - // DTZ stores a 'don't care' value in this case, or even a plain wrong + // DTZ stores a 'don't care value in this case, or even a plain wrong // one as in case the best move is a losing ep, so it cannot be probed. if (*result == ZEROING_BEST_MOVE) return dtz_before_zeroing(wdl); @@ -1490,7 +1490,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // For zeroing moves we want the dtz of the move _before_ doing it, // otherwise we will get the dtz of the next move sequence. Search the // position after the move to get the score sign (because even in a - // winning position we could make a losing capture or going for a draw). + // winning position we could make a losing capture or go for a draw). dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) : -probe_dtz(pos, result); @@ -1548,10 +1548,9 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { } else if (pos.is_draw(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, this must be a true 3-fold - // repetition inside the game history. + // 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, + // this must be a true 3-fold repetition inside the game history. dtz = 0; } else diff --git a/src/thread.cpp b/src/thread.cpp index 60f760ed..c752e732 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -40,8 +40,8 @@ namespace Stockfish { ThreadPool Threads; // Global object -/// Thread constructor launches the thread and waits until it goes to sleep -/// in idle_loop(). Note that 'searching' and 'exit' should be already set. +// Thread constructor launches the thread and waits until it goes to sleep +// in idle_loop(). Note that 'searching' and 'exit' should be already set. Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { @@ -49,8 +49,8 @@ Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { } -/// Thread destructor wakes up the thread in idle_loop() and waits -/// for its termination. Thread should be already waiting. +// Thread destructor wakes up the thread in idle_loop() and waits +// for its termination. Thread should be already waiting. Thread::~Thread() { @@ -62,7 +62,7 @@ Thread::~Thread() { } -/// Thread::clear() reset histories, usually before a new game +// Thread::clear() reset histories, usually before a new game void Thread::clear() { @@ -78,7 +78,7 @@ void Thread::clear() { } -/// Thread::start_searching() wakes up the thread that will start the search +// Thread::start_searching() wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); @@ -88,8 +88,8 @@ void Thread::start_searching() { } -/// Thread::wait_for_search_finished() blocks on the condition variable -/// until the thread has finished searching. +// Thread::wait_for_search_finished() blocks on the condition variable +// until the thread has finished searching. void Thread::wait_for_search_finished() { @@ -98,15 +98,15 @@ void Thread::wait_for_search_finished() { } -/// Thread::idle_loop() is where the thread is parked, blocked on the -/// condition variable, when it has no work to do. +// Thread::idle_loop() is where the thread is parked, blocked on the +// condition variable, when it has no work to do. void Thread::idle_loop() { // If OS already scheduled us on a different group than 0 then don't overwrite // the choice, eventually we are one of many one-threaded processes running on // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case all this + // just check if running threads are below a threshold, in this case, all this // NUMA machinery is not needed. if (Options["Threads"] > 8) WinProcGroup::bindThisThread(idx); @@ -127,9 +127,9 @@ void Thread::idle_loop() { } } -/// ThreadPool::set() creates/destroys threads to match the requested number. -/// Created and launched threads will immediately go to sleep in idle_loop. -/// Upon resizing, threads are recreated to allow for binding if necessary. +// ThreadPool::set() creates/destroys threads to match the requested number. +// Created and launched threads will immediately go to sleep in idle_loop. +// Upon resizing, threads are recreated to allow for binding if necessary. void ThreadPool::set(size_t requested) { @@ -158,7 +158,7 @@ void ThreadPool::set(size_t requested) { } -/// ThreadPool::clear() sets threadPool data to initial values +// ThreadPool::clear() sets threadPool data to initial values void ThreadPool::clear() { @@ -172,8 +172,8 @@ void ThreadPool::clear() { } -/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and -/// returns immediately. Main thread will wake up other threads and start the search. +// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and +// returns immediately. Main thread will wake up other threads and start the search. void ThreadPool::start_thinking(Position& pos, StateListPtr& states, const Search::LimitsType& limits, bool ponderMode) { @@ -225,7 +225,7 @@ Thread* ThreadPool::get_best_thread() const { std::map votes; Value minScore = VALUE_NONE; - // Find minimum score of all threads + // Find the minimum score of all threads for (Thread* th: threads) minScore = std::min(minScore, th->rootMoves[0].score); @@ -256,7 +256,7 @@ Thread* ThreadPool::get_best_thread() const { } -/// Start non-main threads +// Start non-main threads void ThreadPool::start_searching() { @@ -266,7 +266,7 @@ void ThreadPool::start_searching() { } -/// Wait for non-main threads +// Wait for non-main threads void ThreadPool::wait_for_search_finished() const { diff --git a/src/thread.h b/src/thread.h index 8d0adcf0..44cc5672 100644 --- a/src/thread.h +++ b/src/thread.h @@ -34,10 +34,10 @@ namespace Stockfish { -/// Thread class keeps together all the thread-related stuff. We use -/// per-thread pawn and material hash tables so that once we get a -/// pointer to an entry its life time is unlimited and we don't have -/// to care about someone changing the entry under our feet. +// Thread class keeps together all the thread-related stuff. We use +// per-thread pawn and material hash tables so that once we get a +// pointer to an entry its lifetime is unlimited and we don't have +// to care about someone changing the entry under our feet. class Thread { @@ -75,7 +75,7 @@ public: }; -/// MainThread is a derived class specific for main thread +// MainThread is a derived class specific for main thread struct MainThread : public Thread { @@ -94,9 +94,9 @@ struct MainThread : public Thread { }; -/// ThreadPool struct handles all the threads-related stuff like init, starting, -/// parking and, most importantly, launching a thread. All the access to threads -/// is done through this class. +// ThreadPool struct handles all the threads-related stuff like init, starting, +// parking and, most importantly, launching a thread. All the access to threads +// is done through this class. struct ThreadPool { diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 330a8341..77352aa0 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -21,11 +21,11 @@ #include -/// On OSX threads other than the main thread are created with a reduced stack -/// size of 512KB by default, this is too low for deep searches, which require -/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. -/// The implementation calls pthread_create() with the stack size parameter -/// equal to the linux 8MB default, on platforms that support it. +// On OSX threads other than the main thread are created with a reduced stack +// size of 512KB by default, this is too low for deep searches, which require +// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. +// The implementation calls pthread_create() with the stack size parameter +// equal to the Linux 8MB default, on platforms that support it. #if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) diff --git a/src/timeman.cpp b/src/timeman.cpp index 5e57f8f9..74f59d90 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -29,14 +29,14 @@ namespace Stockfish { TimeManagement Time; // Our global time management object -/// TimeManagement::init() is called at the beginning of the search and calculates -/// the bounds of time allowed for the current game ply. We currently support: +// TimeManagement::init() is called at the beginning of the search and calculates +// the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - // if we have no time, no need to initialize TM, except for the start time, + // If we have no time, no need to initialize TM, except for the start time, // which is used by movetime. startTime = limits.startTime; if (limits.time[us] == 0) diff --git a/src/timeman.h b/src/timeman.h index 9ad6bdcc..6acdf0ac 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -28,8 +28,8 @@ namespace Stockfish { -/// The TimeManagement class computes the optimal time to think depending on -/// the maximum available time, the game move number and other parameters. +// The TimeManagement class computes the optimal time to think depending on +// the maximum available time, the game move number, and other parameters. class TimeManagement { public: diff --git a/src/tt.cpp b/src/tt.cpp index adcfe628..c3aec8d3 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -33,8 +33,8 @@ namespace Stockfish { TranspositionTable TT; // Our global transposition table -/// TTEntry::save() populates the TTEntry with a new node's data, possibly -/// overwriting an old position. Update is not atomic and can be racy. +// TTEntry::save() populates the TTEntry with a new node's data, possibly +// overwriting an old position. The update is not atomic and can be racy. void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { @@ -59,9 +59,9 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) } -/// TranspositionTable::resize() sets the size of the transposition table, -/// measured in megabytes. Transposition table consists of a power of 2 number -/// of clusters and each cluster consists of ClusterSize number of TTEntry. +// TranspositionTable::resize() sets the size of the transposition table, +// measured in megabytes. Transposition table consists of a power of 2 number +// of clusters and each cluster consists of ClusterSize number of TTEntry. void TranspositionTable::resize(size_t mbSize) { @@ -83,7 +83,7 @@ void TranspositionTable::resize(size_t mbSize) { } -/// TranspositionTable::clear() initializes the entire transposition table to zero, +// TranspositionTable::clear() initializes the entire transposition table to zero, // in a multi-threaded way. void TranspositionTable::clear() { @@ -113,12 +113,12 @@ void TranspositionTable::clear() { } -/// TranspositionTable::probe() looks up the current position in the transposition -/// table. It returns true and a pointer to the TTEntry if the position is found. -/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry -/// to be replaced later. The replace value of an entry is calculated as its depth -/// minus 8 times its relative age. TTEntry t1 is considered more valuable than -/// TTEntry t2 if its replace value is greater than that of t2. +// TranspositionTable::probe() looks up the current position in the transposition +// table. It returns true and a pointer to the TTEntry if the position is found. +// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry +// to be replaced later. The replace value of an entry is calculated as its depth +// minus 8 times its relative age. TTEntry t1 is considered more valuable than +// TTEntry t2 if its replace value is greater than that of t2. TTEntry* TranspositionTable::probe(const Key key, bool& found) const { @@ -149,8 +149,8 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { } -/// TranspositionTable::hashfull() returns an approximation of the hashtable -/// occupation during a search. The hash is x permill full, as per UCI protocol. +// TranspositionTable::hashfull() returns an approximation of the hashtable +// occupation during a search. The hash is x permill full, as per UCI protocol. int TranspositionTable::hashfull() const { diff --git a/src/tt.h b/src/tt.h index c11cf085..fdea4933 100644 --- a/src/tt.h +++ b/src/tt.h @@ -27,16 +27,16 @@ namespace Stockfish { -/// TTEntry struct is the 10 bytes transposition table entry, defined as below: -/// -/// key 16 bit -/// depth 8 bit -/// generation 5 bit -/// pv node 1 bit -/// bound type 2 bit -/// move 16 bit -/// value 16 bit -/// eval value 16 bit +// TTEntry struct is the 10 bytes transposition table entry, defined as below: +// +// key 16 bit +// depth 8 bit +// generation 5 bit +// pv node 1 bit +// bound type 2 bit +// move 16 bit +// value 16 bit +// eval value 16 bit struct TTEntry { @@ -60,11 +60,11 @@ private: }; -/// A TranspositionTable is an array of Cluster, of size clusterCount. Each -/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry -/// contains information on exactly one position. The size of a Cluster should -/// divide the size of a cache line for best performance, as the cacheline is -/// prefetched when possible. +// A TranspositionTable is an array of Cluster, of size clusterCount. Each +// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry +// contains information on exactly one position. The size of a Cluster should +// divide the size of a cache line for best performance, as the cacheline is +// prefetched when possible. class TranspositionTable { diff --git a/src/tune.h b/src/tune.h index dde03b32..a9a7331e 100644 --- a/src/tune.h +++ b/src/tune.h @@ -49,31 +49,30 @@ struct SetRange { #define SetDefaultRange SetRange(default_range) -/// Tune class implements the 'magic' code that makes the setup of a fishtest -/// tuning session as easy as it can be. Mainly you have just to remove const -/// qualifiers from the variables you want to tune and flag them for tuning, so -/// if you have: -/// -/// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; -/// -/// If you have a my_post_update() function to run after values have been updated, -/// and a my_range() function to set custom Option's min-max values, then you just -/// remove the 'const' qualifiers and write somewhere below in the file: -/// -/// TUNE(SetRange(my_range), myValue, my_post_update); -/// -/// You can also set the range directly, and restore the default at the end -/// -/// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); -/// -/// In case update function is slow and you have many parameters, you can add: -/// -/// UPDATE_ON_LAST(); -/// -/// And the values update, including post update function call, will be done only -/// once, after the engine receives the last UCI option, that is the one defined -/// and created as the last one, so the GUI should send the options in the same -/// order in which have been defined. +// Tune class implements the 'magic' code that makes the setup of a fishtest tuning +// session as easy as it can be. Mainly you have just to remove const qualifiers +// from the variables you want to tune and flag them for tuning, so if you have: +// +// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; +// +// If you have a my_post_update() function to run after values have been updated, +// and a my_range() function to set custom Option's min-max values, then you just +// remove the 'const' qualifiers and write somewhere below in the file: +// +// TUNE(SetRange(my_range), myValue, my_post_update); +// +// You can also set the range directly, and restore the default at the end +// +// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); +// +// In case update function is slow and you have many parameters, you can add: +// +// UPDATE_ON_LAST(); +// +// And the values update, including post update function call, will be done only +// once, after the engine receives the last UCI option, that is the one defined +// and created as the last one, so the GUI should send the options in the same +// order in which have been defined. class Tune { @@ -151,7 +150,7 @@ public: static bool update_on_last; }; -// Some macro magic :-) we define a dummy int variable that compiler initializes calling Tune::add() +// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() #define STRINGIFY(x) #x #define UNIQUE2(x, y) x ## y #define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ diff --git a/src/types.h b/src/types.h index f682e764..1fc4d33a 100644 --- a/src/types.h +++ b/src/types.h @@ -19,22 +19,22 @@ #ifndef TYPES_H_INCLUDED #define TYPES_H_INCLUDED -/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration -/// is done automatically. To get started type 'make help'. -/// -/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches -/// need to be set manually: -/// -/// -DNDEBUG | Disable debugging mode. Always use this for release. -/// -/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to -/// | run on some very old machines. -/// -/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works -/// | only in 64-bit mode and requires hardware with popcnt support. -/// -/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works -/// | only in 64-bit mode and requires hardware with pext support. +// When compiling with provided Makefile (e.g. for Linux and OSX), configuration +// is done automatically. To get started type 'make help'. +// +// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches +// need to be set manually: +// +// -DNDEBUG | Disable debugging mode. Always use this for release. +// +// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to +// | run on some very old machines. +// +// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works +// | only in 64-bit mode and requires hardware with popcnt support. +// +// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works +// | only in 64-bit mode and requires hardware with pext support. #include #include @@ -46,14 +46,14 @@ #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' #endif -/// Predefined macros hell: -/// -/// __GNUC__ Compiler is GCC, Clang or ICX -/// __clang__ Compiler is Clang or ICX -/// __INTEL_LLVM_COMPILER Compiler is ICX -/// _MSC_VER Compiler is MSVC -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +// Predefined macros hell: +// +// __GNUC__ Compiler is GCC, Clang or ICX +// __clang__ Compiler is Clang or ICX +// __INTEL_LLVM_COMPILER Compiler is ICX +// _MSC_VER Compiler is MSVC +// _WIN32 Building on Windows (any) +// _WIN64 Building on Windows 64 bit #if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) #define ALIGNAS_ON_STACK_VARIABLES_BROKEN @@ -107,17 +107,17 @@ using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; -/// A move needs 16 bits to be stored -/// -/// bit 0- 5: destination square (from 0 to 63) -/// bit 6-11: origin square (from 0 to 63) -/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) -/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) -/// NOTE: en passant bit is set only when a pawn can be captured -/// -/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in -/// any normal move destination square is always different from origin square -/// while MOVE_NONE and MOVE_NULL have the same origin and destination square. +// A move needs 16 bits to be stored +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +// NOTE: en passant bit is set only when a pawn can be captured +// +// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in +// any normal move destination square is always different from origin square +// while MOVE_NONE and MOVE_NULL have the same origin and destination square. enum Move : int { MOVE_NONE, @@ -291,7 +291,7 @@ ENABLE_INCR_OPERATORS_ON(Rank) #undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON -/// Additional operators to add a Direction to a Square +// Additional operators to add a Direction to a Square constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } inline Square& operator+=(Square& s, Direction d) { return s = s + d; } @@ -405,7 +405,7 @@ constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -/// Based on a congruential pseudo-random number generator +// Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } diff --git a/src/uci.cpp b/src/uci.cpp index f62bb8bf..81bf7aff 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -233,11 +233,11 @@ namespace { } // namespace -/// UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate -/// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a -/// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, -/// like running 'bench', the function returns immediately after the command is executed. -/// In addition to the UCI ones, some additional debug commands are also supported. +// UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate +// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a +// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, +// like running 'bench', the function returns immediately after the command is executed. +// In addition to the UCI ones, some additional debug commands are also supported. void UCI::loop(int argc, char* argv[]) { @@ -310,18 +310,18 @@ void UCI::loop(int argc, char* argv[]) { } -/// Turns a Value to an integer centipawn number, -/// without treatment of mate and similar special scores. +// Turns a Value to an integer centipawn number, +// without treatment of mate and similar special scores. int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; } -/// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: -/// -/// cp The score from the engine's point of view in centipawns. -/// mate Mate in 'y' moves (not plies). If the engine is getting mated, -/// uses negative values for 'y'. +// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: +// +// cp The score from the engine's point of view in centipawns. +// mate Mate in 'y' moves (not plies). If the engine is getting mated, +// uses negative values for 'y'. std::string UCI::value(Value v) { @@ -343,8 +343,8 @@ std::string UCI::value(Value v) { } -/// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation -/// and a game ply based on the data gathered for fishtest LTC games. +// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation +// and a game ply based on the data gathered for fishtest LTC games. std::string UCI::wdl(Value v, int ply) { @@ -359,17 +359,17 @@ std::string UCI::wdl(Value v, int ply) { } -/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) +// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; } -/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). -/// The only special case is castling where the e1g1 notation is printed in -/// standard chess mode and in e1h1 notation it is printed in Chess960 mode. -/// Internally, all castling moves are always encoded as 'king captures rook'. +// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). +// The only special case is castling where the e1g1 notation is printed in +// standard chess mode and in e1h1 notation it is printed in Chess960 mode. +// Internally, all castling moves are always encoded as 'king captures rook'. std::string UCI::move(Move m, bool chess960) { @@ -394,8 +394,8 @@ std::string UCI::move(Move m, bool chess960) { } -/// UCI::to_move() converts a string representing a move in coordinate notation -/// (g1f3, a7a8q) to the corresponding legal Move, if any. +// UCI::to_move() converts a string representing a move in coordinate notation +// (g1f3, a7a8q) to the corresponding legal Move, if any. Move UCI::to_move(const Position& pos, std::string& str) { diff --git a/src/uci.h b/src/uci.h index 7ca97d5c..048f8c11 100644 --- a/src/uci.h +++ b/src/uci.h @@ -41,15 +41,15 @@ const int NormalizeToPawnValue = 328; class Option; -/// Define a custom comparator, because the UCI options should be case-insensitive +// Define a custom comparator, because the UCI options should be case-insensitive struct CaseInsensitiveLess { bool operator() (const std::string&, const std::string&) const; }; -/// The options container is defined as a std::map +// The options container is defined as a std::map using OptionsMap = std::map; -/// The Option class implements each option as specified by the UCI protocol +// The Option class implements each option as specified by the UCI protocol class Option { using OnChange = void (*)(const Option&); diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 8d2c5c09..b822ccf9 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -44,7 +44,7 @@ UCI::OptionsMap Options; // Global object namespace UCI { -/// 'On change' actions, triggered by an option's value change +// 'On change' actions, triggered by an option's value change static void on_clear_hash(const Option&) { Search::clear(); } static void on_hash_size(const Option& o) { TT.resize(size_t(o)); } static void on_logger(const Option& o) { start_logger(o); } @@ -52,7 +52,7 @@ static void on_threads(const Option& o) { Threads.set(size_t(o)); } static void on_tb_path(const Option& o) { Tablebases::init(o); } static void on_eval_file(const Option&) { Eval::NNUE::init(); } -/// Our case insensitive less() function as required by UCI protocol +// Our case insensitive less() function as required by UCI protocol bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), @@ -60,7 +60,7 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const } -/// UCI::init() initializes the UCI options to their hard-coded default values +// UCI::init() initializes the UCI options to their hard-coded default values void init(OptionsMap& o) { @@ -89,8 +89,8 @@ void init(OptionsMap& o) { } -/// operator<<() is used to print all the options default values in chronological -/// insertion order (the idx field) and in the format defined by the UCI protocol. +// operator<<() is used to print all the options default values in chronological +// insertion order (the idx field) and in the format defined by the UCI protocol. std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { @@ -116,7 +116,7 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { } -/// Option class constructors and conversion operators +// Option class constructors and conversion operators Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) { defaultValue = currentValue = v; } @@ -150,7 +150,7 @@ bool Option::operator==(const char* s) const { } -/// operator<<() inits options and assigns idx in the correct printing order +// operator<<() inits options and assigns idx in the correct printing order void Option::operator<<(const Option& o) { @@ -161,9 +161,9 @@ void Option::operator<<(const Option& o) { } -/// operator=() 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. +// operator=() 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. Option& Option::operator=(const string& v) { From 057046cc9a114e38d9f616fa58cf230e9b15b9a7 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Sat, 14 Oct 2023 18:08:51 +0300 Subject: [PATCH 434/678] Cleanup qsearch continuation histories Only (ss-1) and (ss-2) are used in qsearch. closes https://github.com/official-stockfish/Stockfish/pull/4828 No functional change --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 16c6b0f3..0ebf4e20 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1506,9 +1506,7 @@ moves_loop: // When in check, search starts here futilityBase = std::min(ss->staticEval, bestValue) + 200; } - const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - (ss-3)->continuationHistory, (ss-4)->continuationHistory, - nullptr , (ss-6)->continuationHistory }; + const PieceToHistory* contHist[] = {(ss-1)->continuationHistory, (ss-2)->continuationHistory}; // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, From d3d0c69dc1baf03c93252da3583b1b746c5a7ab6 Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 18 Oct 2023 19:25:09 -0700 Subject: [PATCH 435/678] Remove outdated Tile naming. cleanup variable naming after #4816 closes #4833 No functional change --- src/nnue/nnue_feature_transformer.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 25f686da..9f02830a 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -395,9 +395,9 @@ namespace Stockfish::Eval::NNUE { { assert(states_to_update[0]); - auto accTileIn = reinterpret_cast( + auto accIn = reinterpret_cast( &st->accumulator.accumulation[Perspective][0]); - auto accTileOut = reinterpret_cast( + auto accOut = reinterpret_cast( &states_to_update[0]->accumulator.accumulation[Perspective][0]); const IndexType offsetR0 = HalfDimensions * removed[0][0]; @@ -408,7 +408,7 @@ namespace Stockfish::Eval::NNUE { if (removed[0].size() == 1) { for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) - accTileOut[k] = vec_add_16(vec_sub_16(accTileIn[k], columnR0[k]), columnA[k]); + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); } else { @@ -416,14 +416,14 @@ namespace Stockfish::Eval::NNUE { auto columnR1 = reinterpret_cast(&weights[offsetR1]); for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) - accTileOut[k] = vec_sub_16( - vec_add_16(accTileIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); + accOut[k] = vec_sub_16( + vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); } - auto accTilePsqtIn = reinterpret_cast( + auto accPsqtIn = reinterpret_cast( &st->accumulator.psqtAccumulation[Perspective][0]); - auto accTilePsqtOut = reinterpret_cast( + auto accPsqtOut = reinterpret_cast( &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]); const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; @@ -434,8 +434,8 @@ namespace Stockfish::Eval::NNUE { if (removed[0].size() == 1) { for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) - accTilePsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32( - accTilePsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]); + accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32( + accPsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]); } else { @@ -443,9 +443,9 @@ namespace Stockfish::Eval::NNUE { auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) - accTilePsqtOut[k] = vec_sub_psqt_32( - vec_add_psqt_32(accTilePsqtIn[k], columnPsqtA[k]), - vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); + accPsqtOut[k] = vec_sub_psqt_32( + vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]), + vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); } } else From 90c18b0b500a5226717353a37a82cd026d71b616 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 19 Oct 2023 14:38:07 +0300 Subject: [PATCH 436/678] Removing history condition Removing the bad history condition from the skip futility pruning formula. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 142688 W: 36420 L: 36317 D: 69951 Ptnml(0-2): 481, 16653, 36970, 16762, 478 https://tests.stockfishchess.org/tests/view/65270a663125598fc7eb8c67 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 435378 W: 110723 L: 110925 D: 213730 Ptnml(0-2): 278, 47251, 122788, 47139, 233 https://tests.stockfishchess.org/tests/view/6528595f3125598fc7eba8f5 closes https://github.com/official-stockfish/Stockfish/pull/4834 Bench: 1110579 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0ebf4e20..6214fb5b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -786,8 +786,7 @@ namespace { && eval >= beta && eval < 29462 // smaller than TB wins && !( !ttCapture - && ttMove - && thisThread->mainHistory[us][from_to(ttMove)] < 989)) + && ttMove)) return eval; // Step 9. Null move search with verification search (~35 Elo) From e18619d07802882136b583a01e56791b61abede6 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:31:23 +0200 Subject: [PATCH 437/678] Subtract the margin from the value returned by ProbCut This patch subtracts the margin from the value returned by ProbCut. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 46112 W: 11940 L: 11610 D: 22562 Ptnml(0-2): 131, 5318, 11842, 5620, 145 https://tests.stockfishchess.org/tests/view/652ea42ade6d262d08d329dd Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 86880 W: 22192 L: 21776 D: 42912 Ptnml(0-2): 43, 9213, 24510, 9633, 41 https://tests.stockfishchess.org/tests/view/652f70ffde6d262d08d33e8d closes https://github.com/official-stockfish/Stockfish/pull/4835 bench: 1135313 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 6214fb5b..baf81968 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -897,7 +897,7 @@ namespace { { // Save ProbCut data into transposition table tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); - return value; + return value - (probCutBeta - beta); } } From 8366ec48ae6fc57dffad849b20844d5b07f963b4 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 20 Oct 2023 02:23:46 -0700 Subject: [PATCH 438/678] Scale down stat bonus STC https://tests.stockfishchess.org/tests/view/652eff58de6d262d08d33353 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 88224 W: 22618 L: 22228 D: 43378 Ptnml(0-2): 282, 10177, 22783, 10609, 261 LTC https://tests.stockfishchess.org/tests/view/652fd13bde6d262d08d3481a LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 143508 W: 36674 L: 36142 D: 70692 Ptnml(0-2): 92, 15240, 40534, 15820, 68 Reduces the stat bonus by 20%. Maybe future patches can tune the actual bonus calculations for different histories. closes https://github.com/official-stockfish/Stockfish/pull/4836 bench: 1185932 --- src/movepick.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.h b/src/movepick.h index 652ef161..457defa5 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -51,7 +51,7 @@ public: assert(abs(bonus) <= D); // Ensure range is [-D, D] static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += bonus - entry * abs(bonus) / D; + entry += (bonus * D - entry * abs(bonus)) / (D * 5 / 4); assert(abs(entry) <= D); } From 2d0237db3f0e596fb06e3ffbadba84dcc4e018f6 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 21 Oct 2023 11:40:56 +0200 Subject: [PATCH 439/678] add clang-format This introduces clang-format to enforce a consistent code style for Stockfish. Having a documented and consistent style across the code will make contributing easier for new developers, and will make larger changes to the codebase easier to make. To facilitate formatting, this PR includes a Makefile target (`make format`) to format the code, this requires clang-format (version 17 currently) to be installed locally. Installing clang-format is straightforward on most OS and distros (e.g. with https://apt.llvm.org/, brew install clang-format, etc), as this is part of quite commonly used suite of tools and compilers (llvm / clang). Additionally, a CI action is present that will verify if the code requires formatting, and comment on the PR as needed. Initially, correct formatting is not required, it will be done by maintainers as part of the merge or in later commits, but obviously this is encouraged. fixes https://github.com/official-stockfish/Stockfish/issues/3608 closes https://github.com/official-stockfish/Stockfish/pull/4790 Co-Authored-By: Joost VandeVondele --- .clang-format | 44 + .github/workflows/stockfish_format_check.yml | 51 + CONTRIBUTING.md | 5 +- src/Makefile | 17 + src/benchmark.cpp | 84 +- src/benchmark.h | 4 +- src/bitboard.cpp | 122 +- src/bitboard.h | 272 +-- src/evaluate.cpp | 172 +- src/evaluate.h | 30 +- src/main.cpp | 24 +- src/misc.cpp | 770 ++++--- src/misc.h | 106 +- src/movegen.cpp | 123 +- src/movegen.h | 53 +- src/movepick.cpp | 440 ++-- src/movepick.h | 137 +- src/nnue/evaluate_nnue.cpp | 358 ++-- src/nnue/evaluate_nnue.h | 54 +- src/nnue/features/half_ka_v2_hm.cpp | 87 +- src/nnue/features/half_ka_v2_hm.h | 70 +- src/nnue/layers/affine_transform.h | 414 ++-- .../layers/affine_transform_sparse_input.h | 320 +-- src/nnue/layers/clipped_relu.h | 210 +- src/nnue/layers/simd.h | 274 ++- src/nnue/layers/sqr_clipped_relu.h | 91 +- src/nnue/nnue_accumulator.h | 10 +- src/nnue/nnue_architecture.h | 141 +- src/nnue/nnue_common.h | 375 ++-- src/nnue/nnue_feature_transformer.h | 1003 ++++----- src/position.cpp | 1605 +++++++------- src/position.h | 447 ++-- src/search.cpp | 1900 ++++++++--------- src/search.h | 90 +- src/syzygy/tbprobe.cpp | 607 +++--- src/syzygy/tbprobe.h | 30 +- src/thread.cpp | 221 +- src/thread.h | 132 +- src/thread_win32_osx.h | 43 +- src/timeman.cpp | 114 +- src/timeman.h | 27 +- src/tt.cpp | 144 +- src/tt.h | 88 +- src/tune.cpp | 95 +- src/tune.h | 159 +- src/types.h | 416 ++-- src/uci.cpp | 383 ++-- src/uci.h | 52 +- src/ucioption.cpp | 186 +- 49 files changed, 6403 insertions(+), 6197 deletions(-) create mode 100644 .clang-format create mode 100644 .github/workflows/stockfish_format_check.yml diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..c71f0368 --- /dev/null +++ b/.clang-format @@ -0,0 +1,44 @@ +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveDeclarations: Consecutive +AlignEscapedNewlines: DontAlign +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AlwaysBreakTemplateDeclarations: Yes +BasedOnStyle: WebKit +BitFieldColonSpacing: After +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BraceWrapping: + AfterFunction: false + AfterClass: false + AfterControlStatement: true + BeforeElse: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +BreakStringLiterals: false +ColumnLimit: 100 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +IndentGotoLabels: false +IndentPPDirectives: BeforeHash +IndentWidth: 4 +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +PackConstructorInitializers: Never +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: false +SpaceBeforeCaseColon: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeInheritanceColon: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 2 diff --git a/.github/workflows/stockfish_format_check.yml b/.github/workflows/stockfish_format_check.yml new file mode 100644 index 00000000..cb16b327 --- /dev/null +++ b/.github/workflows/stockfish_format_check.yml @@ -0,0 +1,51 @@ +# This workflow will run clang-format and comment on the PR. +# Because of security reasons, it is crucial that this workflow +# executes no shell script nor runs make. +# Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + +name: Stockfish +on: + pull_request_target: + branches: + - 'master' + paths: + - '**.cpp' + - '**.h' +jobs: + Stockfish: + name: clang-format check + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Run clang-format style check + uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e + id: clang-format + continue-on-error: true + with: + clang-format-version: '17' + exclude-regex: 'incbin' + + - name: Comment on PR + if: steps.clang-format.outcome == 'failure' + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 + with: + message: | + clang-format 17 needs to be run on this PR. + If you do not have clang-format installed, the maintainer will run it when merging. + For the exact version please see https://packages.ubuntu.com/mantic/clang-format-17. + + _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ + comment_tag: execution + + - name: Comment on PR + if: steps.clang-format.outcome != 'failure' + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 + with: + message: | + _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ + create_if_not_exists: false + comment_tag: execution + mode: delete diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7667a942..9e72e1db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,8 +57,9 @@ discussion._ ## Code Style -We do not have a strict code style. But it is best to stick to the existing -style of the file you are editing. +Changes to Stockfish C++ code should respect our coding style defined by +[.clang-format](.clang-format). You can format your changes by running +`make format`. This requires clang-format version 17 to be installed on your system. ## Community and Communication diff --git a/src/Makefile b/src/Makefile index 5b43c35f..7b7ee41b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -57,6 +57,14 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp +HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ + nnue/evaluate_nnue.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ + nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ + nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ + search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ + tt.h tune.h types.h uci.h + OBJS = $(notdir $(SRCS:.cpp=.o)) VPATH = syzygy:nnue:nnue/features @@ -145,6 +153,12 @@ dotprod = no arm_version = 0 STRIP = strip +ifneq ($(shell command -v clang-format-17),) + CLANG-FORMAT = clang-format-17 +else + CLANG-FORMAT = clang-format +endif + ### 2.2 Architecture specific ifeq ($(findstring x86,$(ARCH)),x86) @@ -936,6 +950,9 @@ net: netvariables fi; \ fi; \ +format: + $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file:../.clang-format + # default target default: help diff --git a/src/benchmark.cpp b/src/benchmark.cpp index d67e37f6..63598e75 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -27,6 +27,7 @@ namespace { +// clang-format off const std::vector Defaults = { "setoption name UCI_Chess960 value false", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", @@ -90,8 +91,9 @@ const std::vector Defaults = { "nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1", "setoption name UCI_Chess960 value false" }; +// clang-format on -} // namespace +} // namespace namespace Stockfish { @@ -109,56 +111,56 @@ namespace Stockfish { std::vector setup_bench(const Position& current, std::istream& is) { - std::vector fens, list; - std::string go, token; + std::vector fens, list; + std::string go, token; - // Assign default values to missing arguments - std::string ttSize = (is >> token) ? token : "16"; - std::string threads = (is >> token) ? token : "1"; - std::string limit = (is >> token) ? token : "13"; - std::string fenFile = (is >> token) ? token : "default"; - std::string limitType = (is >> token) ? token : "depth"; + // Assign default values to missing arguments + std::string ttSize = (is >> token) ? token : "16"; + std::string threads = (is >> token) ? token : "1"; + std::string limit = (is >> token) ? token : "13"; + std::string fenFile = (is >> token) ? token : "default"; + std::string limitType = (is >> token) ? token : "depth"; - go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; + go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; - if (fenFile == "default") - fens = Defaults; + if (fenFile == "default") + fens = Defaults; - else if (fenFile == "current") - fens.push_back(current.fen()); + else if (fenFile == "current") + fens.push_back(current.fen()); - else - { - std::string fen; - std::ifstream file(fenFile); + else + { + std::string fen; + std::ifstream file(fenFile); - if (!file.is_open()) - { - std::cerr << "Unable to open file " << fenFile << std::endl; - exit(EXIT_FAILURE); - } + if (!file.is_open()) + { + std::cerr << "Unable to open file " << fenFile << std::endl; + exit(EXIT_FAILURE); + } - while (getline(file, fen)) - if (!fen.empty()) - fens.push_back(fen); + while (getline(file, fen)) + if (!fen.empty()) + fens.push_back(fen); - file.close(); - } + file.close(); + } - list.emplace_back("setoption name Threads value " + threads); - list.emplace_back("setoption name Hash value " + ttSize); - list.emplace_back("ucinewgame"); + list.emplace_back("setoption name Threads value " + threads); + list.emplace_back("setoption name Hash value " + ttSize); + list.emplace_back("ucinewgame"); - for (const std::string& fen : fens) - if (fen.find("setoption") != std::string::npos) - list.emplace_back(fen); - else - { - list.emplace_back("position fen " + fen); - list.emplace_back(go); - } + for (const std::string& fen : fens) + if (fen.find("setoption") != std::string::npos) + list.emplace_back(fen); + else + { + list.emplace_back("position fen " + fen); + list.emplace_back(go); + } - return list; + return list; } -} // namespace Stockfish +} // namespace Stockfish \ No newline at end of file diff --git a/src/benchmark.h b/src/benchmark.h index 64acf833..e6206d19 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -29,6 +29,6 @@ class Position; std::vector setup_bench(const Position&, std::istream&); -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef BENCHMARK_H_INCLUDED +#endif // #ifndef BENCHMARK_H_INCLUDED diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 89eeee61..fff7eba9 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -39,10 +39,10 @@ Magic BishopMagics[SQUARE_NB]; namespace { - Bitboard RookTable[0x19000]; // To store rook attacks - Bitboard BishopTable[0x1480]; // To store bishop attacks +Bitboard RookTable[0x19000]; // To store rook attacks +Bitboard BishopTable[0x1480]; // To store bishop attacks - void init_magics(PieceType pt, Bitboard table[], Magic magics[]); +void init_magics(PieceType pt, Bitboard table[], Magic magics[]); } @@ -60,18 +60,18 @@ inline Bitboard safe_destination(Square s, int step) { std::string Bitboards::pretty(Bitboard b) { - std::string s = "+---+---+---+---+---+---+---+---+\n"; + std::string s = "+---+---+---+---+---+---+---+---+\n"; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - s += b & make_square(f, r) ? "| X " : "| "; + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + s += b & make_square(f, r) ? "| X " : "| "; - s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; - } - s += " a b c d e f g h\n"; + s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; + } + s += " a b c d e f g h\n"; - return s; + return s; } @@ -80,49 +80,50 @@ std::string Bitboards::pretty(Bitboard b) { void Bitboards::init() { - for (unsigned i = 0; i < (1 << 16); ++i) - PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); + for (unsigned i = 0; i < (1 << 16); ++i) + PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); - init_magics(ROOK, RookTable, RookMagics); - init_magics(BISHOP, BishopTable, BishopMagics); + init_magics(ROOK, RookTable, RookMagics); + init_magics(BISHOP, BishopTable, BishopMagics); - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - { - PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); - PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); - for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} ) - PseudoAttacks[KING][s1] |= safe_destination(s1, step); + for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) + PseudoAttacks[KING][s1] |= safe_destination(s1, step); - for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} ) - PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); + for (int step : {-17, -15, -10, -6, 6, 10, 15, 17}) + PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); - PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); - PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0); + PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); + PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb(s1, 0); - for (PieceType pt : { BISHOP, ROOK }) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - { - if (PseudoAttacks[pt][s1] & s2) - { - LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; - BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); - } - BetweenBB[s1][s2] |= s2; - } - } + for (PieceType pt : {BISHOP, ROOK}) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { + if (PseudoAttacks[pt][s1] & s2) + { + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = + (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + } + BetweenBB[s1][s2] |= s2; + } + } } namespace { - Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { +Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { - Bitboard attacks = 0; - Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; + Bitboard attacks = 0; + Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) @@ -133,22 +134,22 @@ namespace { } return attacks; - } +} - // init_magics() computes all rook and bishop attacks at startup. Magic - // bitboards are used to look up attacks of sliding pieces. As a reference see - // www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so - // called "fancy" approach. +// init_magics() computes all rook and bishop attacks at startup. Magic +// bitboards are used to look up attacks of sliding pieces. As a reference see +// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so +// called "fancy" approach. - void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { +void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { // Optimal PRNG seeds to pick the correct magics in the shortest time - int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, - { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; + int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020}, + {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}; Bitboard occupancy[4096], reference[4096], edges, b; - int epoch[4096] = {}, cnt = 0, size = 0; + int epoch[4096] = {}, cnt = 0, size = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) { @@ -161,8 +162,8 @@ namespace { // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. Magic& m = magics[s]; - m.mask = sliding_attack(pt, s, 0) & ~edges; - m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); + m.mask = sliding_attack(pt, s, 0) & ~edges; + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); // Set the offset for the attacks table of the square. We have individual // table sizes for each square with "Fancy Magic Bitboards". @@ -171,7 +172,8 @@ namespace { // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. b = size = 0; - do { + do + { occupancy[size] = b; reference[size] = sliding_attack(pt, s, b); @@ -189,9 +191,9 @@ namespace { // Find a magic for square 's' picking up an (almost) random number // until we find the one that passes the verification test. - for (int i = 0; i < size; ) + for (int i = 0; i < size;) { - for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; ) + for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;) m.magic = rng.sparse_rand(); // A good magic must map every possible occupancy to an index that @@ -206,7 +208,7 @@ namespace { if (epoch[idx] < cnt) { - epoch[idx] = cnt; + epoch[idx] = cnt; m.attacks[idx] = reference[i]; } else if (m.attacks[idx] != reference[i]) @@ -214,7 +216,7 @@ namespace { } } } - } +} } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/bitboard.h b/src/bitboard.h index 0908c957..03a51136 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -32,10 +32,10 @@ namespace Stockfish { namespace Bitboards { -void init(); +void init(); std::string pretty(Bitboard b); -} // namespace Stockfish::Bitboards +} // namespace Stockfish::Bitboards constexpr Bitboard FileABB = 0x0101010101010101ULL; constexpr Bitboard FileBBB = FileABB << 1; @@ -66,85 +66,80 @@ extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; // Magic holds all magic bitboards relevant data for a single square struct Magic { - Bitboard mask; - Bitboard magic; - Bitboard* attacks; - unsigned shift; + Bitboard mask; + Bitboard magic; + Bitboard* attacks; + unsigned shift; - // Compute the attack's index using the 'magic bitboards' approach - unsigned index(Bitboard occupied) const { + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { - if (HasPext) - return unsigned(pext(occupied, mask)); + if (HasPext) + return unsigned(pext(occupied, mask)); - if (Is64Bit) - return unsigned(((occupied & mask) * magic) >> shift); + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); - unsigned lo = unsigned(occupied) & unsigned(mask); - unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); - return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; - } + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; + } }; extern Magic RookMagics[SQUARE_NB]; extern Magic BishopMagics[SQUARE_NB]; inline Bitboard square_bb(Square s) { - assert(is_ok(s)); - return (1ULL << s); + assert(is_ok(s)); + return (1ULL << s); } // Overloads of bitwise operators between a Bitboard and a Square for testing // whether a given bit is set in a bitboard, and for setting and clearing bits. -inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); } -inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); } -inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); } +inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } +inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } +inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } -inline Bitboard operator&(Square s, Bitboard b) { return b & s; } -inline Bitboard operator|(Square s, Bitboard b) { return b | s; } -inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } +inline Bitboard operator&(Square s, Bitboard b) { return b & s; } +inline Bitboard operator|(Square s, Bitboard b) { return b | s; } +inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } -inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } +inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } -constexpr bool more_than_one(Bitboard b) { - return b & (b - 1); -} +constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } // rank_bb() and file_bb() return a bitboard representing all the squares on // the given file or rank. -constexpr Bitboard rank_bb(Rank r) { - return Rank1BB << (8 * r); -} +constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); } -constexpr Bitboard rank_bb(Square s) { - return rank_bb(rank_of(s)); -} +constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); } -constexpr Bitboard file_bb(File f) { - return FileABB << f; -} +constexpr Bitboard file_bb(File f) { return FileABB << f; } -constexpr Bitboard file_bb(Square s) { - return file_bb(file_of(s)); -} +constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); } // shift() moves a bitboard one or two steps as specified by the direction D template constexpr Bitboard shift(Bitboard b) { - return D == NORTH ? b << 8 : D == SOUTH ? b >> 8 - : D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16 - : D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1 - : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7 - : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9 - : 0; + return D == NORTH ? b << 8 + : D == SOUTH ? b >> 8 + : D == NORTH + NORTH ? b << 16 + : D == SOUTH + SOUTH ? b >> 16 + : D == EAST ? (b & ~FileHBB) << 1 + : D == WEST ? (b & ~FileABB) >> 1 + : D == NORTH_EAST ? (b & ~FileHBB) << 9 + : D == NORTH_WEST ? (b & ~FileABB) << 7 + : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 + : D == SOUTH_WEST ? (b & ~FileABB) >> 9 + : 0; } @@ -153,14 +148,14 @@ constexpr Bitboard shift(Bitboard b) { template constexpr Bitboard pawn_attacks_bb(Bitboard b) { - return C == WHITE ? shift(b) | shift(b) - : shift(b) | shift(b); + return C == WHITE ? shift(b) | shift(b) + : shift(b) | shift(b); } inline Bitboard pawn_attacks_bb(Color c, Square s) { - assert(is_ok(s)); - return PawnAttacks[c][s]; + assert(is_ok(s)); + return PawnAttacks[c][s]; } // line_bb() returns a bitboard representing an entire line (from board edge @@ -170,9 +165,9 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { inline Bitboard line_bb(Square s1, Square s2) { - assert(is_ok(s1) && is_ok(s2)); + assert(is_ok(s1) && is_ok(s2)); - return LineBB[s1][s2]; + return LineBB[s1][s2]; } @@ -186,26 +181,34 @@ inline Bitboard line_bb(Square s1, Square s2) { inline Bitboard between_bb(Square s1, Square s2) { - assert(is_ok(s1) && is_ok(s2)); + assert(is_ok(s1) && is_ok(s2)); - return BetweenBB[s1][s2]; + return BetweenBB[s1][s2]; } // aligned() returns true if the squares s1, s2 and s3 are aligned either on a // straight or on a diagonal line. -inline bool aligned(Square s1, Square s2, Square s3) { - return line_bb(s1, s2) & s3; -} +inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } // distance() functions return the distance between x and y, defined as the // number of steps for a king in x to reach y. -template inline int distance(Square x, Square y); -template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } -template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } -template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } +template +inline int distance(Square x, Square y); +template<> +inline int distance(Square x, Square y) { + return std::abs(file_of(x) - file_of(y)); +} +template<> +inline int distance(Square x, Square y) { + return std::abs(rank_of(x) - rank_of(y)); +} +template<> +inline int distance(Square x, Square y) { + return SquareDistance[x][y]; +} inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } @@ -215,9 +218,9 @@ inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } template inline Bitboard attacks_bb(Square s) { - assert((Pt != PAWN) && (is_ok(s))); + assert((Pt != PAWN) && (is_ok(s))); - return PseudoAttacks[Pt][s]; + return PseudoAttacks[Pt][s]; } @@ -228,28 +231,36 @@ inline Bitboard attacks_bb(Square s) { template inline Bitboard attacks_bb(Square s, Bitboard occupied) { - assert((Pt != PAWN) && (is_ok(s))); + assert((Pt != PAWN) && (is_ok(s))); - switch (Pt) - { - case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; - case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)]; - case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return PseudoAttacks[Pt][s]; - } + switch (Pt) + { + case BISHOP : + return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; + case ROOK : + return RookMagics[s].attacks[RookMagics[s].index(occupied)]; + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[Pt][s]; + } } inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { - assert((pt != PAWN) && (is_ok(s))); + assert((pt != PAWN) && (is_ok(s))); - switch (pt) - { - case BISHOP: return attacks_bb(s, occupied); - case ROOK : return attacks_bb< ROOK>(s, occupied); - case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return PseudoAttacks[pt][s]; - } + switch (pt) + { + case BISHOP : + return attacks_bb(s, occupied); + case ROOK : + return attacks_bb(s, occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[pt][s]; + } } @@ -259,16 +270,19 @@ 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]]; + 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]]; #elif defined(_MSC_VER) - return int(_mm_popcnt_u64(b)); + return int(_mm_popcnt_u64(b)); -#else // Assumed gcc or compatible compiler +#else // Assumed gcc or compatible compiler - return __builtin_popcountll(b); + return __builtin_popcountll(b); #endif } @@ -279,66 +293,72 @@ inline int popcount(Bitboard b) { #if defined(__GNUC__) // GCC, Clang, ICX inline Square lsb(Bitboard b) { - assert(b); - return Square(__builtin_ctzll(b)); + assert(b); + return Square(__builtin_ctzll(b)); } inline Square msb(Bitboard b) { - assert(b); - return Square(63 ^ __builtin_clzll(b)); + assert(b); + return Square(63 ^ __builtin_clzll(b)); } #elif defined(_MSC_VER) // MSVC -#ifdef _WIN64 // MSVC, WIN64 + #ifdef _WIN64 // MSVC, WIN64 inline Square lsb(Bitboard b) { - assert(b); - unsigned long idx; - _BitScanForward64(&idx, b); - return (Square) idx; + assert(b); + unsigned long idx; + _BitScanForward64(&idx, b); + return (Square) idx; } inline Square msb(Bitboard b) { - assert(b); - unsigned long idx; - _BitScanReverse64(&idx, b); - return (Square) idx; + assert(b); + unsigned long idx; + _BitScanReverse64(&idx, b); + return (Square) idx; } -#else // MSVC, WIN32 + #else // MSVC, WIN32 inline Square lsb(Bitboard b) { - assert(b); - unsigned long idx; + assert(b); + unsigned long idx; - if (b & 0xffffffff) { - _BitScanForward(&idx, int32_t(b)); - return Square(idx); - } else { - _BitScanForward(&idx, int32_t(b >> 32)); - return Square(idx + 32); - } + if (b & 0xffffffff) + { + _BitScanForward(&idx, int32_t(b)); + return Square(idx); + } + else + { + _BitScanForward(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } } inline Square msb(Bitboard b) { - assert(b); - unsigned long idx; + assert(b); + unsigned long idx; - if (b >> 32) { - _BitScanReverse(&idx, int32_t(b >> 32)); - return Square(idx + 32); - } else { - _BitScanReverse(&idx, int32_t(b)); - return Square(idx); - } + if (b >> 32) + { + _BitScanReverse(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } + else + { + _BitScanReverse(&idx, int32_t(b)); + return Square(idx); + } } -#endif + #endif #else // Compiler is neither GCC nor MSVC compatible -#error "Compiler not supported." + #error "Compiler not supported." #endif @@ -346,19 +366,19 @@ inline Square msb(Bitboard b) { // square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). inline Bitboard least_significant_square_bb(Bitboard b) { - assert(b); - return b & -b; + assert(b); + return b & -b; } // pop_lsb() finds and clears the least significant bit in a non-zero bitboard inline Square pop_lsb(Bitboard& b) { - assert(b); - const Square s = lsb(b); - b &= b - 1; - return s; + assert(b); + const Square s = lsb(b); + b &= b - 1; + return s; } -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef BITBOARD_H_INCLUDED +#endif // #ifndef BITBOARD_H_INCLUDED diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3eb7ee85..00498bf0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -43,11 +43,11 @@ // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) - INCBIN(EmbeddedNNUE, EvalFileDefaultName); +INCBIN(EmbeddedNNUE, EvalFileDefaultName); #else - const unsigned char gEmbeddedNNUEData[1] = {0x0}; - const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; - const unsigned int gEmbeddedNNUESize = 1; +const unsigned char gEmbeddedNNUEData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; +const unsigned int gEmbeddedNNUESize = 1; #endif @@ -55,27 +55,28 @@ namespace Stockfish { namespace Eval { - std::string currentEvalFileName = "None"; +std::string currentEvalFileName = "None"; - // NNUE::init() tries to load a NNUE network at startup time, or when the engine - // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" - // The name of the NNUE network is always retrieved from the EvalFile option. - // We search the given network in three locations: internally (the default - // network may be embedded in the binary), in the active working directory and - // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY - // variable to have the engine search in a special directory in their distro. +// NNUE::init() tries to load a NNUE network at startup time, or when the engine +// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" +// The name of the NNUE network is always retrieved from the EvalFile option. +// We search the given network in three locations: internally (the default +// network may be embedded in the binary), in the active working directory and +// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY +// variable to have the engine search in a special directory in their distro. - void NNUE::init() { +void NNUE::init() { std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; - #if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; - #else - std::vector dirs = { "" , "" , CommandLine::binaryDirectory }; - #endif +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", CommandLine::binaryDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", CommandLine::binaryDirectory}; +#endif for (const std::string& directory : dirs) if (currentEvalFileName != eval_file) @@ -90,23 +91,28 @@ namespace Eval { if (directory == "" && eval_file == EvalFileDefaultName) { // C++ way to prepare a buffer for a memory stream - class MemoryBuffer : public std::basic_streambuf { - public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } }; - MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), - size_t(gEmbeddedNNUESize)); - (void) gEmbeddedNNUEEnd; // Silence warning on unused variable + MemoryBuffer buffer( + const_cast(reinterpret_cast(gEmbeddedNNUEData)), + size_t(gEmbeddedNNUESize)); + (void) gEmbeddedNNUEEnd; // Silence warning on unused variable std::istream stream(&buffer); if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } } - } +} - // NNUE::verify() verifies that the last net used was loaded successfully - void NNUE::verify() { +// NNUE::verify() verifies that the last net used was loaded successfully +void NNUE::verify() { std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) @@ -115,10 +121,14 @@ namespace Eval { if (currentEvalFileName != eval_file) { - std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); + std::string msg3 = + "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; + std::string msg4 = + "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + + std::string(EvalFileDefaultName); std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; @@ -131,7 +141,7 @@ namespace Eval { } sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; - } +} } @@ -140,8 +150,8 @@ namespace Eval { // an approximation of the material advantage on the board in terms of pawns. Value Eval::simple_eval(const Position& pos, Color c) { - return PawnValue * (pos.count(c) - pos.count(~c)) - + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } @@ -150,43 +160,41 @@ Value Eval::simple_eval(const Position& pos, Color c) { Value Eval::evaluate(const Position& pos) { - assert(!pos.checkers()); + assert(!pos.checkers()); - Value v; - Color stm = pos.side_to_move(); - int shuffling = pos.rule50_count(); - int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); + Value v; + Color stm = pos.side_to_move(); + int shuffling = pos.rule50_count(); + int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); - bool lazy = abs(simpleEval) >= RookValue + KnightValue - + 16 * shuffling * shuffling - + abs(pos.this_thread()->bestValue) - + abs(pos.this_thread()->rootSimpleEval); + bool lazy = abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling + + abs(pos.this_thread()->bestValue) + + abs(pos.this_thread()->rootSimpleEval); - if (lazy) - v = Value(simpleEval); - else - { - int nnueComplexity; - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + if (lazy) + v = Value(simpleEval); + else + { + int nnueComplexity; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - Value optimism = pos.this_thread()->optimism[stm]; + Value optimism = pos.this_thread()->optimism[stm]; - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512; - nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768; + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768; - int npm = pos.non_pawn_material() / 64; - v = ( nnue * (915 + npm + 9 * pos.count()) - + optimism * (154 + npm )) / 1024; - } + int npm = pos.non_pawn_material() / 64; + v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; + } - // Damp down the evaluation linearly when shuffling - v = v * (200 - shuffling) / 214; + // Damp down the evaluation linearly when shuffling + v = v * (200 - shuffling) / 214; - // Guarantee evaluation does not hit the tablebase range - v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - return v; + return v; } // trace() is like evaluate(), but instead of returning a value, it returns @@ -196,33 +204,33 @@ Value Eval::evaluate(const Position& pos) { std::string Eval::trace(Position& pos) { - if (pos.checkers()) - return "Final evaluation: none (in check)"; + if (pos.checkers()) + return "Final evaluation: none (in check)"; - // Reset any global variable used in eval - pos.this_thread()->bestValue = VALUE_ZERO; - pos.this_thread()->rootSimpleEval = VALUE_ZERO; - pos.this_thread()->optimism[WHITE] = VALUE_ZERO; - pos.this_thread()->optimism[BLACK] = VALUE_ZERO; + // Reset any global variable used in eval + pos.this_thread()->bestValue = VALUE_ZERO; + pos.this_thread()->rootSimpleEval = VALUE_ZERO; + pos.this_thread()->optimism[WHITE] = VALUE_ZERO; + pos.this_thread()->optimism[BLACK] = VALUE_ZERO; - std::stringstream ss; - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos) << '\n'; + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos) << '\n'; - ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v; - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; + Value v; + v = NNUE::evaluate(pos, false); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; - ss << " [with scaled NNUE, ...]"; - ss << "\n"; + v = evaluate(pos); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; + ss << " [with scaled NNUE, ...]"; + ss << "\n"; - return ss.str(); + return ss.str(); } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/evaluate.h b/src/evaluate.h index 26f2fc4f..2ab477ec 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,27 +29,27 @@ class Position; namespace Eval { - std::string trace(Position& pos); +std::string trace(Position& pos); - Value simple_eval(const Position& pos, Color c); - Value evaluate(const Position& pos); +Value simple_eval(const Position& pos, Color c); +Value evaluate(const Position& pos); - extern std::string currentEvalFileName; +extern std::string currentEvalFileName; - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue - // for the build process (profile-build and fishtest) to work. Do not change the - // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-0000000000a0.nnue" +// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue +// for the build process (profile-build and fishtest) to work. Do not change the +// name of the macro, as it is used in the Makefile. +#define EvalFileDefaultName "nn-0000000000a0.nnue" - namespace NNUE { +namespace NNUE { - void init(); - void verify(); +void init(); +void verify(); - } // namespace NNUE +} // namespace NNUE -} // namespace Eval +} // namespace Eval -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef EVALUATE_H_INCLUDED +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index eee149fb..04879cc4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,19 +33,19 @@ using namespace Stockfish; int main(int argc, char* argv[]) { - std::cout << engine_info() << std::endl; + std::cout << engine_info() << std::endl; - CommandLine::init(argc, argv); - UCI::init(Options); - Tune::init(); - Bitboards::init(); - Position::init(); - Threads.set(size_t(Options["Threads"])); - Search::clear(); // After threads are up - Eval::NNUE::init(); + CommandLine::init(argc, argv); + UCI::init(Options); + Tune::init(); + Bitboards::init(); + Position::init(); + Threads.set(size_t(Options["Threads"])); + Search::clear(); // After threads are up + Eval::NNUE::init(); - UCI::loop(argc, argv); + UCI::loop(argc, argv); - Threads.set(0); - return 0; + Threads.set(0); + return 0; } diff --git a/src/misc.cpp b/src/misc.cpp index 5abdaf07..05181325 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -19,30 +19,31 @@ #include "misc.h" #ifdef _WIN32 -#if _WIN32_WINNT < 0x0601 -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes -#endif + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif -#ifndef NOMINMAX -#define NOMINMAX -#endif + #ifndef NOMINMAX + #define NOMINMAX + #endif -#include + #include // The needed Windows API for processor groups could be missed from old Windows // versions, so instead of calling them directly (forcing the linker to resolve // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. extern "C" { -using fun1_t = bool(*)(LOGICAL_PROCESSOR_RELATIONSHIP, - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); -using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY); -using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); -using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); -using fun5_t = WORD(*)(); -using fun6_t = bool(*)(HANDLE, DWORD, PHANDLE); -using fun7_t = bool(*)(LPCSTR, LPCSTR, PLUID); -using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, + PDWORD); +using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY); +using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); +using fun5_t = WORD (*)(); +using fun6_t = bool (*)(HANDLE, DWORD, PHANDLE); +using fun7_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); } #endif @@ -59,12 +60,14 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES #include "types.h" #if defined(__linux__) && !defined(__ANDROID__) -#include + #include #endif -#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__) -#define POSIXALIGNEDALLOC -#include +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \ + || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \ + || defined(__e2k__) + #define POSIXALIGNEDALLOC + #include #endif namespace Stockfish { @@ -80,65 +83,69 @@ constexpr std::string_view version = "dev"; // usual I/O functionality, all without changing a single line of code! // Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 -struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout +struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout - Tie(std::streambuf* b, std::streambuf* l) : buf(b), logBuf(l) {} + Tie(std::streambuf* b, std::streambuf* l) : + buf(b), + logBuf(l) {} - int sync() override { return logBuf->pubsync(), buf->pubsync(); } - int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); } - int underflow() override { return buf->sgetc(); } - int uflow() override { return log(buf->sbumpc(), ">> "); } + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } - std::streambuf *buf, *logBuf; + std::streambuf *buf, *logBuf; - int log(int c, const char* prefix) { + int log(int c, const char* prefix) { - static int last = '\n'; // Single log file + static int last = '\n'; // Single log file - if (last == '\n') - logBuf->sputn(prefix, 3); + if (last == '\n') + logBuf->sputn(prefix, 3); - return last = logBuf->sputc(char(c)); - } + return last = logBuf->sputc(char(c)); + } }; class Logger { - Logger() : in(std::cin.rdbuf(), file.rdbuf()), out(std::cout.rdbuf(), file.rdbuf()) {} - ~Logger() { start(""); } + Logger() : + in(std::cin.rdbuf(), file.rdbuf()), + out(std::cout.rdbuf(), file.rdbuf()) {} + ~Logger() { start(""); } - std::ofstream file; - Tie in, out; + std::ofstream file; + Tie in, out; -public: - static void start(const std::string& fname) { + public: + static void start(const std::string& fname) { - static Logger l; + static Logger l; - if (l.file.is_open()) - { - std::cout.rdbuf(l.out.buf); - std::cin.rdbuf(l.in.buf); - l.file.close(); - } - - if (!fname.empty()) - { - l.file.open(fname, std::ifstream::out); - - if (!l.file.is_open()) + if (l.file.is_open()) { - std::cerr << "Unable to open debug log file " << fname << std::endl; - exit(EXIT_FAILURE); + std::cout.rdbuf(l.out.buf); + std::cin.rdbuf(l.in.buf); + l.file.close(); } - std::cin.rdbuf(&l.in); - std::cout.rdbuf(&l.out); + if (!fname.empty()) + { + l.file.open(fname, std::ifstream::out); + + if (!l.file.is_open()) + { + std::cerr << "Unable to open debug log file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + std::cin.rdbuf(&l.in); + std::cout.rdbuf(&l.out); + } } - } }; -} // namespace +} // namespace // engine_info() returns the full name of the current Stockfish version. @@ -152,36 +159,36 @@ public: // Stockfish version std::string engine_info(bool to_uci) { - std::stringstream ss; - ss << "Stockfish " << version << std::setfill('0'); + std::stringstream ss; + ss << "Stockfish " << version << std::setfill('0'); - if constexpr (version == "dev") - { - ss << "-"; - #ifdef GIT_DATE - ss << stringify(GIT_DATE); - #else - constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); - std::string month, day, year; - std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" + if constexpr (version == "dev") + { + ss << "-"; +#ifdef GIT_DATE + ss << stringify(GIT_DATE); +#else + constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + std::string month, day, year; + std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" - date >> month >> day >> year; - ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) << std::setw(2) << std::setfill('0') << day; - #endif + date >> month >> day >> year; + ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) + << std::setw(2) << std::setfill('0') << day; +#endif - ss << "-"; + ss << "-"; - #ifdef GIT_SHA - ss << stringify(GIT_SHA); - #else - ss << "nogit"; - #endif - } +#ifdef GIT_SHA + ss << stringify(GIT_SHA); +#else + ss << "nogit"; +#endif + } - ss << (to_uci ? "\nid author ": " by ") - << "the Stockfish developers (see AUTHORS file)"; + ss << (to_uci ? "\nid author " : " by ") << "the Stockfish developers (see AUTHORS file)"; - return ss.str(); + return ss.str(); } @@ -189,119 +196,118 @@ std::string engine_info(bool to_uci) { std::string compiler_info() { - #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) +#define make_version_string(major, minor, patch) \ + stringify(major) "." stringify(minor) "." stringify(patch) -// Predefined macros hell: -// -// __GNUC__ Compiler is GCC, Clang or ICX -// __clang__ Compiler is Clang or ICX -// __INTEL_LLVM_COMPILER Compiler is ICX -// _MSC_VER Compiler is MSVC -// _WIN32 Building on Windows (any) -// _WIN64 Building on Windows 64 bit + // Predefined macros hell: + // + // __GNUC__ Compiler is GCC, Clang or ICX + // __clang__ Compiler is Clang or ICX + // __INTEL_LLVM_COMPILER Compiler is ICX + // _MSC_VER Compiler is MSVC + // _WIN32 Building on Windows (any) + // _WIN64 Building on Windows 64 bit - std::string compiler = "\nCompiled by : "; + std::string compiler = "\nCompiled by : "; - #if defined(__INTEL_LLVM_COMPILER) - compiler += "ICX "; - compiler += stringify(__INTEL_LLVM_COMPILER); - #elif defined(__clang__) - compiler += "clang++ "; - compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); - #elif _MSC_VER - compiler += "MSVC "; - compiler += "(version "; - compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); - compiler += ")"; - #elif defined(__e2k__) && defined(__LCC__) +#if defined(__INTEL_LLVM_COMPILER) + compiler += "ICX "; + compiler += stringify(__INTEL_LLVM_COMPILER); +#elif defined(__clang__) + compiler += "clang++ "; + compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); +#elif _MSC_VER + compiler += "MSVC "; + compiler += "(version "; + compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); + compiler += ")"; +#elif defined(__e2k__) && defined(__LCC__) #define dot_ver2(n) \ - compiler += char('.'); \ - compiler += char('0' + (n) / 10); \ - compiler += char('0' + (n) % 10); + compiler += char('.'); \ + compiler += char('0' + (n) / 10); \ + compiler += char('0' + (n) % 10); - compiler += "MCST LCC "; - compiler += "(version "; - compiler += std::to_string(__LCC__ / 100); - dot_ver2(__LCC__ % 100) - dot_ver2(__LCC_MINOR__) - compiler += ")"; - #elif __GNUC__ - compiler += "g++ (GNUC) "; - compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); - #else - compiler += "Unknown compiler "; - compiler += "(unknown version)"; - #endif + compiler += "MCST LCC "; + compiler += "(version "; + compiler += std::to_string(__LCC__ / 100); + dot_ver2(__LCC__ % 100) dot_ver2(__LCC_MINOR__) compiler += ")"; +#elif __GNUC__ + compiler += "g++ (GNUC) "; + compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); +#else + compiler += "Unknown compiler "; + compiler += "(unknown version)"; +#endif - #if defined(__APPLE__) - compiler += " on Apple"; - #elif defined(__CYGWIN__) - compiler += " on Cygwin"; - #elif defined(__MINGW64__) - compiler += " on MinGW64"; - #elif defined(__MINGW32__) - compiler += " on MinGW32"; - #elif defined(__ANDROID__) - compiler += " on Android"; - #elif defined(__linux__) - compiler += " on Linux"; - #elif defined(_WIN64) - compiler += " on Microsoft Windows 64-bit"; - #elif defined(_WIN32) - compiler += " on Microsoft Windows 32-bit"; - #else - compiler += " on unknown system"; - #endif +#if defined(__APPLE__) + compiler += " on Apple"; +#elif defined(__CYGWIN__) + compiler += " on Cygwin"; +#elif defined(__MINGW64__) + compiler += " on MinGW64"; +#elif defined(__MINGW32__) + compiler += " on MinGW32"; +#elif defined(__ANDROID__) + compiler += " on Android"; +#elif defined(__linux__) + compiler += " on Linux"; +#elif defined(_WIN64) + compiler += " on Microsoft Windows 64-bit"; +#elif defined(_WIN32) + compiler += " on Microsoft Windows 32-bit"; +#else + compiler += " on unknown system"; +#endif - compiler += "\nCompilation architecture : "; - #if defined(ARCH) - compiler += stringify(ARCH); - #else - compiler += "(undefined architecture)"; - #endif + compiler += "\nCompilation architecture : "; +#if defined(ARCH) + compiler += stringify(ARCH); +#else + compiler += "(undefined architecture)"; +#endif - compiler += "\nCompilation settings : "; - compiler += (Is64Bit ? "64bit" : "32bit"); - #if defined(USE_VNNI) + compiler += "\nCompilation settings : "; + compiler += (Is64Bit ? "64bit" : "32bit"); +#if defined(USE_VNNI) compiler += " VNNI"; - #endif - #if defined(USE_AVX512) +#endif +#if defined(USE_AVX512) compiler += " AVX512"; - #endif - compiler += (HasPext ? " BMI2" : ""); - #if defined(USE_AVX2) +#endif + compiler += (HasPext ? " BMI2" : ""); +#if defined(USE_AVX2) compiler += " AVX2"; - #endif - #if defined(USE_SSE41) +#endif +#if defined(USE_SSE41) compiler += " SSE41"; - #endif - #if defined(USE_SSSE3) +#endif +#if defined(USE_SSSE3) compiler += " SSSE3"; - #endif - #if defined(USE_SSE2) +#endif +#if defined(USE_SSE2) compiler += " SSE2"; - #endif - compiler += (HasPopCnt ? " POPCNT" : ""); - #if defined(USE_NEON_DOTPROD) +#endif + compiler += (HasPopCnt ? " POPCNT" : ""); +#if defined(USE_NEON_DOTPROD) compiler += " NEON_DOTPROD"; - #elif defined(USE_NEON) +#elif defined(USE_NEON) compiler += " NEON"; - #endif +#endif - #if !defined(NDEBUG) +#if !defined(NDEBUG) compiler += " DEBUG"; - #endif +#endif - compiler += "\nCompiler __VERSION__ macro : "; - #ifdef __VERSION__ - compiler += __VERSION__; - #else - compiler += "(undefined macro)"; - #endif + compiler += "\nCompiler __VERSION__ macro : "; +#ifdef __VERSION__ + compiler += __VERSION__; +#else + compiler += "(undefined macro)"; +#endif - compiler += "\n"; + compiler += "\n"; - return compiler; + return compiler; } @@ -312,7 +318,7 @@ namespace { template struct DebugInfo { - std::atomic data[N] = { 0 }; + std::atomic data[N] = {0}; constexpr inline std::atomic& operator[](int index) { return data[index]; } }; @@ -357,42 +363,34 @@ void dbg_correl_of(int64_t value1, int64_t value2, int slot) { void dbg_print() { int64_t n; - auto E = [&n](int64_t x) { return double(x) / n; }; - auto sqr = [](double x) { return x * x; }; + auto E = [&n](int64_t x) { return double(x) / n; }; + auto sqr = [](double x) { return x * x; }; for (int i = 0; i < MaxDebugSlots; ++i) if ((n = hit[i][0])) - std::cerr << "Hit #" << i - << ": Total " << n << " Hits " << hit[i][1] - << " Hit Rate (%) " << 100.0 * E(hit[i][1]) - << std::endl; + std::cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1] + << " Hit Rate (%) " << 100.0 * E(hit[i][1]) << std::endl; for (int i = 0; i < MaxDebugSlots; ++i) if ((n = mean[i][0])) { - std::cerr << "Mean #" << i - << ": Total " << n << " Mean " << E(mean[i][1]) - << std::endl; + std::cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) << std::endl; } for (int i = 0; i < MaxDebugSlots; ++i) if ((n = stdev[i][0])) { double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1]))); - std::cerr << "Stdev #" << i - << ": Total " << n << " Stdev " << r - << std::endl; + std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl; } for (int i = 0; i < MaxDebugSlots; ++i) if ((n = correl[i][0])) { double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3])) - / ( sqrt(E(correl[i][2]) - sqr(E(correl[i][1]))) - * sqrt(E(correl[i][4]) - sqr(E(correl[i][3])))); - std::cerr << "Correl. #" << i - << ": Total " << n << " Coefficient " << r - << std::endl; + / (sqrt(E(correl[i][2]) - sqr(E(correl[i][1]))) + * sqrt(E(correl[i][4]) - sqr(E(correl[i][3])))); + std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::endl; } } @@ -402,15 +400,15 @@ void dbg_print() { std::ostream& operator<<(std::ostream& os, SyncCout sc) { - static std::mutex m; + static std::mutex m; - if (sc == IO_LOCK) - m.lock(); + if (sc == IO_LOCK) + m.lock(); - if (sc == IO_UNLOCK) - m.unlock(); + if (sc == IO_UNLOCK) + m.unlock(); - return os; + return os; } @@ -429,11 +427,11 @@ void prefetch(void*) {} void prefetch(void* addr) { -# if defined(_MSC_VER) - _mm_prefetch((char*)addr, _MM_HINT_T0); -# else - __builtin_prefetch(addr); -# endif + #if defined(_MSC_VER) + _mm_prefetch((char*) addr, _MM_HINT_T0); + #else + __builtin_prefetch(addr); + #endif } #endif @@ -446,27 +444,27 @@ void prefetch(void* addr) { void* std_aligned_alloc(size_t alignment, size_t size) { #if defined(POSIXALIGNEDALLOC) - void *mem; - return posix_memalign(&mem, alignment, size) ? nullptr : mem; + void* mem; + return posix_memalign(&mem, alignment, size) ? nullptr : mem; #elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) - return _mm_malloc(size, alignment); + return _mm_malloc(size, alignment); #elif defined(_WIN32) - return _aligned_malloc(size, alignment); + return _aligned_malloc(size, alignment); #else - return std::aligned_alloc(alignment, size); + return std::aligned_alloc(alignment, size); #endif } void std_aligned_free(void* ptr) { #if defined(POSIXALIGNEDALLOC) - free(ptr); + free(ptr); #elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) - _mm_free(ptr); + _mm_free(ptr); #elif defined(_WIN32) - _aligned_free(ptr); + _aligned_free(ptr); #else - free(ptr); + free(ptr); #endif } @@ -476,104 +474,104 @@ void std_aligned_free(void* ptr) { static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { - #if !defined(_WIN64) + #if !defined(_WIN64) return nullptr; - #else + #else - HANDLE hProcessToken { }; - LUID luid { }; - void* mem = nullptr; + HANDLE hProcessToken{}; + LUID luid{}; + void* mem = nullptr; - const size_t largePageSize = GetLargePageMinimum(); - if (!largePageSize) - return nullptr; + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return nullptr; - // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges - HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); - if (!hAdvapi32) - hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - auto fun6 = fun6_t((void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken")); - if (!fun6) - return nullptr; - auto fun7 = fun7_t((void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); - if (!fun7) - return nullptr; - auto fun8 = fun8_t((void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); - if (!fun8) - return nullptr; + auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!fun6) + return nullptr; + auto fun7 = fun7_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!fun7) + return nullptr; + auto fun8 = fun8_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!fun8) + return nullptr; - // We need SeLockMemoryPrivilege, so try to enable it for the process - if (!fun6( // OpenProcessToken() - GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) - return nullptr; + // We need SeLockMemoryPrivilege, so try to enable it for the process + if (!fun6( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; - if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) - nullptr, "SeLockMemoryPrivilege", &luid)) - { - TOKEN_PRIVILEGES tp { }; - TOKEN_PRIVILEGES prevTp { }; - DWORD prevTpLen = 0; + if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) + nullptr, "SeLockMemoryPrivilege", &luid)) + { + TOKEN_PRIVILEGES tp{}; + TOKEN_PRIVILEGES prevTp{}; + DWORD prevTpLen = 0; - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, - // we still need to query GetLastError() to ensure that the privileges were actually obtained. - if (fun8( // AdjustTokenPrivileges() - hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && - GetLastError() == ERROR_SUCCESS) - { - // Round up size to full pages and allocate - allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); - mem = VirtualAlloc( - nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, + // we still need to query GetLastError() to ensure that the privileges were actually obtained. + if (fun8( // AdjustTokenPrivileges() + hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) + && GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, + PAGE_READWRITE); - // Privilege no longer needed, restore previous state - fun8( // AdjustTokenPrivileges () + // Privilege no longer needed, restore previous state + fun8( // AdjustTokenPrivileges () hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); - } - } + } + } - CloseHandle(hProcessToken); + CloseHandle(hProcessToken); - return mem; + return mem; - #endif + #endif } void* aligned_large_pages_alloc(size_t allocSize) { - // Try to allocate large pages - void* mem = aligned_large_pages_alloc_windows(allocSize); + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_windows(allocSize); - // Fall back to regular, page-aligned, allocation if necessary - if (!mem) - mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + // Fall back to regular, page-aligned, allocation if necessary + if (!mem) + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - return mem; + return mem; } #else void* aligned_large_pages_alloc(size_t allocSize) { -#if defined(__linux__) - constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size -#else - constexpr size_t alignment = 4096; // assumed small page size -#endif + #if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size + #else + constexpr size_t alignment = 4096; // assumed small page size + #endif - // round up to multiples of alignment - size_t size = ((allocSize + alignment - 1) / alignment) * alignment; - void *mem = std_aligned_alloc(alignment, size); -#if defined(MADV_HUGEPAGE) - madvise(mem, size, MADV_HUGEPAGE); -#endif - return mem; + // round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void* mem = std_aligned_alloc(alignment, size); + #if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); + #endif + return mem; } #endif @@ -585,21 +583,18 @@ void* aligned_large_pages_alloc(size_t allocSize) { void aligned_large_pages_free(void* mem) { - if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) - { - DWORD err = GetLastError(); - std::cerr << "Failed to free large page memory. Error code: 0x" - << std::hex << err - << std::dec << std::endl; - exit(EXIT_FAILURE); - } + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err + << std::dec << std::endl; + exit(EXIT_FAILURE); + } } #else -void aligned_large_pages_free(void *mem) { - std_aligned_free(mem); -} +void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } #endif @@ -618,69 +613,69 @@ void bindThisThread(size_t) {} static int best_node(size_t idx) { - int threads = 0; - int nodes = 0; - int cores = 0; - DWORD returnLength = 0; - DWORD byteOffset = 0; + int threads = 0; + int nodes = 0; + int cores = 0; + DWORD returnLength = 0; + DWORD byteOffset = 0; - // Early exit if the needed API is not available at runtime - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx"); - if (!fun1) - return -1; + // Early exit if the needed API is not available at runtime + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto fun1 = (fun1_t) (void (*)()) GetProcAddress(k32, "GetLogicalProcessorInformationEx"); + if (!fun1) + return -1; - // First call to GetLogicalProcessorInformationEx() to get returnLength. - // We expect the call to fail due to null buffer. - if (fun1(RelationAll, nullptr, &returnLength)) - return -1; + // First call to GetLogicalProcessorInformationEx() to get returnLength. + // We expect the call to fail due to null buffer. + if (fun1(RelationAll, nullptr, &returnLength)) + return -1; - // Once we know returnLength, allocate the buffer - SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; - ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength); + // Once we know returnLength, allocate the buffer + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; + ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) malloc(returnLength); - // Second call to GetLogicalProcessorInformationEx(), now we expect to succeed - if (!fun1(RelationAll, buffer, &returnLength)) - { - free(buffer); - return -1; - } + // Second call to GetLogicalProcessorInformationEx(), now we expect to succeed + if (!fun1(RelationAll, buffer, &returnLength)) + { + free(buffer); + return -1; + } - while (byteOffset < returnLength) - { - if (ptr->Relationship == RelationNumaNode) - nodes++; + while (byteOffset < returnLength) + { + if (ptr->Relationship == RelationNumaNode) + nodes++; - else if (ptr->Relationship == RelationProcessorCore) - { - cores++; - threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; - } + else if (ptr->Relationship == RelationProcessorCore) + { + cores++; + threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; + } - assert(ptr->Size); - byteOffset += ptr->Size; - ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); - } + assert(ptr->Size); + byteOffset += ptr->Size; + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) (((char*) ptr) + ptr->Size); + } - free(buffer); + free(buffer); - std::vector groups; + std::vector groups; - // Run as many threads as possible on the same node until the core limit is - // reached, then move on to filling the next node. - for (int n = 0; n < nodes; n++) - for (int i = 0; i < cores / nodes; i++) - groups.push_back(n); + // Run as many threads as possible on the same node until the core limit is + // reached, then move on to filling the next node. + for (int n = 0; n < nodes; n++) + for (int i = 0; i < cores / nodes; i++) + groups.push_back(n); - // In case a core has more than one logical processor (we assume 2) and we - // have still threads to allocate, then spread them evenly across available - // nodes. - for (int t = 0; t < threads - cores; t++) - groups.push_back(t % nodes); + // In case a core has more than one logical processor (we assume 2) and we + // have still threads to allocate, then spread them evenly across available + // nodes. + for (int t = 0; t < threads - cores; t++) + groups.push_back(t % nodes); - // If we still have more threads than the total number of logical processors - // then return -1 and let the OS to decide what to do. - return idx < groups.size() ? groups[idx] : -1; + // If we still have more threads than the total number of logical processors + // then return -1 and let the OS to decide what to do. + return idx < groups.size() ? groups[idx] : -1; } @@ -688,58 +683,59 @@ static int best_node(size_t idx) { void bindThisThread(size_t idx) { - // Use only local variables to be thread-safe - int node = best_node(idx); + // Use only local variables to be thread-safe + int node = best_node(idx); - if (node == -1) - return; + if (node == -1) + return; - // Early exit if the needed API are not available at runtime - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun2 = fun2_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx")); - auto fun3 = fun3_t((void(*)())GetProcAddress(k32, "SetThreadGroupAffinity")); - auto fun4 = fun4_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2")); - auto fun5 = fun5_t((void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount")); + // Early exit if the needed API are not available at runtime + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto fun2 = fun2_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMaskEx")); + auto fun3 = fun3_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity")); + auto fun4 = fun4_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMask2")); + auto fun5 = fun5_t((void (*)()) GetProcAddress(k32, "GetMaximumProcessorGroupCount")); - if (!fun2 || !fun3) - return; + if (!fun2 || !fun3) + return; - if (!fun4 || !fun5) - { - GROUP_AFFINITY affinity; - if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx - fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity - } - else - { - // If a numa node has more than one processor group, we assume they are - // sized equal and we spread threads evenly across the groups. - USHORT elements, returnedElements; - elements = fun5(); // GetMaximumProcessorGroupCount - GROUP_AFFINITY *affinity = (GROUP_AFFINITY*)malloc(elements * sizeof(GROUP_AFFINITY)); - if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2 - fun3(GetCurrentThread(), &affinity[idx % returnedElements], nullptr); // SetThreadGroupAffinity - free(affinity); - } + if (!fun4 || !fun5) + { + GROUP_AFFINITY affinity; + if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx + fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity + } + else + { + // If a numa node has more than one processor group, we assume they are + // sized equal and we spread threads evenly across the groups. + USHORT elements, returnedElements; + elements = fun5(); // GetMaximumProcessorGroupCount + GROUP_AFFINITY* affinity = (GROUP_AFFINITY*) malloc(elements * sizeof(GROUP_AFFINITY)); + if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2 + fun3(GetCurrentThread(), &affinity[idx % returnedElements], + nullptr); // SetThreadGroupAffinity + free(affinity); + } } #endif -} // namespace WinProcGroup +} // namespace WinProcGroup #ifdef _WIN32 -#include -#define GETCWD _getcwd + #include + #define GETCWD _getcwd #else -#include -#define GETCWD getcwd + #include + #define GETCWD getcwd #endif namespace CommandLine { -std::string argv0; // path+name of the executable binary, as given by argv[0] -std::string binaryDirectory; // path of the executable directory -std::string workingDirectory; // path of the working directory +std::string argv0; // path+name of the executable binary, as given by argv[0] +std::string binaryDirectory; // path of the executable directory +std::string workingDirectory; // path of the working directory void init([[maybe_unused]] int argc, char* argv[]) { std::string pathSeparator; @@ -749,27 +745,27 @@ void init([[maybe_unused]] int argc, char* argv[]) { #ifdef _WIN32 pathSeparator = "\\"; - #ifdef _MSC_VER + #ifdef _MSC_VER // Under windows argv[0] may not have the extension. Also _get_pgmptr() had // issues in some Windows 10 versions, so check returned values carefully. char* pgmptr = nullptr; if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) argv0 = pgmptr; - #endif + #endif #else pathSeparator = "/"; #endif // extract the working directory workingDirectory = ""; - char buff[40000]; + char buff[40000]; char* cwd = GETCWD(buff, 40000); if (cwd) workingDirectory = cwd; // extract the binary directory path from argv0 binaryDirectory = argv0; - size_t pos = binaryDirectory.find_last_of("\\/"); + size_t pos = binaryDirectory.find_last_of("\\/"); if (pos == std::string::npos) binaryDirectory = "." + pathSeparator; else @@ -781,6 +777,6 @@ void init([[maybe_unused]] int argc, char* argv[]) { } -} // namespace CommandLine +} // namespace CommandLine -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index 60602048..3cd3315a 100644 --- a/src/misc.h +++ b/src/misc.h @@ -33,12 +33,13 @@ namespace Stockfish { std::string engine_info(bool to_uci = false); std::string compiler_info(); -void prefetch(void* addr); -void start_logger(const std::string& fname); -void* std_aligned_alloc(size_t alignment, size_t size); -void std_aligned_free(void* ptr); -void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes -void aligned_large_pages_free(void* mem); // nop if mem == nullptr +void prefetch(void* addr); +void start_logger(const std::string& fname); +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); +void* aligned_large_pages_alloc( + size_t size); // memory aligned by page size, min alignment: 4096 bytes +void aligned_large_pages_free(void* mem); // nop if mem == nullptr void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); @@ -46,15 +47,19 @@ void dbg_stdev_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); -using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds +using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); inline TimePoint now() { - return std::chrono::duration_cast - (std::chrono::steady_clock::now().time_since_epoch()).count(); + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); } -enum SyncCout { IO_LOCK, IO_UNLOCK }; +enum SyncCout { + IO_LOCK, + IO_UNLOCK +}; std::ostream& operator<<(std::ostream&, SyncCout); #define sync_cout std::cout << IO_LOCK @@ -64,34 +69,37 @@ std::ostream& operator<<(std::ostream&, SyncCout); // align_ptr_up() : get the first aligned element of an array. // ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, // where N is the number of elements in the array. -template -T* align_ptr_up(T* ptr) -{ - static_assert(alignof(T) < Alignment); +template +T* align_ptr_up(T* ptr) { + static_assert(alignof(T) < Alignment); - const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); - return reinterpret_cast(reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast( + reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); } // IsLittleEndian : 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 union { + uint32_t i; + char c[4]; +} Le = {0x01020304}; static inline const bool IsLittleEndian = (Le.c[0] == 4); -template +template class ValueList { -public: - std::size_t size() const { return size_; } - void push_back(const T& value) { values_[size_++] = value; } - const T* begin() const { return values_; } - const T* end() const { return values_ + size_; } - const T& operator[](int index) const { return values_[index]; } + public: + std::size_t size() const { return size_; } + void push_back(const T& value) { values_[size_++] = value; } + const T* begin() const { return values_; } + const T* end() const { return values_ + size_; } + const T& operator[](int index) const { return values_[index]; } -private: - T values_[MaxSize]; - std::size_t size_ = 0; + private: + T values_[MaxSize]; + std::size_t size_ = 0; }; @@ -112,23 +120,31 @@ private: class PRNG { - uint64_t s; + uint64_t s; - uint64_t rand64() { + uint64_t rand64() { - s ^= s >> 12, s ^= s << 25, s ^= s >> 27; - return s * 2685821657736338717LL; - } + s ^= s >> 12, s ^= s << 25, s ^= s >> 27; + return s * 2685821657736338717LL; + } -public: - PRNG(uint64_t seed) : s(seed) { assert(seed); } + public: + PRNG(uint64_t seed) : + s(seed) { + assert(seed); + } - template T rand() { return T(rand64()); } + template + T rand() { + return T(rand64()); + } - // Special generator used to fast init magic numbers. - // Output values only have 1/8th of their bits set on average. - template T sparse_rand() - { return T(rand64() & rand64() & rand64()); } + // Special generator used to fast init magic numbers. + // Output values only have 1/8th of their bits set on average. + template + T sparse_rand() { + return T(rand64() & rand64() & rand64()); + } }; inline uint64_t mul_hi64(uint64_t a, uint64_t b) { @@ -152,16 +168,16 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { // Peter Österlund. namespace WinProcGroup { - void bindThisThread(size_t idx); +void bindThisThread(size_t idx); } namespace CommandLine { - void init(int argc, char* argv[]); +void init(int argc, char* argv[]); - extern std::string binaryDirectory; // path of the executable directory - extern std::string workingDirectory; // path of the working directory +extern std::string binaryDirectory; // path of the executable directory +extern std::string workingDirectory; // path of the working directory } -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MISC_H_INCLUDED +#endif // #ifndef MISC_H_INCLUDED diff --git a/src/movegen.cpp b/src/movegen.cpp index 82ad6061..cf457d11 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -28,8 +28,8 @@ namespace Stockfish { namespace { - template - ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { +template +ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { @@ -50,33 +50,32 @@ namespace { } return moveList; - } +} - template - ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { +template +ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { constexpr Color Them = ~Us; - constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); - constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); constexpr Direction Up = pawn_push(Us); constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); const Bitboard emptySquares = ~pos.pieces(); - const Bitboard enemies = Type == EVASIONS ? pos.checkers() - : pos.pieces(Them); + const Bitboard enemies = Type == EVASIONS ? pos.checkers() : pos.pieces(Them); - Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; + Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; // Single and double pawn pushes, no promotions if constexpr (Type != CAPTURES) { - Bitboard b1 = shift(pawnsNotOn7) & emptySquares; + Bitboard b1 = shift(pawnsNotOn7) & emptySquares; Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; - if constexpr (Type == EVASIONS) // Consider only blocking squares + if constexpr (Type == EVASIONS) // Consider only blocking squares { b1 &= target; b2 &= target; @@ -87,21 +86,21 @@ namespace { // To make a quiet check, you either make a direct check by pushing a pawn // or push a blocker pawn that is not on the same file as the enemy king. // Discovered check promotion has been already generated amongst the captures. - Square ksq = pos.square(Them); + Square ksq = pos.square(Them); Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); - b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns); - b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); + b1 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); + b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); } while (b1) { - Square to = pop_lsb(b1); + Square to = pop_lsb(b1); *moveList++ = make_move(to - Up, to); } while (b2) { - Square to = pop_lsb(b2); + Square to = pop_lsb(b2); *moveList++ = make_move(to - Up - Up, to); } } @@ -110,8 +109,8 @@ namespace { if (pawnsOn7) { Bitboard b1 = shift(pawnsOn7) & enemies; - Bitboard b2 = shift(pawnsOn7) & enemies; - Bitboard b3 = shift(pawnsOn7) & emptySquares; + Bitboard b2 = shift(pawnsOn7) & enemies; + Bitboard b3 = shift(pawnsOn7) & emptySquares; if constexpr (Type == EVASIONS) b3 &= target; @@ -123,24 +122,24 @@ namespace { moveList = make_promotions(moveList, pop_lsb(b2)); while (b3) - moveList = make_promotions(moveList, pop_lsb(b3)); + moveList = make_promotions(moveList, pop_lsb(b3)); } // Standard and en passant captures if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { Bitboard b1 = shift(pawnsNotOn7) & enemies; - Bitboard b2 = shift(pawnsNotOn7) & enemies; + Bitboard b2 = shift(pawnsNotOn7) & enemies; while (b1) { - Square to = pop_lsb(b1); + Square to = pop_lsb(b1); *moveList++ = make_move(to - UpRight, to); } while (b2) { - Square to = pop_lsb(b2); + Square to = pop_lsb(b2); *moveList++ = make_move(to - UpLeft, to); } @@ -162,11 +161,11 @@ namespace { } return moveList; - } +} - template - ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { +template +ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); @@ -174,8 +173,8 @@ namespace { while (bb) { - Square from = pop_lsb(bb); - Bitboard b = attacks_bb(from, pos.pieces()) & target; + Square from = pop_lsb(bb); + Bitboard b = attacks_bb(from, pos.pieces()) & target; // To check, you either move freely a blocker or make a direct check. if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from))) @@ -186,31 +185,31 @@ namespace { } return moveList; - } +} - template - ExtMove* generate_all(const Position& pos, ExtMove* moveList) { +template +ExtMove* generate_all(const Position& pos, ExtMove* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate_all()"); - constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations - const Square ksq = pos.square(Us); - Bitboard target; + constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations + const Square ksq = pos.square(Us); + Bitboard target; // Skip generating non-king moves when in double check if (Type != EVASIONS || !more_than_one(pos.checkers())) { - target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) - : Type == NON_EVASIONS ? ~pos.pieces( Us) - : Type == CAPTURES ? pos.pieces(~Us) - : ~pos.pieces( ); // QUIETS || QUIET_CHECKS + target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) + : Type == NON_EVASIONS ? ~pos.pieces(Us) + : Type == CAPTURES ? pos.pieces(~Us) + : ~pos.pieces(); // QUIETS || QUIET_CHECKS moveList = generate_pawn_moves(pos, moveList, target); moveList = generate_moves(pos, moveList, target); moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); } if (!Checks || pos.blockers_for_king(~Us) & ksq) @@ -223,15 +222,15 @@ namespace { *moveList++ = make_move(ksq, pop_lsb(b)); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) - for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) + for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) *moveList++ = make(ksq, pos.castling_rook_square(cr)); } return moveList; - } +} -} // namespace +} // namespace // Generates all pseudo-legal captures plus queen promotions @@ -246,13 +245,13 @@ namespace { template ExtMove* generate(const Position& pos, ExtMove* moveList) { - static_assert(Type != LEGAL, "Unsupported type in generate()"); - assert((Type == EVASIONS) == bool(pos.checkers())); + static_assert(Type != LEGAL, "Unsupported type in generate()"); + assert((Type == EVASIONS) == bool(pos.checkers())); - Color us = pos.side_to_move(); + Color us = pos.side_to_move(); - return us == WHITE ? generate_all(pos, moveList) - : generate_all(pos, moveList); + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); } // Explicit template instantiations @@ -268,21 +267,21 @@ template ExtMove* generate(const Position&, ExtMove*); template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { - Color us = pos.side_to_move(); - Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); - Square ksq = pos.square(us); - ExtMove* cur = moveList; + Color us = pos.side_to_move(); + Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); + Square ksq = pos.square(us); + ExtMove* cur = moveList; - moveList = pos.checkers() ? generate(pos, moveList) - : generate(pos, moveList); - while (cur != moveList) - if ( ((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) - && !pos.legal(*cur)) - *cur = (--moveList)->move; - else - ++cur; + moveList = + pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); + while (cur != moveList) + if (((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) + && !pos.legal(*cur)) + *cur = (--moveList)->move; + else + ++cur; - return moveList; + return moveList; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/movegen.h b/src/movegen.h index e913a13e..9a39d1c5 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -19,7 +19,7 @@ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED -#include // IWYU pragma: keep +#include // IWYU pragma: keep #include #include "types.h" @@ -29,29 +29,27 @@ namespace Stockfish { class Position; enum GenType { - CAPTURES, - QUIETS, - QUIET_CHECKS, - EVASIONS, - NON_EVASIONS, - LEGAL + CAPTURES, + QUIETS, + QUIET_CHECKS, + EVASIONS, + NON_EVASIONS, + LEGAL }; struct ExtMove { - Move move; - int value; + Move move; + int value; - operator Move() const { return move; } - void operator=(Move m) { move = m; } + operator Move() const { return move; } + void operator=(Move m) { move = m; } - // Inhibit unwanted implicit conversions to Move - // with an ambiguity that yields to a compile error. - operator float() const = delete; + // Inhibit unwanted implicit conversions to Move + // with an ambiguity that yields to a compile error. + operator float() const = delete; }; -inline bool operator<(const ExtMove& f, const ExtMove& s) { - return f.value < s.value; -} +inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; } template ExtMove* generate(const Position& pos, ExtMove* moveList); @@ -62,18 +60,17 @@ ExtMove* generate(const Position& pos, ExtMove* moveList); template struct MoveList { - explicit MoveList(const Position& pos) : last(generate(pos, moveList)) {} - const ExtMove* begin() const { return moveList; } - const ExtMove* end() const { return last; } - size_t size() const { return last - moveList; } - bool contains(Move move) const { - return std::find(begin(), end(), move) != end(); - } + explicit MoveList(const Position& pos) : + last(generate(pos, moveList)) {} + const ExtMove* begin() const { return moveList; } + const ExtMove* end() const { return last; } + size_t size() const { return last - moveList; } + bool contains(Move move) const { return std::find(begin(), end(), move) != end(); } -private: - ExtMove moveList[MAX_MOVES], *last; + private: + ExtMove moveList[MAX_MOVES], *last; }; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MOVEGEN_H_INCLUDED +#endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp index 5bb0fd6c..41ad0dd6 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -30,29 +30,50 @@ namespace Stockfish { namespace { - enum Stages { - MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE, - EVASION_TT, EVASION_INIT, EVASION, - PROBCUT_TT, PROBCUT_INIT, PROBCUT, - QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK - }; +enum Stages { + // generate main search moves + MAIN_TT, + CAPTURE_INIT, + GOOD_CAPTURE, + REFUTATION, + QUIET_INIT, + QUIET, + BAD_CAPTURE, - // partial_insertion_sort() sorts moves in descending order up to and including - // a given limit. The order of moves smaller than the limit is left unspecified. - void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { + // generate evasion moves + EVASION_TT, + EVASION_INIT, + EVASION, + + // generate probcut moves + PROBCUT_TT, + PROBCUT_INIT, + PROBCUT, + + // generate qsearch moves + QSEARCH_TT, + QCAPTURE_INIT, + QCAPTURE, + QCHECK_INIT, + QCHECK +}; + +// partial_insertion_sort() sorts moves in descending order up to and including +// a given limit. The order of moves smaller than the limit is left unspecified. +void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) if (p->value >= limit) { ExtMove tmp = *p, *q; - *p = *++sortedEnd; + *p = *++sortedEnd; for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) *q = *(q - 1); *q = tmp; } - } +} -} // namespace +} // namespace // Constructors of the MovePicker class. As arguments, we pass information @@ -62,44 +83,57 @@ namespace { // move ordering is at the current node. // MovePicker constructor for the main search -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, - const PieceToHistory** ch, - Move cm, - const Move* killers) - : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), - ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) -{ - assert(d > 0); +MovePicker::MovePicker(const Position& p, + Move ttm, + Depth d, + const ButterflyHistory* mh, + const CapturePieceToHistory* cph, + const PieceToHistory** ch, + Move cm, + const Move* killers) : + pos(p), + mainHistory(mh), + captureHistory(cph), + continuationHistory(ch), + ttMove(ttm), + refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, + depth(d) { + assert(d > 0); - stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + - !(ttm && pos.pseudo_legal(ttm)); + stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); } // MovePicker constructor for quiescence search -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, - const PieceToHistory** ch, - Square rs) - : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) -{ - assert(d <= 0); +MovePicker::MovePicker(const Position& p, + Move ttm, + Depth d, + const ButterflyHistory* mh, + const CapturePieceToHistory* cph, + const PieceToHistory** ch, + Square rs) : + pos(p), + mainHistory(mh), + captureHistory(cph), + continuationHistory(ch), + ttMove(ttm), + recaptureSquare(rs), + depth(d) { + assert(d <= 0); - stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + - !( ttm - && pos.pseudo_legal(ttm)); + stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); } // MovePicker constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) - : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) -{ - assert(!pos.checkers()); +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : + pos(p), + captureHistory(cph), + ttMove(ttm), + threshold(th) { + assert(!pos.checkers()); - stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) - && pos.pseudo_legal(ttm) - && pos.see_ge(ttm, threshold)); + stage = PROBCUT_TT + + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); } // MovePicker::score() assigns a numerical value to each move in a list, used @@ -108,76 +142,78 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece template void MovePicker::score() { - static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); + static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, threatenedPieces; - if constexpr (Type == QUIETS) - { - Color us = pos.side_to_move(); + [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, + threatenedPieces; + if constexpr (Type == QUIETS) + { + Color us = pos.side_to_move(); - threatenedByPawn = pos.attacks_by(~us); - threatenedByMinor = pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; - threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; + threatenedByPawn = pos.attacks_by(~us); + threatenedByMinor = + pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; + threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; - // Pieces threatened by pieces of lesser material value - threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) - | (pos.pieces(us, ROOK) & threatenedByMinor) - | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); - } + // Pieces threatened by pieces of lesser material value + threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) + | (pos.pieces(us, ROOK) & threatenedByMinor) + | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); + } - for (auto& m : *this) - if constexpr (Type == CAPTURES) - m.value = (7 * int(PieceValue[pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; + for (auto& m : *this) + if constexpr (Type == CAPTURES) + m.value = + (7 * int(PieceValue[pos.piece_on(to_sq(m))]) + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) + / 16; - else if constexpr (Type == QUIETS) - { - Piece pc = pos.moved_piece(m); - PieceType pt = type_of(pos.moved_piece(m)); - Square from = from_sq(m); - Square to = to_sq(m); + else if constexpr (Type == QUIETS) + { + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pos.moved_piece(m)); + Square from = from_sq(m); + Square to = to_sq(m); - // histories - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; - m.value += 2 * (*continuationHistory[0])[pc][to]; - m.value += (*continuationHistory[1])[pc][to]; - m.value += (*continuationHistory[2])[pc][to] / 4; - m.value += (*continuationHistory[3])[pc][to]; - m.value += (*continuationHistory[5])[pc][to]; + // histories + m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value += 2 * (*continuationHistory[0])[pc][to]; + m.value += (*continuationHistory[1])[pc][to]; + m.value += (*continuationHistory[2])[pc][to] / 4; + m.value += (*continuationHistory[3])[pc][to]; + m.value += (*continuationHistory[5])[pc][to]; - // bonus for checks - m.value += bool(pos.check_squares(pt) & to) * 16384; + // bonus for checks + m.value += bool(pos.check_squares(pt) & to) * 16384; - // bonus for escaping from capture - m.value += threatenedPieces & from ? - (pt == QUEEN && !(to & threatenedByRook) ? 50000 - : pt == ROOK && !(to & threatenedByMinor) ? 25000 - : !(to & threatenedByPawn) ? 15000 - : 0 ) - : 0 ; + // bonus for escaping from capture + m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 50000 + : pt == ROOK && !(to & threatenedByMinor) ? 25000 + : !(to & threatenedByPawn) ? 15000 + : 0) + : 0; - // malus for putting piece en prise - m.value -= !(threatenedPieces & from) ? - (pt == QUEEN ? bool(to & threatenedByRook) * 50000 - + bool(to & threatenedByMinor) * 10000 - + bool(to & threatenedByPawn) * 20000 - : pt == ROOK ? bool(to & threatenedByMinor) * 25000 - + bool(to & threatenedByPawn) * 10000 - : pt != PAWN ? bool(to & threatenedByPawn) * 15000 - : 0 ) - : 0 ; - } + // malus for putting piece en prise + m.value -= !(threatenedPieces & from) + ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 + + bool(to & threatenedByMinor) * 10000 + + bool(to & threatenedByPawn) * 20000 + : pt == ROOK ? bool(to & threatenedByMinor) * 25000 + + bool(to & threatenedByPawn) * 10000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0) + : 0; + } - else // Type == EVASIONS - { - if (pos.capture_stage(m)) - m.value = PieceValue[pos.piece_on(to_sq(m))] - - Value(type_of(pos.moved_piece(m))) - + (1 << 28); - else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]; - } + else // Type == EVASIONS + { + if (pos.capture_stage(m)) + m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + + (1 << 28); + else + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]; + } } // MovePicker::select() returns the next move satisfying a predicate function. @@ -185,17 +221,17 @@ void MovePicker::score() { template Move MovePicker::select(Pred filter) { - while (cur < endMoves) - { - if constexpr (T == Best) - std::swap(*cur, *std::max_element(cur, endMoves)); + while (cur < endMoves) + { + if constexpr (T == Best) + std::swap(*cur, *std::max_element(cur, endMoves)); - if (*cur != ttMove && filter()) - return *cur++; + if (*cur != ttMove && filter()) + return *cur++; - cur++; - } - return MOVE_NONE; + cur++; + } + return MOVE_NONE; } // MovePicker::next_move() is the most important method of the MovePicker class. It @@ -204,122 +240,126 @@ Move MovePicker::select(Pred filter) { Move MovePicker::next_move(bool skipQuiets) { top: - switch (stage) { + switch (stage) + { - case MAIN_TT: - case EVASION_TT: - case QSEARCH_TT: - case PROBCUT_TT: - ++stage; - return ttMove; + case MAIN_TT : + case EVASION_TT : + case QSEARCH_TT : + case PROBCUT_TT : + ++stage; + return ttMove; - case CAPTURE_INIT: - case PROBCUT_INIT: - case QCAPTURE_INIT: - cur = endBadCaptures = moves; - endMoves = generate(pos, cur); + case CAPTURE_INIT : + case PROBCUT_INIT : + case QCAPTURE_INIT : + cur = endBadCaptures = moves; + endMoves = generate(pos, cur); - score(); - partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); - ++stage; - goto top; + score(); + partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); + ++stage; + goto top; - case GOOD_CAPTURE: - if (select([&](){ - return pos.see_ge(*cur, Value(-cur->value)) ? - // Move losing capture to endBadCaptures to be tried later - true : (*endBadCaptures++ = *cur, false); })) - return *(cur - 1); + case GOOD_CAPTURE : + if (select([&]() { + return pos.see_ge(*cur, Value(-cur->value)) + ? + // Move losing capture to endBadCaptures to be tried later + true + : (*endBadCaptures++ = *cur, false); + })) + return *(cur - 1); - // Prepare the pointers to loop over the refutations array - cur = std::begin(refutations); - endMoves = std::end(refutations); + // Prepare the pointers to loop over the refutations array + cur = std::begin(refutations); + endMoves = std::end(refutations); - // If the countermove is the same as a killer, skip it - if ( refutations[0].move == refutations[2].move - || refutations[1].move == refutations[2].move) - --endMoves; + // If the countermove is the same as a killer, skip it + if (refutations[0].move == refutations[2].move + || refutations[1].move == refutations[2].move) + --endMoves; - ++stage; - [[fallthrough]]; + ++stage; + [[fallthrough]]; - case REFUTATION: - if (select([&](){ return *cur != MOVE_NONE - && !pos.capture_stage(*cur) - && pos.pseudo_legal(*cur); })) - return *(cur - 1); - ++stage; - [[fallthrough]]; + case REFUTATION : + if (select([&]() { + return *cur != MOVE_NONE && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); + })) + return *(cur - 1); + ++stage; + [[fallthrough]]; - case QUIET_INIT: - if (!skipQuiets) - { - cur = endBadCaptures; - endMoves = generate(pos, cur); + case QUIET_INIT : + if (!skipQuiets) + { + cur = endBadCaptures; + endMoves = generate(pos, cur); - score(); - partial_insertion_sort(cur, endMoves, -3000 * depth); - } + score(); + partial_insertion_sort(cur, endMoves, -3000 * depth); + } - ++stage; - [[fallthrough]]; + ++stage; + [[fallthrough]]; - case QUIET: - if ( !skipQuiets - && select([&](){return *cur != refutations[0].move - && *cur != refutations[1].move - && *cur != refutations[2].move;})) - return *(cur - 1); + case QUIET : + if (!skipQuiets && select([&]() { + return *cur != refutations[0].move && *cur != refutations[1].move + && *cur != refutations[2].move; + })) + return *(cur - 1); - // Prepare the pointers to loop over the bad captures - cur = moves; - endMoves = endBadCaptures; + // Prepare the pointers to loop over the bad captures + cur = moves; + endMoves = endBadCaptures; - ++stage; - [[fallthrough]]; + ++stage; + [[fallthrough]]; - case BAD_CAPTURE: - return select([](){ return true; }); + case BAD_CAPTURE : + return select([]() { return true; }); - case EVASION_INIT: - cur = moves; - endMoves = generate(pos, cur); + case EVASION_INIT : + cur = moves; + endMoves = generate(pos, cur); - score(); - ++stage; - [[fallthrough]]; + score(); + ++stage; + [[fallthrough]]; - case EVASION: - return select([](){ return true; }); + case EVASION : + return select([]() { return true; }); - case PROBCUT: - return select([&](){ return pos.see_ge(*cur, threshold); }); + case PROBCUT : + return select([&]() { return pos.see_ge(*cur, threshold); }); - case QCAPTURE: - if (select([&](){ return depth > DEPTH_QS_RECAPTURES - || to_sq(*cur) == recaptureSquare; })) - return *(cur - 1); + case QCAPTURE : + if (select( + [&]() { return depth > DEPTH_QS_RECAPTURES || to_sq(*cur) == recaptureSquare; })) + return *(cur - 1); - // If we did not find any move and we do not try checks, we have finished - if (depth != DEPTH_QS_CHECKS) - return MOVE_NONE; + // If we did not find any move and we do not try checks, we have finished + if (depth != DEPTH_QS_CHECKS) + return MOVE_NONE; - ++stage; - [[fallthrough]]; + ++stage; + [[fallthrough]]; - case QCHECK_INIT: - cur = moves; - endMoves = generate(pos, cur); + case QCHECK_INIT : + cur = moves; + endMoves = generate(pos, cur); - ++stage; - [[fallthrough]]; + ++stage; + [[fallthrough]]; - case QCHECK: - return select([](){ return true; }); - } + case QCHECK : + return select([]() { return true; }); + } - assert(false); - return MOVE_NONE; // Silence warning + assert(false); + return MOVE_NONE; // Silence warning } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 457defa5..65e93dda 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -24,7 +24,7 @@ #include #include #include -#include // IWYU pragma: keep +#include // IWYU pragma: keep #include "movegen.h" #include "types.h" @@ -39,22 +39,22 @@ class Position; template class StatsEntry { - T entry; + T entry; -public: - void operator=(const T& v) { entry = v; } - T* operator&() { return &entry; } - T* operator->() { return &entry; } - operator const T&() const { return entry; } + public: + void operator=(const T& v) { entry = v; } + T* operator&() { return &entry; } + T* operator->() { return &entry; } + operator const T&() const { return entry; } - void operator<<(int bonus) { - assert(abs(bonus) <= D); // Ensure range is [-D, D] - static_assert(D <= std::numeric_limits::max(), "D overflows T"); + void operator<<(int bonus) { + assert(abs(bonus) <= D); // Ensure range is [-D, D] + static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += (bonus * D - entry * abs(bonus)) / (D * 5 / 4); + entry += (bonus * D - entry * abs(bonus)) / (D * 5 / 4); - assert(abs(entry) <= D); - } + assert(abs(entry) <= D); + } }; // Stats is a generic N-dimensional array used to store various statistics. @@ -62,28 +62,32 @@ public: // template parameter D limits the range of updates in [-D, D] when we update // values with the << operator, while the last parameters (Size and Sizes) // encode the dimensions of the array. -template -struct Stats : public std::array, Size> -{ - using stats = Stats; +template +struct Stats: public std::array, Size> { + using stats = Stats; - void fill(const T& v) { + void fill(const T& v) { - // For standard-layout 'this' points to the first struct member - assert(std::is_standard_layout_v); + // For standard-layout 'this' points to the first struct member + assert(std::is_standard_layout_v); - using entry = StatsEntry; - entry* p = reinterpret_cast(this); - std::fill(p, p + sizeof(*this) / sizeof(entry), v); - } + using entry = StatsEntry; + entry* p = reinterpret_cast(this); + std::fill(p, p + sizeof(*this) / sizeof(entry), v); + } }; -template -struct Stats : public std::array, Size> {}; +template +struct Stats: public std::array, Size> {}; // In stats table, D=0 means that the template parameter is not used -enum StatsParams { NOT_USED = 0 }; -enum StatsType { NoCaptures, Captures }; +enum StatsParams { + NOT_USED = 0 +}; +enum StatsType { + NoCaptures, + Captures +}; // ButterflyHistory records how often quiet moves have been successful or // unsuccessful during the current search, and is used for reduction and move @@ -117,42 +121,53 @@ using ContinuationHistory = Stats // likely to get a cut-off first. class MovePicker { - enum PickType { Next, Best }; + enum PickType { + Next, + Best + }; -public: - MovePicker(const MovePicker&) = delete; - MovePicker& operator=(const MovePicker&) = delete; - MovePicker(const Position&, Move, Depth, const ButterflyHistory*, - const CapturePieceToHistory*, - const PieceToHistory**, - Move, - const Move*); - MovePicker(const Position&, Move, Depth, const ButterflyHistory*, - const CapturePieceToHistory*, - const PieceToHistory**, - Square); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); - Move next_move(bool skipQuiets = false); + public: + MovePicker(const MovePicker&) = delete; + MovePicker& operator=(const MovePicker&) = delete; + MovePicker(const Position&, + Move, + Depth, + const ButterflyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + Move, + const Move*); + MovePicker(const Position&, + Move, + Depth, + const ButterflyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + Square); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + Move next_move(bool skipQuiets = false); -private: - template Move select(Pred); - template void score(); - ExtMove* begin() { return cur; } - ExtMove* end() { return endMoves; } + private: + template + Move select(Pred); + template + void score(); + ExtMove* begin() { return cur; } + ExtMove* end() { return endMoves; } - const Position& pos; - const ButterflyHistory* mainHistory; - const CapturePieceToHistory* captureHistory; - const PieceToHistory** continuationHistory; - Move ttMove; - ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; - int stage; - Square recaptureSquare; - Value threshold; - Depth depth; - ExtMove moves[MAX_MOVES]; + const Position& pos; + const ButterflyHistory* mainHistory; + const CapturePieceToHistory* captureHistory; + const PieceToHistory** continuationHistory; + Move ttMove; + ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; + int stage; + Square recaptureSquare; + Value threshold; + Depth depth; + ExtMove moves[MAX_MOVES]; }; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MOVEPICK_H_INCLUDED +#endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 1f821cf9..679192d4 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -39,136 +39,144 @@ namespace Stockfish::Eval::NNUE { - // Input feature converter - LargePagePtr featureTransformer; +// Input feature converter +LargePagePtr featureTransformer; - // Evaluation function - AlignedPtr network[LayerStacks]; +// Evaluation function +AlignedPtr network[LayerStacks]; - // Evaluation function file name - std::string fileName; - std::string netDescription; +// Evaluation function file name +std::string fileName; +std::string netDescription; - namespace Detail { +namespace Detail { - // Initialize the evaluation function parameters - template - void initialize(AlignedPtr& pointer) { +// Initialize the evaluation function parameters +template +void initialize(AlignedPtr& pointer) { pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); - } +} - template - void initialize(LargePagePtr& pointer) { +template +void initialize(LargePagePtr& pointer) { - static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); - } +} - // Read evaluation function parameters - template - bool read_parameters(std::istream& stream, T& reference) { +// Read evaluation function parameters +template +bool read_parameters(std::istream& stream, T& reference) { std::uint32_t header; header = read_little_endian(stream); - if (!stream || header != T::get_hash_value()) return false; + if (!stream || header != T::get_hash_value()) + return false; return reference.read_parameters(stream); - } +} - // Write evaluation function parameters - template - bool write_parameters(std::ostream& stream, const T& reference) { +// Write evaluation function parameters +template +bool write_parameters(std::ostream& stream, const T& reference) { write_little_endian(stream, T::get_hash_value()); return reference.write_parameters(stream); - } +} - } // namespace Detail +} // namespace Detail - // Initialize the evaluation function parameters - static void initialize() { +// Initialize the evaluation function parameters +static void initialize() { Detail::initialize(featureTransformer); for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(network[i]); - } + Detail::initialize(network[i]); +} - // Read network header - static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) - { +// Read network header +static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { std::uint32_t version, size; - version = read_little_endian(stream); - *hashValue = read_little_endian(stream); - size = read_little_endian(stream); - if (!stream || version != Version) return false; + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; desc->resize(size); stream.read(&(*desc)[0], size); return !stream.fail(); - } +} - // Write network header - static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) - { +// Write network header +static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { write_little_endian(stream, Version); write_little_endian(stream, hashValue); - write_little_endian(stream, (std::uint32_t)desc.size()); + write_little_endian(stream, (std::uint32_t) desc.size()); stream.write(&desc[0], desc.size()); return !stream.fail(); - } +} - // Read network parameters - static bool read_parameters(std::istream& stream) { +// Read network parameters +static bool read_parameters(std::istream& stream) { std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) return false; - if (hashValue != HashValue) return false; - if (!Detail::read_parameters(stream, *featureTransformer)) return false; + if (!read_header(stream, &hashValue, &netDescription)) + return false; + if (hashValue != HashValue) + return false; + if (!Detail::read_parameters(stream, *featureTransformer)) + return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::read_parameters(stream, *(network[i]))) return false; + if (!Detail::read_parameters(stream, *(network[i]))) + return false; return stream && stream.peek() == std::ios::traits_type::eof(); - } +} - // Write network parameters - static bool write_parameters(std::ostream& stream) { +// Write network parameters +static bool write_parameters(std::ostream& stream) { - if (!write_header(stream, HashValue, netDescription)) return false; - if (!Detail::write_parameters(stream, *featureTransformer)) return false; + if (!write_header(stream, HashValue, netDescription)) + return false; + if (!Detail::write_parameters(stream, *featureTransformer)) + return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::write_parameters(stream, *(network[i]))) return false; + if (!Detail::write_parameters(stream, *(network[i]))) + return false; return bool(stream); - } +} - void hint_common_parent_position(const Position& pos) { +void hint_common_parent_position(const Position& pos) { featureTransformer->hint_common_access(pos); - } +} - // Evaluation function. Perform differential calculation. - Value evaluate(const Position& pos, bool adjusted, int* complexity) { +// Evaluation function. Perform differential calculation. +Value evaluate(const Position& pos, bool adjusted, int* complexity) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; - constexpr int delta = 24; + constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned[ - FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) - TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); + const int bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); const auto positional = network[bucket]->propagate(transformedFeatures); if (complexity) @@ -176,158 +184,164 @@ namespace Stockfish::Eval::NNUE { // Give more value to positional evaluation when adjusted flag is set if (adjusted) - return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) / (1024 * OutputScale)); + return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) + / (1024 * OutputScale)); else return static_cast((psqt + positional) / OutputScale); - } +} - struct NnueEvalTrace { +struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); - Value psqt[LayerStacks]; - Value positional[LayerStacks]; + Value psqt[LayerStacks]; + Value positional[LayerStacks]; std::size_t correctBucket; - }; +}; - static NnueEvalTrace trace_evaluate(const Position& pos) { +static NnueEvalTrace trace_evaluate(const Position& pos) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned[ - FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) - TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); NnueEvalTrace t{}; t.correctBucket = (pos.count() - 1) / 4; - for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { - const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) + { + const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket); + const auto positional = network[bucket]->propagate(transformedFeatures); - t.psqt[bucket] = static_cast( materialist / OutputScale ); - t.positional[bucket] = static_cast( positional / OutputScale ); + t.psqt[bucket] = static_cast(materialist / OutputScale); + t.positional[bucket] = static_cast(positional / OutputScale); } return t; - } +} - constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); - // format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. - // The buffer must have capacity for at least 5 chars. - static void format_cp_compact(Value v, char* buffer) { +// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +static void format_cp_compact(Value v, char* buffer) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); int cp = std::abs(UCI::to_cp(v)); if (cp >= 10000) { - buffer[1] = '0' + cp / 10000; cp %= 10000; - buffer[2] = '0' + cp / 1000; cp %= 1000; + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; buffer[3] = '0' + cp / 100; buffer[4] = ' '; } else if (cp >= 1000) { - buffer[1] = '0' + cp / 1000; cp %= 1000; - buffer[2] = '0' + cp / 100; cp %= 100; + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; buffer[3] = '.'; buffer[4] = '0' + cp / 10; } else { - buffer[1] = '0' + cp / 100; cp %= 100; + buffer[1] = '0' + cp / 100; + cp %= 100; buffer[2] = '.'; - buffer[3] = '0' + cp / 10; cp %= 10; + buffer[3] = '0' + cp / 10; + cp %= 10; buffer[4] = '0' + cp / 1; } - } +} - // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals - static void format_cp_aligned_dot(Value v, std::stringstream &stream) { +// format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals +static void format_cp_aligned_dot(Value v, std::stringstream& stream) { const double pawns = std::abs(0.01 * UCI::to_cp(v)); - stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') - << std::setiosflags(std::ios::fixed) - << std::setw(6) - << std::setprecision(2) - << pawns; - } + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} - // trace() returns a string with the value of each piece on a board, - // and a table for (PSQT, Layers) values bucket by bucket. - std::string trace(Position& pos) { +// trace() returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string trace(Position& pos) { std::stringstream ss; - char board[3*8+1][8*8+2]; + char board[3 * 8 + 1][8 * 8 + 2]; std::memset(board, ' ', sizeof(board)); - for (int row = 0; row < 3*8+1; ++row) - board[row][8*8+1] = '\0'; + for (int row = 0; row < 3 * 8 + 1; ++row) + board[row][8 * 8 + 1] = '\0'; // A lambda to output one box of the board auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - - const int x = int(file) * 8; - const int y = (7 - int(rank)) * 3; - for (int i = 1; i < 8; ++i) - board[y][x+i] = board[y+3][x+i] = '-'; - for (int i = 1; i < 3; ++i) - board[y+i][x] = board[y+i][x+8] = '|'; - board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+'; - if (pc != NO_PIECE) - board[y+1][x+4] = PieceToChar[pc]; - if (value != VALUE_NONE) - format_cp_compact(value, &board[y+2][x+2]); + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y + 2][x + 2]); }; // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. Value base = evaluate(pos); - base = pos.side_to_move() == WHITE ? base : -base; + base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= FILE_H; ++f) - for (Rank r = RANK_1; r <= RANK_8; ++r) - { - Square sq = make_square(f, r); - Piece pc = pos.piece_on(sq); - Value v = VALUE_NONE; - - if (pc != NO_PIECE && type_of(pc) != KING) + for (Rank r = RANK_1; r <= RANK_8; ++r) { - auto st = pos.state(); + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; - pos.remove_piece(sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + if (pc != NO_PIECE && type_of(pc) != KING) + { + auto st = pos.state(); - Value eval = evaluate(pos); - eval = pos.side_to_move() == WHITE ? eval : -eval; - v = base - eval; + pos.remove_piece(sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; - pos.put_piece(pc, sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + Value eval = evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + } + + writeSquare(f, r, pc, v); } - writeSquare(f, r, pc, v); - } - ss << " NNUE derived piece values:\n"; - for (int row = 0; row < 3*8+1; ++row) + for (int row = 0; row < 3 * 8 + 1; ++row) ss << board[row] << '\n'; ss << '\n'; @@ -342,41 +356,47 @@ namespace Stockfish::Eval::NNUE { for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { - ss << "| " << bucket << " "; - ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss); ss << " " - << " | "; format_cp_aligned_dot(t.positional[bucket], ss); ss << " " - << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); ss << " " - << " |"; - if (bucket == t.correctBucket) - ss << " <-- this bucket is used"; - ss << '\n'; + ss << "| " << bucket << " "; + ss << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + ss << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; } ss << "+------------+------------+------------+------------+\n"; return ss.str(); - } +} - // Load eval, from a file stream or a memory stream - bool load_eval(std::string name, std::istream& stream) { +// Load eval, from a file stream or a memory stream +bool load_eval(std::string name, std::istream& stream) { initialize(); fileName = name; return read_parameters(stream); - } +} - // Save eval, to a file stream or a memory stream - bool save_eval(std::ostream& stream) { +// Save eval, to a file stream or a memory stream +bool save_eval(std::ostream& stream) { if (fileName.empty()) - return false; + return false; return write_parameters(stream); - } +} - // Save eval, to a file given by its name - bool save_eval(const std::optional& filename) { +// Save eval, to a file given by its name +bool save_eval(const std::optional& filename) { std::string actualFilename; std::string msg; @@ -387,23 +407,23 @@ namespace Stockfish::Eval::NNUE { { if (currentEvalFileName != EvalFileDefaultName) { - msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; + msg = + "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; - sync_cout << msg << sync_endl; - return false; + sync_cout << msg << sync_endl; + return false; } actualFilename = EvalFileDefaultName; } std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream); + bool saved = save_eval(stream); - msg = saved ? "Network saved successfully to " + actualFilename - : "Failed to export a net"; + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; sync_cout << msg << sync_endl; return saved; - } +} -} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 8faec6cc..6edc212f 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -32,48 +32,48 @@ #include "nnue_feature_transformer.h" namespace Stockfish { - class Position; - enum Value : int; +class Position; +enum Value : int; } namespace Stockfish::Eval::NNUE { - // Hash value of evaluation function structure - constexpr std::uint32_t HashValue = - FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); +// Hash value of evaluation function structure +constexpr std::uint32_t HashValue = + FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); - // Deleter for automating release of memory area - template - struct AlignedDeleter { +// Deleter for automating release of memory area +template +struct AlignedDeleter { void operator()(T* ptr) const { - ptr->~T(); - std_aligned_free(ptr); + ptr->~T(); + std_aligned_free(ptr); } - }; +}; - template - struct LargePageDeleter { +template +struct LargePageDeleter { void operator()(T* ptr) const { - ptr->~T(); - aligned_large_pages_free(ptr); + ptr->~T(); + aligned_large_pages_free(ptr); } - }; +}; - template - using AlignedPtr = std::unique_ptr>; +template +using AlignedPtr = std::unique_ptr>; - template - using LargePagePtr = std::unique_ptr>; +template +using LargePagePtr = std::unique_ptr>; - std::string trace(Position& pos); - Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); - void hint_common_parent_position(const Position& pos); +std::string trace(Position& pos); +Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); +void hint_common_parent_position(const Position& pos); - bool load_eval(std::string name, std::istream& stream); - bool save_eval(std::ostream& stream); - bool save_eval(const std::optional& filename); +bool load_eval(std::string name, std::istream& stream); +bool save_eval(std::ostream& stream); +bool save_eval(const std::optional& filename); } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED +#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 016934b8..6c3fdfdb 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -27,61 +27,60 @@ namespace Stockfish::Eval::NNUE::Features { - // Index of a feature for a given king position and another piece on some square - template - inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + KingBuckets[Perspective][ksq]); - } +// Index of a feature for a given king position and another piece on some square +template +inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { + return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + + KingBuckets[Perspective][ksq]); +} - // Get a list of indices for active features - template - void HalfKAv2_hm::append_active_indices( - const Position& pos, - IndexList& active - ) { - Square ksq = pos.square(Perspective); - Bitboard bb = pos.pieces(); +// Get a list of indices for active features +template +void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { + Square ksq = pos.square(Perspective); + Bitboard bb = pos.pieces(); while (bb) { - Square s = pop_lsb(bb); - active.push_back(make_index(s, pos.piece_on(s), ksq)); + Square s = pop_lsb(bb); + active.push_back(make_index(s, pos.piece_on(s), ksq)); } - } +} - // Explicit template instantiations - template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); - template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +// Explicit template instantiations +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); - // append_changed_indices() : get a list of indices for recently changed features - template - void HalfKAv2_hm::append_changed_indices( - Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added - ) { - for (int i = 0; i < dp.dirty_num; ++i) { - if (dp.from[i] != SQ_NONE) - removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); - if (dp.to[i] != SQ_NONE) - added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); +// append_changed_indices() : get a list of indices for recently changed features +template +void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added) { + for (int i = 0; i < dp.dirty_num; ++i) + { + if (dp.from[i] != SQ_NONE) + removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); + if (dp.to[i] != SQ_NONE) + added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); } - } +} - // Explicit template instantiations - template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); +// Explicit template instantiations +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); - int HalfKAv2_hm::update_cost(const StateInfo* st) { - return st->dirtyPiece.dirty_num; - } +int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; } - int HalfKAv2_hm::refresh_cost(const Position& pos) { - return pos.count(); - } +int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count(); } - bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { +bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { return st->dirtyPiece.piece[0] == make_piece(perspective, KING); - } +} } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 9da1cc05..540ff895 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -28,41 +28,40 @@ #include "../nnue_common.h" namespace Stockfish { - struct StateInfo; - class Position; +struct StateInfo; +class Position; } namespace Stockfish::Eval::NNUE::Features { - // Feature HalfKAv2_hm: Combination of the position of own king - // and the position of pieces. Position mirrored such that king always on e..h files. - class HalfKAv2_hm { +// Feature HalfKAv2_hm: Combination of the position of own king +// and the position of pieces. Position mirrored such that king always on e..h files. +class HalfKAv2_hm { // unique number for each piece type on each square enum { - PS_NONE = 0, - PS_W_PAWN = 0, - PS_B_PAWN = 1 * SQUARE_NB, - PS_W_KNIGHT = 2 * SQUARE_NB, - PS_B_KNIGHT = 3 * SQUARE_NB, - PS_W_BISHOP = 4 * SQUARE_NB, - PS_B_BISHOP = 5 * SQUARE_NB, - PS_W_ROOK = 6 * SQUARE_NB, - PS_B_ROOK = 7 * SQUARE_NB, - PS_W_QUEEN = 8 * SQUARE_NB, - PS_B_QUEEN = 9 * SQUARE_NB, - PS_KING = 10 * SQUARE_NB, - PS_NB = 11 * SQUARE_NB + PS_NONE = 0, + PS_W_PAWN = 0, + PS_B_PAWN = 1 * SQUARE_NB, + PS_W_KNIGHT = 2 * SQUARE_NB, + PS_B_KNIGHT = 3 * SQUARE_NB, + PS_W_BISHOP = 4 * SQUARE_NB, + PS_B_BISHOP = 5 * SQUARE_NB, + PS_W_ROOK = 6 * SQUARE_NB, + PS_B_ROOK = 7 * SQUARE_NB, + PS_W_QUEEN = 8 * SQUARE_NB, + PS_B_QUEEN = 9 * SQUARE_NB, + PS_KING = 10 * SQUARE_NB, + PS_NB = 11 * SQUARE_NB }; static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { // convention: W - us, B - them // viewed from other side, W and B are reversed - { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, - PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE }, - { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, - PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE } - }; + {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE}, + {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}}; // Index of a feature for a given king position and another piece on some square template @@ -77,9 +76,10 @@ namespace Stockfish::Eval::NNUE::Features { // Number of feature dimensions static constexpr IndexType Dimensions = - static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; + static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; #define B(v) (v * PS_NB) + // clang-format off static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = { { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), @@ -98,8 +98,9 @@ namespace Stockfish::Eval::NNUE::Features { B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) } }; + // clang-format on #undef B - + // clang-format off // Orient a square according to perspective (rotates by 180 for black) static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, @@ -119,25 +120,20 @@ namespace Stockfish::Eval::NNUE::Features { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 } }; + // clang-format on // Maximum number of simultaneously active features. static constexpr IndexType MaxActiveDimensions = 32; - using IndexList = ValueList; + using IndexList = ValueList; // Get a list of indices for active features template - static void append_active_indices( - const Position& pos, - IndexList& active); + static void append_active_indices(const Position& pos, IndexList& active); // Get a list of indices for recently changed features template - static void append_changed_indices( - Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added - ); + static void + append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); // Returns the cost of updating one perspective, the most costly one. // Assumes no refresh needed. @@ -147,8 +143,8 @@ namespace Stockfish::Eval::NNUE::Features { // Returns whether the change stored in this StateInfo means that // a full accumulator refresh is required. static bool requires_refresh(const StateInfo* st, Color perspective); - }; +}; } // namespace Stockfish::Eval::NNUE::Features -#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED +#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index fc65c343..3fba45ed 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -42,95 +42,102 @@ namespace Stockfish::Eval::NNUE::Layers { // Fallback implementation for older/other architectures. // Requires the input to be padded to at least 16 values. #if !defined(USE_SSSE3) - template - static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) - { -# if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) -# if defined(USE_SSE2) +template +static void affine_transform_non_ssse3(std::int32_t* output, + const std::int8_t* weights, + const std::int32_t* biases, + const std::uint8_t* input) { + #if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) + #if defined(USE_SSE2) // At least a multiple of 16, with SSE2. - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const __m128i Zeros = _mm_setzero_si128(); - const auto inputVector = reinterpret_cast(input); + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const __m128i Zeros = _mm_setzero_si128(); + const auto inputVector = reinterpret_cast(input); -# elif defined(USE_NEON_DOTPROD) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const auto inputVector = reinterpret_cast(input); + #elif defined(USE_NEON_DOTPROD) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); -# elif defined(USE_NEON) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const auto inputVector = reinterpret_cast(input); -# endif + #elif defined(USE_NEON) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); + #endif - for (IndexType i = 0; i < OutputDimensions; ++i) { - const IndexType offset = i * PaddedInputDimensions; + for (IndexType i = 0; i < OutputDimensions; ++i) + { + const IndexType offset = i * PaddedInputDimensions; -# if defined(USE_SSE2) - __m128i sumLo = _mm_cvtsi32_si128(biases[i]); - __m128i sumHi = Zeros; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - __m128i row_j = _mm_load_si128(&row[j]); - __m128i input_j = _mm_load_si128(&inputVector[j]); - __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); - __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); - __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); - __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); - __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); - __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); - sumLo = _mm_add_epi32(sumLo, productLo); - sumHi = _mm_add_epi32(sumHi, productHi); - } - __m128i sum = _mm_add_epi32(sumLo, sumHi); - __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); - sum = _mm_add_epi32(sum, sumHigh_64); - __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); - sum = _mm_add_epi32(sum, sum_second_32); - output[i] = _mm_cvtsi128_si32(sum); + #if defined(USE_SSE2) + __m128i sumLo = _mm_cvtsi32_si128(biases[i]); + __m128i sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m128i row_j = _mm_load_si128(&row[j]); + __m128i input_j = _mm_load_si128(&inputVector[j]); + __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); + __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); + __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); + __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); + __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); + __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_epi32(sumLo, productLo); + sumHi = _mm_add_epi32(sumHi, productHi); + } + __m128i sum = _mm_add_epi32(sumLo, sumHi); + __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sumHigh_64); + __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sum_second_32); + output[i] = _mm_cvtsi128_si32(sum); -# elif defined(USE_NEON_DOTPROD) - int32x4_t sum = {biases[i]}; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - sum = vdotq_s32(sum, inputVector[j], row[j]); - } - output[i] = vaddvq_s32(sum); + #elif defined(USE_NEON_DOTPROD) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + sum = vdotq_s32(sum, inputVector[j], row[j]); + } + output[i] = vaddvq_s32(sum); -# elif defined(USE_NEON) - int32x4_t sum = {biases[i]}; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); - 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]; + #elif defined(USE_NEON) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); + 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]; -# endif + #endif } -# else - std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); + #else + std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); - // Traverse weights in transpose order to take advantage of input sparsity - for (IndexType i = 0; i < InputDimensions; ++i) - if (input[i]) { - const std::int8_t* w = &weights[i]; - const int in = input[i]; - for (IndexType j = 0; j < OutputDimensions; ++j) - output[j] += w[j * PaddedInputDimensions] * in; - } -# endif - } + // Traverse weights in transpose order to take advantage of input sparsity + for (IndexType i = 0; i < InputDimensions; ++i) + if (input[i]) + { + const std::int8_t* w = &weights[i]; + const int in = input[i]; + for (IndexType j = 0; j < OutputDimensions; ++j) + output[j] += w[j * PaddedInputDimensions] * in; + } + #endif +} #endif - template - class AffineTransform { +template +class AffineTransform { public: // Input/output type - using InputType = std::uint8_t; + using InputType = std::uint8_t; using OutputType = std::int32_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = OutDims; static constexpr IndexType PaddedInputDimensions = @@ -142,175 +149,168 @@ namespace Stockfish::Eval::NNUE::Layers { // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; } - static constexpr IndexType get_weight_index_scrambled(IndexType i) - { - return - (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + - i / PaddedInputDimensions * 4 + - i % 4; + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + i % 4; } - static constexpr IndexType get_weight_index(IndexType i) - { -#if defined (USE_SSSE3) - return get_weight_index_scrambled(i); + static constexpr IndexType get_weight_index(IndexType i) { +#if defined(USE_SSSE3) + return get_weight_index_scrambled(i); #else - return i; + return i; #endif } // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); + write_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); - return !stream.fail(); + return !stream.fail(); } // Forward propagation - void propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { -#if defined (USE_SSSE3) +#if defined(USE_SSSE3) - if constexpr (OutputDimensions > 1) - { - -#if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_setzero _mm512_setzero_si512 - #define vec_set_32 _mm512_set1_epi32 - #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 - #define vec_hadd Simd::m512_hadd -#elif defined (USE_AVX2) - using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 - #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_hadd Simd::m256_hadd -#elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 - #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::m128_hadd -#endif - - static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); - - static_assert(OutputDimensions % OutputSimdWidth == 0); - - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; - constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; - - const auto input32 = reinterpret_cast(input); - const vec_t* biasvec = reinterpret_cast(biases); - vec_t acc[NumRegs]; - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasvec[k]; - - for (IndexType i = 0; i < NumChunks; i += 2) + if constexpr (OutputDimensions > 1) { - const vec_t in0 = vec_set_32(input32[i + 0]); - const vec_t in1 = vec_set_32(input32[i + 1]); - const auto col0 = reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); - const auto col1 = reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]); + + #if defined(USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 + #define vec_hadd Simd::m512_hadd + #elif defined(USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 + #define vec_hadd Simd::m256_hadd + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 + #define vec_hadd Simd::m128_hadd + #endif + + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + + static_assert(OutputDimensions % OutputSimdWidth == 0); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + + const auto input32 = reinterpret_cast(input); + const vec_t* biasvec = reinterpret_cast(biases); + vec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType i = 0; i < NumChunks; i += 2) + { + const vec_t in0 = vec_set_32(input32[i + 0]); + const vec_t in1 = vec_set_32(input32[i + 1]); + const auto col0 = + reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); + const auto col1 = + reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]); + } + + vec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + + #undef vec_setzero + #undef vec_set_32 + #undef vec_add_dpbusd_32 + #undef vec_add_dpbusd_32x2 + #undef vec_hadd } - - vec_t* outptr = reinterpret_cast(output); - for (IndexType k = 0; k < NumRegs; ++k) - outptr[k] = acc[k]; - -# undef vec_setzero -# undef vec_set_32 -# undef vec_add_dpbusd_32 -# undef vec_add_dpbusd_32x2 -# undef vec_hadd - - } - else if constexpr (OutputDimensions == 1) - { - -// We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements. -#if defined (USE_AVX2) - using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 - #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_hadd Simd::m256_hadd -#elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 - #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::m128_hadd -#endif - - const auto inputVector = reinterpret_cast(input); - - static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); - - static_assert(PaddedInputDimensions % InputSimdWidth == 0); - - constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; - vec_t sum0 = vec_setzero(); - const auto row0 = reinterpret_cast(&weights[0]); - - for (int j = 0; j < int(NumChunks); ++j) + else if constexpr (OutputDimensions == 1) { - const vec_t in = inputVector[j]; - vec_add_dpbusd_32(sum0, in, row0[j]); + + // We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements. + #if defined(USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 + #define vec_hadd Simd::m256_hadd + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 + #define vec_hadd Simd::m128_hadd + #endif + + const auto inputVector = reinterpret_cast(input); + + static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); + + static_assert(PaddedInputDimensions % InputSimdWidth == 0); + + constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; + vec_t sum0 = vec_setzero(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < int(NumChunks); ++j) + { + const vec_t in = inputVector[j]; + vec_add_dpbusd_32(sum0, in, row0[j]); + } + output[0] = vec_hadd(sum0, biases[0]); + + #undef vec_setzero + #undef vec_set_32 + #undef vec_add_dpbusd_32 + #undef vec_add_dpbusd_32x2 + #undef vec_hadd } - output[0] = vec_hadd(sum0, biases[0]); - -# undef vec_setzero -# undef vec_set_32 -# undef vec_add_dpbusd_32 -# undef vec_add_dpbusd_32x2 -# undef vec_hadd - - } #else - // Use old implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); + // Use old implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); #endif } private: - using BiasType = OutputType; + using BiasType = OutputType; using WeightType = std::int8_t; alignas(CacheLineSize) BiasType biases[OutputDimensions]; alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 1dc42109..6cb4d1a9 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -38,104 +38,110 @@ namespace Stockfish::Eval::NNUE::Layers { #if (USE_SSSE3 | (USE_NEON >= 8)) - alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ - std::array, 256> v{}; - for (unsigned i = 0; i < 256; ++i) - { - std::uint64_t j = i, k = 0; - while(j) - v[i][k++] = pop_lsb(j); - } - return v; +alignas(CacheLineSize) static inline const + std::array, 256> lookup_indices = []() { + std::array, 256> v{}; + for (unsigned i = 0; i < 256; ++i) + { + std::uint64_t j = i, k = 0; + while (j) + v[i][k++] = pop_lsb(j); + } + return v; }(); - // Find indices of nonzero numbers in an int32_t array - template - void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { -#if defined (USE_SSSE3) - #if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) - #elif defined (USE_AVX2) - using vec_t = __m256i; - #if defined(USE_VNNI) && !defined(USE_AVXVNNI) - #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) - #else - #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) +// Find indices of nonzero numbers in an int32_t array +template +void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { + #if defined(USE_SSSE3) + #if defined(USE_AVX512) + using vec_t = __m512i; + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) + #elif defined(USE_AVX2) + using vec_t = __m256i; + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) \ + _mm256_movemask_ps( \ + _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_nnz(a) \ + _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) #endif - #elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) - #endif using vec128_t = __m128i; - #define vec128_zero _mm_setzero_si128() - #define vec128_set_16(a) _mm_set1_epi16(a) - #define vec128_load(a) _mm_load_si128(a) - #define vec128_storeu(a, b) _mm_storeu_si128(a, b) - #define vec128_add(a, b) _mm_add_epi16(a, b) -#elif defined (USE_NEON) - using vec_t = uint32x4_t; + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #define vec128_load(a) _mm_load_si128(a) + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + #elif defined(USE_NEON) + using vec_t = uint32x4_t; static const std::uint32_t Mask[4] = {1, 2, 4, 8}; - #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) - using vec128_t = uint16x8_t; - #define vec128_zero vdupq_n_u16(0) - #define vec128_set_16(a) vdupq_n_u16(a) - #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) - #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) - #define vec128_add(a, b) vaddq_u16(a, b) -#endif + #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) + using vec128_t = uint16x8_t; + #define vec128_zero vdupq_n_u16(0) + #define vec128_set_16(a) vdupq_n_u16(a) + #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) + #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) + #define vec128_add(a, b) vaddq_u16(a, b) + #endif constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) - constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); - constexpr IndexType NumChunks = InputDimensions / ChunkSize; - constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; + constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); + constexpr IndexType NumChunks = InputDimensions / ChunkSize; + constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; constexpr IndexType OutputsPerChunk = ChunkSize / 8; - const auto inputVector = reinterpret_cast(input); - IndexType count = 0; - vec128_t base = vec128_zero; - const vec128_t increment = vec128_set_16(8); + const auto inputVector = reinterpret_cast(input); + IndexType count = 0; + vec128_t base = vec128_zero; + const vec128_t increment = vec128_set_16(8); for (IndexType i = 0; i < NumChunks; ++i) { - // bitmask of nonzero values in this chunk - unsigned nnz = 0; - for (IndexType j = 0; j < InputsPerChunk; ++j) - { - const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; - nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); - } - for (IndexType j = 0; j < OutputsPerChunk; ++j) - { - const auto lookup = (nnz >> (j * 8)) & 0xFF; - const auto offsets = vec128_load(reinterpret_cast(&lookup_indices[lookup])); - vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); - count += popcount(lookup); - base = vec128_add(base, increment); - } + // bitmask of nonzero values in this chunk + unsigned nnz = 0; + for (IndexType j = 0; j < InputsPerChunk; ++j) + { + const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; + nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); + } + for (IndexType j = 0; j < OutputsPerChunk; ++j) + { + const auto lookup = (nnz >> (j * 8)) & 0xFF; + const auto offsets = + vec128_load(reinterpret_cast(&lookup_indices[lookup])); + vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); + count += popcount(lookup); + base = vec128_add(base, increment); + } } count_out = count; - } -# undef vec_nnz -# undef vec128_zero -# undef vec128_set_16 -# undef vec128_load -# undef vec128_storeu -# undef vec128_add +} + #undef vec_nnz + #undef vec128_zero + #undef vec128_set_16 + #undef vec128_load + #undef vec128_storeu + #undef vec128_add #endif - // Sparse input implementation - template - class AffineTransformSparseInput { +// Sparse input implementation +template +class AffineTransformSparseInput { public: // Input/output type - using InputType = std::uint8_t; + using InputType = std::uint8_t; using OutputType = std::int32_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = OutDims; - static_assert(OutputDimensions % 16 == 0, "Only implemented for OutputDimensions divisible by 16."); + static_assert(OutputDimensions % 16 == 0, + "Only implemented for OutputDimensions divisible by 16."); static constexpr IndexType PaddedInputDimensions = ceil_to_multiple(InputDimensions, MaxSimdWidth); @@ -152,127 +158,121 @@ namespace Stockfish::Eval::NNUE::Layers { // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; } - static constexpr IndexType get_weight_index_scrambled(IndexType i) - { - return - (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + - i / PaddedInputDimensions * ChunkSize + - i % ChunkSize; + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + + i / PaddedInputDimensions * ChunkSize + i % ChunkSize; } - static constexpr IndexType get_weight_index(IndexType i) - { + static constexpr IndexType get_weight_index(IndexType i) { #if (USE_SSSE3 | (USE_NEON >= 8)) - return get_weight_index_scrambled(i); + return get_weight_index_scrambled(i); #else - return i; + return i; #endif } // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); + write_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); - return !stream.fail(); + return !stream.fail(); } // Forward propagation - void propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { #if (USE_SSSE3 | (USE_NEON >= 8)) -#if defined (USE_AVX512) - using invec_t = __m512i; - using outvec_t = __m512i; - #define vec_set_32 _mm512_set1_epi32 - #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 -#elif defined (USE_AVX2) - using invec_t = __m256i; - using outvec_t = __m256i; - #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 -#elif defined (USE_SSSE3) - using invec_t = __m128i; - using outvec_t = __m128i; - #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 -#elif defined (USE_NEON_DOTPROD) - using invec_t = int8x16_t; - using outvec_t = int32x4_t; - #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) - #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 -#elif defined (USE_NEON) - using invec_t = int8x16_t; - using outvec_t = int32x4_t; - #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) - #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 -#endif - static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); + #if defined(USE_AVX512) + using invec_t = __m512i; + using outvec_t = __m512i; + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #elif defined(USE_AVX2) + using invec_t = __m256i; + using outvec_t = __m256i; + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #elif defined(USE_SSSE3) + using invec_t = __m128i; + using outvec_t = __m128i; + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #elif defined(USE_NEON_DOTPROD) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 + #elif defined(USE_NEON) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 + #endif + static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; - constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; - std::uint16_t nnz[NumChunks]; - IndexType count; + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + std::uint16_t nnz[NumChunks]; + IndexType count; - const auto input32 = reinterpret_cast(input); + const auto input32 = reinterpret_cast(input); - // Find indices of nonzero 32bit blocks - find_nnz(input32, nnz, count); + // Find indices of nonzero 32bit blocks + find_nnz(input32, nnz, count); - const outvec_t* biasvec = reinterpret_cast(biases); - outvec_t acc[NumRegs]; - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasvec[k]; - - for (IndexType j = 0; j < count; ++j) - { - const auto i = nnz[j]; - const invec_t in = vec_set_32(input32[i]); - const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + const outvec_t* biasvec = reinterpret_cast(biases); + outvec_t acc[NumRegs]; for (IndexType k = 0; k < NumRegs; ++k) - vec_add_dpbusd_32(acc[k], in, col[k]); - } + acc[k] = biasvec[k]; - outvec_t* outptr = reinterpret_cast(output); - for (IndexType k = 0; k < NumRegs; ++k) - outptr[k] = acc[k]; -# undef vec_set_32 -# undef vec_add_dpbusd_32 + for (IndexType j = 0; j < count; ++j) + { + const auto i = nnz[j]; + const invec_t in = vec_set_32(input32[i]); + const auto col = + reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in, col[k]); + } + + outvec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + #undef vec_set_32 + #undef vec_add_dpbusd_32 #else - // Use dense implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); + // Use dense implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); #endif } private: - using BiasType = OutputType; + using BiasType = OutputType; using WeightType = std::int8_t; alignas(CacheLineSize) BiasType biases[OutputDimensions]; alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 48cd6c69..a3a0c1ed 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -29,136 +29,140 @@ namespace Stockfish::Eval::NNUE::Layers { - // Clipped ReLU - template - class ClippedReLU { +// Clipped ReLU +template +class ClippedReLU { public: // Input/output type - using InputType = std::int32_t; + using InputType = std::int32_t; using OutputType = std::uint8_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = InputDimensions; static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, 32); + ceil_to_multiple(OutputDimensions, 32); using OutputBuffer = OutputType[PaddedOutputDimensions]; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0x538D24C7u; - hashValue += prevHash; - return hashValue; + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; } // Read network parameters - bool read_parameters(std::istream&) { - return true; - } + bool read_parameters(std::istream&) { return true; } // Write network parameters - bool write_parameters(std::ostream&) const { - return true; - } + bool write_parameters(std::ostream&) const { return true; } // Forward propagation - void propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { - #if defined(USE_AVX2) - if constexpr (InputDimensions % SimdWidth == 0) { +#if defined(USE_AVX2) + if constexpr (InputDimensions % SimdWidth == 0) + { + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m256i Zero = _mm256_setzero_si256(); + const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m256i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m256i words0 = + _mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]), + _mm256_load_si256(&in[i * 4 + 1])), + WeightScaleBits); + const __m256i words1 = + _mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 2]), + _mm256_load_si256(&in[i * 4 + 3])), + WeightScaleBits); + _mm256_store_si256( + &out[i], _mm256_permutevar8x32_epi32( + _mm256_max_epi8(_mm256_packs_epi16(words0, words1), Zero), Offsets)); + } + } + else + { + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const __m128i Zero = _mm_setzero_si128(); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m128i words0 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + } + } + constexpr IndexType Start = InputDimensions % SimdWidth == 0 + ? InputDimensions / SimdWidth * SimdWidth + : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); + +#elif defined(USE_SSE2) constexpr IndexType NumChunks = InputDimensions / SimdWidth; - const __m256i Zero = _mm256_setzero_si256(); - const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m256i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( - _mm256_load_si256(&in[i * 4 + 0]), - _mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits); - const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( - _mm256_load_si256(&in[i * 4 + 2]), - _mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits); - _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( - _mm256_packs_epi16(words0, words1), Zero), Offsets)); - } - } else { - constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + + #ifdef USE_SSE41 const __m128i Zero = _mm_setzero_si128(); - const auto in = reinterpret_cast(input); + #else + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); - const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m128i words0 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], + + #ifdef USE_SSE41 + _mm_max_epi8(packedbytes, Zero) + #else + _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + #endif + + ); } - } - constexpr IndexType Start = - InputDimensions % SimdWidth == 0 - ? InputDimensions / SimdWidth * SimdWidth - : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); + constexpr IndexType Start = NumChunks * SimdWidth; - #elif defined(USE_SSE2) - constexpr IndexType NumChunks = InputDimensions / SimdWidth; +#elif defined(USE_NEON) + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + int16x8_t shifted; + const auto pack = reinterpret_cast(&shifted); + pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); + pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); + out[i] = vmax_s8(vqmovn_s16(shifted), Zero); + } + constexpr IndexType Start = NumChunks * (SimdWidth / 2); +#else + constexpr IndexType Start = 0; +#endif - #ifdef USE_SSE41 - const __m128i Zero = _mm_setzero_si128(); - #else - const __m128i k0x80s = _mm_set1_epi8(-128); - #endif - - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); - const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - _mm_store_si128(&out[i], - - #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, Zero) - #else - _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) - #endif - - ); - } - constexpr IndexType Start = NumChunks * SimdWidth; - - #elif defined(USE_NEON) - constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); - const int8x8_t Zero = {0}; - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast(output); - for (IndexType i = 0; i < NumChunks; ++i) { - int16x8_t shifted; - const auto pack = reinterpret_cast(&shifted); - pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); - pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); - out[i] = vmax_s8(vqmovn_s16(shifted), Zero); - } - constexpr IndexType Start = NumChunks * (SimdWidth / 2); - #else - constexpr IndexType Start = 0; - #endif - - for (IndexType i = Start; i < InputDimensions; ++i) { - output[i] = static_cast( - std::clamp(input[i] >> WeightScaleBits, 0, 127)); - } + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast(std::clamp(input[i] >> WeightScaleBits, 0, 127)); + } } - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 349217ed..5425ca19 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -20,30 +20,30 @@ #define STOCKFISH_SIMD_H_INCLUDED #if defined(USE_AVX2) -# include + #include #elif defined(USE_SSE41) -# include + #include #elif defined(USE_SSSE3) -# include + #include #elif defined(USE_SSE2) -# include + #include #elif defined(USE_NEON) -# include + #include #endif namespace Stockfish::Simd { -#if defined (USE_AVX512) +#if defined(USE_AVX512) - [[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { - return _mm512_reduce_add_epi32(sum) + bias; - } +[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { + return _mm512_reduce_add_epi32(sum) + bias; +} - /* +/* Parameters: sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]] sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]] @@ -58,186 +58,164 @@ namespace Stockfish::Simd { reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3]) ] */ - [[maybe_unused]] static __m512i m512_hadd128x16_interleave( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) { +[[maybe_unused]] static __m512i +m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) { - __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1); - __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1); + __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1); + __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1); - __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3); - __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3); + __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3); + __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3); - __m512i sum01 = _mm512_add_epi32(sum01a, sum01b); - __m512i sum23 = _mm512_add_epi32(sum23a, sum23b); + __m512i sum01 = _mm512_add_epi32(sum01a, sum01b); + __m512i sum23 = _mm512_add_epi32(sum23a, sum23b); - __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23); - __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23); + __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23); + __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23); - return _mm512_add_epi32(sum0123a, sum0123b); - } + return _mm512_add_epi32(sum0123a, sum0123b); +} - [[maybe_unused]] static void m512_add_dpbusd_epi32( - __m512i& acc, - __m512i a, - __m512i b) { +[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) { -# if defined (USE_VNNI) - acc = _mm512_dpbusd_epi32(acc, a, b); -# else - __m512i product0 = _mm512_maddubs_epi16(a, b); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, product0); -# endif - } + #if defined(USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a, b); + #else + __m512i product0 = _mm512_maddubs_epi16(a, b); + product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, product0); + #endif +} - [[maybe_unused]] static void m512_add_dpbusd_epi32x2( - __m512i& acc, - __m512i a0, __m512i b0, - __m512i a1, __m512i b1) { +[[maybe_unused]] static void +m512_add_dpbusd_epi32x2(__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1) { -# if defined (USE_VNNI) - acc = _mm512_dpbusd_epi32(acc, a0, b0); - acc = _mm512_dpbusd_epi32(acc, a1, b1); -# else - __m512i product0 = _mm512_maddubs_epi16(a0, b0); - __m512i product1 = _mm512_maddubs_epi16(a1, b1); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); -# endif - } + #if defined(USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a0, b0); + acc = _mm512_dpbusd_epi32(acc, a1, b1); + #else + __m512i product0 = _mm512_maddubs_epi16(a0, b0); + __m512i product1 = _mm512_maddubs_epi16(a1, b1); + product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); + product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); + #endif +} #endif -#if defined (USE_AVX2) +#if defined(USE_AVX2) - [[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { - __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); - sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); - sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); - return _mm_cvtsi128_si32(sum128) + bias; - } +[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { + __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); + return _mm_cvtsi128_si32(sum128) + bias; +} - [[maybe_unused]] static void m256_add_dpbusd_epi32( - __m256i& acc, - __m256i a, - __m256i b) { +[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) { -# if defined (USE_VNNI) - acc = _mm256_dpbusd_epi32(acc, a, b); -# else - __m256i product0 = _mm256_maddubs_epi16(a, b); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, product0); -# endif - } + #if defined(USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a, b); + #else + __m256i product0 = _mm256_maddubs_epi16(a, b); + product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, product0); + #endif +} - [[maybe_unused]] static void m256_add_dpbusd_epi32x2( - __m256i& acc, - __m256i a0, __m256i b0, - __m256i a1, __m256i b1) { +[[maybe_unused]] static void +m256_add_dpbusd_epi32x2(__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1) { -# if defined (USE_VNNI) - acc = _mm256_dpbusd_epi32(acc, a0, b0); - acc = _mm256_dpbusd_epi32(acc, a1, b1); -# else - __m256i product0 = _mm256_maddubs_epi16(a0, b0); - __m256i product1 = _mm256_maddubs_epi16(a1, b1); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); -# endif - } + #if defined(USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a0, b0); + acc = _mm256_dpbusd_epi32(acc, a1, b1); + #else + __m256i product0 = _mm256_maddubs_epi16(a0, b0); + __m256i product1 = _mm256_maddubs_epi16(a1, b1); + product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); + product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); + #endif +} #endif -#if defined (USE_SSSE3) +#if defined(USE_SSSE3) - [[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { - sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC - sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB - return _mm_cvtsi128_si32(sum) + bias; - } +[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB + return _mm_cvtsi128_si32(sum) + bias; +} - [[maybe_unused]] static void m128_add_dpbusd_epi32( - __m128i& acc, - __m128i a, - __m128i b) { +[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) { - __m128i product0 = _mm_maddubs_epi16(a, b); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, product0); - } + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, product0); +} - [[maybe_unused]] static void m128_add_dpbusd_epi32x2( - __m128i& acc, - __m128i a0, __m128i b0, - __m128i a1, __m128i b1) { +[[maybe_unused]] static void +m128_add_dpbusd_epi32x2(__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1) { - __m128i product0 = _mm_maddubs_epi16(a0, b0); - __m128i product1 = _mm_maddubs_epi16(a1, b1); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); - } + __m128i product0 = _mm_maddubs_epi16(a0, b0); + __m128i product1 = _mm_maddubs_epi16(a1, b1); + product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); + product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); +} #endif -#if defined (USE_NEON_DOTPROD) +#if defined(USE_NEON_DOTPROD) - [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( - int32x4_t& acc, - int8x16_t a0, int8x16_t b0, - int8x16_t a1, int8x16_t b1) { +[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( + int32x4_t& acc, int8x16_t a0, int8x16_t b0, int8x16_t a1, int8x16_t b1) { - acc = vdotq_s32(acc, a0, b0); - acc = vdotq_s32(acc, a1, b1); - } + acc = vdotq_s32(acc, a0, b0); + acc = vdotq_s32(acc, a1, b1); +} - [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32( - int32x4_t& acc, - int8x16_t a, int8x16_t b) { +[[maybe_unused]] static void +dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { - acc = vdotq_s32(acc, a, b); - } + acc = vdotq_s32(acc, a, b); +} #endif -#if defined (USE_NEON) +#if defined(USE_NEON) - [[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { -# if USE_NEON >= 8 - return vaddvq_s32(s); -# else - return s[0] + s[1] + s[2] + s[3]; -# endif - } +[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { + #if USE_NEON >= 8 + return vaddvq_s32(s); + #else + return s[0] + s[1] + s[2] + s[3]; + #endif +} - [[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { - return neon_m128_reduce_add_epi32(sum) + bias; - } +[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { + return neon_m128_reduce_add_epi32(sum) + bias; +} - [[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2( - int32x4_t& acc, - int8x8_t a0, int8x8_t b0, - int8x8_t a1, int8x8_t b1) { +[[maybe_unused]] static void +neon_m128_add_dpbusd_epi32x2(int32x4_t& acc, int8x8_t a0, int8x8_t b0, int8x8_t a1, int8x8_t b1) { - int16x8_t product = vmull_s8(a0, b0); - product = vmlal_s8(product, a1, b1); - acc = vpadalq_s16(acc, product); - } + int16x8_t product = vmull_s8(a0, b0); + product = vmlal_s8(product, a1, b1); + acc = vpadalq_s16(acc, product); +} #endif #if USE_NEON >= 8 - [[maybe_unused]] static void neon_m128_add_dpbusd_epi32( - int32x4_t& acc, - int8x16_t a, int8x16_t b) { +[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { - int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); - int16x8_t product1 = vmull_high_s8(a, b); - int16x8_t sum = vpaddq_s16(product0, product1); - acc = vpadalq_s16(acc, sum); - } + int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); + int16x8_t product1 = vmull_high_s8(a, b); + int16x8_t sum = vpaddq_s16(product0, product1); + acc = vpadalq_s16(acc, sum); +} #endif } -#endif // STOCKFISH_SIMD_H_INCLUDED +#endif // STOCKFISH_SIMD_H_INCLUDED diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index a3d2059b..987de892 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -29,80 +29,75 @@ namespace Stockfish::Eval::NNUE::Layers { - // Clipped ReLU - template - class SqrClippedReLU { +// Clipped ReLU +template +class SqrClippedReLU { public: // Input/output type - using InputType = std::int32_t; + using InputType = std::int32_t; using OutputType = std::uint8_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = InputDimensions; static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, 32); + ceil_to_multiple(OutputDimensions, 32); using OutputBuffer = OutputType[PaddedOutputDimensions]; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0x538D24C7u; - hashValue += prevHash; - return hashValue; + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; } // Read network parameters - bool read_parameters(std::istream&) { - return true; - } + bool read_parameters(std::istream&) { return true; } // Write network parameters - bool write_parameters(std::ostream&) const { - return true; - } + bool write_parameters(std::ostream&) const { return true; } // Forward propagation - void propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { - #if defined(USE_SSE2) - constexpr IndexType NumChunks = InputDimensions / 16; +#if defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / 16; - static_assert(WeightScaleBits == 6); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - __m128i words0 = _mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])); - __m128i words1 = _mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])); + static_assert(WeightScaleBits == 6); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + __m128i words0 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])); + __m128i words1 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])); - // We shift by WeightScaleBits * 2 = 12 and divide by 128 - // which is an additional shift-right of 7, meaning 19 in total. - // MulHi strips the lower 16 bits so we need to shift out 3 more to match. - words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); - words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); + // We shift by WeightScaleBits * 2 = 12 and divide by 128 + // which is an additional shift-right of 7, meaning 19 in total. + // MulHi strips the lower 16 bits so we need to shift out 3 more to match. + words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); + words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); - _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); - } - constexpr IndexType Start = NumChunks * 16; + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + } + constexpr IndexType Start = NumChunks * 16; - #else - constexpr IndexType Start = 0; - #endif +#else + constexpr IndexType Start = 0; +#endif - for (IndexType i = Start; i < InputDimensions; ++i) { - output[i] = static_cast( - // Really should be /127 but we need to make it fast so we right shift - // by an extra 7 bits instead. Needs to be accounted for in the trainer. - std::min(127ll, ((long long)input[i] * input[i]) >> (2 * WeightScaleBits + 7))); - } + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast( + // Really should be /127 but we need to make it fast so we right shift + // by an extra 7 bits instead. Needs to be accounted for in the trainer. + std::min(127ll, ((long long) input[i] * input[i]) >> (2 * WeightScaleBits + 7))); + } } - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 03fc3bd5..2f1b1d35 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -28,13 +28,13 @@ namespace Stockfish::Eval::NNUE { - // Class that holds the result of affine transformation of input features - struct alignas(CacheLineSize) Accumulator { +// Class that holds the result of affine transformation of input features +struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[2][TransformedFeatureDimensions]; std::int32_t psqtAccumulation[2][PSQTBuckets]; - bool computed[2]; - }; + bool computed[2]; +}; } // namespace Stockfish::Eval::NNUE -#endif // NNUE_ACCUMULATOR_H_INCLUDED +#endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 2a7f064b..be0138f1 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -39,97 +39,90 @@ using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion constexpr IndexType TransformedFeatureDimensions = 2560; -constexpr IndexType PSQTBuckets = 8; -constexpr IndexType LayerStacks = 8; +constexpr IndexType PSQTBuckets = 8; +constexpr IndexType LayerStacks = 8; -struct Network -{ - static constexpr int FC_0_OUTPUTS = 15; - static constexpr int FC_1_OUTPUTS = 32; +struct Network { + static constexpr int FC_0_OUTPUTS = 15; + static constexpr int FC_1_OUTPUTS = 32; - Layers::AffineTransformSparseInput fc_0; - Layers::SqrClippedReLU ac_sqr_0; - Layers::ClippedReLU ac_0; - Layers::AffineTransform fc_1; - Layers::ClippedReLU ac_1; - Layers::AffineTransform fc_2; + Layers::AffineTransformSparseInput fc_0; + Layers::SqrClippedReLU ac_sqr_0; + Layers::ClippedReLU ac_0; + Layers::AffineTransform fc_1; + Layers::ClippedReLU ac_1; + Layers::AffineTransform fc_2; - // Hash value embedded in the evaluation file - static constexpr std::uint32_t get_hash_value() { - // input slice hash - std::uint32_t hashValue = 0xEC42E90Du; - hashValue ^= TransformedFeatureDimensions * 2; + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + // input slice hash + std::uint32_t hashValue = 0xEC42E90Du; + hashValue ^= TransformedFeatureDimensions * 2; - hashValue = decltype(fc_0)::get_hash_value(hashValue); - hashValue = decltype(ac_0)::get_hash_value(hashValue); - hashValue = decltype(fc_1)::get_hash_value(hashValue); - hashValue = decltype(ac_1)::get_hash_value(hashValue); - hashValue = decltype(fc_2)::get_hash_value(hashValue); + hashValue = decltype(fc_0)::get_hash_value(hashValue); + hashValue = decltype(ac_0)::get_hash_value(hashValue); + hashValue = decltype(fc_1)::get_hash_value(hashValue); + hashValue = decltype(ac_1)::get_hash_value(hashValue); + hashValue = decltype(fc_2)::get_hash_value(hashValue); - return hashValue; - } + return hashValue; + } - // Read network parameters - bool read_parameters(std::istream& stream) { - return fc_0.read_parameters(stream) - && ac_0.read_parameters(stream) - && fc_1.read_parameters(stream) - && ac_1.read_parameters(stream) - && fc_2.read_parameters(stream); - } + // Read network parameters + bool read_parameters(std::istream& stream) { + return fc_0.read_parameters(stream) && ac_0.read_parameters(stream) + && fc_1.read_parameters(stream) && ac_1.read_parameters(stream) + && fc_2.read_parameters(stream); + } - // Write network parameters - bool write_parameters(std::ostream& stream) const { - return fc_0.write_parameters(stream) - && ac_0.write_parameters(stream) - && fc_1.write_parameters(stream) - && ac_1.write_parameters(stream) - && fc_2.write_parameters(stream); - } + // Write network parameters + bool write_parameters(std::ostream& stream) const { + return fc_0.write_parameters(stream) && ac_0.write_parameters(stream) + && fc_1.write_parameters(stream) && ac_1.write_parameters(stream) + && fc_2.write_parameters(stream); + } - std::int32_t propagate(const TransformedFeatureType* transformedFeatures) - { - struct alignas(CacheLineSize) Buffer - { - alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; - alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; - alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; - alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; - alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; - alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out; + std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { + struct alignas(CacheLineSize) Buffer { + alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; + alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType + ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; + alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; + alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; + alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; + alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out; - Buffer() - { - std::memset(this, 0, sizeof(*this)); - } - }; + Buffer() { std::memset(this, 0, sizeof(*this)); } + }; #if defined(__clang__) && (__APPLE__) - // workaround for a bug reported with xcode 12 - static thread_local auto tlsBuffer = std::make_unique(); - // Access TLS only once, cache result. - Buffer& buffer = *tlsBuffer; + // workaround for a bug reported with xcode 12 + static thread_local auto tlsBuffer = std::make_unique(); + // Access TLS only once, cache result. + Buffer& buffer = *tlsBuffer; #else - alignas(CacheLineSize) static thread_local Buffer buffer; + alignas(CacheLineSize) static thread_local Buffer buffer; #endif - fc_0.propagate(transformedFeatures, buffer.fc_0_out); - ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); - ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); - std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType)); - fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); - ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); - fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); + fc_0.propagate(transformedFeatures, buffer.fc_0_out); + ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); + ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); + std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, + FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType)); + fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); + ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); + fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); - // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1< + #include #elif defined(USE_SSE41) -#include + #include #elif defined(USE_SSSE3) -#include + #include #elif defined(USE_SSE2) -#include + #include #elif defined(USE_NEON) -#include + #include #endif namespace Stockfish::Eval::NNUE { - // Version of the evaluation file - constexpr std::uint32_t Version = 0x7AF32F20u; +// Version of the evaluation file +constexpr std::uint32_t Version = 0x7AF32F20u; - // Constant used in evaluation value calculation - constexpr int OutputScale = 16; - constexpr int WeightScaleBits = 6; +// Constant used in evaluation value calculation +constexpr int OutputScale = 16; +constexpr int WeightScaleBits = 6; - // Size of cache line (in bytes) - constexpr std::size_t CacheLineSize = 64; +// Size of cache line (in bytes) +constexpr std::size_t CacheLineSize = 64; - constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; - constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; +constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; +constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; - // SIMD width (in bytes) - #if defined(USE_AVX2) - constexpr std::size_t SimdWidth = 32; +// SIMD width (in bytes) +#if defined(USE_AVX2) +constexpr std::size_t SimdWidth = 32; - #elif defined(USE_SSE2) - constexpr std::size_t SimdWidth = 16; +#elif defined(USE_SSE2) +constexpr std::size_t SimdWidth = 16; - #elif defined(USE_NEON) - constexpr std::size_t SimdWidth = 16; - #endif +#elif defined(USE_NEON) +constexpr std::size_t SimdWidth = 16; +#endif - constexpr std::size_t MaxSimdWidth = 32; +constexpr std::size_t MaxSimdWidth = 32; - // Type of input feature after conversion - using TransformedFeatureType = std::uint8_t; - using IndexType = std::uint32_t; +// Type of input feature after conversion +using TransformedFeatureType = std::uint8_t; +using IndexType = std::uint32_t; - // Round n up to be a multiple of base - template - constexpr IntType ceil_to_multiple(IntType n, IntType base) { - return (n + base - 1) / base * base; - } +// Round n up to be a multiple of base +template +constexpr IntType ceil_to_multiple(IntType n, IntType base) { + return (n + base - 1) / base * base; +} - // read_little_endian() is our utility to read an integer (signed or unsigned, any size) - // from a stream in little-endian order. We swap the byte order after the read if - // necessary to return a result with the byte ordering of the compiling machine. - template - inline IntType read_little_endian(std::istream& stream) { - IntType result; +// read_little_endian() is our utility to read an integer (signed or unsigned, any size) +// from a stream in little-endian order. We swap the byte order after the read if +// necessary to return a result with the byte ordering of the compiling machine. +template +inline IntType read_little_endian(std::istream& stream) { + IntType result; - if (IsLittleEndian) - stream.read(reinterpret_cast(&result), sizeof(IntType)); - else - { - std::uint8_t u[sizeof(IntType)]; - std::make_unsigned_t v = 0; + if (IsLittleEndian) + stream.read(reinterpret_cast(&result), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = 0; - stream.read(reinterpret_cast(u), sizeof(IntType)); - for (std::size_t i = 0; i < sizeof(IntType); ++i) - v = (v << 8) | u[sizeof(IntType) - i - 1]; + stream.read(reinterpret_cast(u), sizeof(IntType)); + for (std::size_t i = 0; i < sizeof(IntType); ++i) + v = (v << 8) | u[sizeof(IntType) - i - 1]; - std::memcpy(&result, &v, sizeof(IntType)); - } + std::memcpy(&result, &v, sizeof(IntType)); + } - return result; - } + return result; +} - // write_little_endian() is our utility to write an integer (signed or unsigned, any size) - // to a stream in little-endian order. We swap the byte order before the write if - // necessary to always write in little endian order, independently of the byte - // ordering of the compiling machine. - template - inline void write_little_endian(std::ostream& stream, IntType value) { +// write_little_endian() is our utility to write an integer (signed or unsigned, any size) +// to a stream in little-endian order. We swap the byte order before the write if +// necessary to always write in little endian order, independently of the byte +// ordering of the compiling machine. +template +inline void write_little_endian(std::ostream& stream, IntType value) { - if (IsLittleEndian) - stream.write(reinterpret_cast(&value), sizeof(IntType)); - else - { - std::uint8_t u[sizeof(IntType)]; - std::make_unsigned_t v = value; + if (IsLittleEndian) + stream.write(reinterpret_cast(&value), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = value; - std::size_t i = 0; - // if constexpr to silence the warning about shift by 8 - if constexpr (sizeof(IntType) > 1) - { + std::size_t i = 0; + // if constexpr to silence the warning about shift by 8 + if constexpr (sizeof(IntType) > 1) + { for (; i + 1 < sizeof(IntType); ++i) { - u[i] = (std::uint8_t)v; + u[i] = (std::uint8_t) v; v >>= 8; } - } - u[i] = (std::uint8_t)v; + } + u[i] = (std::uint8_t) v; - stream.write(reinterpret_cast(u), sizeof(IntType)); - } - } + stream.write(reinterpret_cast(u), sizeof(IntType)); + } +} - // read_little_endian(s, out, N) : read integers in bulk from a little indian stream. - // This reads N integers from stream s and put them in array out. - template - inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { - if (IsLittleEndian) - stream.read(reinterpret_cast(out), sizeof(IntType) * count); - else - for (std::size_t i = 0; i < count; ++i) - out[i] = read_little_endian(stream); - } +// read_little_endian(s, out, N) : read integers in bulk from a little indian stream. +// This reads N integers from stream s and put them in array out. +template +inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { + if (IsLittleEndian) + stream.read(reinterpret_cast(out), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + out[i] = read_little_endian(stream); +} - // write_little_endian(s, values, N) : write integers in bulk to a little indian stream. - // This takes N integers from array values and writes them on stream s. - template - inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { - if (IsLittleEndian) - stream.write(reinterpret_cast(values), sizeof(IntType) * count); - else - for (std::size_t i = 0; i < count; ++i) - write_little_endian(stream, values[i]); - } +// write_little_endian(s, values, N) : write integers in bulk to a little indian stream. +// This takes N integers from array values and writes them on stream s. +template +inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { + if (IsLittleEndian) + stream.write(reinterpret_cast(values), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + write_little_endian(stream, values[i]); +} - // read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in - // the array out. The stream is assumed to be compressed using the signed LEB128 format. - // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. - template - inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { +// read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in +// the array out. The stream is assumed to be compressed using the signed LEB128 format. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { - // Check the presence of our LEB128 magic string - char leb128MagicString[Leb128MagicStringSize]; - stream.read(leb128MagicString, Leb128MagicStringSize); - assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + // Check the presence of our LEB128 magic string + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); - static_assert(std::is_signed_v, "Not implemented for unsigned types"); + static_assert(std::is_signed_v, "Not implemented for unsigned types"); - const std::uint32_t BUF_SIZE = 4096; - std::uint8_t buf[BUF_SIZE]; + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; - auto bytes_left = read_little_endian(stream); + auto bytes_left = read_little_endian(stream); - std::uint32_t buf_pos = BUF_SIZE; - for (std::size_t i = 0; i < count; ++i) - { - IntType result = 0; - size_t shift = 0; - do - { - if (buf_pos == BUF_SIZE) - { - stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); - buf_pos = 0; - } + std::uint32_t buf_pos = BUF_SIZE; + for (std::size_t i = 0; i < count; ++i) + { + IntType result = 0; + size_t shift = 0; + do + { + if (buf_pos == BUF_SIZE) + { + stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); + buf_pos = 0; + } - std::uint8_t byte = buf[buf_pos++]; - --bytes_left; - result |= (byte & 0x7f) << shift; - shift += 7; + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << shift; + shift += 7; - if ((byte & 0x80) == 0) - { - out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) ? result - : result | ~((1 << shift) - 1); - break; - } - } - while (shift < sizeof(IntType) * 8); - } + if ((byte & 0x80) == 0) + { + out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) + ? result + : result | ~((1 << shift) - 1); + break; + } + } while (shift < sizeof(IntType) * 8); + } - assert(bytes_left == 0); - } + assert(bytes_left == 0); +} - // write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression. - // This takes N integers from array values, compress them with the LEB128 algorithm and - // writes the result on the stream s. - // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. - template - inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { +// write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression. +// This takes N integers from array values, compress them with the LEB128 algorithm and +// writes the result on the stream s. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { - // Write our LEB128 magic string - stream.write(Leb128MagicString, Leb128MagicStringSize); + // Write our LEB128 magic string + stream.write(Leb128MagicString, Leb128MagicStringSize); - static_assert(std::is_signed_v, "Not implemented for unsigned types"); + static_assert(std::is_signed_v, "Not implemented for unsigned types"); - std::uint32_t byte_count = 0; - for (std::size_t i = 0; i < count; ++i) - { - IntType value = values[i]; - std::uint8_t byte; - do - { - byte = value & 0x7f; - value >>= 7; - ++byte_count; - } - while ((byte & 0x40) == 0 ? value != 0 : value != -1); - } + std::uint32_t byte_count = 0; + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + std::uint8_t byte; + do + { + byte = value & 0x7f; + value >>= 7; + ++byte_count; + } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } - write_little_endian(stream, byte_count); + write_little_endian(stream, byte_count); - const std::uint32_t BUF_SIZE = 4096; - std::uint8_t buf[BUF_SIZE]; - std::uint32_t buf_pos = 0; + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + std::uint32_t buf_pos = 0; - auto flush = [&]() { - if (buf_pos > 0) - { - stream.write(reinterpret_cast(buf), buf_pos); - buf_pos = 0; - } - }; + auto flush = [&]() { + if (buf_pos > 0) + { + stream.write(reinterpret_cast(buf), buf_pos); + buf_pos = 0; + } + }; - auto write = [&](std::uint8_t byte) { - buf[buf_pos++] = byte; - if (buf_pos == BUF_SIZE) - flush(); - }; + auto write = [&](std::uint8_t byte) { + buf[buf_pos++] = byte; + if (buf_pos == BUF_SIZE) + flush(); + }; - for (std::size_t i = 0; i < count; ++i) - { - IntType value = values[i]; - while (true) - { - std::uint8_t byte = value & 0x7f; - value >>= 7; - if ((byte & 0x40) == 0 ? value == 0 : value == -1) - { - write(byte); - break; - } - write(byte | 0x80); - } - } + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + while (true) + { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((byte & 0x40) == 0 ? value == 0 : value == -1) + { + write(byte); + break; + } + write(byte | 0x80); + } + } - flush(); - } + flush(); +} } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_COMMON_H_INCLUDED +#endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 9f02830a..9cb14187 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -36,303 +36,304 @@ namespace Stockfish::Eval::NNUE { - using BiasType = std::int16_t; - using WeightType = std::int16_t; - using PSQTWeightType = std::int32_t; +using BiasType = std::int16_t; +using WeightType = std::int16_t; +using PSQTWeightType = std::int32_t; - // 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. - #define VECTOR +// 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. +#define VECTOR - static_assert(PSQTBuckets % 8 == 0, - "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); +static_assert(PSQTBuckets % 8 == 0, + "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); - #ifdef USE_AVX512 - using vec_t = __m512i; - using psqt_vec_t = __m256i; - #define vec_load(a) _mm512_load_si512(a) - #define vec_store(a,b) _mm512_store_si512(a,b) - #define vec_add_16(a,b) _mm512_add_epi16(a,b) - #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm512_mullo_epi16(a,b) - #define vec_zero() _mm512_setzero_epi32() - #define vec_set_16(a) _mm512_set1_epi16(a) - #define vec_max_16(a,b) _mm512_max_epi16(a,b) - #define vec_min_16(a,b) _mm512_min_epi16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a,7),_mm512_srli_epi16(b,7)); +#ifdef USE_AVX512 +using vec_t = __m512i; +using psqt_vec_t = __m256i; + #define vec_load(a) _mm512_load_si512(a) + #define vec_store(a, b) _mm512_store_si512(a, b) + #define vec_add_16(a, b) _mm512_add_epi16(a, b) + #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm512_mullo_epi16(a, b) + #define vec_zero() _mm512_setzero_epi32() + #define vec_set_16(a) _mm512_set1_epi16(a) + #define vec_max_16(a, b) _mm512_max_epi16(a, b) + #define vec_min_16(a, b) _mm512_min_epi16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7)); return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted); - } - #define vec_load_psqt(a) _mm256_load_si256(a) - #define vec_store_psqt(a,b) _mm256_store_si256(a,b) - #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) - #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 16 - #define MaxChunkSize 64 +} + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + #define MaxChunkSize 64 - #elif USE_AVX2 - using vec_t = __m256i; - using psqt_vec_t = __m256i; - #define vec_load(a) _mm256_load_si256(a) - #define vec_store(a,b) _mm256_store_si256(a,b) - #define vec_add_16(a,b) _mm256_add_epi16(a,b) - #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm256_mullo_epi16(a,b) - #define vec_zero() _mm256_setzero_si256() - #define vec_set_16(a) _mm256_set1_epi16(a) - #define vec_max_16(a,b) _mm256_max_epi16(a,b) - #define vec_min_16(a,b) _mm256_min_epi16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a,7), _mm256_srli_epi16(b,7)); +#elif USE_AVX2 +using vec_t = __m256i; +using psqt_vec_t = __m256i; + #define vec_load(a) _mm256_load_si256(a) + #define vec_store(a, b) _mm256_store_si256(a, b) + #define vec_add_16(a, b) _mm256_add_epi16(a, b) + #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm256_mullo_epi16(a, b) + #define vec_zero() _mm256_setzero_si256() + #define vec_set_16(a) _mm256_set1_epi16(a) + #define vec_max_16(a, b) _mm256_max_epi16(a, b) + #define vec_min_16(a, b) _mm256_min_epi16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7)); return _mm256_permute4x64_epi64(compacted, 0b11011000); - } - #define vec_load_psqt(a) _mm256_load_si256(a) - #define vec_store_psqt(a,b) _mm256_store_si256(a,b) - #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) - #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 16 - #define MaxChunkSize 32 +} + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + #define MaxChunkSize 32 - #elif USE_SSE2 - using vec_t = __m128i; - using psqt_vec_t = __m128i; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) _mm_add_epi16(a,b) - #define vec_sub_16(a,b) _mm_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm_mullo_epi16(a,b) - #define vec_zero() _mm_setzero_si128() - #define vec_set_16(a) _mm_set1_epi16(a) - #define vec_max_16(a,b) _mm_max_epi16(a,b) - #define vec_min_16(a,b) _mm_min_epi16(a,b) - #define vec_msb_pack_16(a,b) _mm_packs_epi16(_mm_srli_epi16(a,7),_mm_srli_epi16(b,7)) - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) _mm_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b) - #define vec_zero_psqt() _mm_setzero_si128() - #define NumRegistersSIMD (Is64Bit ? 16 : 8) - #define MaxChunkSize 16 +#elif USE_SSE2 +using vec_t = __m128i; +using psqt_vec_t = __m128i; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) _mm_add_epi16(a, b) + #define vec_sub_16(a, b) _mm_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm_mullo_epi16(a, b) + #define vec_zero() _mm_setzero_si128() + #define vec_set_16(a) _mm_set1_epi16(a) + #define vec_max_16(a, b) _mm_max_epi16(a, b) + #define vec_min_16(a, b) _mm_min_epi16(a, b) + #define vec_msb_pack_16(a, b) _mm_packs_epi16(_mm_srli_epi16(a, 7), _mm_srli_epi16(b, 7)) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b) + #define vec_zero_psqt() _mm_setzero_si128() + #define NumRegistersSIMD (Is64Bit ? 16 : 8) + #define MaxChunkSize 16 - #elif USE_NEON - using vec_t = int16x8_t; - using psqt_vec_t = int32x4_t; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) vaddq_s16(a,b) - #define vec_sub_16(a,b) vsubq_s16(a,b) - #define vec_mul_16(a,b) vmulq_s16(a,b) - #define vec_zero() vec_t{0} - #define vec_set_16(a) vdupq_n_s16(a) - #define vec_max_16(a,b) vmaxq_s16(a,b) - #define vec_min_16(a,b) vminq_s16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - const int8x8_t shifta = vshrn_n_s16(a, 7); - const int8x8_t shiftb = vshrn_n_s16(b, 7); - const int8x16_t compacted = vcombine_s8(shifta,shiftb); - return *reinterpret_cast (&compacted); - } - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) vaddq_s32(a,b) - #define vec_sub_psqt_32(a,b) vsubq_s32(a,b) - #define vec_zero_psqt() psqt_vec_t{0} - #define NumRegistersSIMD 16 - #define MaxChunkSize 16 +#elif USE_NEON +using vec_t = int16x8_t; +using psqt_vec_t = int32x4_t; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) vaddq_s16(a, b) + #define vec_sub_16(a, b) vsubq_s16(a, b) + #define vec_mul_16(a, b) vmulq_s16(a, b) + #define vec_zero() \ + vec_t { 0 } + #define vec_set_16(a) vdupq_n_s16(a) + #define vec_max_16(a, b) vmaxq_s16(a, b) + #define vec_min_16(a, b) vminq_s16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + const int8x8_t shifta = vshrn_n_s16(a, 7); + const int8x8_t shiftb = vshrn_n_s16(b, 7); + const int8x16_t compacted = vcombine_s8(shifta, shiftb); + return *reinterpret_cast(&compacted); +} + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) vaddq_s32(a, b) + #define vec_sub_psqt_32(a, b) vsubq_s32(a, b) + #define vec_zero_psqt() \ + psqt_vec_t { 0 } + #define NumRegistersSIMD 16 + #define MaxChunkSize 16 - #else - #undef VECTOR +#else + #undef VECTOR - #endif +#endif - #ifdef VECTOR +#ifdef VECTOR - // Compute optimal SIMD register count for feature transformer accumulation. + // Compute optimal SIMD register count for feature transformer accumulation. - // We use __m* types as template arguments, which causes GCC to emit warnings - // about losing some attribute information. This is irrelevant to us as we - // only take their size, so the following pragma are harmless. - #if defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wignored-attributes" - #endif + // We use __m* types as template arguments, which causes GCC to emit warnings + // about losing some attribute information. This is irrelevant to us as we + // only take their size, so the following pragma are harmless. + #if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wignored-attributes" + #endif - template - static constexpr int BestRegisterCount() - { - #define RegisterSize sizeof(SIMDRegisterType) - #define LaneSize sizeof(LaneType) +template +static constexpr int BestRegisterCount() { + #define RegisterSize sizeof(SIMDRegisterType) + #define LaneSize sizeof(LaneType) - static_assert(RegisterSize >= LaneSize); - static_assert(MaxRegisters <= NumRegistersSIMD); - static_assert(MaxRegisters > 0); - static_assert(NumRegistersSIMD > 0); - static_assert(RegisterSize % LaneSize == 0); - static_assert((NumLanes * LaneSize) % RegisterSize == 0); + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); - const int ideal = (NumLanes * LaneSize) / RegisterSize; - if (ideal <= MaxRegisters) - return ideal; + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; - // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters - for (int divisor = MaxRegisters; divisor > 1; --divisor) - if (ideal % divisor == 0) - return divisor; + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; - return 1; - } + return 1; +} - static constexpr int NumRegs = BestRegisterCount(); - static constexpr int NumPsqtRegs = BestRegisterCount(); - #if defined(__GNUC__) - #pragma GCC diagnostic pop - #endif - #endif +static constexpr int NumRegs = + BestRegisterCount(); +static constexpr int NumPsqtRegs = + BestRegisterCount(); + #if defined(__GNUC__) + #pragma GCC diagnostic pop + #endif +#endif - - // Input feature converter - class FeatureTransformer { +// Input feature converter +class FeatureTransformer { private: // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - #ifdef VECTOR - static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; +#ifdef VECTOR + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); - #endif +#endif public: // Output type using OutputType = TransformedFeatureType; // Number of input/output dimensions - static constexpr IndexType InputDimensions = FeatureSet::Dimensions; + static constexpr IndexType InputDimensions = FeatureSet::Dimensions; static constexpr IndexType OutputDimensions = HalfDimensions; // Size of forward propagation buffer - static constexpr std::size_t BufferSize = - OutputDimensions * sizeof(OutputType); + static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType); // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { - return FeatureSet::HashValue ^ (OutputDimensions * 2); + return FeatureSet::HashValue ^ (OutputDimensions * 2); } // Read network parameters bool read_parameters(std::istream& stream) { - read_leb_128(stream, biases , HalfDimensions ); - read_leb_128(stream, weights , HalfDimensions * InputDimensions); - read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + read_leb_128(stream, biases, HalfDimensions); + read_leb_128(stream, weights, HalfDimensions * InputDimensions); + read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_leb_128(stream, biases , HalfDimensions ); - write_leb_128(stream, weights , HalfDimensions * InputDimensions); - write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + write_leb_128(stream, biases, HalfDimensions); + write_leb_128(stream, weights, HalfDimensions * InputDimensions); + write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - return !stream.fail(); + return !stream.fail(); } // Convert input features std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { - update_accumulator(pos); - update_accumulator(pos); + update_accumulator(pos); + update_accumulator(pos); - const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& accumulation = pos.state()->accumulator.accumulation; - const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; + const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; + const auto& accumulation = pos.state()->accumulator.accumulation; + const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; - const auto psqt = ( - psqtAccumulation[perspectives[0]][bucket] - - psqtAccumulation[perspectives[1]][bucket] - ) / 2; + const auto psqt = + (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) + / 2; - for (IndexType p = 0; p < 2; ++p) - { - const IndexType offset = (HalfDimensions / 2) * p; + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = (HalfDimensions / 2) * p; #if defined(VECTOR) - constexpr IndexType OutputChunkSize = MaxChunkSize; - static_assert((HalfDimensions / 2) % OutputChunkSize == 0); - constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; + constexpr IndexType OutputChunkSize = MaxChunkSize; + static_assert((HalfDimensions / 2) % OutputChunkSize == 0); + constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - vec_t Zero = vec_zero(); - vec_t One = vec_set_16(127); + vec_t Zero = vec_zero(); + vec_t One = vec_set_16(127); - const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); - const vec_t* in1 = reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); - vec_t* out = reinterpret_cast< vec_t*>(output + offset); + const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); + const vec_t* in1 = + reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); + vec_t* out = reinterpret_cast(output + offset); - for (IndexType j = 0; j < NumOutputChunks; j += 1) - { - const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); - const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); - const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero); - const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero); + for (IndexType j = 0; j < NumOutputChunks; j += 1) + { + const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); + const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); + const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero); + const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero); - const vec_t pa = vec_mul_16(sum0a, sum1a); - const vec_t pb = vec_mul_16(sum0b, sum1b); + const vec_t pa = vec_mul_16(sum0a, sum1a); + const vec_t pb = vec_mul_16(sum0b, sum1b); - out[j] = vec_msb_pack_16(pa, pb); - } + out[j] = vec_msb_pack_16(pa, pb); + } #else - for (IndexType j = 0; j < HalfDimensions / 2; ++j) { - BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; - BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - sum0 = std::clamp(sum0, 0, 127); - sum1 = std::clamp(sum1, 0, 127); - output[offset + j] = static_cast(unsigned(sum0 * sum1) / 128); - } + for (IndexType j = 0; j < HalfDimensions / 2; ++j) + { + BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; + BiasType sum1 = + accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; + sum0 = std::clamp(sum0, 0, 127); + sum1 = std::clamp(sum1, 0, 127); + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 128); + } #endif - } + } - return psqt; - } // end of function transform() + return psqt; + } // end of function transform() void hint_common_access(const Position& pos) const { - hint_common_access_for_perspective(pos); - hint_common_access_for_perspective(pos); + hint_common_access_for_perspective(pos); + hint_common_access_for_perspective(pos); } private: template - [[nodiscard]] std::pair 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(), *next = nullptr; - int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !st->accumulator.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; - next = st; - st = st->previous; - } - return { st, next }; + [[nodiscard]] std::pair + 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(), *next = nullptr; + int gain = FeatureSet::refresh_cost(pos); + while (st->previous && !st->accumulator.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; + next = st; + st = st->previous; + } + return {st, next}; } // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. @@ -340,364 +341,374 @@ namespace Stockfish::Eval::NNUE { // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. // computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr. template - void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { - static_assert(N > 0); - assert(states_to_update[N-1] == nullptr); + void update_accumulator_incremental(const Position& pos, + StateInfo* computed_st, + StateInfo* states_to_update[N]) const { + static_assert(N > 0); + assert(states_to_update[N - 1] == 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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; - #endif +#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[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; +#endif - if (states_to_update[0] == nullptr) - return; + if (states_to_update[0] == nullptr) + return; - // Update incrementally going back through states_to_update. + // Update incrementally going back through states_to_update. - // Gather all features to be updated. - const Square ksq = pos.square(Perspective); + // Gather all features to be updated. + const Square ksq = pos.square(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. - FeatureSet::IndexList removed[N-1], added[N-1]; + // 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. + FeatureSet::IndexList removed[N - 1], added[N - 1]; - { - int i = N-2; // last potential state to update. Skip last element because it must be nullptr. - while (states_to_update[i] == nullptr) - --i; - - StateInfo* st2 = states_to_update[i]; - - for (; i >= 0; --i) { - states_to_update[i]->accumulator.computed[Perspective] = true; + int i = + N + - 2; // last potential state to update. Skip last element because it must be nullptr. + while (states_to_update[i] == nullptr) + --i; - const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + StateInfo* st2 = states_to_update[i]; - for (; st2 != end_state; st2 = st2->previous) - FeatureSet::append_changed_indices( - ksq, st2->dirtyPiece, removed[i], added[i]); + for (; i >= 0; --i) + { + states_to_update[i]->accumulator.computed[Perspective] = true; + + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + + for (; st2 != end_state; st2 = st2->previous) + FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, + removed[i], added[i]); + } } - } - StateInfo* st = computed_st; + StateInfo* st = computed_st; - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. #ifdef VECTOR - if ( states_to_update[1] == nullptr - && (removed[0].size() == 1 || removed[0].size() == 2) - && added[0].size() == 1) - { - assert(states_to_update[0]); + if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2) + && added[0].size() == 1) + { + assert(states_to_update[0]); - auto accIn = reinterpret_cast( - &st->accumulator.accumulation[Perspective][0]); - auto accOut = reinterpret_cast( + auto accIn = + reinterpret_cast(&st->accumulator.accumulation[Perspective][0]); + auto accOut = reinterpret_cast( &states_to_update[0]->accumulator.accumulation[Perspective][0]); - const IndexType offsetR0 = HalfDimensions * removed[0][0]; - auto columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0][0]; - auto columnA = reinterpret_cast(&weights[offsetA]); + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); - if (removed[0].size() == 1) - { - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) - accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); - } - else - { - const IndexType offsetR1 = HalfDimensions * removed[0][1]; - auto columnR1 = reinterpret_cast(&weights[offsetR1]); + if (removed[0].size() == 1) + { + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) - accOut[k] = vec_sub_16( - vec_add_16(accIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); - } + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); + } - auto accPsqtIn = reinterpret_cast( + auto accPsqtIn = reinterpret_cast( &st->accumulator.psqtAccumulation[Perspective][0]); - auto accPsqtOut = reinterpret_cast( + auto accPsqtOut = reinterpret_cast( &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; - auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - const IndexType offsetPsqtA = PSQTBuckets * added[0][0]; - auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; + auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); + const IndexType offsetPsqtA = PSQTBuckets * added[0][0]; + auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); - if (removed[0].size() == 1) - { - for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) - accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32( - accPsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]); - } - else - { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1]; - auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); + if (removed[0].size() == 1) + { + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); + ++k) + accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[k], columnPsqtR0[k]), + columnPsqtA[k]); + } + else + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1]; + auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); - for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) - accPsqtOut[k] = vec_sub_psqt_32( - vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]), + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); + ++k) + accPsqtOut[k] = + vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]), vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); - } - } - else - { - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - // Load accumulator - auto accTileIn = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTileIn[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - auto accTileOut = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTileOut[k], acc[k]); } - } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - // Load accumulator - auto accTilePsqtIn = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) + } + else + { + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } + // Load accumulator + auto accTileIn = reinterpret_cast( + &st->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTileIn[k]); - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } - // Store accumulator - auto accTilePsqtOut = reinterpret_cast( - &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqtOut[k], psqt[k]); + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); + } } - } - } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + // Load accumulator + auto accTilePsqtIn = reinterpret_cast( + &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + // Store accumulator + auto accTilePsqtOut = reinterpret_cast( + &states_to_update[i] + ->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqtOut[k], psqt[k]); + } + } + } #else - for (IndexType i = 0; states_to_update[i]; ++i) - { - std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], - st->accumulator.accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k]; - - st = states_to_update[i]; - - // Difference calculation for the deactivated features - for (const auto index : removed[i]) + for (IndexType i = 0; states_to_update[i]; ++i) { - const IndexType offset = HalfDimensions * index; + std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], + st->accumulator.accumulation[Perspective], + HalfDimensions * sizeof(BiasType)); - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; + for (std::size_t k = 0; k < PSQTBuckets; ++k) + states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = + st->accumulator.psqtAccumulation[Perspective][k]; - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + st = states_to_update[i]; + + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[Perspective][k] -= + psqtWeights[index * PSQTBuckets + k]; + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[Perspective][k] += + psqtWeights[index * PSQTBuckets + k]; + } } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } - } #endif } template void update_accumulator_refresh(const Position& pos) const { - #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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; - #endif +#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[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; +#endif - // Refresh the accumulator - // Could be extracted to a separate function because it's done in 2 places, - // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->accumulator; - accumulator.computed[Perspective] = true; - FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, active); + // Refresh the accumulator + // Could be extracted to a separate function because it's done in 2 places, + // but it's unclear if compilers would correctly handle register allocation. + auto& accumulator = pos.state()->accumulator; + accumulator.computed[Perspective] = true; + FeatureSet::IndexList active; + FeatureSet::append_active_indices(pos, active); #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast( - &biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - for (const auto index : active) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + auto accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); } - auto accTile = reinterpret_cast( - &accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_zero_psqt(); - - for (const auto index : active) + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_zero_psqt(); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + for (const auto index : active) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + auto accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); } - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - #else - std::memcpy(accumulator.accumulation[Perspective], biases, - HalfDimensions * sizeof(BiasType)); - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; - - for (const auto index : active) - { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; + std::memcpy(accumulator.accumulation[Perspective], biases, + HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } + accumulator.psqtAccumulation[Perspective][k] = 0; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] += + psqtWeights[index * PSQTBuckets + k]; + } #endif } template void hint_common_access_for_perspective(const Position& pos) const { - // Works like update_accumulator, but performs less work. - // Updates ONLY the accumulator for pos. + // Works like update_accumulator, but performs less work. + // Updates ONLY the accumulator for pos. - // Look for a usable accumulator of an earlier position. We keep track - // of the estimated gain in terms of features to be added/subtracted. - // Fast early exit. - if (pos.state()->accumulator.computed[Perspective]) - return; + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + // Fast early exit. + if (pos.state()->accumulator.computed[Perspective]) + return; - auto [oldest_st, _] = try_find_computed_accumulator(pos); + auto [oldest_st, _] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) - { - // Only update current position accumulator to minimize work. - StateInfo* states_to_update[2] = { pos.state(), nullptr }; - update_accumulator_incremental(pos, oldest_st, states_to_update); - } - else - { - update_accumulator_refresh(pos); - } + if (oldest_st->accumulator.computed[Perspective]) + { + // Only update current position accumulator to minimize work. + StateInfo* states_to_update[2] = {pos.state(), nullptr}; + update_accumulator_incremental(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } } template void update_accumulator(const Position& pos) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos); + auto [oldest_st, next] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) - { - if (next == nullptr) - return; + if (oldest_st->accumulator.computed[Perspective]) + { + if (next == nullptr) + return; - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. - // Currently we update 2 accumulators. - // 1. for the current position - // 2. the next accumulator after the computed one - // The heuristic may change in the future. - StateInfo *states_to_update[3] = - { next, next == pos.state() ? nullptr : pos.state(), nullptr }; + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + // Currently we update 2 accumulators. + // 1. for the current position + // 2. the next accumulator after the computed one + // The heuristic may change in the future. + StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(), + nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update); - } - else - { - update_accumulator_refresh(pos); - } + update_accumulator_incremental(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } } alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; - }; +}; } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED +#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp index ada371eb..f7354b3d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -46,59 +46,57 @@ namespace Stockfish { namespace Zobrist { - Key psq[PIECE_NB][SQUARE_NB]; - Key enpassant[FILE_NB]; - Key castling[CASTLING_RIGHT_NB]; - Key side; +Key psq[PIECE_NB][SQUARE_NB]; +Key enpassant[FILE_NB]; +Key castling[CASTLING_RIGHT_NB]; +Key side; } namespace { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; -} // namespace +constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; +} // namespace // operator<<(Position) returns an ASCII representation of the position std::ostream& operator<<(std::ostream& os, const Position& pos) { - os << "\n +---+---+---+---+---+---+---+---+\n"; + os << "\n +---+---+---+---+---+---+---+---+\n"; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; - os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; - } + os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; + } - os << " a b c d e f g h\n" - << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase - << std::setfill('0') << std::setw(16) << pos.key() - << std::setfill(' ') << std::dec << "\nCheckers: "; + os << " a b c d e f g h\n" + << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase << std::setfill('0') + << std::setw(16) << pos.key() << std::setfill(' ') << std::dec << "\nCheckers: "; - for (Bitboard b = pos.checkers(); b; ) - os << UCI::square(pop_lsb(b)) << " "; + for (Bitboard b = pos.checkers(); b;) + os << UCI::square(pop_lsb(b)) << " "; - if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) - && !pos.can_castle(ANY_CASTLING)) - { - StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - Position p; - p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); - Tablebases::ProbeState s1, s2; - Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); - int dtz = Tablebases::probe_dtz(p, &s2); - os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" - << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; - } + Position p; + p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + Tablebases::ProbeState s1, s2; + Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); + int dtz = Tablebases::probe_dtz(p, &s2); + os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" + << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; + } - return os; + return os; } @@ -112,7 +110,7 @@ inline int H1(Key h) { return h & 0x1fff; } inline int H2(Key h) { return (h >> 16) & 0x1fff; } // Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves -Key cuckoo[8192]; +Key cuckoo[8192]; Move cuckooMove[8192]; @@ -120,43 +118,43 @@ Move cuckooMove[8192]; void Position::init() { - PRNG rng(1070372); + PRNG rng(1070372); - for (Piece pc : Pieces) - for (Square s = SQ_A1; s <= SQ_H8; ++s) - Zobrist::psq[pc][s] = rng.rand(); + for (Piece pc : Pieces) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + Zobrist::psq[pc][s] = rng.rand(); - for (File f = FILE_A; f <= FILE_H; ++f) - Zobrist::enpassant[f] = rng.rand(); + for (File f = FILE_A; f <= FILE_H; ++f) + Zobrist::enpassant[f] = rng.rand(); - for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) - Zobrist::castling[cr] = rng.rand(); + for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) + Zobrist::castling[cr] = rng.rand(); - Zobrist::side = rng.rand(); + Zobrist::side = rng.rand(); - // Prepare the cuckoo tables - std::memset(cuckoo, 0, sizeof(cuckoo)); - std::memset(cuckooMove, 0, sizeof(cuckooMove)); - [[maybe_unused]] int count = 0; - for (Piece pc : Pieces) - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) - if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) - { - Move move = make_move(s1, s2); - Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; - int i = H1(key); - while (true) - { - std::swap(cuckoo[i], key); - std::swap(cuckooMove[i], move); - if (move == MOVE_NONE) // Arrived at empty slot? - break; - i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot - } - count++; - } - assert(count == 3668); + // Prepare the cuckoo tables + std::memset(cuckoo, 0, sizeof(cuckoo)); + std::memset(cuckooMove, 0, sizeof(cuckooMove)); + [[maybe_unused]] int count = 0; + for (Piece pc : Pieces) + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) + if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) + { + Move move = make_move(s1, s2); + Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; + int i = H1(key); + while (true) + { + std::swap(cuckoo[i], key); + std::swap(cuckooMove[i], move); + if (move == MOVE_NONE) // Arrived at empty slot? + break; + i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot + } + count++; + } + assert(count == 3668); } @@ -165,7 +163,7 @@ void Position::init() { // this is assumed to be the responsibility of the GUI. Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { -/* + /* A FEN string defines a particular position using only the ASCII character set. A FEN string contains six fields separated by a space. The fields are: @@ -200,100 +198,103 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th incremented after Black's move. */ - unsigned char col, row, token; - size_t idx; - Square sq = SQ_A8; - std::istringstream ss(fenStr); + unsigned char col, row, token; + size_t idx; + Square sq = SQ_A8; + std::istringstream ss(fenStr); - std::memset(this, 0, sizeof(Position)); - std::memset(si, 0, sizeof(StateInfo)); - st = si; + std::memset(this, 0, sizeof(Position)); + std::memset(si, 0, sizeof(StateInfo)); + st = si; - ss >> std::noskipws; + ss >> std::noskipws; - // 1. Piece placement - while ((ss >> token) && !isspace(token)) - { - if (isdigit(token)) - sq += (token - '0') * EAST; // Advance the given number of files + // 1. Piece placement + while ((ss >> token) && !isspace(token)) + { + if (isdigit(token)) + sq += (token - '0') * EAST; // Advance the given number of files - else if (token == '/') - sq += 2 * SOUTH; + else if (token == '/') + sq += 2 * SOUTH; - else if ((idx = PieceToChar.find(token)) != string::npos) { - put_piece(Piece(idx), sq); - ++sq; - } - } + else if ((idx = PieceToChar.find(token)) != string::npos) + { + put_piece(Piece(idx), sq); + ++sq; + } + } - // 2. Active color - ss >> token; - sideToMove = (token == 'w' ? WHITE : BLACK); - ss >> token; + // 2. Active color + ss >> token; + sideToMove = (token == 'w' ? WHITE : BLACK); + ss >> token; - // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, - // Shredder-FEN that uses the letters of the columns on which the rooks began - // the game instead of KQkq and also X-FEN standard that, in case of Chess960, - // if an inner rook is associated with the castling right, the castling tag is - // replaced by the file letter of the involved rook, as for the Shredder-FEN. - while ((ss >> token) && !isspace(token)) - { - Square rsq; - Color c = islower(token) ? BLACK : WHITE; - Piece rook = make_piece(c, ROOK); + // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, + // Shredder-FEN that uses the letters of the columns on which the rooks began + // the game instead of KQkq and also X-FEN standard that, in case of Chess960, + // if an inner rook is associated with the castling right, the castling tag is + // replaced by the file letter of the involved rook, as for the Shredder-FEN. + while ((ss >> token) && !isspace(token)) + { + Square rsq; + Color c = islower(token) ? BLACK : WHITE; + Piece rook = make_piece(c, ROOK); - token = char(toupper(token)); + token = char(toupper(token)); - if (token == 'K') - for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {} + if (token == 'K') + for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) + {} - else if (token == 'Q') - for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {} + else if (token == 'Q') + for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) + {} - else if (token >= 'A' && token <= 'H') - rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + else if (token >= 'A' && token <= 'H') + rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); - else - continue; + else + continue; - set_castling_right(c, rsq); - } + set_castling_right(c, rsq); + } - // 4. En passant square. - // Ignore if square is invalid or not on side to move relative rank 6. - bool enpassant = false; + // 4. En passant square. + // Ignore if square is invalid or not on side to move relative rank 6. + bool enpassant = false; - if ( ((ss >> col) && (col >= 'a' && col <= 'h')) - && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) - { - st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + if (((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) + { + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); - // En passant square will be considered only if - // a) side to move have a pawn threatening epSquare - // b) there is an enemy pawn in front of epSquare - // c) there is no piece on epSquare or behind epSquare - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) - && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); - } + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + } - if (!enpassant) - st->epSquare = SQ_NONE; + if (!enpassant) + st->epSquare = SQ_NONE; - // 5-6. Halfmove clock and fullmove number - ss >> std::skipws >> st->rule50 >> gamePly; + // 5-6. Halfmove clock and fullmove number + ss >> std::skipws >> st->rule50 >> gamePly; - // Convert from fullmove starting from 1 to gamePly starting from 0, - // handle also common incorrect FEN with fullmove = 0. - gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + // Convert from fullmove starting from 1 to gamePly starting from 0, + // handle also common incorrect FEN with fullmove = 0. + gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); - chess960 = isChess960; - thisThread = th; - set_state(); + chess960 = isChess960; + thisThread = th; + set_state(); - assert(pos_is_ok()); + assert(pos_is_ok()); - return *this; + return *this; } @@ -302,19 +303,18 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th void Position::set_castling_right(Color c, Square rfrom) { - Square kfrom = square(c); - CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE); + Square kfrom = square(c); + CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE); - st->castlingRights |= cr; - castlingRightsMask[kfrom] |= cr; - castlingRightsMask[rfrom] |= cr; - castlingRookSquare[cr] = rfrom; + st->castlingRights |= cr; + castlingRightsMask[kfrom] |= cr; + castlingRightsMask[rfrom] |= cr; + castlingRookSquare[cr] = rfrom; - Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); - Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); + Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); + Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); - castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) - & ~(kfrom | rfrom); + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom); } @@ -322,17 +322,17 @@ void Position::set_castling_right(Color c, Square rfrom) { void Position::set_check_info() const { - update_slider_blockers(WHITE); - update_slider_blockers(BLACK); + update_slider_blockers(WHITE); + update_slider_blockers(BLACK); - Square ksq = square(~sideToMove); + Square ksq = square(~sideToMove); - st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); - st->checkSquares[KNIGHT] = attacks_bb(ksq); - st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); - st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); - st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; - st->checkSquares[KING] = 0; + st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[KNIGHT] = attacks_bb(ksq); + st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); + st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); + st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; + st->checkSquares[KING] = 0; } @@ -342,33 +342,33 @@ void Position::set_check_info() const { void Position::set_state() const { - st->key = st->materialKey = 0; - st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; - st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + st->key = st->materialKey = 0; + st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; + st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); - set_check_info(); + set_check_info(); - for (Bitboard b = pieces(); b; ) - { - Square s = pop_lsb(b); - Piece pc = piece_on(s); - st->key ^= Zobrist::psq[pc][s]; + for (Bitboard b = pieces(); b;) + { + Square s = pop_lsb(b); + Piece pc = piece_on(s); + st->key ^= Zobrist::psq[pc][s]; - if (type_of(pc) != KING && type_of(pc) != PAWN) - st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; - } + if (type_of(pc) != KING && type_of(pc) != PAWN) + st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; + } - if (st->epSquare != SQ_NONE) - st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + if (st->epSquare != SQ_NONE) + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; - if (sideToMove == BLACK) - st->key ^= Zobrist::side; + if (sideToMove == BLACK) + st->key ^= Zobrist::side; - st->key ^= Zobrist::castling[st->castlingRights]; + st->key ^= Zobrist::castling[st->castlingRights]; - for (Piece pc : Pieces) - for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][cnt]; + for (Piece pc : Pieces) + for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) + st->materialKey ^= Zobrist::psq[pc][cnt]; } @@ -378,20 +378,20 @@ void Position::set_state() const { Position& Position::set(const string& code, Color c, StateInfo* si) { - assert(code[0] == 'K'); + assert(code[0] == 'K'); - string sides[] = { code.substr(code.find('K', 1)), // Weak - code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong + string sides[] = {code.substr(code.find('K', 1)), // Weak + code.substr(0, std::min(code.find('v'), code.find('K', 1)))}; // Strong - assert(sides[0].length() > 0 && sides[0].length() < 8); - assert(sides[1].length() > 0 && sides[1].length() < 8); + assert(sides[0].length() > 0 && sides[0].length() < 8); + assert(sides[1].length() > 0 && sides[1].length() < 8); - std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); - string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" - + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; + string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1] + + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; - return set(fenStr, false, si, nullptr); + return set(fenStr, false, si, nullptr); } @@ -400,48 +400,48 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { string Position::fen() const { - int emptyCnt; - std::ostringstream ss; + int emptyCnt; + std::ostringstream ss; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - { - for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) - ++emptyCnt; + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + { + for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) + ++emptyCnt; - if (emptyCnt) - ss << emptyCnt; + if (emptyCnt) + ss << emptyCnt; - if (f <= FILE_H) - ss << PieceToChar[piece_on(make_square(f, r))]; - } + if (f <= FILE_H) + ss << PieceToChar[piece_on(make_square(f, r))]; + } - if (r > RANK_1) - ss << '/'; - } + if (r > RANK_1) + ss << '/'; + } - ss << (sideToMove == WHITE ? " w " : " b "); + ss << (sideToMove == WHITE ? " w " : " b "); - if (can_castle(WHITE_OO)) - ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K'); + if (can_castle(WHITE_OO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO))) : 'K'); - if (can_castle(WHITE_OOO)) - ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); + if (can_castle(WHITE_OOO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); - if (can_castle(BLACK_OO)) - ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k'); + if (can_castle(BLACK_OO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO))) : 'k'); - if (can_castle(BLACK_OOO)) - ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); + if (can_castle(BLACK_OOO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); - if (!can_castle(ANY_CASTLING)) - ss << '-'; + if (!can_castle(ANY_CASTLING)) + ss << '-'; - ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") - << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50 + << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; - return ss.str(); + return ss.str(); } // update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], @@ -449,28 +449,29 @@ string Position::fen() const { // and the slider pieces of color ~c pinning pieces of color c to the king. void Position::update_slider_blockers(Color c) const { - Square ksq = square(c); + Square ksq = square(c); - st->blockersForKing[c] = 0; - st->pinners[~c] = 0; + st->blockersForKing[c] = 0; + st->pinners[~c] = 0; - // Snipers are sliders that attack 's' when a piece and other snipers are removed - Bitboard snipers = ( (attacks_bb< ROOK>(ksq) & pieces(QUEEN, ROOK)) - | (attacks_bb(ksq) & pieces(QUEEN, BISHOP))) & pieces(~c); - Bitboard occupancy = pieces() ^ snipers; + // Snipers are sliders that attack 's' when a piece and other snipers are removed + Bitboard snipers = ((attacks_bb(ksq) & pieces(QUEEN, ROOK)) + | (attacks_bb(ksq) & pieces(QUEEN, BISHOP))) + & pieces(~c); + Bitboard occupancy = pieces() ^ snipers; - while (snipers) - { - Square sniperSq = pop_lsb(snipers); - Bitboard b = between_bb(ksq, sniperSq) & occupancy; - - if (b && !more_than_one(b)) + while (snipers) { - st->blockersForKing[c] |= b; - if (b & pieces(c)) - st->pinners[~c] |= sniperSq; + Square sniperSq = pop_lsb(snipers); + Bitboard b = between_bb(ksq, sniperSq) & occupancy; + + if (b && !more_than_one(b)) + { + st->blockersForKing[c] |= b; + if (b & pieces(c)) + st->pinners[~c] |= sniperSq; + } } - } } @@ -479,12 +480,12 @@ void Position::update_slider_blockers(Color c) const { Bitboard Position::attackers_to(Square s, Bitboard occupied) const { - return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) - | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) - | (attacks_bb(s) & pieces(KNIGHT)) - | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) - | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_bb(s) & pieces(KING)); + return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) + | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s) & pieces(KNIGHT)) + | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(s) & pieces(KING)); } @@ -492,60 +493,59 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { bool Position::legal(Move m) const { - assert(is_ok(m)); + assert(is_ok(m)); - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); - assert(color_of(moved_piece(m)) == us); - assert(piece_on(square(us)) == make_piece(us, KING)); + assert(color_of(moved_piece(m)) == us); + assert(piece_on(square(us)) == make_piece(us, KING)); - // En passant captures are a tricky special case. Because they are rather - // uncommon, we do it simply by testing whether the king is attacked after - // the move is made. - if (type_of(m) == EN_PASSANT) - { - Square ksq = square(us); - Square capsq = to - pawn_push(us); - Bitboard occupied = (pieces() ^ from ^ capsq) | to; + // En passant captures are a tricky special case. Because they are rather + // uncommon, we do it simply by testing whether the king is attacked after + // the move is made. + if (type_of(m) == EN_PASSANT) + { + Square ksq = square(us); + Square capsq = to - pawn_push(us); + Bitboard occupied = (pieces() ^ from ^ capsq) | to; - assert(to == ep_square()); - assert(moved_piece(m) == make_piece(us, PAWN)); - assert(piece_on(capsq) == make_piece(~us, PAWN)); - assert(piece_on(to) == NO_PIECE); + assert(to == ep_square()); + assert(moved_piece(m) == make_piece(us, PAWN)); + assert(piece_on(capsq) == make_piece(~us, PAWN)); + assert(piece_on(to) == NO_PIECE); - return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK)) + return !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, ROOK)) && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); - } + } - // Castling moves generation does not check if the castling path is clear of - // enemy attacks, it is delayed at a later time: now! - if (type_of(m) == CASTLING) - { - // After castling, the rook and king final positions are the same in - // Chess960 as they would be in standard chess. - to = relative_square(us, to > from ? SQ_G1 : SQ_C1); - Direction step = to > from ? WEST : EAST; + // Castling moves generation does not check if the castling path is clear of + // enemy attacks, it is delayed at a later time: now! + if (type_of(m) == CASTLING) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + to = relative_square(us, to > from ? SQ_G1 : SQ_C1); + Direction step = to > from ? WEST : EAST; - for (Square s = to; s != from; s += step) - if (attackers_to(s) & pieces(~us)) - return false; + for (Square s = to; s != from; s += step) + if (attackers_to(s) & pieces(~us)) + return false; - // In case of Chess960, verify if the Rook blocks some checks. - // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 || !(blockers_for_king(us) & to_sq(m)); - } + // In case of Chess960, verify if the Rook blocks some checks. + // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. + return !chess960 || !(blockers_for_king(us) & to_sq(m)); + } - // If the moving piece is a king, check whether the destination square is - // attacked by the opponent. - if (type_of(piece_on(from)) == KING) - return !(attackers_to(to, pieces() ^ from) & pieces(~us)); + // If the moving piece is a king, check whether the destination square is + // attacked by the opponent. + if (type_of(piece_on(from)) == KING) + return !(attackers_to(to, pieces() ^ from) & pieces(~us)); - // A non-king move is legal if and only if it is not pinned or it - // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) - || aligned(from, to, square(us)); + // A non-king move is legal if and only if it is not pinned or it + // is moving along the ray towards or away from the king. + return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); } @@ -555,70 +555,68 @@ bool Position::legal(Move m) const { bool Position::pseudo_legal(const Move m) const { - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = moved_piece(m); + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = moved_piece(m); - // Use a slower but simpler function for uncommon cases - // yet we skip the legality check of MoveList(). - if (type_of(m) != NORMAL) - return checkers() ? MoveList< EVASIONS>(*this).contains(m) - : MoveList(*this).contains(m); + // Use a slower but simpler function for uncommon cases + // yet we skip the legality check of MoveList(). + if (type_of(m) != NORMAL) + return checkers() ? MoveList(*this).contains(m) + : MoveList(*this).contains(m); - // Is not a promotion, so the promotion piece must be empty - assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); + // Is not a promotion, so the promotion piece must be empty + assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); - // If the 'from' square is not occupied by a piece belonging to the side to - // move, the move is obviously not legal. - if (pc == NO_PIECE || color_of(pc) != us) - return false; + // If the 'from' square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (pc == NO_PIECE || color_of(pc) != us) + return false; - // The destination square cannot be occupied by a friendly piece - if (pieces(us) & to) - return false; + // The destination square cannot be occupied by a friendly piece + if (pieces(us) & to) + return false; - // Handle the special case of a pawn move - if (type_of(pc) == PAWN) - { - // We have already handled promotion moves, so destination - // cannot be on the 8th/1st rank. - if ((Rank8BB | Rank1BB) & to) - return false; + // Handle the special case of a pawn move + if (type_of(pc) == PAWN) + { + // We have already handled promotion moves, so destination + // cannot be on the 8th/1st rank. + if ((Rank8BB | Rank1BB) & to) + return false; - if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !( (from + 2 * pawn_push(us) == to) // Not a double push - && (relative_rank(us, from) == RANK_2) - && empty(to) - && empty(to - pawn_push(us)))) - return false; - } - else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) - return false; + if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !((from + 2 * pawn_push(us) == to) // Not a double push + && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) + return false; + } + else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) + return false; - // Evasions generator already takes care to avoid some kind of illegal moves - // and legal() relies on this. We therefore have to take care that the same - // kind of moves are filtered out here. - if (checkers()) - { - if (type_of(pc) != KING) - { - // Double check? In this case, a king move is required - if (more_than_one(checkers())) - return false; + // Evasions generator already takes care to avoid some kind of illegal moves + // and legal() relies on this. We therefore have to take care that the same + // kind of moves are filtered out here. + if (checkers()) + { + if (type_of(pc) != KING) + { + // Double check? In this case, a king move is required + if (more_than_one(checkers())) + return false; - // Our move must be a blocking interposition or a capture of the checking piece - if (!(between_bb(square(us), lsb(checkers())) & to)) - return false; - } - // In case of king moves under check we have to remove the king so as to catch - // invalid moves like b1a1 when opposite queen is on c1. - else if (attackers_to(to, pieces() ^ from) & pieces(~us)) - return false; - } + // Our move must be a blocking interposition or a capture of the checking piece + if (!(between_bb(square(us), lsb(checkers())) & to)) + return false; + } + // In case of king moves under check we have to remove the king so as to catch + // invalid moves like b1a1 when opposite queen is on c1. + else if (attackers_to(to, pieces() ^ from) & pieces(~us)) + return false; + } - return true; + return true; } @@ -626,49 +624,48 @@ bool Position::pseudo_legal(const Move m) const { bool Position::gives_check(Move m) const { - assert(is_ok(m)); - assert(color_of(moved_piece(m)) == sideToMove); + assert(is_ok(m)); + assert(color_of(moved_piece(m)) == sideToMove); - Square from = from_sq(m); - Square to = to_sq(m); + Square from = from_sq(m); + Square to = to_sq(m); - // Is there a direct check? - if (check_squares(type_of(piece_on(from))) & to) - return true; + // Is there a direct check? + if (check_squares(type_of(piece_on(from))) & to) + return true; - // Is there a discovered check? - if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) - || type_of(m) == CASTLING; + // Is there a discovered check? + if (blockers_for_king(~sideToMove) & from) + return !aligned(from, to, square(~sideToMove)) || type_of(m) == CASTLING; - switch (type_of(m)) - { - case NORMAL: - return false; + switch (type_of(m)) + { + case NORMAL : + return false; - case PROMOTION: - return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + case PROMOTION : + return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); - // En passant capture with check? We have already handled the case - // of direct checks and ordinary discovered check, so the only case we - // need to handle is the unusual case of a discovered check through - // the captured pawn. - case EN_PASSANT: - { - Square capsq = make_square(file_of(to), rank_of(from)); - Bitboard b = (pieces() ^ from ^ capsq) | to; + // En passant capture with check? We have already handled the case + // of direct checks and ordinary discovered check, so the only case we + // need to handle is the unusual case of a discovered check through + // the captured pawn. + case EN_PASSANT : { + Square capsq = make_square(file_of(to), rank_of(from)); + Bitboard b = (pieces() ^ from ^ capsq) | to; - return (attacks_bb< ROOK>(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) - | (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP)); - } - default: //CASTLING - { - // Castling is encoded as 'king captures the rook' - Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); + return (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) + | (attacks_bb(square(~sideToMove), b) + & pieces(sideToMove, QUEEN, BISHOP)); + } + default : //CASTLING + { + // Castling is encoded as 'king captures the rook' + Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); - return check_squares(ROOK) & rto; - } - } + return check_squares(ROOK) & rto; + } + } } @@ -678,195 +675,195 @@ bool Position::gives_check(Move m) const { void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { - assert(is_ok(m)); - assert(&newSt != st); + assert(is_ok(m)); + assert(&newSt != st); - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - Key k = st->key ^ Zobrist::side; + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + Key k = st->key ^ Zobrist::side; - // Copy some fields of the old state to our new StateInfo object except the - // ones which are going to be recalculated from scratch anyway and then switch - // our state pointer to point to the new (ready to be updated) state. - std::memcpy(&newSt, st, offsetof(StateInfo, key)); - newSt.previous = st; - st = &newSt; + // Copy some fields of the old state to our new StateInfo object except the + // ones which are going to be recalculated from scratch anyway and then switch + // our state pointer to point to the new (ready to be updated) state. + std::memcpy(&newSt, st, offsetof(StateInfo, key)); + newSt.previous = st; + st = &newSt; - // Increment ply counters. In particular, rule50 will be reset to zero later on - // in case of a capture or a pawn move. - ++gamePly; - ++st->rule50; - ++st->pliesFromNull; + // Increment ply counters. In particular, rule50 will be reset to zero later on + // in case of a capture or a pawn move. + ++gamePly; + ++st->rule50; + ++st->pliesFromNull; - // Used by NNUE - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; - auto& dp = st->dirtyPiece; - dp.dirty_num = 1; + // Used by NNUE + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; - Color us = sideToMove; - Color them = ~us; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(from); - Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + Color us = sideToMove; + Color them = ~us; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(from); + Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); - assert(color_of(pc) == us); - assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); - assert(type_of(captured) != KING); + assert(color_of(pc) == us); + assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); + assert(type_of(captured) != KING); - if (type_of(m) == CASTLING) - { - assert(pc == make_piece(us, KING)); - assert(captured == make_piece(us, ROOK)); + if (type_of(m) == CASTLING) + { + assert(pc == make_piece(us, KING)); + assert(captured == make_piece(us, ROOK)); - Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); - k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; - captured = NO_PIECE; - } + k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; + captured = NO_PIECE; + } - if (captured) - { - Square capsq = to; + if (captured) + { + Square capsq = to; - // If the captured piece is a pawn, update pawn hash key, otherwise - // update non-pawn material. - if (type_of(captured) == PAWN) - { - if (type_of(m) == EN_PASSANT) - { - capsq -= pawn_push(us); + // If the captured piece is a pawn, update pawn hash key, otherwise + // update non-pawn material. + if (type_of(captured) == PAWN) + { + if (type_of(m) == EN_PASSANT) + { + capsq -= pawn_push(us); - assert(pc == make_piece(us, PAWN)); - assert(to == st->epSquare); - assert(relative_rank(us, to) == RANK_6); - assert(piece_on(to) == NO_PIECE); - assert(piece_on(capsq) == make_piece(them, PAWN)); - } - } - else - st->nonPawnMaterial[them] -= PieceValue[captured]; + assert(pc == make_piece(us, PAWN)); + assert(to == st->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(to) == NO_PIECE); + assert(piece_on(capsq) == make_piece(them, PAWN)); + } + } + else + st->nonPawnMaterial[them] -= PieceValue[captured]; - dp.dirty_num = 2; // 1 piece moved, 1 piece captured - dp.piece[1] = captured; - dp.from[1] = capsq; - dp.to[1] = SQ_NONE; + dp.dirty_num = 2; // 1 piece moved, 1 piece captured + dp.piece[1] = captured; + dp.from[1] = capsq; + dp.to[1] = SQ_NONE; - // Update board and piece lists - remove_piece(capsq); + // Update board and piece lists + remove_piece(capsq); - // Update material hash key and prefetch access to materialTable - k ^= Zobrist::psq[captured][capsq]; - st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; + // Update material hash key and prefetch access to materialTable + k ^= Zobrist::psq[captured][capsq]; + st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; - // Reset rule 50 counter - st->rule50 = 0; - } + // Reset rule 50 counter + st->rule50 = 0; + } - // Update hash key - k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + // Update hash key + k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - // Reset en passant square - if (st->epSquare != SQ_NONE) - { - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - st->epSquare = SQ_NONE; - } + // Reset en passant square + if (st->epSquare != SQ_NONE) + { + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } - // Update castling rights if needed - if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) - { - k ^= Zobrist::castling[st->castlingRights]; - st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); - k ^= Zobrist::castling[st->castlingRights]; - } + // Update castling rights if needed + if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) + { + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + k ^= Zobrist::castling[st->castlingRights]; + } - // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) - { - dp.piece[0] = pc; - dp.from[0] = from; - dp.to[0] = to; + // Move the piece. The tricky Chess960 castling is handled earlier + if (type_of(m) != CASTLING) + { + dp.piece[0] = pc; + dp.from[0] = from; + dp.to[0] = to; - move_piece(from, to); - } + move_piece(from, to); + } - // If the moving piece is a pawn do some special extra work - if (type_of(pc) == PAWN) - { - // Set en passant square if the moved pawn can be captured - if ( (int(to) ^ int(from)) == 16 - && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) - { - st->epSquare = to - pawn_push(us); - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - } + // If the moving piece is a pawn do some special extra work + if (type_of(pc) == PAWN) + { + // Set en passant square if the moved pawn can be captured + if ((int(to) ^ int(from)) == 16 + && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) + { + st->epSquare = to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + } - else if (type_of(m) == PROMOTION) - { - Piece promotion = make_piece(us, promotion_type(m)); + else if (type_of(m) == PROMOTION) + { + Piece promotion = make_piece(us, promotion_type(m)); - assert(relative_rank(us, to) == RANK_8); - assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); + assert(relative_rank(us, to) == RANK_8); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); - remove_piece(to); - put_piece(promotion, to); + remove_piece(to); + put_piece(promotion, to); - // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE - dp.to[0] = SQ_NONE; - dp.piece[dp.dirty_num] = promotion; - dp.from[dp.dirty_num] = SQ_NONE; - dp.to[dp.dirty_num] = to; - dp.dirty_num++; + // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.piece[dp.dirty_num] = promotion; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = to; + dp.dirty_num++; - // Update hash keys - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; - st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] - ^ Zobrist::psq[pc][pieceCount[pc]]; + // Update hash keys + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; + st->materialKey ^= + Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; - // Update material - st->nonPawnMaterial[us] += PieceValue[promotion]; - } + // Update material + st->nonPawnMaterial[us] += PieceValue[promotion]; + } - // Reset rule 50 draw counter - st->rule50 = 0; - } + // Reset rule 50 draw counter + st->rule50 = 0; + } - // Set capture piece - st->capturedPiece = captured; + // Set capture piece + st->capturedPiece = captured; - // Update the key with the final value - st->key = k; + // Update the key with the final value + st->key = k; - // Calculate checkers bitboard (if move gives check) - st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + // Calculate checkers bitboard (if move gives check) + st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; - sideToMove = ~sideToMove; + sideToMove = ~sideToMove; - // Update king attacks used for fast check detection - set_check_info(); + // Update king attacks used for fast check detection + set_check_info(); - // Calculate the repetition info. It is the ply distance from the previous - // occurrence of the same position, negative in the 3-fold case, or zero - // if the position was not repeated. - st->repetition = 0; - int end = std::min(st->rule50, st->pliesFromNull); - if (end >= 4) - { - StateInfo* stp = st->previous->previous; - for (int i = 4; i <= end; i += 2) - { - stp = stp->previous->previous; - if (stp->key == st->key) - { - st->repetition = stp->repetition ? -i : i; - break; - } - } - } + // Calculate the repetition info. It is the ply distance from the previous + // occurrence of the same position, negative in the 3-fold case, or zero + // if the position was not repeated. + st->repetition = 0; + int end = std::min(st->rule50, st->pliesFromNull); + if (end >= 4) + { + StateInfo* stp = st->previous->previous; + for (int i = 4; i <= end; i += 2) + { + stp = stp->previous->previous; + if (stp->key == st->key) + { + st->repetition = stp->repetition ? -i : i; + break; + } + } + } - assert(pos_is_ok()); + assert(pos_is_ok()); } @@ -875,62 +872,62 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { void Position::undo_move(Move m) { - assert(is_ok(m)); + assert(is_ok(m)); - sideToMove = ~sideToMove; + sideToMove = ~sideToMove; - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(to); + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(to); - assert(empty(from) || type_of(m) == CASTLING); - assert(type_of(st->capturedPiece) != KING); + assert(empty(from) || type_of(m) == CASTLING); + assert(type_of(st->capturedPiece) != KING); - if (type_of(m) == PROMOTION) - { - assert(relative_rank(us, to) == RANK_8); - assert(type_of(pc) == promotion_type(m)); - assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); + if (type_of(m) == PROMOTION) + { + assert(relative_rank(us, to) == RANK_8); + assert(type_of(pc) == promotion_type(m)); + assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); - remove_piece(to); - pc = make_piece(us, PAWN); - put_piece(pc, to); - } + remove_piece(to); + pc = make_piece(us, PAWN); + put_piece(pc, to); + } - if (type_of(m) == CASTLING) - { - Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); - } - else - { - move_piece(to, from); // Put the piece back at the source square + if (type_of(m) == CASTLING) + { + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + } + else + { + move_piece(to, from); // Put the piece back at the source square - if (st->capturedPiece) - { - Square capsq = to; + if (st->capturedPiece) + { + Square capsq = to; - if (type_of(m) == EN_PASSANT) - { - capsq -= pawn_push(us); + if (type_of(m) == EN_PASSANT) + { + capsq -= pawn_push(us); - assert(type_of(pc) == PAWN); - assert(to == st->previous->epSquare); - assert(relative_rank(us, to) == RANK_6); - assert(piece_on(capsq) == NO_PIECE); - assert(st->capturedPiece == make_piece(~us, PAWN)); - } + assert(type_of(pc) == PAWN); + assert(to == st->previous->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(capsq) == NO_PIECE); + assert(st->capturedPiece == make_piece(~us, PAWN)); + } - put_piece(st->capturedPiece, capsq); // Restore the captured piece - } - } + put_piece(st->capturedPiece, capsq); // Restore the captured piece + } + } - // Finally point our state pointer back to the previous state - st = st->previous; - --gamePly; + // Finally point our state pointer back to the previous state + st = st->previous; + --gamePly; - assert(pos_is_ok()); + assert(pos_is_ok()); } @@ -939,29 +936,30 @@ void Position::undo_move(Move m) { template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { - bool kingSide = to > from; - rfrom = to; // Castling is encoded as "king captures friendly rook" - rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); - to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + bool kingSide = to > from; + rfrom = to; // Castling is encoded as "king captures friendly rook" + rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); + to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); - if (Do) - { - auto& dp = st->dirtyPiece; - dp.piece[0] = make_piece(us, KING); - dp.from[0] = from; - dp.to[0] = to; - dp.piece[1] = make_piece(us, ROOK); - dp.from[1] = rfrom; - dp.to[1] = rto; - dp.dirty_num = 2; - } + if (Do) + { + auto& dp = st->dirtyPiece; + dp.piece[0] = make_piece(us, KING); + dp.from[0] = from; + dp.to[0] = to; + dp.piece[1] = make_piece(us, ROOK); + dp.from[1] = rfrom; + dp.to[1] = rto; + dp.dirty_num = 2; + } - // Remove both pieces first since squares could overlap in Chess960 - remove_piece(Do ? from : to); - remove_piece(Do ? rfrom : rto); - board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // remove_piece does not do this for us - put_piece(make_piece(us, KING), Do ? to : from); - put_piece(make_piece(us, ROOK), Do ? rto : rfrom); + // Remove both pieces first since squares could overlap in Chess960 + remove_piece(Do ? from : to); + remove_piece(Do ? rfrom : rto); + board[Do ? from : to] = board[Do ? rfrom : rto] = + NO_PIECE; // remove_piece does not do this for us + put_piece(make_piece(us, KING), Do ? to : from); + put_piece(make_piece(us, ROOK), Do ? rto : rfrom); } @@ -970,38 +968,38 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ void Position::do_null_move(StateInfo& newSt) { - assert(!checkers()); - assert(&newSt != st); + assert(!checkers()); + assert(&newSt != st); - std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); + std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); - newSt.previous = st; - st = &newSt; + newSt.previous = st; + st = &newSt; - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; - if (st->epSquare != SQ_NONE) - { - st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; - st->epSquare = SQ_NONE; - } + if (st->epSquare != SQ_NONE) + { + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } - st->key ^= Zobrist::side; - ++st->rule50; - prefetch(TT.first_entry(key())); + st->key ^= Zobrist::side; + ++st->rule50; + prefetch(TT.first_entry(key())); - st->pliesFromNull = 0; + st->pliesFromNull = 0; - sideToMove = ~sideToMove; + sideToMove = ~sideToMove; - set_check_info(); + set_check_info(); - st->repetition = 0; + st->repetition = 0; - assert(pos_is_ok()); + assert(pos_is_ok()); } @@ -1009,10 +1007,10 @@ void Position::do_null_move(StateInfo& newSt) { void Position::undo_null_move() { - assert(!checkers()); + assert(!checkers()); - st = st->previous; - sideToMove = ~sideToMove; + st = st->previous; + sideToMove = ~sideToMove; } @@ -1022,19 +1020,18 @@ void Position::undo_null_move() { Key Position::key_after(Move m) const { - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(from); - Piece captured = piece_on(to); - Key k = st->key ^ Zobrist::side; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(from); + Piece captured = piece_on(to); + Key k = st->key ^ Zobrist::side; - if (captured) - k ^= Zobrist::psq[captured][to]; + if (captured) + k ^= Zobrist::psq[captured][to]; - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; - return (captured || type_of(pc) == PAWN) - ? k : adjust_key50(k); + return (captured || type_of(pc) == PAWN) ? k : adjust_key50(k); } @@ -1044,103 +1041,103 @@ Key Position::key_after(Move m) const { bool Position::see_ge(Move m, Value threshold) const { - assert(is_ok(m)); + assert(is_ok(m)); - // Only deal with normal moves, assume others pass a simple SEE - if (type_of(m) != NORMAL) - return VALUE_ZERO >= threshold; + // Only deal with normal moves, assume others pass a simple SEE + if (type_of(m) != NORMAL) + return VALUE_ZERO >= threshold; - Square from = from_sq(m), to = to_sq(m); + Square from = from_sq(m), to = to_sq(m); - int swap = PieceValue[piece_on(to)] - threshold; - if (swap < 0) - return false; + int swap = PieceValue[piece_on(to)] - threshold; + if (swap < 0) + return false; - swap = PieceValue[piece_on(from)] - swap; - if (swap <= 0) - return true; + swap = PieceValue[piece_on(from)] - swap; + if (swap <= 0) + return true; - assert(color_of(piece_on(from)) == sideToMove); - Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic - Color stm = sideToMove; - Bitboard attackers = attackers_to(to, occupied); - Bitboard stmAttackers, bb; - int res = 1; + assert(color_of(piece_on(from)) == sideToMove); + Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + Color stm = sideToMove; + Bitboard attackers = attackers_to(to, occupied); + Bitboard stmAttackers, bb; + int res = 1; - while (true) - { - stm = ~stm; - attackers &= occupied; + while (true) + { + stm = ~stm; + attackers &= occupied; - // If stm has no more attackers then give up: stm loses - if (!(stmAttackers = attackers & pieces(stm))) - break; + // If stm has no more attackers then give up: stm loses + if (!(stmAttackers = attackers & pieces(stm))) + break; - // Don't allow pinned pieces to attack as long as there are - // pinners on their original square. - if (pinners(~stm) & occupied) - { - stmAttackers &= ~blockers_for_king(stm); + // Don't allow pinned pieces to attack as long as there are + // pinners on their original square. + if (pinners(~stm) & occupied) + { + stmAttackers &= ~blockers_for_king(stm); - if (!stmAttackers) - break; - } + if (!stmAttackers) + break; + } - res ^= 1; + res ^= 1; - // Locate and remove the next least valuable attacker, and add to - // the bitboard 'attackers' any X-ray attackers behind it. - if ((bb = stmAttackers & pieces(PAWN))) - { - if ((swap = PawnValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); + // Locate and remove the next least valuable attacker, and add to + // the bitboard 'attackers' any X-ray attackers behind it. + if ((bb = stmAttackers & pieces(PAWN))) + { + if ((swap = PawnValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); - attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); - } + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } - else if ((bb = stmAttackers & pieces(KNIGHT))) - { - if ((swap = KnightValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); - } + else if ((bb = stmAttackers & pieces(KNIGHT))) + { + if ((swap = KnightValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + } - else if ((bb = stmAttackers & pieces(BISHOP))) - { - if ((swap = BishopValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); + else if ((bb = stmAttackers & pieces(BISHOP))) + { + if ((swap = BishopValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); - attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); - } + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } - else if ((bb = stmAttackers & pieces(ROOK))) - { - if ((swap = RookValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); + else if ((bb = stmAttackers & pieces(ROOK))) + { + if ((swap = RookValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); - attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); - } + attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); + } - else if ((bb = stmAttackers & pieces(QUEEN))) - { - if ((swap = QueenValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); + else if ((bb = stmAttackers & pieces(QUEEN))) + { + if ((swap = QueenValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); - attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); - } + attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(to, occupied) & pieces(ROOK, QUEEN)); + } - else // KING - // If we "capture" with the king but the opponent still has attackers, - // reverse the result. - return (attackers & ~pieces(stm)) ? res ^ 1 : res; - } + else // KING + // If we "capture" with the king but the opponent still has attackers, + // reverse the result. + return (attackers & ~pieces(stm)) ? res ^ 1 : res; + } - return bool(res); + return bool(res); } // Position::is_draw() tests whether the position is drawn by 50-move rule @@ -1148,12 +1145,12 @@ bool Position::see_ge(Move m, Value threshold) const { bool Position::is_draw(int ply) const { - if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) - return true; + if (st->rule50 > 99 && (!checkers() || MoveList(*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 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; } @@ -1163,7 +1160,7 @@ bool Position::is_draw(int ply) const { bool Position::has_repeated() const { StateInfo* stc = st; - int end = std::min(st->rule50, st->pliesFromNull); + int end = std::min(st->rule50, st->pliesFromNull); while (end-- >= 4) { if (stc->repetition) @@ -1180,47 +1177,46 @@ bool Position::has_repeated() const { bool Position::has_game_cycle(int ply) const { - int j; + int j; - int end = std::min(st->rule50, st->pliesFromNull); + int end = std::min(st->rule50, st->pliesFromNull); - if (end < 3) + if (end < 3) + return false; + + Key originalKey = st->key; + StateInfo* stp = st->previous; + + for (int i = 3; i <= end; i += 2) + { + stp = stp->previous->previous; + + Key moveKey = originalKey ^ stp->key; + if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) + { + Move move = cuckooMove[j]; + Square s1 = from_sq(move); + Square s2 = to_sq(move); + + if (!((between_bb(s1, s2) ^ s2) & pieces())) + { + if (ply > i) + return true; + + // For nodes before or at the root, check that the move is a + // repetition rather than a move to the current position. + // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in + // the same location, so we have to select which square to check. + if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) + continue; + + // For repetitions before or at the root, require one more + if (stp->repetition) + return true; + } + } + } return false; - - Key originalKey = st->key; - StateInfo* stp = st->previous; - - for (int i = 3; i <= end; i += 2) - { - stp = stp->previous->previous; - - Key moveKey = originalKey ^ stp->key; - if ( (j = H1(moveKey), cuckoo[j] == moveKey) - || (j = H2(moveKey), cuckoo[j] == moveKey)) - { - Move move = cuckooMove[j]; - Square s1 = from_sq(move); - Square s2 = to_sq(move); - - if (!((between_bb(s1, s2) ^ s2) & pieces())) - { - if (ply > i) - return true; - - // For nodes before or at the root, check that the move is a - // repetition rather than a move to the current position. - // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in - // the same location, so we have to select which square to check. - if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) - continue; - - // For repetitions before or at the root, require one more - if (stp->repetition) - return true; - } - } - } - return false; } @@ -1229,33 +1225,33 @@ bool Position::has_game_cycle(int ply) const { void Position::flip() { - string f, token; - std::stringstream ss(fen()); + string f, token; + std::stringstream ss(fen()); - for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement - { - std::getline(ss, token, r > RANK_1 ? '/' : ' '); - f.insert(0, token + (f.empty() ? " " : "/")); - } + for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement + { + std::getline(ss, token, r > RANK_1 ? '/' : ' '); + f.insert(0, token + (f.empty() ? " " : "/")); + } - ss >> token; // Active color - f += (token == "w" ? "B " : "W "); // Will be lowercased later + ss >> token; // Active color + f += (token == "w" ? "B " : "W "); // Will be lowercased later - ss >> token; // Castling availability - f += token + " "; + ss >> token; // Castling availability + f += token + " "; - std::transform(f.begin(), f.end(), f.begin(), - [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); + std::transform(f.begin(), f.end(), f.begin(), + [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); - ss >> token; // En passant square - f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); + ss >> token; // En passant square + f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); - std::getline(ss, token); // Half and full moves - f += token; + std::getline(ss, token); // Half and full moves + f += token; - set(f, is_chess960(), st, this_thread()); + set(f, is_chess960(), st, this_thread()); - assert(pos_is_ok()); + assert(pos_is_ok()); } @@ -1265,58 +1261,51 @@ void Position::flip() { bool Position::pos_is_ok() const { - constexpr bool Fast = true; // Quick (default) or full check? + constexpr bool Fast = true; // Quick (default) or full check? - if ( (sideToMove != WHITE && sideToMove != BLACK) - || piece_on(square(WHITE)) != W_KING - || piece_on(square(BLACK)) != B_KING - || ( ep_square() != SQ_NONE - && relative_rank(sideToMove, ep_square()) != RANK_6)) - assert(0 && "pos_is_ok: Default"); + if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square(WHITE)) != W_KING + || piece_on(square(BLACK)) != B_KING + || (ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6)) + assert(0 && "pos_is_ok: Default"); - if (Fast) - return true; + if (Fast) + return true; - if ( pieceCount[W_KING] != 1 - || pieceCount[B_KING] != 1 - || attackers_to(square(~sideToMove)) & pieces(sideToMove)) - assert(0 && "pos_is_ok: Kings"); + if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1 + || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + assert(0 && "pos_is_ok: Kings"); - if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) - || pieceCount[W_PAWN] > 8 - || pieceCount[B_PAWN] > 8) - assert(0 && "pos_is_ok: Pawns"); + if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8) + assert(0 && "pos_is_ok: Pawns"); - if ( (pieces(WHITE) & pieces(BLACK)) - || (pieces(WHITE) | pieces(BLACK)) != pieces() - || popcount(pieces(WHITE)) > 16 - || popcount(pieces(BLACK)) > 16) - assert(0 && "pos_is_ok: Bitboards"); + if ((pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces() + || popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16) + assert(0 && "pos_is_ok: Bitboards"); - for (PieceType p1 = PAWN; p1 <= KING; ++p1) - for (PieceType p2 = PAWN; p2 <= KING; ++p2) - if (p1 != p2 && (pieces(p1) & pieces(p2))) - assert(0 && "pos_is_ok: Bitboards"); + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + assert(0 && "pos_is_ok: Bitboards"); - for (Piece pc : Pieces) - if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) - || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) - assert(0 && "pos_is_ok: Pieces"); + for (Piece pc : Pieces) + if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); - for (Color c : { WHITE, BLACK }) - for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) - { - if (!can_castle(cr)) - continue; + for (Color c : {WHITE, BLACK}) + for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) + { + if (!can_castle(cr)) + continue; - if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) - || castlingRightsMask[castlingRookSquare[cr]] != cr - || (castlingRightsMask[square(c)] & cr) != cr) - assert(0 && "pos_is_ok: Castling"); - } + if (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[cr]] != cr + || (castlingRightsMask[square(c)] & cr) != cr) + assert(0 && "pos_is_ok: Castling"); + } - return true; + return true; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/position.h b/src/position.h index 23fd5bf5..2aeb8fcd 100644 --- a/src/position.h +++ b/src/position.h @@ -37,27 +37,27 @@ namespace Stockfish { struct StateInfo { - // Copied when making a move - Key materialKey; - Value nonPawnMaterial[COLOR_NB]; - int castlingRights; - int rule50; - int pliesFromNull; - Square epSquare; + // Copied when making a move + Key materialKey; + Value nonPawnMaterial[COLOR_NB]; + int castlingRights; + int rule50; + int pliesFromNull; + Square epSquare; - // Not copied when making a move (will be recomputed anyhow) - Key key; - Bitboard checkersBB; - StateInfo* previous; - Bitboard blockersForKing[COLOR_NB]; - Bitboard pinners[COLOR_NB]; - Bitboard checkSquares[PIECE_TYPE_NB]; - Piece capturedPiece; - int repetition; + // Not copied when making a move (will be recomputed anyhow) + Key key; + Bitboard checkersBB; + StateInfo* previous; + Bitboard blockersForKing[COLOR_NB]; + Bitboard pinners[COLOR_NB]; + Bitboard checkSquares[PIECE_TYPE_NB]; + Piece capturedPiece; + int repetition; - // Used by NNUE - Eval::NNUE::Accumulator accumulator; - DirtyPiece dirtyPiece; + // Used by NNUE + Eval::NNUE::Accumulator accumulator; + DirtyPiece dirtyPiece; }; @@ -75,329 +75,290 @@ using StateListPtr = std::unique_ptr>; class Thread; class Position { -public: - static void init(); + public: + static void init(); - Position() = default; - Position(const Position&) = delete; - Position& operator=(const Position&) = delete; + Position() = default; + Position(const Position&) = delete; + Position& operator=(const Position&) = delete; - // FEN string input/output - Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); - Position& set(const std::string& code, Color c, StateInfo* si); - std::string fen() const; + // FEN string input/output + Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + Position& set(const std::string& code, Color c, StateInfo* si); + std::string fen() const; - // Position representation - Bitboard pieces(PieceType pt = ALL_PIECES) const; - template Bitboard pieces(PieceType pt, PieceTypes... pts) const; - Bitboard pieces(Color c) const; - template Bitboard pieces(Color c, PieceTypes... pts) const; - Piece piece_on(Square s) const; - Square ep_square() const; - bool empty(Square s) const; - template int count(Color c) const; - template int count() const; - template Square square(Color c) const; + // Position representation + Bitboard pieces(PieceType pt = ALL_PIECES) const; + template + Bitboard pieces(PieceType pt, PieceTypes... pts) const; + Bitboard pieces(Color c) const; + template + Bitboard pieces(Color c, PieceTypes... pts) const; + Piece piece_on(Square s) const; + Square ep_square() const; + bool empty(Square s) const; + template + int count(Color c) const; + template + int count() const; + template + Square square(Color c) const; - // Castling - CastlingRights castling_rights(Color c) const; - bool can_castle(CastlingRights cr) const; - bool castling_impeded(CastlingRights cr) const; - Square castling_rook_square(CastlingRights cr) const; + // Castling + CastlingRights castling_rights(Color c) const; + bool can_castle(CastlingRights cr) const; + bool castling_impeded(CastlingRights cr) const; + Square castling_rook_square(CastlingRights cr) const; - // Checking - Bitboard checkers() const; - Bitboard blockers_for_king(Color c) const; - Bitboard check_squares(PieceType pt) const; - Bitboard pinners(Color c) const; + // Checking + Bitboard checkers() const; + Bitboard blockers_for_king(Color c) const; + Bitboard check_squares(PieceType pt) const; + Bitboard pinners(Color c) const; - // Attacks to/from a given square - Bitboard attackers_to(Square s) const; - Bitboard attackers_to(Square s, Bitboard occupied) const; - void update_slider_blockers(Color c) const; - template Bitboard attacks_by(Color c) const; + // Attacks to/from a given square + Bitboard attackers_to(Square s) const; + Bitboard attackers_to(Square s, Bitboard occupied) const; + void update_slider_blockers(Color c) const; + template + Bitboard attacks_by(Color c) const; - // Properties of moves - bool legal(Move m) const; - bool pseudo_legal(const Move m) const; - bool capture(Move m) const; - bool capture_stage(Move m) const; - bool gives_check(Move m) const; - Piece moved_piece(Move m) const; - Piece captured_piece() const; + // Properties of moves + bool legal(Move m) const; + bool pseudo_legal(const Move m) const; + bool capture(Move m) const; + bool capture_stage(Move m) const; + bool gives_check(Move m) const; + Piece moved_piece(Move m) const; + Piece captured_piece() const; - // Doing and undoing moves - void do_move(Move m, StateInfo& newSt); - void do_move(Move m, StateInfo& newSt, bool givesCheck); - void undo_move(Move m); - void do_null_move(StateInfo& newSt); - void undo_null_move(); + // Doing and undoing moves + void do_move(Move m, StateInfo& newSt); + void do_move(Move m, StateInfo& newSt, bool givesCheck); + void undo_move(Move m); + void do_null_move(StateInfo& newSt); + void undo_null_move(); - // Static Exchange Evaluation - bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + // Static Exchange Evaluation + bool see_ge(Move m, Value threshold = VALUE_ZERO) const; - // Accessing hash keys - Key key() const; - Key key_after(Move m) const; - Key material_key() const; + // Accessing hash keys + Key key() const; + Key key_after(Move m) const; + Key material_key() const; - // Other properties of the position - Color side_to_move() const; - int game_ply() const; - bool is_chess960() const; - Thread* this_thread() const; - bool is_draw(int ply) const; - bool has_game_cycle(int ply) const; - bool has_repeated() const; - int rule50_count() const; - Value non_pawn_material(Color c) const; - Value non_pawn_material() const; + // Other properties of the position + Color side_to_move() const; + int game_ply() const; + bool is_chess960() const; + Thread* this_thread() const; + bool is_draw(int ply) const; + bool has_game_cycle(int ply) const; + bool has_repeated() const; + int rule50_count() const; + Value non_pawn_material(Color c) const; + Value non_pawn_material() const; - // Position consistency check, for debugging - bool pos_is_ok() const; - void flip(); + // Position consistency check, for debugging + bool pos_is_ok() const; + void flip(); - // Used by NNUE - StateInfo* state() const; + // Used by NNUE + StateInfo* state() const; - void put_piece(Piece pc, Square s); - void remove_piece(Square s); + void put_piece(Piece pc, Square s); + void remove_piece(Square s); -private: - // Initialization helpers (used while setting up a position) - void set_castling_right(Color c, Square rfrom); - void set_state() const; - void set_check_info() const; + private: + // Initialization helpers (used while setting up a position) + void set_castling_right(Color c, Square rfrom); + void set_state() const; + void set_check_info() const; - // Other helpers - void move_piece(Square from, Square to); - template - void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); - template - Key adjust_key50(Key k) const; + // Other helpers + void move_piece(Square from, Square to); + template + void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + template + Key adjust_key50(Key k) const; - // Data members - Piece board[SQUARE_NB]; - Bitboard byTypeBB[PIECE_TYPE_NB]; - Bitboard byColorBB[COLOR_NB]; - int pieceCount[PIECE_NB]; - int castlingRightsMask[SQUARE_NB]; - Square castlingRookSquare[CASTLING_RIGHT_NB]; - Bitboard castlingPath[CASTLING_RIGHT_NB]; - Thread* thisThread; - StateInfo* st; - int gamePly; - Color sideToMove; - bool chess960; + // Data members + Piece board[SQUARE_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + Bitboard byColorBB[COLOR_NB]; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + Thread* thisThread; + StateInfo* st; + int gamePly; + Color sideToMove; + bool chess960; }; std::ostream& operator<<(std::ostream& os, const Position& pos); -inline Color Position::side_to_move() const { - return sideToMove; -} +inline Color Position::side_to_move() const { return sideToMove; } inline Piece Position::piece_on(Square s) const { - assert(is_ok(s)); - return board[s]; + assert(is_ok(s)); + return board[s]; } -inline bool Position::empty(Square s) const { - return piece_on(s) == NO_PIECE; -} +inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } -inline Piece Position::moved_piece(Move m) const { - return piece_on(from_sq(m)); -} +inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); } -inline Bitboard Position::pieces(PieceType pt) const { - return byTypeBB[pt]; -} +inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } -template +template inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const { - return pieces(pt) | pieces(pts...); + return pieces(pt) | pieces(pts...); } -inline Bitboard Position::pieces(Color c) const { - return byColorBB[c]; -} +inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; } -template +template inline Bitboard Position::pieces(Color c, PieceTypes... pts) const { - return pieces(c) & pieces(pts...); + return pieces(c) & pieces(pts...); } -template inline int Position::count(Color c) const { - return pieceCount[make_piece(c, Pt)]; +template +inline int Position::count(Color c) const { + return pieceCount[make_piece(c, Pt)]; } -template inline int Position::count() const { - return count(WHITE) + count(BLACK); +template +inline int Position::count() const { + return count(WHITE) + count(BLACK); } -template inline Square Position::square(Color c) const { - assert(count(c) == 1); - return lsb(pieces(c, Pt)); +template +inline Square Position::square(Color c) const { + assert(count(c) == 1); + return lsb(pieces(c, Pt)); } -inline Square Position::ep_square() const { - return st->epSquare; -} +inline Square Position::ep_square() const { return st->epSquare; } -inline bool Position::can_castle(CastlingRights cr) const { - return st->castlingRights & cr; -} +inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; } inline CastlingRights Position::castling_rights(Color c) const { - return c & CastlingRights(st->castlingRights); + return c & CastlingRights(st->castlingRights); } inline bool Position::castling_impeded(CastlingRights cr) const { - assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - return pieces() & castlingPath[cr]; + return pieces() & castlingPath[cr]; } inline Square Position::castling_rook_square(CastlingRights cr) const { - assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - return castlingRookSquare[cr]; + return castlingRookSquare[cr]; } -inline Bitboard Position::attackers_to(Square s) const { - return attackers_to(s, pieces()); -} +inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); } template inline Bitboard Position::attacks_by(Color c) const { - if constexpr (Pt == PAWN) - return c == WHITE ? pawn_attacks_bb(pieces(WHITE, PAWN)) - : pawn_attacks_bb(pieces(BLACK, PAWN)); - else - { - Bitboard threats = 0; - Bitboard attackers = pieces(c, Pt); - while (attackers) - threats |= attacks_bb(pop_lsb(attackers), pieces()); - return threats; - } + if constexpr (Pt == PAWN) + return c == WHITE ? pawn_attacks_bb(pieces(WHITE, PAWN)) + : pawn_attacks_bb(pieces(BLACK, PAWN)); + else + { + Bitboard threats = 0; + Bitboard attackers = pieces(c, Pt); + while (attackers) + threats |= attacks_bb(pop_lsb(attackers), pieces()); + return threats; + } } -inline Bitboard Position::checkers() const { - return st->checkersBB; -} +inline Bitboard Position::checkers() const { return st->checkersBB; } -inline Bitboard Position::blockers_for_king(Color c) const { - return st->blockersForKing[c]; -} +inline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; } -inline Bitboard Position::pinners(Color c) const { - return st->pinners[c]; -} +inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; } -inline Bitboard Position::check_squares(PieceType pt) const { - return st->checkSquares[pt]; -} +inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } -inline Key Position::key() const { - return adjust_key50(st->key); -} +inline Key Position::key() const { return adjust_key50(st->key); } template -inline Key Position::adjust_key50(Key k) const -{ - return st->rule50 < 14 - AfterMove - ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); +inline Key Position::adjust_key50(Key k) const { + return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); } -inline Key Position::material_key() const { - return st->materialKey; -} +inline Key Position::material_key() const { return st->materialKey; } -inline Value Position::non_pawn_material(Color c) const { - return st->nonPawnMaterial[c]; -} +inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } inline Value Position::non_pawn_material() const { - return non_pawn_material(WHITE) + non_pawn_material(BLACK); + return non_pawn_material(WHITE) + non_pawn_material(BLACK); } -inline int Position::game_ply() const { - return gamePly; -} +inline int Position::game_ply() const { return gamePly; } -inline int Position::rule50_count() const { - return st->rule50; -} +inline int Position::rule50_count() const { return st->rule50; } -inline bool Position::is_chess960() const { - return chess960; -} +inline bool Position::is_chess960() const { return chess960; } inline bool Position::capture(Move m) const { - assert(is_ok(m)); - return (!empty(to_sq(m)) && type_of(m) != CASTLING) - || type_of(m) == EN_PASSANT; + assert(is_ok(m)); + return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; } // Returns true if a move is generated from the capture stage, having also // queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { - assert(is_ok(m)); - return capture(m) || promotion_type(m) == QUEEN; + assert(is_ok(m)); + return capture(m) || promotion_type(m) == QUEEN; } -inline Piece Position::captured_piece() const { - return st->capturedPiece; -} +inline Piece Position::captured_piece() const { return st->capturedPiece; } -inline Thread* Position::this_thread() const { - return thisThread; -} +inline Thread* Position::this_thread() const { return thisThread; } inline void Position::put_piece(Piece pc, Square s) { - board[s] = pc; - byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; - byColorBB[color_of(pc)] |= s; - pieceCount[pc]++; - pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; + board[s] = pc; + byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; + byColorBB[color_of(pc)] |= s; + pieceCount[pc]++; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; } inline void Position::remove_piece(Square s) { - Piece pc = board[s]; - byTypeBB[ALL_PIECES] ^= s; - byTypeBB[type_of(pc)] ^= s; - byColorBB[color_of(pc)] ^= s; - board[s] = NO_PIECE; - pieceCount[pc]--; - pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; + Piece pc = board[s]; + byTypeBB[ALL_PIECES] ^= s; + byTypeBB[type_of(pc)] ^= s; + byColorBB[color_of(pc)] ^= s; + board[s] = NO_PIECE; + pieceCount[pc]--; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; } inline void Position::move_piece(Square from, Square to) { - Piece pc = board[from]; - Bitboard fromTo = from | to; - byTypeBB[ALL_PIECES] ^= fromTo; - byTypeBB[type_of(pc)] ^= fromTo; - byColorBB[color_of(pc)] ^= fromTo; - board[from] = NO_PIECE; - board[to] = pc; + Piece pc = board[from]; + Bitboard fromTo = from | to; + byTypeBB[ALL_PIECES] ^= fromTo; + byTypeBB[type_of(pc)] ^= fromTo; + byColorBB[color_of(pc)] ^= fromTo; + board[from] = NO_PIECE; + board[to] = pc; } -inline void Position::do_move(Move m, StateInfo& newSt) { - do_move(m, newSt, gives_check(m)); -} +inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); } -inline StateInfo* Position::state() const { +inline StateInfo* Position::state() const { return st; } - return st; -} +} // namespace Stockfish -} // namespace Stockfish - -#endif // #ifndef POSITION_H_INCLUDED +#endif // #ifndef POSITION_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index baf81968..43f0c872 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -49,15 +49,15 @@ namespace Stockfish { namespace Search { - LimitsType Limits; +LimitsType Limits; } namespace Tablebases { - int Cardinality; - bool RootInTB; - bool UseRule50; - Depth ProbeDepth; +int Cardinality; +bool RootInTB; +bool UseRule50; +Depth ProbeDepth; } namespace TB = Tablebases; @@ -68,45 +68,46 @@ using namespace Search; namespace { - // Different node types, used as a template parameter - enum NodeType { NonPV, PV, Root }; +// Different node types, used as a template parameter +enum NodeType { + NonPV, + PV, + Root +}; - // Futility margin - Value futility_margin(Depth d, bool noTtCutNode, bool improving) { +// Futility margin +Value futility_margin(Depth d, bool noTtCutNode, bool improving) { return Value((126 - 42 * noTtCutNode) * (d - improving)); - } +} - // Reductions lookup table initialized at startup - int Reductions[MAX_MOVES]; // [depth or moveNumber] +// Reductions lookup table initialized at startup +int Reductions[MAX_MOVES]; // [depth or moveNumber] - Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { +Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024 - + (!i && reductionScale > 791); - } + return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024 + + (!i && reductionScale > 791); +} - constexpr int futility_move_count(bool improving, Depth depth) { - return improving ? (3 + depth * depth) - : (3 + depth * depth) / 2; - } +constexpr int futility_move_count(bool improving, Depth depth) { + return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; +} - // History and stats update bonus, based on depth - int stat_bonus(Depth d) { - return std::min(334 * d - 531, 1538); - } +// History and stats update bonus, based on depth +int stat_bonus(Depth d) { return std::min(334 * d - 531, 1538); } - // Add a small random component to draw evaluations to avoid 3-fold blindness - Value value_draw(const Thread* thisThread) { +// Add a small random component to draw evaluations to avoid 3-fold blindness +Value value_draw(const Thread* thisThread) { return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); - } +} - // Skill structure is used to implement strength limit. If we have a UCI_Elo, - // we convert it to an appropriate skill level, anchored to the Stash engine. - // This method is based on a fit of the Elo results for games played between - // Stockfish at various skill levels and various versions of the Stash engine. - // Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately - // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 - struct Skill { +// Skill structure is used to implement strength limit. If we have a UCI_Elo, +// we convert it to an appropriate skill level, anchored to the Stash engine. +// This method is based on a fit of the Elo results for games played between +// Stockfish at various skill levels and various versions of the Stash engine. +// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately +// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 +struct Skill { Skill(int skill_level, int uci_elo) { if (uci_elo) { @@ -121,32 +122,41 @@ namespace { Move pick_best(size_t multiPV); double level; - Move best = MOVE_NONE; - }; + Move best = MOVE_NONE; +}; - template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); +template +Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); - template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); +template +Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); - Value value_to_tt(Value v, int ply); - Value value_from_tt(Value v, int ply, int r50c); - void update_pv(Move* pv, Move move, const Move* childPv); - void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); - void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); - void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, - Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth); +Value value_to_tt(Value v, int ply); +Value value_from_tt(Value v, int ply, int r50c); +void update_pv(Move* pv, Move move, const Move* childPv); +void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); +void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); +void update_all_stats(const Position& pos, + Stack* ss, + Move bestMove, + Value bestValue, + Value beta, + Square prevSq, + Move* quietsSearched, + int quietCount, + Move* capturesSearched, + int captureCount, + Depth depth); - // perft() is our utility to verify move generation. All the leaf nodes up - // to the given depth are generated and counted, and the sum is returned. - template - uint64_t perft(Position& pos, Depth depth) { +// perft() is our utility to verify move generation. All the leaf nodes up +// to the given depth are generated and counted, and the sum is returned. +template +uint64_t perft(Position& pos, Depth depth) { StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - uint64_t cnt, nodes = 0; + uint64_t cnt, nodes = 0; const bool leaf = (depth == 2); for (const auto& m : MoveList(pos)) @@ -164,17 +174,17 @@ namespace { sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; } return nodes; - } +} -} // namespace +} // namespace // Search::init() is called at startup to initialize various lookup tables void Search::init() { - for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i)); + for (int i = 1; i < MAX_MOVES; ++i) + Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -182,12 +192,12 @@ void Search::init() { void Search::clear() { - Threads.main()->wait_for_search_finished(); + Threads.main()->wait_for_search_finished(); - Time.availableNodes = 0; - TT.clear(); - Threads.clear(); - Tablebases::init(Options["SyzygyPath"]); // Free mapped files + Time.availableNodes = 0; + TT.clear(); + Threads.clear(); + Tablebases::init(Options["SyzygyPath"]); // Free mapped files } @@ -196,75 +206,74 @@ void Search::clear() { void MainThread::search() { - if (Limits.perft) - { - nodes = perft(rootPos, Limits.perft); - sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; - return; - } + if (Limits.perft) + { + nodes = perft(rootPos, Limits.perft); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return; + } - Color us = rootPos.side_to_move(); - Time.init(Limits, us, rootPos.game_ply()); - TT.new_search(); + Color us = rootPos.side_to_move(); + Time.init(Limits, us, rootPos.game_ply()); + TT.new_search(); - Eval::NNUE::verify(); + Eval::NNUE::verify(); - if (rootMoves.empty()) - { - rootMoves.emplace_back(MOVE_NONE); - sync_cout << "info depth 0 score " - << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) - << sync_endl; - } - else - { - Threads.start_searching(); // start non-main threads - Thread::search(); // main thread start searching - } + if (rootMoves.empty()) + { + rootMoves.emplace_back(MOVE_NONE); + sync_cout << "info depth 0 score " + << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; + } + else + { + Threads.start_searching(); // start non-main threads + Thread::search(); // main thread start searching + } - // When we reach the maximum depth, we can arrive here without a raise of - // Threads.stop. However, if we are pondering or in an infinite search, - // the UCI protocol states that we shouldn't print the best move before the - // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here - // until the GUI sends one of those commands. + // When we reach the maximum depth, we can arrive here without a raise of + // Threads.stop. However, if we are pondering or in an infinite search, + // the UCI protocol states that we shouldn't print the best move before the + // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here + // until the GUI sends one of those commands. - while (!Threads.stop && (ponder || Limits.infinite)) - {} // Busy wait for a stop or a ponder reset + while (!Threads.stop && (ponder || Limits.infinite)) + {} // Busy wait for a stop or a ponder reset - // Stop the threads if not already stopped (also raise the stop if - // "ponderhit" just reset Threads.ponder). - Threads.stop = true; + // Stop the threads if not already stopped (also raise the stop if + // "ponderhit" just reset Threads.ponder). + Threads.stop = true; - // Wait until all threads have finished - Threads.wait_for_search_finished(); + // Wait until all threads have finished + Threads.wait_for_search_finished(); - // When playing in 'nodes as time' mode, subtract the searched nodes from - // the available ones before exiting. - if (Limits.npmsec) - Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (Limits.npmsec) + Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); - Thread* bestThread = this; - Skill skill = Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); + Thread* bestThread = this; + Skill skill = + Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); - if ( int(Options["MultiPV"]) == 1 - && !Limits.depth - && !skill.enabled() - && rootMoves[0].pv[0] != MOVE_NONE) - bestThread = Threads.get_best_thread(); + if (int(Options["MultiPV"]) == 1 && !Limits.depth && !skill.enabled() + && rootMoves[0].pv[0] != MOVE_NONE) + bestThread = Threads.get_best_thread(); - bestPreviousScore = bestThread->rootMoves[0].score; - bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; + bestPreviousScore = bestThread->rootMoves[0].score; + bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; - // Send again PV info if we have a new best thread - if (bestThread != this) - sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; + // Send again PV info if we have a new best thread + if (bestThread != this) + sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; - sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); - if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) - std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + if (bestThread->rootMoves[0].pv.size() > 1 + || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) + std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); - std::cout << sync_endl; + std::cout << sync_endl; } @@ -274,266 +283,259 @@ void MainThread::search() { void Thread::search() { - // Allocate stack with extra size to allow access from (ss-7) to (ss+2): - // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6), - // (ss+2) is needed for initialization of statScore and killers. - Stack stack[MAX_PLY+10], *ss = stack+7; - Move pv[MAX_PLY+1]; - Value alpha, beta, delta; - Move lastBestMove = MOVE_NONE; - Depth lastBestMoveDepth = 0; - MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int iterIdx = 0; + // Allocate stack with extra size to allow access from (ss-7) to (ss+2): + // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6), + // (ss+2) is needed for initialization of statScore and killers. + Stack stack[MAX_PLY + 10], *ss = stack + 7; + Move pv[MAX_PLY + 1]; + Value alpha, beta, delta; + Move lastBestMove = MOVE_NONE; + Depth lastBestMoveDepth = 0; + MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); + double timeReduction = 1, totBestMoveChanges = 0; + Color us = rootPos.side_to_move(); + int iterIdx = 0; - std::memset(ss-7, 0, 10 * sizeof(Stack)); - for (int i = 7; i > 0; --i) - { - (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel - (ss-i)->staticEval = VALUE_NONE; - } + std::memset(ss - 7, 0, 10 * sizeof(Stack)); + for (int i = 7; i > 0; --i) + { + (ss - i)->continuationHistory = + &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + (ss - i)->staticEval = VALUE_NONE; + } - for (int i = 0; i <= MAX_PLY + 2; ++i) - (ss+i)->ply = i; + for (int i = 0; i <= MAX_PLY + 2; ++i) + (ss + i)->ply = i; - ss->pv = pv; + ss->pv = pv; - bestValue = -VALUE_INFINITE; + bestValue = -VALUE_INFINITE; - if (mainThread) - { - if (mainThread->bestPreviousScore == VALUE_INFINITE) - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = VALUE_ZERO; - else - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = mainThread->bestPreviousScore; - } + if (mainThread) + { + if (mainThread->bestPreviousScore == VALUE_INFINITE) + for (int i = 0; i < 4; ++i) + mainThread->iterValue[i] = VALUE_ZERO; + else + for (int i = 0; i < 4; ++i) + mainThread->iterValue[i] = mainThread->bestPreviousScore; + } - size_t multiPV = size_t(Options["MultiPV"]); - Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); + size_t multiPV = size_t(Options["MultiPV"]); + Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); - // When playing with strength handicap enable MultiPV search that we will - // use behind-the-scenes to retrieve a set of possible moves. - if (skill.enabled()) - multiPV = std::max(multiPV, size_t(4)); + // When playing with strength handicap enable MultiPV search that we will + // use behind-the-scenes to retrieve a set of possible moves. + if (skill.enabled()) + multiPV = std::max(multiPV, size_t(4)); - multiPV = std::min(multiPV, rootMoves.size()); + multiPV = std::min(multiPV, rootMoves.size()); - int searchAgainCounter = 0; + int searchAgainCounter = 0; - // Iterative deepening loop until requested to stop or the target depth is reached - while ( ++rootDepth < MAX_PLY - && !Threads.stop - && !(Limits.depth && mainThread && rootDepth > Limits.depth)) - { - // Age out PV variability metric - if (mainThread) - totBestMoveChanges /= 2; + // Iterative deepening loop until requested to stop or the target depth is reached + while (++rootDepth < MAX_PLY && !Threads.stop + && !(Limits.depth && mainThread && rootDepth > Limits.depth)) + { + // Age out PV variability metric + if (mainThread) + totBestMoveChanges /= 2; - // Save the last iteration's scores before the first PV line is searched and - // all the move scores except the (new) PV are set to -VALUE_INFINITE. - for (RootMove& rm : rootMoves) - rm.previousScore = rm.score; + // Save the last iteration's scores before the first PV line is searched and + // all the move scores except the (new) PV are set to -VALUE_INFINITE. + for (RootMove& rm : rootMoves) + rm.previousScore = rm.score; - size_t pvFirst = 0; - pvLast = 0; + size_t pvFirst = 0; + pvLast = 0; - if (!Threads.increaseDepth) - searchAgainCounter++; + if (!Threads.increaseDepth) + searchAgainCounter++; - // MultiPV loop. We perform a full root search for each PV line - for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) - { - if (pvIdx == pvLast) - { - pvFirst = pvLast; - for (pvLast++; pvLast < rootMoves.size(); pvLast++) - if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) - break; - } + // MultiPV loop. We perform a full root search for each PV line + for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) + { + if (pvIdx == pvLast) + { + pvFirst = pvLast; + for (pvLast++; pvLast < rootMoves.size(); pvLast++) + if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) + break; + } - // Reset UCI info selDepth for each depth and each PV line - selDepth = 0; + // Reset UCI info selDepth for each depth and each PV line + selDepth = 0; - // Reset aspiration window starting size - Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 17470; - alpha = std::max(prev - delta,-VALUE_INFINITE); - beta = std::min(prev + delta, VALUE_INFINITE); + // Reset aspiration window starting size + Value prev = rootMoves[pvIdx].averageScore; + delta = Value(10) + int(prev) * prev / 17470; + alpha = std::max(prev - delta, -VALUE_INFINITE); + beta = std::min(prev + delta, VALUE_INFINITE); - // Adjust optimism based on root move's previousScore (~4 Elo) - int opt = 113 * prev / (std::abs(prev) + 109); - optimism[ us] = Value(opt); - optimism[~us] = -optimism[us]; + // Adjust optimism based on root move's previousScore (~4 Elo) + int opt = 113 * prev / (std::abs(prev) + 109); + optimism[us] = Value(opt); + optimism[~us] = -optimism[us]; - // Start with a small aspiration window and, in the case of a fail - // high/low, re-search with a bigger window until we don't fail - // high/low anymore. - int failedHighCnt = 0; - while (true) - { - // Adjust the effective depth searched, but ensure at least one effective increment for every - // four searchAgain steps (see issue #2717). - Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); + // Start with a small aspiration window and, in the case of a fail + // high/low, re-search with a bigger window until we don't fail + // high/low anymore. + int failedHighCnt = 0; + while (true) + { + // Adjust the effective depth searched, but ensure at least one effective increment for every + // four searchAgain steps (see issue #2717). + Depth adjustedDepth = + std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); + bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); - // Bring the best move to the front. It is critical that sorting - // is done with a stable algorithm because all the values but the - // first and eventually the new best one is set to -VALUE_INFINITE - // and we want to keep the same order for all the moves except the - // new PV that goes to the front. Note that in the case of MultiPV - // search the already searched PV lines are preserved. - std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); + // Bring the best move to the front. It is critical that sorting + // is done with a stable algorithm because all the values but the + // first and eventually the new best one is set to -VALUE_INFINITE + // and we want to keep the same order for all the moves except the + // new PV that goes to the front. Note that in the case of MultiPV + // search the already searched PV lines are preserved. + std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); - // If search has been stopped, we break immediately. Sorting is - // safe because RootMoves is still valid, although it refers to - // the previous iteration. - if (Threads.stop) - break; + // If search has been stopped, we break immediately. Sorting is + // safe because RootMoves is still valid, although it refers to + // the previous iteration. + if (Threads.stop) + break; - // When failing high/low give some update (without cluttering - // the UI) before a re-search. - if ( mainThread - && multiPV == 1 - && (bestValue <= alpha || bestValue >= beta) - && Time.elapsed() > 3000) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; + // When failing high/low give some update (without cluttering + // the UI) before a re-search. + if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) + && Time.elapsed() > 3000) + sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; - // In case of failing low/high increase aspiration window and - // re-search, otherwise exit the loop. - if (bestValue <= alpha) - { - beta = (alpha + beta) / 2; - alpha = std::max(bestValue - delta, -VALUE_INFINITE); + // In case of failing low/high increase aspiration window and + // re-search, otherwise exit the loop. + if (bestValue <= alpha) + { + beta = (alpha + beta) / 2; + alpha = std::max(bestValue - delta, -VALUE_INFINITE); - failedHighCnt = 0; - if (mainThread) - mainThread->stopOnPonderhit = false; - } - else if (bestValue >= beta) - { - beta = std::min(bestValue + delta, VALUE_INFINITE); - ++failedHighCnt; - } - else - break; + failedHighCnt = 0; + if (mainThread) + mainThread->stopOnPonderhit = false; + } + else if (bestValue >= beta) + { + beta = std::min(bestValue + delta, VALUE_INFINITE); + ++failedHighCnt; + } + else + break; - delta += delta / 3; + delta += delta / 3; - assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); - } + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } - // Sort the PV lines searched so far and update the GUI - std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); + // Sort the PV lines searched so far and update the GUI + std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); - if ( mainThread - && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; - } + if (mainThread && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) + sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; + } - if (!Threads.stop) - completedDepth = rootDepth; + if (!Threads.stop) + completedDepth = rootDepth; - if (rootMoves[0].pv[0] != lastBestMove) - { - lastBestMove = rootMoves[0].pv[0]; - lastBestMoveDepth = rootDepth; - } + if (rootMoves[0].pv[0] != lastBestMove) + { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; + } - // Have we found a "mate in x"? - if ( Limits.mate - && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * Limits.mate) - Threads.stop = true; + // Have we found a "mate in x"? + if (Limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * Limits.mate) + Threads.stop = true; - if (!mainThread) - continue; + if (!mainThread) + continue; - // If the skill level is enabled and time is up, pick a sub-optimal best move - if (skill.enabled() && skill.time_to_pick(rootDepth)) - skill.pick_best(multiPV); + // If the skill level is enabled and time is up, pick a sub-optimal best move + if (skill.enabled() && skill.time_to_pick(rootDepth)) + skill.pick_best(multiPV); - // Use part of the gained time from a previous stable move for the current move - for (Thread* th : Threads) - { - totBestMoveChanges += th->bestMoveChanges; - th->bestMoveChanges = 0; - } + // Use part of the gained time from a previous stable move for the current move + for (Thread* th : Threads) + { + totBestMoveChanges += th->bestMoveChanges; + th->bestMoveChanges = 0; + } - // Do we have time for the next iteration? Can we stop searching now? - if ( Limits.use_time_management() - && !Threads.stop - && !mainThread->stopOnPonderhit) - { - double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 619.6; - fallingEval = std::clamp(fallingEval, 0.5, 1.5); + // Do we have time for the next iteration? Can we stop searching now? + if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit) + { + double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) + / 619.6; + fallingEval = std::clamp(fallingEval, 0.5, 1.5); - // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); - double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); + // If the bestMove is stable over several iterations, reduce time accordingly + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); + double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; - // Cap used time in case of a single legal move for a better viewer experience - if (rootMoves.size() == 1) - totalTime = std::min(500.0, totalTime); + // Cap used time in case of a single legal move for a better viewer experience + if (rootMoves.size() == 1) + totalTime = std::min(500.0, totalTime); - // Stop the search if we have exceeded the totalTime - if (Time.elapsed() > totalTime) - { - // If we are allowed to ponder do not stop the search now but - // keep pondering until the GUI sends "ponderhit" or "stop". - if (mainThread->ponder) - mainThread->stopOnPonderhit = true; - else - Threads.stop = true; - } - else if ( !mainThread->ponder - && Time.elapsed() > totalTime * 0.50) - Threads.increaseDepth = false; - else - Threads.increaseDepth = true; - } + // Stop the search if we have exceeded the totalTime + if (Time.elapsed() > totalTime) + { + // If we are allowed to ponder do not stop the search now but + // keep pondering until the GUI sends "ponderhit" or "stop". + if (mainThread->ponder) + mainThread->stopOnPonderhit = true; + else + Threads.stop = true; + } + else if (!mainThread->ponder && Time.elapsed() > totalTime * 0.50) + Threads.increaseDepth = false; + else + Threads.increaseDepth = true; + } - mainThread->iterValue[iterIdx] = bestValue; - iterIdx = (iterIdx + 1) & 3; - } + mainThread->iterValue[iterIdx] = bestValue; + iterIdx = (iterIdx + 1) & 3; + } - if (!mainThread) - return; + if (!mainThread) + return; - mainThread->previousTimeReduction = timeReduction; + mainThread->previousTimeReduction = timeReduction; - // If the skill level is enabled, swap the best PV line with the sub-optimal one - if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), - skill.best ? skill.best : skill.pick_best(multiPV))); + // If the skill level is enabled, swap the best PV line with the sub-optimal one + if (skill.enabled()) + std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(multiPV))); } namespace { - // search<>() is the main search function for both PV and non-PV nodes +// search<>() is the main search function for both PV and non-PV nodes - template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { +template +Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { - constexpr bool PvNode = nodeType != NonPV; + constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch(pos, ss, alpha, beta); + return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. - if ( !rootNode - && alpha < VALUE_DRAW - && pos.has_game_cycle(ss->ply)) + if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(pos.this_thread()); if (alpha >= beta) @@ -545,43 +547,41 @@ namespace { assert(0 < depth && depth < MAX_PLY); assert(!(PvNode && cutNode)); - Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[32]; + Move pv[MAX_PLY + 1], capturesSearched[32], quietsSearched[32]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); TTEntry* tte; - Key posKey; - Move ttMove, move, excludedMove, bestMove; - Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture, singularQuietLMR; - bool capture, moveCountPruning, ttCapture; - Piece movedPiece; - int moveCount, captureCount, quietCount; + Key posKey; + Move ttMove, move, excludedMove, bestMove; + Depth extension, newDepth; + Value bestValue, value, ttValue, eval, maxValue, probCutBeta; + bool givesCheck, improving, priorCapture, singularQuietLMR; + bool capture, moveCountPruning, ttCapture; + Piece movedPiece; + int moveCount, captureCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); - moveCount = captureCount = quietCount = ss->moveCount = 0; - bestValue = -VALUE_INFINITE; - maxValue = VALUE_INFINITE; + moveCount = captureCount = quietCount = ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; // Check for the available remaining time if (thisThread == Threads.main()) static_cast(thisThread)->check_time(); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) - if ( PvNode - && thisThread->selDepth < ss->ply + 1) + if (PvNode && thisThread->selDepth < ss->ply + 1) thisThread->selDepth = ss->ply + 1; if (!rootNode) { // Step 2. Check for aborted search and immediate draw - if ( Threads.stop.load(std::memory_order_relaxed) - || pos.is_draw(ss->ply) + if (Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : value_draw(pos.this_thread()); @@ -593,7 +593,7 @@ namespace { // signs apply also in the opposite condition of being mated instead of giving // mate. In this case, return a fail-high score. alpha = std::max(mated_in(ss->ply), alpha); - beta = std::min(mate_in(ss->ply+1), beta); + beta = std::min(mate_in(ss->ply + 1), beta); if (alpha >= beta) return alpha; } @@ -602,20 +602,21 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss+1)->excludedMove = bestMove = MOVE_NONE; - (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; - (ss+2)->cutoffCnt = 0; - ss->doubleExtensions = (ss-1)->doubleExtensions; - Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - ss->statScore = 0; + (ss + 1)->excludedMove = bestMove = MOVE_NONE; + (ss + 2)->killers[0] = (ss + 2)->killers[1] = MOVE_NONE; + (ss + 2)->cutoffCnt = 0; + ss->doubleExtensions = (ss - 1)->doubleExtensions; + Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + ss->statScore = 0; // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; - posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); - ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] - : ss->ttHit ? tte->move() : MOVE_NONE; + posKey = pos.key(); + tte = TT.probe(posKey, ss->ttHit); + ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; + ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] + : ss->ttHit ? tte->move() + : MOVE_NONE; ttCapture = ttMove && pos.capture_stage(ttMove); // At this point, if excluded, skip straight to step 6, static eval. However, @@ -624,10 +625,8 @@ namespace { ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); // At non-PV nodes we check for an early TT cutoff - if ( !PvNode - && !excludedMove - && tte->depth() > depth - && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit + if (!PvNode && !excludedMove && tte->depth() > depth + && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) @@ -640,10 +639,9 @@ namespace { update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) - if ( prevSq != SQ_NONE - && (ss-1)->moveCount <= 2 - && !priorCapture) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); + if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + -stat_bonus(depth + 1)); } // Penalty for a quiet ttMove that fails low (~1 Elo) else if (!ttCapture) @@ -665,13 +663,12 @@ namespace { { int piecesCount = pos.count(); - if ( piecesCount <= TB::Cardinality - && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) - && pos.rule50_count() == 0 + if (piecesCount <= TB::Cardinality + && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING)) { TB::ProbeState err; - TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); // Force check of time on the next occasion if (thisThread == Threads.main()) @@ -684,19 +681,18 @@ namespace { int drawScore = TB::UseRule50 ? 1 : 0; // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score - value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 - : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 - : VALUE_DRAW + 2 * wdl * drawScore; + value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 + : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 + : VALUE_DRAW + 2 * wdl * drawScore; - Bound b = wdl < -drawScore ? BOUND_UPPER - : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER + : BOUND_EXACT; - if ( b == BOUND_EXACT - || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), - MOVE_NONE, VALUE_NONE); + std::min(MAX_PLY - 1, depth + 6), MOVE_NONE, VALUE_NONE); return value; } @@ -719,7 +715,7 @@ namespace { { // Skip early pruning when in check ss->staticEval = eval = VALUE_NONE; - improving = false; + improving = false; goto moves_loop; } else if (excludedMove) @@ -738,8 +734,7 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); // ttValue can be used as a better position evaluation (~7 Elo) - if ( ttValue != VALUE_NONE - && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) + if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttValue; } else @@ -750,12 +745,10 @@ namespace { } // Use static evaluation difference to improve quiet move ordering (~4 Elo) - if ( is_ok((ss-1)->currentMove) - && !(ss-1)->inCheck - && !priorCapture) + if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1812, 1812); - thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; + int bonus = std::clamp(-18 * int((ss - 1)->staticEval + ss->staticEval), -1812, 1812); + thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; } // Set up the improving flag, which is true if current static evaluation is @@ -763,15 +756,15 @@ namespace { // check at our previous move we look at static evaluation at move prior to it // and if we were in check at move prior to it flag is set to true) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval > (ss-2)->staticEval - : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval > (ss-4)->staticEval - : true; + improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval + : (ss - 4)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 4)->staticEval + : true; // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 492 - (257 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 492 - (257 - 200 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -780,38 +773,31 @@ namespace { // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if ( !ss->ttPv - && depth < 9 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 321 >= beta - && eval >= beta - && eval < 29462 // smaller than TB wins - && !( !ttCapture - && ttMove)) + if (!ss->ttPv && depth < 9 + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) + - (ss - 1)->statScore / 321 + >= beta + && eval >= beta && eval < 29462 // smaller than TB wins + && !(!ttCapture && ttMove)) return eval; // Step 9. Null move search with verification search (~35 Elo) - if ( !PvNode - && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 17257 - && eval >= beta - && eval >= ss->staticEval - && ss->staticEval >= beta - 24 * depth + 281 - && !excludedMove - && pos.non_pawn_material(us) - && ss->ply >= thisThread->nmpMinPly - && beta > VALUE_TB_LOSS_IN_MAX_PLY) + if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17257 && eval >= beta + && eval >= ss->staticEval && ss->staticEval >= beta - 24 * depth + 281 && !excludedMove + && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly + && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; - ss->currentMove = MOVE_NULL; + ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; pos.do_null_move(st); - Value nullValue = -search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); + Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, !cutNode); pos.undo_null_move(); @@ -821,13 +807,13 @@ namespace { if (thisThread->nmpMinPly || depth < 14) return nullValue; - assert(!thisThread->nmpMinPly); // Recursive verification is not allowed + assert(!thisThread->nmpMinPly); // Recursive verification is not allowed // Do verification search at high depths, with null move pruning disabled // until ply exceeds nmpMinPly. - thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; + thisThread->nmpMinPly = ss->ply + 3 * (depth - R) / 4; - Value v = search(pos, ss, beta-1, beta, depth-R, false); + Value v = search(pos, ss, beta - 1, beta, depth - R, false); thisThread->nmpMinPly = 0; @@ -839,16 +825,13 @@ namespace { // Step 10. If the position doesn't have a ttMove, decrease depth by 2 // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). // Use qsearch if depth is equal or below zero (~9 Elo) - if ( PvNode - && !ttMove) + if (PvNode && !ttMove) depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); - if ( cutNode - && depth >= 8 - && !ttMove) + if (cutNode && depth >= 8 && !ttMove) depth -= 2; probCutBeta = beta + 168 - 70 * improving; @@ -856,16 +839,14 @@ namespace { // Step 11. ProbCut (~10 Elo) // 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. - if ( !PvNode - && depth > 3 - && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY - // 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 - && !( tte->depth() >= depth - 3 - && ttValue != VALUE_NONE - && ttValue < probCutBeta)) + if ( + !PvNode && depth > 3 + && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + // 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 + && !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE); @@ -877,26 +858,27 @@ namespace { assert(pos.capture_stage(move)); ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [true] - [pos.moved_piece(move)] - [to_sq(move)]; + ss->continuationHistory = + &thisThread + ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][to_sq(move)]; pos.do_move(move, st); // Perform a preliminary qsearch to verify that the move holds - value = -qsearch(pos, ss+1, -probCutBeta, -probCutBeta+1); + value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); // If the qsearch held, perform the regular search if (value >= probCutBeta) - value = -search(pos, ss+1, -probCutBeta, -probCutBeta+1, depth - 4, !cutNode); + value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4, + !cutNode); pos.undo_move(move); if (value >= probCutBeta) { // Save ProbCut data into transposition table - tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); + tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, + move, ss->staticEval); return value - (probCutBeta - beta); } } @@ -904,447 +886,416 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); } -moves_loop: // When in check, search starts here +moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) probCutBeta = beta + 416; - if ( ss->inCheck - && !PvNode - && ttCapture - && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 4 - && ttValue >= probCutBeta - && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY - && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 4 && ttValue >= probCutBeta + && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; - const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - (ss-3)->continuationHistory, (ss-4)->continuationHistory, - nullptr , (ss-6)->continuationHistory }; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, + (ss - 2)->continuationHistory, + (ss - 3)->continuationHistory, + (ss - 4)->continuationHistory, + nullptr, + (ss - 6)->continuationHistory}; - Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; + Move countermove = + prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, - &captureHistory, - contHist, - countermove, - ss->killers); + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, + countermove, ss->killers); - value = bestValue; + value = bestValue; moveCountPruning = singularQuietLMR = false; // Indicate PvNodes that will probably fail low if the node was searched // at a depth equal to or greater than the current depth, and the result // of this search was a fail low. - bool likelyFailLow = PvNode - && ttMove - && (tte->bound() & BOUND_UPPER) - && tte->depth() >= depth; + bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) && tte->depth() >= depth; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) { - assert(is_ok(move)); + assert(is_ok(move)); - if (move == excludedMove) - continue; + if (move == excludedMove) + continue; - // Check for legality - if (!pos.legal(move)) - continue; + // Check for legality + if (!pos.legal(move)) + continue; - // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. In MultiPV mode we also skip PV moves that have been already - // searched and those of lower "TB rank" if we are in a TB root position. - if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, - thisThread->rootMoves.begin() + thisThread->pvLast, move)) - continue; + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. In MultiPV mode we also skip PV moves that have been already + // searched and those of lower "TB rank" if we are in a TB root position. + if (rootNode + && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, + thisThread->rootMoves.begin() + thisThread->pvLast, move)) + continue; - ss->moveCount = ++moveCount; + ss->moveCount = ++moveCount; - if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) - sync_cout << "info depth " << depth - << " currmove " << UCI::move(move, pos.is_chess960()) - << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; - if (PvNode) - (ss+1)->pv = nullptr; + if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) + sync_cout << "info depth " << depth << " currmove " + << UCI::move(move, pos.is_chess960()) << " currmovenumber " + << moveCount + thisThread->pvIdx << sync_endl; + if (PvNode) + (ss + 1)->pv = nullptr; - extension = 0; - capture = pos.capture_stage(move); - movedPiece = pos.moved_piece(move); - givesCheck = pos.gives_check(move); + extension = 0; + capture = pos.capture_stage(move); + movedPiece = pos.moved_piece(move); + givesCheck = pos.gives_check(move); - // Calculate new depth for this move - newDepth = depth - 1; + // Calculate new depth for this move + newDepth = depth - 1; - Value delta = beta - alpha; + Value delta = beta - alpha; - Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); + Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); - // Step 14. Pruning at shallow depth (~120 Elo). - // Depth conditions are important for mate finding. - if ( !rootNode - && pos.non_pawn_material(us) - && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) - { - // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - if (!moveCountPruning) - moveCountPruning = moveCount >= futility_move_count(improving, depth); + // Step 14. Pruning at shallow depth (~120 Elo). + // Depth conditions are important for mate finding. + if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + { + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) + if (!moveCountPruning) + moveCountPruning = moveCount >= futility_move_count(improving, depth); - // Reduced depth of the next LMR search - int lmrDepth = newDepth - r; + // Reduced depth of the next LMR search + int lmrDepth = newDepth - r; - if ( capture - || givesCheck) - { - // Futility pruning for captures (~2 Elo) - if ( !givesCheck - && lmrDepth < 7 - && !ss->inCheck - && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) - continue; + if (capture || givesCheck) + { + // Futility pruning for captures (~2 Elo) + if (!givesCheck && lmrDepth < 7 && !ss->inCheck + && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] + + captureHistory[movedPiece][to_sq(move)] + [type_of(pos.piece_on(to_sq(move)))] + / 7 + < alpha) + continue; - // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, Value(-185) * depth)) - continue; - } - else - { - int history = (*contHist[0])[movedPiece][to_sq(move)] + // SEE based pruning for captures and checks (~11 Elo) + if (!pos.see_ge(move, Value(-185) * depth)) + continue; + } + else + { + int history = (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)]; - // Continuation history based pruning (~2 Elo) - if ( lmrDepth < 6 - && history < -3232 * depth) - continue; + // Continuation history based pruning (~2 Elo) + if (lmrDepth < 6 && history < -3232 * depth) + continue; - history += 2 * thisThread->mainHistory[us][from_to(move)]; + history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 5793; - lmrDepth = std::max(lmrDepth, -2); + lmrDepth += history / 5793; + lmrDepth = std::max(lmrDepth, -2); - // Futility pruning: parent node (~13 Elo) - if ( !ss->inCheck - && lmrDepth < 13 - && ss->staticEval + 115 + 122 * lmrDepth <= alpha) - continue; + // Futility pruning: parent node (~13 Elo) + if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 115 + 122 * lmrDepth <= alpha) + continue; - lmrDepth = std::max(lmrDepth, 0); + lmrDepth = std::max(lmrDepth, 0); - // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth))) - continue; - } - } + // Prune moves with negative SEE (~4 Elo) + if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth))) + continue; + } + } - // Step 15. Extensions (~100 Elo) - // We take care to not overdo to avoid search getting stuck. - if (ss->ply < thisThread->rootDepth * 2) - { - // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the result - // is lower than ttValue minus a margin, then we will extend the ttMove. Note - // that 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 this type of time controls. - if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) - && move == ttMove - && !excludedMove // Avoid recursive singular search - && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY - && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 3) - { - Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; - Depth singularDepth = (depth - 1) / 2; + // Step 15. Extensions (~100 Elo) + // We take care to not overdo to avoid search getting stuck. + if (ss->ply < thisThread->rootDepth * 2) + { + // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the result + // is lower than ttValue minus a margin, then we will extend the ttMove. Note + // that 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 this type of time controls. + if (!rootNode + && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) + && move == ttMove && !excludedMove // Avoid recursive singular search + && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3) + { + Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; + Depth singularDepth = (depth - 1) / 2; - ss->excludedMove = move; - value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - ss->excludedMove = MOVE_NONE; + ss->excludedMove = move; + value = + search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + ss->excludedMove = MOVE_NONE; - if (value < singularBeta) - { - extension = 1; - singularQuietLMR = !ttCapture; + if (value < singularBeta) + { + extension = 1; + singularQuietLMR = !ttCapture; - // Avoid search explosion by limiting the number of double extensions - if ( !PvNode - && value < singularBeta - 18 - && ss->doubleExtensions <= 11) - { - extension = 2; - depth += depth < 15; - } - } + // Avoid search explosion by limiting the number of double extensions + if (!PvNode && value < singularBeta - 18 && ss->doubleExtensions <= 11) + { + extension = 2; + depth += depth < 15; + } + } - // Multi-cut pruning - // Our ttMove is assumed to fail high, and now we failed high also on a - // reduced search without the ttMove. So we assume this expected cut-node - // is not singular, that multiple moves fail high, and we can prune the - // whole subtree by returning a softbound. - else if (singularBeta >= beta) - return singularBeta; + // Multi-cut pruning + // Our ttMove is assumed to fail high, and now we failed high also on a + // reduced search without the ttMove. So we assume this expected cut-node + // is not singular, that multiple moves fail high, and we can prune the + // whole subtree by returning a softbound. + else if (singularBeta >= beta) + return singularBeta; - // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) - else if (ttValue >= beta) - extension = -2 - !PvNode; + // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) + else if (ttValue >= beta) + extension = -2 - !PvNode; - // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) - else if (cutNode) - extension = depth < 19 ? -2 : -1; + // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) + else if (cutNode) + extension = depth < 19 ? -2 : -1; - // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) - else if (ttValue <= value) - extension = -1; - } + // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) + else if (ttValue <= value) + extension = -1; + } - // Check extensions (~1 Elo) - else if ( givesCheck - && depth > 9) - extension = 1; + // Check extensions (~1 Elo) + else if (givesCheck && depth > 9) + extension = 1; - // Quiet ttMove extensions (~1 Elo) - else if ( PvNode - && move == ttMove - && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) - extension = 1; - } + // Quiet ttMove extensions (~1 Elo) + else if (PvNode && move == ttMove && move == ss->killers[0] + && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) + extension = 1; + } - // Add extension to new depth - newDepth += extension; - ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2); + // Add extension to new depth + newDepth += extension; + ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension == 2); - // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + // Speculative prefetch as early as possible + prefetch(TT.first_entry(pos.key_after(move))); - // Update the current move (this must be done after singular extension search) - ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [capture] - [movedPiece] - [to_sq(move)]; + // Update the current move (this must be done after singular extension search) + ss->currentMove = move; + ss->continuationHistory = + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][to_sq(move)]; - // Step 16. Make the move - pos.do_move(move, st, givesCheck); + // Step 16. Make the move + pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV (~4 Elo) - if ( ss->ttPv - && !likelyFailLow) - r -= cutNode && tte->depth() >= depth ? 3 : 2; + // Decrease reduction if position is or has been on the PV (~4 Elo) + if (ss->ttPv && !likelyFailLow) + r -= cutNode && tte->depth() >= depth ? 3 : 2; - // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss-1)->moveCount > 7) - r--; + // Decrease reduction if opponent's move count is high (~1 Elo) + if ((ss - 1)->moveCount > 7) + r--; - // Increase reduction for cut nodes (~3 Elo) - if (cutNode) - r += 2; + // Increase reduction for cut nodes (~3 Elo) + if (cutNode) + r += 2; - // Increase reduction if ttMove is a capture (~3 Elo) - if (ttCapture) - r++; + // Increase reduction if ttMove is a capture (~3 Elo) + if (ttCapture) + r++; - // Decrease reduction for PvNodes (~2 Elo) - if (PvNode) - r--; + // Decrease reduction for PvNodes (~2 Elo) + if (PvNode) + r--; - // Decrease reduction if ttMove has been singularly extended (~1 Elo) - if (singularQuietLMR) - r--; + // Decrease reduction if ttMove has been singularly extended (~1 Elo) + if (singularQuietLMR) + r--; - // Increase reduction on repetition (~1 Elo) - if ( move == (ss-4)->currentMove - && pos.has_repeated()) - r += 2; + // Increase reduction on repetition (~1 Elo) + if (move == (ss - 4)->currentMove && pos.has_repeated()) + r += 2; - // Increase reduction if next ply has a lot of fail high (~5 Elo) - if ((ss+1)->cutoffCnt > 3) - r++; + // Increase reduction if next ply has a lot of fail high (~5 Elo) + if ((ss + 1)->cutoffCnt > 3) + r++; - // Decrease reduction for first generated move (ttMove) - else if (move == ttMove) - r--; + // Decrease reduction for first generated move (ttMove) + else if (move == ttMove) + r--; - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - - 3848; + ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + + (*contHist[0])[movedPiece][to_sq(move)] + + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] - 3848; - // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23)); + // Decrease/increase reduction for moves with a good/bad history (~25 Elo) + r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23)); - // Step 17. Late moves reduction / extension (LMR, ~117 Elo) - // We use various heuristics for the sons of a node after the first son has - // been searched. In general, we would like to reduce them, but there are many - // cases where we extend a son if it has good chances to be "interesting". - if ( depth >= 2 - && moveCount > 1 + (PvNode && ss->ply <= 1) - && ( !ss->ttPv - || !capture - || (cutNode && (ss-1)->moveCount > 1))) - { - // In general we want to cap the LMR depth search at newDepth, but when - // reduction is negative, we allow this move a limited search extension - // beyond the first move depth. This may lead to hidden double extensions. - Depth d = std::clamp(newDepth - r, 1, newDepth + 1); + // Step 17. Late moves reduction / extension (LMR, ~117 Elo) + // We use various heuristics for the sons of a node after the first son has + // been searched. In general, we would like to reduce them, but there are many + // cases where we extend a son if it has good chances to be "interesting". + if (depth >= 2 && moveCount > 1 + (PvNode && ss->ply <= 1) + && (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1))) + { + // In general we want to cap the LMR depth search at newDepth, but when + // reduction is negative, we allow this move a limited search extension + // beyond the first move depth. This may lead to hidden double extensions. + Depth d = std::clamp(newDepth - r, 1, newDepth + 1); - value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); + value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); - // Do a full-depth search when reduced LMR search fails high - if ( value > alpha - && d < newDepth) - { - // 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 + 51 + 10 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; - const bool doShallowerSearch = value < bestValue + newDepth; + // Do a full-depth search when reduced LMR search fails high + if (value > alpha && d < newDepth) + { + // 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 + 51 + 10 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; + const bool doShallowerSearch = value < bestValue + newDepth; - ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; + ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; - newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; + newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; - if (newDepth > d) - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + if (newDepth > d) + value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); - int bonus = value <= alpha ? -stat_bonus(newDepth) - : value >= beta ? stat_bonus(newDepth) - : 0; + int bonus = value <= alpha ? -stat_bonus(newDepth) + : value >= beta ? stat_bonus(newDepth) + : 0; - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); - } - } + update_continuation_histories(ss, movedPiece, to_sq(move), bonus); + } + } - // Step 18. Full-depth search when LMR is skipped - else if (!PvNode || moveCount > 1) - { - // Increase reduction for cut nodes and not ttMove (~1 Elo) - if ( !ttMove - && cutNode) - r += 2; + // Step 18. Full-depth search when LMR is skipped + else if (!PvNode || moveCount > 1) + { + // Increase reduction for cut nodes and not ttMove (~1 Elo) + if (!ttMove && cutNode) + r += 2; - // Note that if expected reduction is high, we reduce search depth by 1 here - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); - } + // Note that if expected reduction is high, we reduce search depth by 1 here + value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3), !cutNode); + } - // For PV nodes only, do a full PV search on the first move or after a fail high, - // otherwise let the parent node fail low with value <= alpha and try another move. - if ( PvNode - && (moveCount == 1 || value > alpha)) - { - (ss+1)->pv = pv; - (ss+1)->pv[0] = MOVE_NONE; + // For PV nodes only, do a full PV search on the first move or after a fail high, + // otherwise let the parent node fail low with value <= alpha and try another move. + if (PvNode && (moveCount == 1 || value > alpha)) + { + (ss + 1)->pv = pv; + (ss + 1)->pv[0] = MOVE_NONE; - value = -search(pos, ss+1, -beta, -alpha, newDepth, false); - } + value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); + } - // Step 19. Undo move - pos.undo_move(move); + // Step 19. Undo move + pos.undo_move(move); - assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // Step 20. Check for a new best move - // Finished searching the move. If a stop occurred, the return value of - // the search cannot be trusted, and we return immediately without - // updating best move, PV and TT. - if (Threads.stop.load(std::memory_order_relaxed)) - return VALUE_ZERO; + // Step 20. Check for a new best move + // Finished searching the move. If a stop occurred, the return value of + // the search cannot be trusted, and we return immediately without + // updating best move, PV and TT. + if (Threads.stop.load(std::memory_order_relaxed)) + return VALUE_ZERO; - if (rootNode) - { - RootMove& rm = *std::find(thisThread->rootMoves.begin(), - thisThread->rootMoves.end(), move); + if (rootNode) + { + RootMove& rm = + *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); - rm.averageScore = rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; + rm.averageScore = + rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; - // PV move or new best move? - if (moveCount == 1 || value > alpha) - { - rm.score = rm.uciScore = value; - rm.selDepth = thisThread->selDepth; - rm.scoreLowerbound = rm.scoreUpperbound = false; + // PV move or new best move? + if (moveCount == 1 || value > alpha) + { + rm.score = rm.uciScore = value; + rm.selDepth = thisThread->selDepth; + rm.scoreLowerbound = rm.scoreUpperbound = false; - if (value >= beta) - { - rm.scoreLowerbound = true; - rm.uciScore = beta; - } - else if (value <= alpha) - { - rm.scoreUpperbound = true; - rm.uciScore = alpha; - } + if (value >= beta) + { + rm.scoreLowerbound = true; + rm.uciScore = beta; + } + else if (value <= alpha) + { + rm.scoreUpperbound = true; + rm.uciScore = alpha; + } - rm.pv.resize(1); + rm.pv.resize(1); - assert((ss+1)->pv); + assert((ss + 1)->pv); - for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m) - rm.pv.push_back(*m); + for (Move* m = (ss + 1)->pv; *m != MOVE_NONE; ++m) + rm.pv.push_back(*m); - // We record how often the best move has been changed in each iteration. - // This information is used for time management. In MultiPV mode, - // we must take care to only do this for the first PV line. - if ( moveCount > 1 - && !thisThread->pvIdx) - ++thisThread->bestMoveChanges; - } - else - // All other moves but the PV, are set to the lowest value: this - // is not a problem when sorting because the sort is stable and the - // move position in the list is preserved - just the PV is pushed up. - rm.score = -VALUE_INFINITE; - } + // We record how often the best move has been changed in each iteration. + // This information is used for time management. In MultiPV mode, + // we must take care to only do this for the first PV line. + if (moveCount > 1 && !thisThread->pvIdx) + ++thisThread->bestMoveChanges; + } + else + // All other moves but the PV, are set to the lowest value: this + // is not a problem when sorting because the sort is stable and the + // move position in the list is preserved - just the PV is pushed up. + rm.score = -VALUE_INFINITE; + } - if (value > bestValue) - { - bestValue = value; + if (value > bestValue) + { + bestValue = value; - if (value > alpha) - { - bestMove = move; + if (value > alpha) + { + bestMove = move; - if (PvNode && !rootNode) // Update pv even in fail-high case - update_pv(ss->pv, move, (ss+1)->pv); + if (PvNode && !rootNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss + 1)->pv); - if (value >= beta) - { - ss->cutoffCnt += 1 + !ttMove; - 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 < 12 - && beta < 13828 - && value > -11369) - depth -= 2; + if (value >= beta) + { + ss->cutoffCnt += 1 + !ttMove; + 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 < 12 && beta < 13828 && value > -11369) + depth -= 2; - assert(depth > 0); - alpha = value; // Update alpha! Always alpha < beta - } - } - } + assert(depth > 0); + alpha = value; // Update alpha! Always alpha < beta + } + } + } - // If the move is worse than some previously searched move, - // remember it, to update its stats later. - if (move != bestMove && moveCount <= 32) - { - if (capture) - capturesSearched[captureCount++] = move; + // If the move is worse than some previously searched move, + // remember it, to update its stats later. + if (move != bestMove && moveCount <= 32) + { + if (capture) + capturesSearched[captureCount++] = move; - else - quietsSearched[quietCount++] = move; - } + else + quietsSearched[quietCount++] = move; + } } // Step 21. Check for mate and stalemate @@ -1355,21 +1306,22 @@ moves_loop: // When in check, search starts here assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); if (!moveCount) - bestValue = excludedMove ? alpha : - ss->inCheck ? mated_in(ss->ply) - : VALUE_DRAW; + bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; // If there is a move that produces search value greater than alpha we update the stats of searched moves else if (bestMove) - update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, - quietsSearched, quietCount, capturesSearched, captureCount, depth); + update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, + capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653) + ((ss-1)->moveCount > 11); - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus / 2; + int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653) + + ((ss - 1)->moveCount > 11); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] + << stat_bonus(depth) * bonus / 2; } if (PvNode) @@ -1378,26 +1330,27 @@ moves_loop: // When in check, search starts here // 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) if (bestValue <= alpha) - ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); + ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); // Write gathered information in transposition table if (!excludedMove && !(rootNode && thisThread->pvIdx)) tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, - bestValue >= beta ? BOUND_LOWER : - PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, + bestValue >= beta ? BOUND_LOWER + : PvNode && bestMove ? BOUND_EXACT + : BOUND_UPPER, depth, bestMove, ss->staticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; - } +} - // qsearch() is the quiescence search function, which is called by the main search - // function with zero depth, or recursively with further decreasing depth per call. - // (~155 Elo) - template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { +// qsearch() is the quiescence search function, which is called by the main search +// function with zero depth, or recursively with further decreasing depth per call. +// (~155 Elo) +template +Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { static_assert(nodeType != Root); constexpr bool PvNode = nodeType == PV; @@ -1408,42 +1361,40 @@ moves_loop: // When in check, search starts here // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. - if ( alpha < VALUE_DRAW - && pos.has_game_cycle(ss->ply)) + if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(pos.this_thread()); if (alpha >= beta) return alpha; } - Move pv[MAX_PLY+1]; + Move pv[MAX_PLY + 1]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); TTEntry* tte; - Key posKey; - Move ttMove, move, bestMove; - Depth ttDepth; - Value bestValue, value, ttValue, futilityValue, futilityBase; - bool pvHit, givesCheck, capture; - int moveCount; - Color us = pos.side_to_move(); + Key posKey; + Move ttMove, move, bestMove; + Depth ttDepth; + Value bestValue, value, ttValue, futilityValue, futilityBase; + bool pvHit, givesCheck, capture; + int moveCount; + Color us = pos.side_to_move(); // Step 1. Initialize node if (PvNode) { - (ss+1)->pv = pv; - ss->pv[0] = MOVE_NONE; + (ss + 1)->pv = pv; + ss->pv[0] = MOVE_NONE; } Thread* thisThread = pos.this_thread(); - bestMove = MOVE_NONE; - ss->inCheck = pos.checkers(); - moveCount = 0; + bestMove = MOVE_NONE; + ss->inCheck = pos.checkers(); + moveCount = 0; // Step 2. Check for an immediate draw or maximum ply reached - if ( pos.is_draw(ss->ply) - || ss->ply >= MAX_PLY) + if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1451,20 +1402,18 @@ moves_loop: // When in check, search starts here // Decide whether or not to include checks: this fixes also the type of // TT entry depth that we are going to use. Note that in qsearch we use // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. - ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS - : DEPTH_QS_NO_CHECKS; + ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; // Step 3. Transposition table lookup - posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); + posKey = pos.key(); + tte = TT.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = ss->ttHit ? tte->move() : MOVE_NONE; - pvHit = ss->ttHit && tte->is_pv(); + ttMove = ss->ttHit ? tte->move() : MOVE_NONE; + pvHit = ss->ttHit && tte->is_pv(); // At non-PV nodes we check for an early TT cutoff - if ( !PvNode - && tte->depth() >= ttDepth - && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit + if (!PvNode && tte->depth() >= ttDepth + && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; @@ -1480,21 +1429,21 @@ moves_loop: // When in check, search starts here ss->staticEval = bestValue = evaluate(pos); // ttValue can be used as a better position evaluation (~13 Elo) - if ( ttValue != VALUE_NONE + if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttValue; } else // In case of null move search use previous static eval with a different sign - ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) - : -(ss-1)->staticEval; + ss->staticEval = bestValue = + (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, - DEPTH_NONE, MOVE_NONE, ss->staticEval); + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, + MOVE_NONE, ss->staticEval); return bestValue; } @@ -1505,17 +1454,16 @@ moves_loop: // When in check, search starts here futilityBase = std::min(ss->staticEval, bestValue) + 200; } - const PieceToHistory* contHist[] = {(ss-1)->continuationHistory, (ss-2)->continuationHistory}; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, + (ss - 2)->continuationHistory}; // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, - &thisThread->captureHistory, - contHist, - prevSq); + Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, + contHist, prevSq); int quietCheckEvasions = 0; @@ -1530,19 +1478,16 @@ moves_loop: // When in check, search starts here continue; givesCheck = pos.gives_check(move); - capture = pos.capture_stage(move); + capture = pos.capture_stage(move); moveCount++; // Step 6. Pruning - if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && pos.non_pawn_material(us)) + if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) - if ( !givesCheck - && to_sq(move) != prevSq - && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY - && type_of(move) != PROMOTION) + if (!givesCheck && to_sq(move) != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY + && type_of(move) != PROMOTION) { if (moveCount > 2) continue; @@ -1559,8 +1504,7 @@ moves_loop: // When in check, search starts here // If static eval is much lower than alpha and move is not winning material // we can prune this move. - if ( futilityBase <= alpha - && !pos.see_ge(move, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1582,8 +1526,7 @@ moves_loop: // When in check, search starts here break; // Continuation history based pruning (~3 Elo) - if ( !capture - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 + if (!capture && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) continue; @@ -1597,16 +1540,15 @@ moves_loop: // When in check, search starts here // Update the current move ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [capture] - [pos.moved_piece(move)] - [to_sq(move)]; + ss->continuationHistory = + &thisThread + ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][to_sq(move)]; quietCheckEvasions += !capture && ss->inCheck; // Step 7. Make and search the move pos.do_move(move, st, givesCheck); - value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); + value = -qsearch(pos, ss + 1, -beta, -alpha, depth - 1); pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1620,13 +1562,13 @@ moves_loop: // When in check, search starts here { bestMove = move; - if (PvNode) // Update pv even in fail-high case - update_pv(ss->pv, move, (ss+1)->pv); + if (PvNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss + 1)->pv); - if (value < beta) // Update alpha here! + if (value < beta) // Update alpha here! alpha = value; else - break; // Fail high + break; // Fail high } } } @@ -1638,40 +1580,38 @@ moves_loop: // When in check, search starts here { assert(!MoveList(pos).size()); - return mated_in(ss->ply); // Plies to mate from the root + return mated_in(ss->ply); // Plies to mate from the root } // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, - bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, - ttDepth, bestMove, ss->staticEval); + bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, ss->staticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; - } +} - // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" - // to "plies to mate from the current position". Standard scores are unchanged. - // The function is called before storing a value in the transposition table. +// value_to_tt() adjusts a mate or TB score from "plies to mate from the root" +// to "plies to mate from the current position". Standard scores are unchanged. +// The function is called before storing a value in the transposition table. - Value value_to_tt(Value v, int ply) { +Value value_to_tt(Value v, int ply) { assert(v != VALUE_NONE); - return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply - : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; - } + return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; +} - // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score - // from the transposition table (which refers to the plies to mate/be mated from - // current position) to "plies to mate/be mated (TB win/loss) from the root". - // However, to avoid potentially false mate scores related to the 50 moves rule - // and the graph history interaction problem, we return an optimal TB score instead. +// value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score +// from the transposition table (which refers to the plies to mate/be mated from +// current position) to "plies to mate/be mated (TB win/loss) from the root". +// However, to avoid potentially false mate scores related to the 50 moves rule +// and the graph history interaction problem, we return an optimal TB score instead. - Value value_from_tt(Value v, int ply, int r50c) { +Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) return VALUE_NONE; @@ -1679,50 +1619,59 @@ moves_loop: // When in check, search starts here if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better { if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c) - return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score + return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score return v - ply; } - if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse + if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse { if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c) - return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score + return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score return v + ply; } return v; - } +} - // update_pv() adds current move and appends child pv[] +// update_pv() adds current move and appends child pv[] - void update_pv(Move* pv, Move move, const Move* childPv) { +void update_pv(Move* pv, Move move, const Move* childPv) { - for (*pv++ = move; childPv && *childPv != MOVE_NONE; ) + for (*pv++ = move; childPv && *childPv != MOVE_NONE;) *pv++ = *childPv++; *pv = MOVE_NONE; - } +} - // update_all_stats() updates stats at the end of search() when a bestMove is found +// update_all_stats() updates stats at the end of search() when a bestMove is found - void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, - Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) { +void update_all_stats(const Position& pos, + Stack* ss, + Move bestMove, + Value bestValue, + Value beta, + Square prevSq, + Move* quietsSearched, + int quietCount, + Move* capturesSearched, + int captureCount, + Depth depth) { - Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); + Color us = pos.side_to_move(); + Thread* thisThread = pos.this_thread(); CapturePieceToHistory& captureHistory = thisThread->captureHistory; - Piece moved_piece = pos.moved_piece(bestMove); - PieceType captured; + Piece moved_piece = pos.moved_piece(bestMove); + PieceType captured; int quietMoveBonus = stat_bonus(depth + 1); if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus - : stat_bonus(depth); // smaller bonus + int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus + : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bestMoveBonus); @@ -1731,7 +1680,8 @@ moves_loop: // When in check, search starts here for (int i = 0; i < quietCount; ++i) { thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus; - update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bestMoveBonus); + update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), + to_sq(quietsSearched[i]), -bestMoveBonus); } } else @@ -1743,40 +1693,41 @@ moves_loop: // When in check, search starts here // Extra penalty for a quiet early move that was not a TT move or // main killer move in previous ply when it gets refuted. - if ( prevSq != SQ_NONE - && ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) + if (prevSq != SQ_NONE + && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit + || ((ss - 1)->currentMove == (ss - 1)->killers[0])) && !pos.captured_piece()) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -quietMoveBonus); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveBonus); // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); - captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); + captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveBonus; } - } +} - // update_continuation_histories() updates histories of the move pairs formed - // by moves at ply -1, -2, -4, and -6 with current move. +// update_continuation_histories() updates histories of the move pairs formed +// by moves at ply -1, -2, -4, and -6 with current move. - void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { +void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { for (int i : {1, 2, 3, 4, 6}) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; - if (is_ok((ss-i)->currentMove)) - (*(ss-i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); + if (is_ok((ss - i)->currentMove)) + (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); } - } +} - // update_quiet_stats() updates move sorting heuristics +// update_quiet_stats() updates move sorting heuristics - void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { +void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { // Update killers if (ss->killers[0] != move) @@ -1785,31 +1736,31 @@ moves_loop: // When in check, search starts here ss->killers[0] = move; } - Color us = pos.side_to_move(); + Color us = pos.side_to_move(); Thread* thisThread = pos.this_thread(); thisThread->mainHistory[us][from_to(move)] << bonus; update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); // Update countermove history - if (is_ok((ss-1)->currentMove)) + if (is_ok((ss - 1)->currentMove)) { - Square prevSq = to_sq((ss-1)->currentMove); + Square prevSq = to_sq((ss - 1)->currentMove); thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } - } +} - // When playing with strength handicap, choose the best move among a set of RootMoves - // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. +// When playing with strength handicap, choose the best move among a set of RootMoves +// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - Move Skill::pick_best(size_t multiPV) { +Move Skill::pick_best(size_t multiPV) { const RootMoves& rootMoves = Threads.main()->rootMoves; - static PRNG rng(now()); // PRNG sequence should be non-deterministic + static PRNG rng(now()); // PRNG sequence should be non-deterministic // RootMoves are already sorted by score in descending order - Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue); - int maxScore = -VALUE_INFINITE; + Value topScore = rootMoves[0].score; + int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue); + int maxScore = -VALUE_INFINITE; double weakness = 120 - 2 * level; // Choose best move. For each move score we add two terms, both dependent on @@ -1818,20 +1769,21 @@ moves_loop: // When in check, search starts here for (size_t i = 0; i < multiPV; ++i) { // This is our magic formula - int push = int(( weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) / 128); + int push = int((weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) + / 128); if (rootMoves[i].score + push >= maxScore) { maxScore = rootMoves[i].score + push; - best = rootMoves[i].pv[0]; + best = rootMoves[i].pv[0]; } } return best; - } +} -} // namespace +} // namespace // MainThread::check_time() is used to print debug info and, more importantly, @@ -1839,31 +1791,31 @@ moves_loop: // When in check, search starts here void MainThread::check_time() { - if (--callsCnt > 0) - return; + if (--callsCnt > 0) + return; - // When using nodes, ensure checking rate is not lower than 0.1% of nodes - callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512; + // When using nodes, ensure checking rate is not lower than 0.1% of nodes + callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512; - static TimePoint lastInfoTime = now(); + static TimePoint lastInfoTime = now(); - TimePoint elapsed = Time.elapsed(); - TimePoint tick = Limits.startTime + elapsed; + TimePoint elapsed = Time.elapsed(); + TimePoint tick = Limits.startTime + elapsed; - if (tick - lastInfoTime >= 1000) - { - lastInfoTime = tick; - dbg_print(); - } + if (tick - lastInfoTime >= 1000) + { + lastInfoTime = tick; + dbg_print(); + } - // We should not stop pondering until told so by the GUI - if (ponder) - return; + // We should not stop pondering until told so by the GUI + if (ponder) + return; - if ( (Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) - || (Limits.movetime && elapsed >= Limits.movetime) - || (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))) - Threads.stop = true; + if ((Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) + || (Limits.movetime && elapsed >= Limits.movetime) + || (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))) + Threads.stop = true; } @@ -1872,57 +1824,53 @@ void MainThread::check_time() { string UCI::pv(const Position& pos, Depth depth) { - std::stringstream ss; - TimePoint elapsed = Time.elapsed() + 1; - const RootMoves& rootMoves = pos.this_thread()->rootMoves; - size_t pvIdx = pos.this_thread()->pvIdx; - size_t multiPV = std::min(size_t(Options["MultiPV"]), rootMoves.size()); - uint64_t nodesSearched = Threads.nodes_searched(); - uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); + std::stringstream ss; + TimePoint elapsed = Time.elapsed() + 1; + const RootMoves& rootMoves = pos.this_thread()->rootMoves; + size_t pvIdx = pos.this_thread()->pvIdx; + size_t multiPV = std::min(size_t(Options["MultiPV"]), rootMoves.size()); + uint64_t nodesSearched = Threads.nodes_searched(); + uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); - for (size_t i = 0; i < multiPV; ++i) - { - bool updated = rootMoves[i].score != -VALUE_INFINITE; + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; - if (depth == 1 && !updated && i > 0) - continue; + if (depth == 1 && !updated && i > 0) + continue; - Depth d = updated ? depth : std::max(1, depth - 1); - Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; - if (v == -VALUE_INFINITE) - v = VALUE_ZERO; + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; - bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; - v = tb ? rootMoves[i].tbScore : v; + bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; + v = tb ? rootMoves[i].tbScore : v; - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; - ss << "info" - << " depth " << d - << " seldepth " << rootMoves[i].selDepth - << " multipv " << i + 1 - << " score " << UCI::value(v); + ss << "info" + << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 + << " score " << UCI::value(v); - if (Options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos.game_ply()); + if (Options["UCI_ShowWDL"]) + ss << UCI::wdl(v, pos.game_ply()); - if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound ? " lowerbound" : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + ss << (rootMoves[i].scoreLowerbound + ? " lowerbound" + : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); - ss << " nodes " << nodesSearched - << " nps " << nodesSearched * 1000 / elapsed - << " hashfull " << TT.hashfull() - << " tbhits " << tbHits - << " time " << elapsed - << " pv"; + ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / elapsed + << " hashfull " << TT.hashfull() << " tbhits " << tbHits << " time " << elapsed << " pv"; - for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); - } + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(m, pos.is_chess960()); + } - return ss.str(); + return ss.str(); } @@ -1948,7 +1896,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { if (ttHit) { - Move m = tte->move(); // Local copy to be SMP safe + Move m = tte->move(); // Local copy to be SMP safe if (MoveList(pos).contains(m)) pv.push_back(m); } @@ -1959,10 +1907,10 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { - RootInTB = false; - UseRule50 = bool(Options["Syzygy50MoveRule"]); - ProbeDepth = int(Options["SyzygyProbeDepth"]); - Cardinality = int(Options["SyzygyProbeLimit"]); + RootInTB = false; + UseRule50 = bool(Options["Syzygy50MoveRule"]); + ProbeDepth = int(Options["SyzygyProbeDepth"]); + Cardinality = int(Options["SyzygyProbeLimit"]); bool dtz_available = true; // Tables with fewer pieces than SyzygyProbeLimit are searched with @@ -1970,7 +1918,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { if (Cardinality > MaxCardinality) { Cardinality = MaxCardinality; - ProbeDepth = 0; + ProbeDepth = 0; } if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) @@ -1982,7 +1930,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { { // DTZ tables are missing; try to rank moves using WDL tables dtz_available = false; - RootInTB = root_probe_wdl(pos, rootMoves); + RootInTB = root_probe_wdl(pos, rootMoves); } } @@ -1990,7 +1938,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { { // Sort moves according to TB rank std::stable_sort(rootMoves.begin(), rootMoves.end(), - [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } ); + [](const RootMove& a, const RootMove& b) { return a.tbRank > b.tbRank; }); // Probe during search only if DTZ is not available and we are winning if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) @@ -2004,4 +1952,4 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { } } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/search.h b/src/search.h index c434ba75..37cd5e5a 100644 --- a/src/search.h +++ b/src/search.h @@ -38,20 +38,20 @@ namespace Search { // its own array of Stack objects, indexed by the current ply. struct Stack { - Move* pv; - PieceToHistory* continuationHistory; - int ply; - Move currentMove; - Move excludedMove; - Move killers[2]; - Value staticEval; - int statScore; - int moveCount; - bool inCheck; - bool ttPv; - bool ttHit; - int doubleExtensions; - int cutoffCnt; + Move* pv; + PieceToHistory* continuationHistory; + int ply; + Move currentMove; + Move excludedMove; + Move killers[2]; + Value staticEval; + int statScore; + int moveCount; + bool inCheck; + bool ttPv; + bool ttHit; + int doubleExtensions; + int cutoffCnt; }; @@ -61,24 +61,24 @@ struct Stack { struct RootMove { - explicit RootMove(Move m) : pv(1, m) {} - bool extract_ponder_from_tt(Position& pos); - bool operator==(const Move& m) const { return pv[0] == m; } - bool operator<(const RootMove& m) const { // Sort in descending order - return m.score != score ? m.score < score - : m.previousScore < previousScore; - } + explicit RootMove(Move m) : + pv(1, m) {} + bool extract_ponder_from_tt(Position& pos); + bool operator==(const Move& m) const { return pv[0] == m; } + bool operator<(const RootMove& m) const { // Sort in descending order + return m.score != score ? m.score < score : m.previousScore < previousScore; + } - Value score = -VALUE_INFINITE; - Value previousScore = -VALUE_INFINITE; - Value averageScore = -VALUE_INFINITE; - Value uciScore = -VALUE_INFINITE; - bool scoreLowerbound = false; - bool scoreUpperbound = false; - int selDepth = 0; - int tbRank = 0; - Value tbScore; - std::vector pv; + Value score = -VALUE_INFINITE; + Value previousScore = -VALUE_INFINITE; + Value averageScore = -VALUE_INFINITE; + Value uciScore = -VALUE_INFINITE; + bool scoreLowerbound = false; + bool scoreUpperbound = false; + int selDepth = 0; + int tbRank = 0; + Value tbScore; + std::vector pv; }; using RootMoves = std::vector; @@ -89,20 +89,18 @@ using RootMoves = std::vector; struct LimitsType { - LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC - time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); - movestogo = depth = mate = perft = infinite = 0; - nodes = 0; - } + LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC + time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); + movestogo = depth = mate = perft = infinite = 0; + nodes = 0; + } - bool use_time_management() const { - return time[WHITE] || time[BLACK]; - } + bool use_time_management() const { return time[WHITE] || time[BLACK]; } - std::vector searchmoves; - TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; - int movestogo, depth, mate, perft, infinite; - int64_t nodes; + std::vector searchmoves; + TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; + int movestogo, depth, mate, perft, infinite; + int64_t nodes; }; extern LimitsType Limits; @@ -110,8 +108,8 @@ extern LimitsType Limits; void init(); void clear(); -} // namespace Search +} // namespace Search -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef SEARCH_H_INCLUDED +#endif // #ifndef SEARCH_H_INCLUDED diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 4114db60..c8e60ab6 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -45,15 +45,15 @@ #include "../uci.h" #ifndef _WIN32 -#include -#include -#include + #include + #include + #include #else -#define WIN32_LEAN_AND_MEAN -#ifndef NOMINMAX -# define NOMINMAX // Disable macros min() and max() -#endif -#include + #define WIN32_LEAN_AND_MEAN + #ifndef NOMINMAX + #define NOMINMAX // Disable macros min() and max() + #endif + #include #endif using namespace Stockfish::Tablebases; @@ -64,60 +64,69 @@ namespace Stockfish { namespace { -constexpr int TBPIECES = 7; // Max number of supported pieces -constexpr int MAX_DTZ = 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit. +constexpr int TBPIECES = 7; // Max number of supported pieces +constexpr int MAX_DTZ = + 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit. -enum { BigEndian, LittleEndian }; -enum TBType { WDL, DTZ }; // Used as template parameter +enum { + BigEndian, + LittleEndian +}; +enum TBType { + WDL, + DTZ +}; // Used as template parameter // Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables -enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 }; +enum TBFlag { + STM = 1, + Mapped = 2, + WinPlies = 4, + LossPlies = 8, + Wide = 16, + SingleValue = 128 +}; inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } -inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } +inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } constexpr std::string_view PieceToChar = " PNBRQK pnbrqk"; int MapPawns[SQUARE_NB]; int MapB1H1H7[SQUARE_NB]; int MapA1D1D4[SQUARE_NB]; -int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] +int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] -int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements -int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] -int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] +int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements +int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] +int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] // Comparison function to sort leading pawns in ascending MapPawns[] order bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } -int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } +int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } -constexpr Value WDL_to_value[] = { - -VALUE_MATE + MAX_PLY + 1, - VALUE_DRAW - 2, - VALUE_DRAW, - VALUE_DRAW + 2, - VALUE_MATE - MAX_PLY - 1 -}; +constexpr Value WDL_to_value[] = {-VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW, + VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1}; template -inline void swap_endian(T& x) -{ +inline void swap_endian(T& x) { static_assert(std::is_unsigned_v, "Argument of swap_endian not unsigned"); - uint8_t tmp, *c = (uint8_t*)&x; + uint8_t tmp, *c = (uint8_t*) &x; for (int i = 0; i < Half; ++i) tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; } -template<> inline void swap_endian(uint8_t&) {} +template<> +inline void swap_endian(uint8_t&) {} -template T number(void* addr) -{ +template +T number(void* addr) { T v; - if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare) + if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare) std::memcpy(&v, addr, sizeof(T)); else - v = *((T*)addr); + v = *((T*) addr); if (LE != IsLittleEndian) swap_endian(v); @@ -128,14 +137,16 @@ template T number(void* addr) // like captures and pawn moves but we can easily recover the correct dtz of the // previous move if we know the position's WDL score. int dtz_before_zeroing(WDLScore wdl) { - return wdl == WDLWin ? 1 : - wdl == WDLCursedWin ? 101 : - wdl == WDLBlessedLoss ? -101 : - wdl == WDLLoss ? -1 : 0; + return wdl == WDLWin ? 1 + : wdl == WDLCursedWin ? 101 + : wdl == WDLBlessedLoss ? -101 + : wdl == WDLLoss ? -1 + : 0; } // Return the sign of a number (-1, 0, 1) -template int sign_of(T val) { +template +int sign_of(T val) { return (T(0) < val) - (val < T(0)); } @@ -147,18 +158,22 @@ struct SparseEntry { static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); -using Sym = uint16_t; // Huffman symbol +using Sym = uint16_t; // Huffman symbol struct LR { - enum Side { Left, Right }; + enum Side { + Left, + Right + }; - uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 - // bits is the right-hand symbol. If the symbol has length 1, - // then the left-hand symbol is the stored value. + uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 + // bits is the right-hand symbol. If the symbol has length 1, + // then the left-hand symbol is the stored value. template Sym get() { - return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] : - S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1)); + return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] + : S == Right ? (lr[2] << 4) | (lr[1] >> 4) + : (assert(false), Sym(-1)); } }; @@ -173,11 +188,11 @@ static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes"); // class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are // memory mapped for best performance. Files are mapped at first access: at init // time only existence of the file is checked. -class TBFile : public std::ifstream { +class TBFile: public std::ifstream { std::string fname; -public: + public: // Look for and open the file among the Paths directories where the .rtbw // and .rtbz files can be found. Multiple directories are separated by ";" // on Windows and by ":" on Unix-based operating systems. @@ -194,7 +209,7 @@ public: constexpr char SepChar = ';'; #endif std::stringstream ss(Paths); - std::string path; + std::string path; while (std::getline(ss, path, SepChar)) { @@ -208,11 +223,11 @@ public: // Memory map the file and check it. uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) { if (is_open()) - close(); // Need to re-open to get native file descriptor + close(); // Need to re-open to get native file descriptor #ifndef _WIN32 struct stat statbuf; - int fd = ::open(fname.c_str(), O_RDONLY); + int fd = ::open(fname.c_str(), O_RDONLY); if (fd == -1) return *baseAddress = nullptr, nullptr; @@ -225,11 +240,11 @@ public: exit(EXIT_FAILURE); } - *mapping = statbuf.st_size; + *mapping = statbuf.st_size; *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); -#if defined(MADV_RANDOM) + #if defined(MADV_RANDOM) madvise(*baseAddress, statbuf.st_size, MADV_RANDOM); -#endif + #endif ::close(fd); if (*baseAddress == MAP_FAILED) @@ -240,7 +255,7 @@ public: #else // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); + OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); if (fd == INVALID_HANDLE_VALUE) return *baseAddress = nullptr, nullptr; @@ -263,7 +278,7 @@ public: exit(EXIT_FAILURE); } - *mapping = uint64_t(mmap); + *mapping = uint64_t(mmap); *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); if (!*baseAddress) @@ -273,10 +288,9 @@ public: exit(EXIT_FAILURE); } #endif - uint8_t* data = (uint8_t*)*baseAddress; + uint8_t* data = (uint8_t*) *baseAddress; - constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 }, - { 0x71, 0xE8, 0x23, 0x5D } }; + constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}}; if (memcmp(data, Magics[type == WDL], 4)) { @@ -285,7 +299,7 @@ public: return *baseAddress = nullptr, nullptr; } - return data + 4; // Skip Magics's header + return data + 4; // Skip Magics's header } static void unmap(void* baseAddress, uint64_t mapping) { @@ -294,7 +308,7 @@ public: munmap(baseAddress, mapping); #else UnmapViewOfFile(baseAddress); - CloseHandle((HANDLE)mapping); + CloseHandle((HANDLE) mapping); #endif } }; @@ -305,25 +319,27 @@ std::string TBFile::Paths; // There are 8, 4, or 2 PairsData records for each TBTable, according to the type // of table and if positions have pawns or not. It is populated at first access. struct PairsData { - uint8_t flags; // Table flags, see enum TBFlag - uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols - uint8_t minSymLen; // Minimum length in bits of the Huffman symbols - uint32_t blocksNum; // Number of blocks in the TB file - size_t sizeofBlock; // Block size in bytes - size_t span; // About every span values there is a SparseIndex[] entry - Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value - LR* btree; // btree[sym] stores the left and right symbols that expand sym - uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 - uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum - SparseEntry* sparseIndex; // Partial indices into blockLength[] - size_t sparseIndexSize; // Size of SparseIndex[] table - uint8_t* data; // Start of Huffman compressed data - std::vector base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l - std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 - Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups - uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces - int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1) - uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) + uint8_t flags; // Table flags, see enum TBFlag + uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols + uint8_t minSymLen; // Minimum length in bits of the Huffman symbols + uint32_t blocksNum; // Number of blocks in the TB file + size_t sizeofBlock; // Block size in bytes + size_t span; // About every span values there is a SparseIndex[] entry + Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value + LR* btree; // btree[sym] stores the left and right symbols that expand sym + uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 + uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum + SparseEntry* sparseIndex; // Partial indices into blockLength[] + size_t sparseIndexSize; // Size of SparseIndex[] table + uint8_t* data; // Start of Huffman compressed data + std::vector + base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l + std::vector + symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 + Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups + uint64_t groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces + int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1) + uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) }; // struct TBTable contains indexing information to access the corresponding TBFile. @@ -337,22 +353,22 @@ struct TBTable { static constexpr int Sides = Type == WDL ? 2 : 1; std::atomic_bool ready; - void* baseAddress; - uint8_t* map; - uint64_t mapping; - Key key; - Key key2; - int pieceCount; - bool hasPawns; - bool hasUniquePieces; - uint8_t pawnCount[2]; // [Lead color / other color] - PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] + void* baseAddress; + uint8_t* map; + uint64_t mapping; + Key key; + Key key2; + int pieceCount; + bool hasPawns; + bool hasUniquePieces; + uint8_t pawnCount[2]; // [Lead color / other color] + PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] - PairsData* get(int stm, int f) { - return &items[stm % Sides][hasPawns ? f : 0]; - } + PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; } - TBTable() : ready(false), baseAddress(nullptr) {} + TBTable() : + ready(false), + baseAddress(nullptr) {} explicit TBTable(const std::string& code); explicit TBTable(const TBTable& wdl); @@ -363,26 +379,26 @@ struct TBTable { }; template<> -TBTable::TBTable(const std::string& code) : TBTable() { +TBTable::TBTable(const std::string& code) : + TBTable() { StateInfo st; - Position pos; + Position pos; - key = pos.set(code, WHITE, &st).material_key(); + key = pos.set(code, WHITE, &st).material_key(); pieceCount = pos.count(); - hasPawns = pos.pieces(PAWN); + hasPawns = pos.pieces(PAWN); hasUniquePieces = false; - for (Color c : { WHITE, BLACK }) + for (Color c : {WHITE, BLACK}) for (PieceType pt = PAWN; pt < KING; ++pt) if (popcount(pos.pieces(c, pt)) == 1) hasUniquePieces = true; // Set the leading color. In case both sides have pawns the leading color // is the side with fewer pawns because this leads to better compression. - bool c = !pos.count(BLACK) - || ( pos.count(WHITE) - && pos.count(BLACK) >= pos.count(WHITE)); + bool c = !pos.count(BLACK) + || (pos.count(WHITE) && pos.count(BLACK) >= pos.count(WHITE)); pawnCount[0] = pos.count(c ? WHITE : BLACK); pawnCount[1] = pos.count(c ? BLACK : WHITE); @@ -391,16 +407,17 @@ TBTable::TBTable(const std::string& code) : TBTable() { } template<> -TBTable::TBTable(const TBTable& wdl) : TBTable() { +TBTable::TBTable(const TBTable& wdl) : + TBTable() { // Use the corresponding WDL table to avoid recalculating all from scratch - key = wdl.key; - key2 = wdl.key2; - pieceCount = wdl.pieceCount; - hasPawns = wdl.hasPawns; + key = wdl.key; + key2 = wdl.key2; + pieceCount = wdl.pieceCount; + hasPawns = wdl.hasPawns; hasUniquePieces = wdl.hasUniquePieces; - pawnCount[0] = wdl.pawnCount[0]; - pawnCount[1] = wdl.pawnCount[1]; + pawnCount[0] = wdl.pawnCount[0]; + pawnCount[1] = wdl.pawnCount[1]; } // class TBTables creates and keeps ownership of the TBTable objects, one for @@ -408,19 +425,18 @@ TBTable::TBTable(const TBTable& wdl) : TBTable() { // at init time, accessed at probe time. class TBTables { - struct Entry - { - Key key; + struct Entry { + Key key; TBTable* wdl; TBTable* dtz; - template + template TBTable* get() const { - return (TBTable*)(Type == WDL ? (void*)wdl : (void*)dtz); + return (TBTable*) (Type == WDL ? (void*) wdl : (void*) dtz); } }; - static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb + static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket Entry hashTable[Size + Overflow]; @@ -430,12 +446,14 @@ class TBTables { void insert(Key key, TBTable* wdl, TBTable* dtz) { uint32_t homeBucket = uint32_t(key) & (Size - 1); - Entry entry{ key, wdl, dtz }; + Entry entry{key, wdl, dtz}; // Ensure last element is empty to avoid overflow when looking up - for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) { + for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) + { Key otherKey = hashTable[bucket].key; - if (otherKey == key || !hashTable[bucket].get()) { + if (otherKey == key || !hashTable[bucket].get()) + { hashTable[bucket] = entry; return; } @@ -443,9 +461,10 @@ class TBTables { // Robin Hood hashing: If we've probed for longer than this element, // insert here and search for a new spot for the other element instead. uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1); - if (otherHomeBucket > homeBucket) { + if (otherHomeBucket > homeBucket) + { std::swap(entry, hashTable[bucket]); - key = otherKey; + key = otherKey; homeBucket = otherHomeBucket; } } @@ -453,10 +472,11 @@ class TBTables { exit(EXIT_FAILURE); } -public: + public: template TBTable* get(Key key) { - for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)]; ; ++entry) { + for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry) + { if (entry->key == key || !entry->get()) return entry->get(); } @@ -468,7 +488,7 @@ public: dtzTable.clear(); } size_t size() const { return wdlTable.size(); } - void add(const std::vector& pieces); + void add(const std::vector& pieces); }; TBTables TBTables; @@ -482,9 +502,9 @@ void TBTables::add(const std::vector& pieces) { for (PieceType pt : pieces) code += PieceToChar[pt]; - TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK - if (!file.is_open()) // Only WDL file is checked + if (!file.is_open()) // Only WDL file is checked return; file.close(); @@ -495,7 +515,7 @@ void TBTables::add(const std::vector& pieces) { dtzTable.emplace_back(wdlTable.back()); // Insert into the hash keys for both colors: KRvK with KR white and black - insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back()); + insert(wdlTable.back().key, &wdlTable.back(), &dtzTable.back()); insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); } @@ -538,8 +558,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { uint32_t k = uint32_t(idx / d->span); // Then we read the corresponding SparseIndex[] entry - uint32_t block = number(&d->sparseIndex[k].block); - int offset = number(&d->sparseIndex[k].offset); + uint32_t block = number(&d->sparseIndex[k].block); + int offset = number(&d->sparseIndex[k].offset); // Now compute the difference idx - I(k). From the definition of k, we know that // @@ -560,18 +580,19 @@ int decompress_pairs(PairsData* d, uint64_t idx) { offset -= d->blockLength[block++] + 1; // Finally, we find the start address of our block of canonical Huffman symbols - uint32_t* ptr = (uint32_t*)(d->data + (uint64_t(block) * d->sizeofBlock)); + uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock)); // Read the first 64 bits in our block, this is a (truncated) sequence of // unknown number of symbols of unknown length but we know the first one // is at the beginning of this 64-bit sequence. - uint64_t buf64 = number(ptr); ptr += 2; + uint64_t buf64 = number(ptr); + ptr += 2; int buf64Size = 64; Sym sym; while (true) { - int len = 0; // This is the symbol length - d->min_sym_len + int len = 0; // This is the symbol length - d->min_sym_len // Now get the symbol length. For any symbol s64 of length l right-padded // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we @@ -594,11 +615,12 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // ...otherwise update the offset and continue to iterate offset -= d->symlen[sym] + 1; - len += d->minSymLen; // Get the real length - buf64 <<= len; // Consume the just processed symbol + len += d->minSymLen; // Get the real length + buf64 <<= len; // Consume the just processed symbol buf64Size -= len; - if (buf64Size <= 32) { // Refill the buffer + if (buf64Size <= 32) + { // Refill the buffer buf64Size += 32; buf64 |= uint64_t(number(ptr++)) << (64 - buf64Size); } @@ -618,7 +640,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // the left side because in Recursive Pairing child symbols are adjacent. if (offset < d->symlen[left] + 1) sym = left; - else { + else + { offset -= d->symlen[left] + 1; sym = d->btree[sym].get(); } @@ -632,8 +655,7 @@ bool check_dtz_stm(TBTable*, int, File) { return true; } bool check_dtz_stm(TBTable* entry, int stm, File f) { auto flags = entry->get(stm, f)->flags; - return (flags & TBFlag::STM) == stm - || ((entry->key == entry->key2) && !entry->hasPawns); + return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns); } // DTZ scores are sorted by frequency of occurrence and then assigned the @@ -644,25 +666,25 @@ WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(v int map_score(TBTable* entry, File f, int value, WDLScore wdl) { - constexpr int WDLMap[] = { 1, 3, 0, 2, 0 }; + constexpr int WDLMap[] = {1, 3, 0, 2, 0}; auto flags = entry->get(0, f)->flags; - uint8_t* map = entry->map; + uint8_t* map = entry->map; uint16_t* idx = entry->get(0, f)->map_idx; - if (flags & TBFlag::Mapped) { + if (flags & TBFlag::Mapped) + { if (flags & TBFlag::Wide) - value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value]; + value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value]; else value = map[idx[WDLMap[wdl + 2]] + value]; } // DTZ tables store distance to zero in number of moves or plies. We // want to return plies, so we have to convert to plies when needed. - if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) - || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) - || wdl == WDLCursedWin - || wdl == WDLBlessedLoss) + if ((wdl == WDLWin && !(flags & TBFlag::WinPlies)) + || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin + || wdl == WDLBlessedLoss) value *= 2; return value + 1; @@ -677,13 +699,13 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { template Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { - Square squares[TBPIECES]; - Piece pieces[TBPIECES]; - uint64_t idx; - int next = 0, size = 0, leadPawnsCnt = 0; + Square squares[TBPIECES]; + Piece pieces[TBPIECES]; + uint64_t idx; + int next = 0, size = 0, leadPawnsCnt = 0; PairsData* d; - Bitboard b, leadPawns = 0; - File tbFile = FILE_A; + Bitboard b, leadPawns = 0; + File tbFile = FILE_A; // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. // If both sides have the same pieces keys are equal. In this case TB tables @@ -704,7 +726,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // For pawns, TB files store 4 separate tables according if leading pawn is on // file a, b, c or d after reordering. The leading pawn is the one with maximum // MapPawns[] value, that is the one most toward the edges and with lowest rank. - if (entry->hasPawns) { + if (entry->hasPawns) + { // In all the 4 tables, pawns are at the beginning of the piece sequence and // their color is the reference one. So we just pick the first one. @@ -733,9 +756,10 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Now we are ready to get all the position pieces (but the lead pawns) and // directly map them to the correct color and square. b = pos.pieces() ^ leadPawns; - do { - Square s = pop_lsb(b); - squares[size] = s ^ flipSquares; + do + { + Square s = pop_lsb(b); + squares[size] = s ^ flipSquares; pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); } while (b); @@ -762,7 +786,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Encode leading pawns starting with the one with minimum MapPawns[] and // proceeding in ascending order. - if (entry->hasPawns) { + if (entry->hasPawns) + { idx = LeadPawnIdx[leadPawnsCnt][squares[0]]; std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp); @@ -770,7 +795,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu for (int i = 1; i < leadPawnsCnt; ++i) idx += Binomial[i][MapPawns[squares[i]]]; - goto encode_remaining; // With pawns we have finished special treatments + goto encode_remaining; // With pawns we have finished special treatments } // In positions without pawns, we further flip the squares to ensure leading @@ -781,11 +806,12 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Look for the first piece of the leading group not on the A1-D4 diagonal // and ensure it is mapped below the diagonal. - for (int i = 0; i < d->groupLen[0]; ++i) { + for (int i = 0; i < d->groupLen[0]; ++i) + { if (!off_A1H8(squares[i])) continue; - if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 + if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 for (int j = i; j < size; ++j) squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); break; @@ -818,41 +844,36 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // // In case we have at least 3 unique pieces (including kings) we encode them // together. - if (entry->hasUniquePieces) { + if (entry->hasUniquePieces) + { - int adjust1 = squares[1] > squares[0]; + int adjust1 = squares[1] > squares[0]; int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 // triangle to 0...5. There are 63 squares for second piece and and 62 // (mapped to 0...61) for the third. if (off_A1H8(squares[0])) - idx = ( MapA1D1D4[squares[0]] * 63 - + (squares[1] - adjust1)) * 62 - + squares[2] - adjust2; + idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2; // First piece is on a1-h8 diagonal, second below: map this occurrence to // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27. else if (off_A1H8(squares[1])) - idx = ( 6 * 63 + rank_of(squares[0]) * 28 - + MapB1H1H7[squares[1]]) * 62 - + squares[2] - adjust2; + idx = (6 * 63 + rank_of(squares[0]) * 28 + MapB1H1H7[squares[1]]) * 62 + squares[2] + - adjust2; // First two pieces are on a1-h8 diagonal, third below else if (off_A1H8(squares[2])) - idx = 6 * 63 * 62 + 4 * 28 * 62 - + rank_of(squares[0]) * 7 * 28 - + (rank_of(squares[1]) - adjust1) * 28 - + MapB1H1H7[squares[2]]; + idx = 6 * 63 * 62 + 4 * 28 * 62 + rank_of(squares[0]) * 7 * 28 + + (rank_of(squares[1]) - adjust1) * 28 + MapB1H1H7[squares[2]]; // All 3 pieces on the diagonal a1-h8 else - idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 - + rank_of(squares[0]) * 7 * 6 - + (rank_of(squares[1]) - adjust1) * 6 - + (rank_of(squares[2]) - adjust2); - } else + idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + rank_of(squares[0]) * 7 * 6 + + (rank_of(squares[1]) - adjust1) * 6 + (rank_of(squares[2]) - adjust2); + } + else // We don't have at least 3 unique pieces, like in KRRvKBB, just map // the kings. idx = MapKK[MapA1D1D4[squares[0]]][squares[1]]; @@ -873,7 +894,7 @@ encode_remaining: // groups (similar to what was done earlier for leading group pieces). for (int i = 0; i < d->groupLen[next]; ++i) { - auto f = [&](Square s) { return groupSq[i] > s; }; + auto f = [&](Square s) { return groupSq[i] > s; }; auto adjust = std::count_if(squares, groupSq, f); n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns]; } @@ -911,7 +932,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) { else d->groupLen[++n] = 1; - d->groupLen[++n] = 0; // Zero-terminated + d->groupLen[++n] = 0; // Zero-terminated // The sequence in pieces[] defines the groups, but not the order in which // they are encoded. If the pieces in a group g can be combined on the board @@ -924,24 +945,23 @@ void set_groups(T& e, PairsData* d, int order[], File f) { // pawns/pieces -> remaining pawns -> remaining pieces. In particular the // first group is at order[0] position and the remaining pawns, when present, // are at order[1] position. - bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides - int next = pp ? 2 : 1; - int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); - uint64_t idx = 1; + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + int next = pp ? 2 : 1; + int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); + uint64_t idx = 1; for (int k = 0; next < n || k == order[0] || k == order[1]; ++k) - if (k == order[0]) // Leading pawns or pieces + if (k == order[0]) // Leading pawns or pieces { d->groupIdx[0] = idx; - idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] - : e.hasUniquePieces ? 31332 : 462; + idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462; } - else if (k == order[1]) // Remaining pawns + else if (k == order[1]) // Remaining pawns { d->groupIdx[1] = idx; idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]]; } - else // Remaining pieces + else // Remaining pieces { d->groupIdx[next] = idx; idx *= Binomial[d->groupLen[next]][freeSquares]; @@ -956,8 +976,8 @@ void set_groups(T& e, PairsData* d, int order[], File f) { // symbol until reaching the leaves that represent the symbol value. uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { - visited[s] = true; // We can set it now because tree is acyclic - Sym sr = d->btree[s].get(); + visited[s] = true; // We can set it now because tree is acyclic + Sym sr = d->btree[s].get(); if (sr == 0xFFF) return 0; @@ -977,10 +997,11 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { d->flags = *data++; - if (d->flags & TBFlag::SingleValue) { + if (d->flags & TBFlag::SingleValue) + { d->blocksNum = d->blockLengthSize = 0; - d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init - d->minSymLen = *data++; // Here we store the single value + d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init + d->minSymLen = *data++; // Here we store the single value return data; } @@ -988,16 +1009,17 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // element stores the biggest index that is the tb size. uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; - d->sizeofBlock = 1ULL << *data++; - d->span = 1ULL << *data++; - d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up - auto padding = number(data++); - d->blocksNum = number(data); data += sizeof(uint32_t); - d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] - // does not point out of range. + d->sizeofBlock = 1ULL << *data++; + d->span = 1ULL << *data++; + d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up + auto padding = number(data++); + d->blocksNum = number(data); + data += sizeof(uint32_t); + d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] + // does not point out of range. d->maxSymLen = *data++; d->minSymLen = *data++; - d->lowestSym = (Sym*)data; + d->lowestSym = (Sym*) data; d->base64.resize(d->maxSymLen - d->minSymLen + 1); // See https://en.wikipedia.org/wiki/Huffman_coding @@ -1012,11 +1034,13 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // avoiding unsigned overflow warnings. int base64_size = static_cast(d->base64.size()); - for (int i = base64_size - 2; i >= 0; --i) { + for (int i = base64_size - 2; i >= 0; --i) + { d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) - - number(&d->lowestSym[i + 1])) / 2; + - number(&d->lowestSym[i + 1])) + / 2; - assert(d->base64[i] * 2 >= d->base64[i+1]); + assert(d->base64[i] * 2 >= d->base64[i + 1]); } // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more @@ -1024,11 +1048,12 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. for (int i = 0; i < base64_size; ++i) - d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits + d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits data += base64_size * sizeof(Sym); - d->symlen.resize(number(data)); data += sizeof(uint16_t); - d->btree = (LR*)data; + d->symlen.resize(number(data)); + data += sizeof(uint16_t); + d->btree = (LR*) data; // The compression scheme used is "Recursive Pairing", that replaces the most // frequent adjacent pair of symbols in the source message by a new symbol, @@ -1050,18 +1075,24 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { e.map = data; - for (File f = FILE_A; f <= maxFile; ++f) { + for (File f = FILE_A; f <= maxFile; ++f) + { auto flags = e.get(0, f)->flags; - if (flags & TBFlag::Mapped) { - if (flags & TBFlag::Wide) { + if (flags & TBFlag::Mapped) + { + if (flags & TBFlag::Wide) + { data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table - for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x - e.get(0, f)->map_idx[i] = uint16_t((uint16_t*)data - (uint16_t*)e.map + 1); + for (int i = 0; i < 4; ++i) + { // Sequence like 3,x,x,x,1,x,0,2,x,x + e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1); data += 2 * number(data) + 2; } } - else { - for (int i = 0; i < 4; ++i) { + else + { + for (int i = 0; i < 4; ++i) + { e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1); data += *data + 1; } @@ -1069,7 +1100,7 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { } } - return data += uintptr_t(data) & 1; // Word alignment + return data += uintptr_t(data) & 1; // Word alignment } // Populate entry's PairsData records with data from the just memory-mapped file. @@ -1079,38 +1110,42 @@ void set(T& e, uint8_t* data) { PairsData* d; - enum { Split = 1, HasPawns = 2 }; + enum { + Split = 1, + HasPawns = 2 + }; - assert(e.hasPawns == bool(*data & HasPawns)); + assert(e.hasPawns == bool(*data & HasPawns)); assert((e.key != e.key2) == bool(*data & Split)); - data++; // First byte stores flags + data++; // First byte stores flags - const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; + const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; const File maxFile = e.hasPawns ? FILE_D : FILE_A; - bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides assert(!pp || e.pawnCount[0]); - for (File f = FILE_A; f <= maxFile; ++f) { + for (File f = FILE_A; f <= maxFile; ++f) + { for (int i = 0; i < sides; i++) *e.get(i, f) = PairsData(); - int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF }, - { *data >> 4, pp ? *(data + 1) >> 4 : 0xF } }; + int order[][2] = {{*data & 0xF, pp ? *(data + 1) & 0xF : 0xF}, + {*data >> 4, pp ? *(data + 1) >> 4 : 0xF}}; data += 1 + pp; for (int k = 0; k < e.pieceCount; ++k, ++data) for (int i = 0; i < sides; i++) - e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); + e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); for (int i = 0; i < sides; ++i) set_groups(e, e.get(i, f), order[i], f); } - data += uintptr_t(data) & 1; // Word alignment + data += uintptr_t(data) & 1; // Word alignment for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) @@ -1119,20 +1154,23 @@ void set(T& e, uint8_t* data) { data = set_dtz_map(e, data, maxFile); for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - (d = e.get(i, f))->sparseIndex = (SparseEntry*)data; + for (int i = 0; i < sides; i++) + { + (d = e.get(i, f))->sparseIndex = (SparseEntry*) data; data += d->sparseIndexSize * sizeof(SparseEntry); } for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - (d = e.get(i, f))->blockLength = (uint16_t*)data; + for (int i = 0; i < sides; i++) + { + (d = e.get(i, f))->blockLength = (uint16_t*) data; data += d->blockLengthSize * sizeof(uint16_t); } for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - data = (uint8_t*)((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment + for (int i = 0; i < sides; i++) + { + data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment (d = e.get(i, f))->data = data; data += d->blocksNum * d->sizeofBlock; } @@ -1150,22 +1188,23 @@ void* mapped(TBTable& e, const Position& pos) { // Use 'acquire' to avoid a thread reading 'ready' == true while // another is still working. (compiler reordering may cause this). if (e.ready.load(std::memory_order_acquire)) - return e.baseAddress; // Could be nullptr if file does not exist + return e.baseAddress; // Could be nullptr if file does not exist std::scoped_lock lk(mutex); - if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock + if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock return e.baseAddress; // Pieces strings in decreasing order for each color, like ("KPP","KR") std::string fname, w, b; - for (PieceType pt = KING; pt >= PAWN; --pt) { + for (PieceType pt = KING; pt >= PAWN; --pt) + { w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); } - fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) - + (Type == WDL ? ".rtbw" : ".rtbz"); + fname = + (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz"); uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type); @@ -1179,7 +1218,7 @@ void* mapped(TBTable& e, const Position& pos) { template::Ret> Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { - if (pos.count() == 2) // KvK + if (pos.count() == 2) // KvK return Ret(WDLDraw); TBTable* entry = TBTables.get(pos.material_key()); @@ -1206,16 +1245,15 @@ Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) template WDLScore search(Position& pos, ProbeState* result) { - WDLScore value, bestValue = WDLLoss; + WDLScore value, bestValue = WDLLoss; StateInfo st; - auto moveList = MoveList(pos); + auto moveList = MoveList(pos); size_t totalCount = moveList.size(), moveCount = 0; for (const Move move : moveList) { - if ( !pos.capture(move) - && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) + if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) continue; moveCount++; @@ -1233,7 +1271,7 @@ WDLScore search(Position& pos, ProbeState* result) { if (value >= WDLWin) { - *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move + *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move return value; } } @@ -1259,13 +1297,12 @@ WDLScore search(Position& pos, ProbeState* result) { // DTZ stores a "don't care" value if bestValue is a win if (bestValue >= value) - return *result = ( bestValue > WDLDraw - || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; + return *result = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; return *result = OK, value; } -} // namespace +} // namespace // Tablebases::init() is called at startup and after every change to @@ -1275,7 +1312,7 @@ void Tablebases::init(const std::string& paths) { TBTables.clear(); MaxCardinality = 0; - TBFile::Paths = paths; + TBFile::Paths = paths; if (paths.empty() || paths == "") return; @@ -1307,14 +1344,14 @@ void Tablebases::init(const std::string& paths) { code = 0; for (int idx = 0; idx < 10; idx++) for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1) - if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 + if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 { for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) if ((PseudoAttacks[KING][s1] | s1) & s2) - continue; // Illegal position + continue; // Illegal position else if (!off_A1H8(s1) && off_A1H8(s2) > 0) - continue; // First on diagonal, second above + continue; // First on diagonal, second above else if (!off_A1H8(s1) && !off_A1H8(s2)) bothOnDiagonal.emplace_back(idx, s2); @@ -1331,16 +1368,16 @@ void Tablebases::init(const std::string& paths) { // are Binomial[k][n] ways to choose k elements from a set of n elements. Binomial[0][0] = 1; - for (int n = 1; n < 64; n++) // Squares - for (int k = 0; k < 6 && k <= n; ++k) // Pieces - Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0) - + (k < n ? Binomial[k ][n - 1] : 0); + for (int n = 1; n < 64; n++) // Squares + for (int k = 0; k < 6 && k <= n; ++k) // Pieces + Binomial[k][n] = + (k > 0 ? Binomial[k - 1][n - 1] : 0) + (k < n ? Binomial[k][n - 1] : 0); // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible // available squares when the leading one is in 's'. Moreover the pawn with // highest MapPawns[] is the leading pawn, the one nearest the edge, and // among pawns with the same file, the one with the lowest rank. - int availableSquares = 47; // Available squares when lead pawn is in a2 + int availableSquares = 47; // Available squares when lead pawn is in a2 // Init the tables for the encoding of leading pawns group: with 7-men TB we // can have up to 5 leading pawns (KPPPPPK). @@ -1364,7 +1401,7 @@ void Tablebases::init(const std::string& paths) { // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45 if (leadPawnsCnt == 1) { - MapPawns[sq] = availableSquares--; + MapPawns[sq] = availableSquares--; MapPawns[flip_file(sq)] = availableSquares--; } LeadPawnIdx[leadPawnsCnt][sq] = idx; @@ -1375,20 +1412,24 @@ void Tablebases::init(const std::string& paths) { } // Add entries in TB tables if the corresponding ".rtbw" file exists - for (PieceType p1 = PAWN; p1 < KING; ++p1) { + for (PieceType p1 = PAWN; p1 < KING; ++p1) + { TBTables.add({KING, p1, KING}); - for (PieceType p2 = PAWN; p2 <= p1; ++p2) { + for (PieceType p2 = PAWN; p2 <= p1; ++p2) + { TBTables.add({KING, p1, p2, KING}); TBTables.add({KING, p1, KING, p2}); for (PieceType p3 = PAWN; p3 < KING; ++p3) TBTables.add({KING, p1, p2, KING, p3}); - for (PieceType p3 = PAWN; p3 <= p2; ++p3) { + for (PieceType p3 = PAWN; p3 <= p2; ++p3) + { TBTables.add({KING, p1, p2, p3, KING}); - for (PieceType p4 = PAWN; p4 <= p3; ++p4) { + for (PieceType p4 = PAWN; p4 <= p3; ++p4) + { TBTables.add({KING, p1, p2, p3, p4, KING}); for (PieceType p5 = PAWN; p5 <= p4; ++p5) @@ -1398,7 +1439,8 @@ void Tablebases::init(const std::string& paths) { TBTables.add({KING, p1, p2, p3, p4, KING, p5}); } - for (PieceType p4 = PAWN; p4 < KING; ++p4) { + for (PieceType p4 = PAWN; p4 < KING; ++p4) + { TBTables.add({KING, p1, p2, p3, KING, p4}); for (PieceType p5 = PAWN; p5 <= p4; ++p5) @@ -1457,10 +1499,10 @@ WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { // then do not accept moves leading to dtz + 50-move-counter == 100. int Tablebases::probe_dtz(Position& pos, ProbeState* result) { - *result = OK; + *result = OK; WDLScore wdl = search(pos, result); - if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws + if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws return 0; // DTZ stores a 'don't care value in this case, or even a plain wrong @@ -1479,7 +1521,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // DTZ stores results for the other side, so we need to do a 1-ply search and // find the winning move that minimizes DTZ. StateInfo st; - int minDTZ = 0xFFFF; + int minDTZ = 0xFFFF; for (const Move move : MoveList(pos)) { @@ -1491,8 +1533,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // otherwise we will get the dtz of the next move sequence. Search the // position after the move to get the score sign (because even in a // winning position we could make a losing capture or go for a draw). - dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) - : -probe_dtz(pos, result); + dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) : -probe_dtz(pos, result); // If the move mates, force minDTZ to 1 if (dtz == 1 && pos.checkers() && MoveList(pos).size() == 0) @@ -1524,7 +1565,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { ProbeState result = OK; - StateInfo st; + StateInfo st; // Obtain 50-move counter for the root position int cnt50 = pos.rule50_count(); @@ -1544,7 +1585,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { { // In case of a zeroing move, dtz is one of -101/-1/0/1/101 WDLScore wdl = -probe_wdl(pos, &result); - dtz = dtz_before_zeroing(wdl); + dtz = dtz_before_zeroing(wdl); } else if (pos.is_draw(1)) { @@ -1557,14 +1598,11 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { { // Otherwise, take dtz for the new position and correct by 1 ply dtz = -probe_dtz(pos, &result); - dtz = dtz > 0 ? dtz + 1 - : dtz < 0 ? dtz - 1 : dtz; + dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz; } // Make sure that a mating move is assigned a dtz value of 1 - if ( pos.checkers() - && dtz == 2 - && MoveList(pos).size() == 0) + if (pos.checkers() && dtz == 2 && MoveList(pos).size() == 0) dtz = 1; pos.undo_move(m.pv[0]); @@ -1574,19 +1612,19 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // Better moves are ranked higher. Certain wins are ranked equally. // Losing moves are ranked equally unless a 50-move draw is in sight. - int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50)) - : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50)) - : 0; + int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50)) + : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50)) + : 0; m.tbRank = r; // Determine the score to be displayed for this move. Assign at least // 1 cp to cursed wins and let it grow to 49 cp as the positions gets // closer to a real win. - m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 - : r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200) - : r == 0 ? VALUE_DRAW - : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200) - : -VALUE_MATE + MAX_PLY + 1; + m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 + : r > 0 ? Value((std::max(3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200) + : r == 0 ? VALUE_DRAW + : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200) + : -VALUE_MATE + MAX_PLY + 1; } return true; @@ -1599,11 +1637,11 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // A return value false indicates that not all probes were successful. bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { - static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ }; + static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ}; ProbeState result = OK; - StateInfo st; - WDLScore wdl; + StateInfo st; + WDLScore wdl; bool rule50 = Options["Syzygy50MoveRule"]; @@ -1625,12 +1663,11 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { m.tbRank = WDL_to_rank[wdl + 2]; if (!rule50) - wdl = wdl > WDLDraw ? WDLWin - : wdl < WDLDraw ? WDLLoss : WDLDraw; + wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw; m.tbScore = WDL_to_value[wdl + 2]; } return true; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index b2ba35ff..3b7c8aa7 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -30,30 +30,30 @@ class Position; namespace Stockfish::Tablebases { enum WDLScore { - WDLLoss = -2, // Loss - WDLBlessedLoss = -1, // Loss, but draw under 50-move rule - WDLDraw = 0, // Draw - WDLCursedWin = 1, // Win, but draw under 50-move rule - WDLWin = 2, // Win + WDLLoss = -2, // Loss + WDLBlessedLoss = -1, // Loss, but draw under 50-move rule + WDLDraw = 0, // Draw + WDLCursedWin = 1, // Win, but draw under 50-move rule + WDLWin = 2, // Win }; // Possible states after a probing operation enum ProbeState { - FAIL = 0, // Probe failed (missing file table) - OK = 1, // Probe successful - CHANGE_STM = -1, // DTZ should check the other side - ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) + FAIL = 0, // Probe failed (missing file table) + OK = 1, // Probe successful + CHANGE_STM = -1, // DTZ should check the other side + ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) }; extern int MaxCardinality; -void init(const std::string& paths); +void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); -int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves); -bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); -void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); +int probe_dtz(Position& pos, ProbeState* result); +bool root_probe(Position& pos, Search::RootMoves& rootMoves); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); +void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); -} // namespace Stockfish::Tablebases +} // namespace Stockfish::Tablebases #endif diff --git a/src/thread.cpp b/src/thread.cpp index c752e732..9f8a63bd 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -37,15 +37,17 @@ namespace Stockfish { -ThreadPool Threads; // Global object +ThreadPool Threads; // Global object // Thread constructor launches the thread and waits until it goes to sleep // in idle_loop(). Note that 'searching' and 'exit' should be already set. -Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { +Thread::Thread(size_t n) : + idx(n), + stdThread(&Thread::idle_loop, this) { - wait_for_search_finished(); + wait_for_search_finished(); } @@ -54,11 +56,11 @@ Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { Thread::~Thread() { - assert(!searching); + assert(!searching); - exit = true; - start_searching(); - stdThread.join(); + exit = true; + start_searching(); + stdThread.join(); } @@ -66,25 +68,25 @@ Thread::~Thread() { void Thread::clear() { - counterMoves.fill(MOVE_NONE); - mainHistory.fill(0); - captureHistory.fill(0); + counterMoves.fill(MOVE_NONE); + mainHistory.fill(0); + captureHistory.fill(0); - for (bool inCheck : { false, true }) - for (StatsType c : { NoCaptures, Captures }) - for (auto& to : continuationHistory[inCheck][c]) - for (auto& h : to) - h->fill(-71); + for (bool inCheck : {false, true}) + for (StatsType c : {NoCaptures, Captures}) + for (auto& to : continuationHistory[inCheck][c]) + for (auto& h : to) + h->fill(-71); } // Thread::start_searching() wakes up the thread that will start the search void Thread::start_searching() { - mutex.lock(); - searching = true; - mutex.unlock(); // Unlock before notifying saves a few CPU-cycles - cv.notify_one(); // Wake up the thread in idle_loop() + mutex.lock(); + searching = true; + mutex.unlock(); // Unlock before notifying saves a few CPU-cycles + cv.notify_one(); // Wake up the thread in idle_loop() } @@ -93,8 +95,8 @@ void Thread::start_searching() { void Thread::wait_for_search_finished() { - std::unique_lock lk(mutex); - cv.wait(lk, [&]{ return !searching; }); + std::unique_lock lk(mutex); + cv.wait(lk, [&] { return !searching; }); } @@ -103,28 +105,28 @@ void Thread::wait_for_search_finished() { void Thread::idle_loop() { - // If OS already scheduled us on a different group than 0 then don't overwrite - // the choice, eventually we are one of many one-threaded processes running on - // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case, all this - // NUMA machinery is not needed. - if (Options["Threads"] > 8) - WinProcGroup::bindThisThread(idx); + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case, all this + // NUMA machinery is not needed. + if (Options["Threads"] > 8) + WinProcGroup::bindThisThread(idx); - while (true) - { - std::unique_lock lk(mutex); - searching = false; - cv.notify_one(); // Wake up anyone waiting for search finished - cv.wait(lk, [&]{ return searching; }); + while (true) + { + std::unique_lock lk(mutex); + searching = false; + cv.notify_one(); // Wake up anyone waiting for search finished + cv.wait(lk, [&] { return searching; }); - if (exit) - return; + if (exit) + return; - lk.unlock(); + lk.unlock(); - search(); - } + search(); + } } // ThreadPool::set() creates/destroys threads to match the requested number. @@ -133,28 +135,28 @@ void Thread::idle_loop() { void ThreadPool::set(size_t requested) { - if (threads.size() > 0) // destroy any existing thread(s) - { - main()->wait_for_search_finished(); + if (threads.size() > 0) // destroy any existing thread(s) + { + main()->wait_for_search_finished(); - while (threads.size() > 0) - delete threads.back(), threads.pop_back(); - } + while (threads.size() > 0) + delete threads.back(), threads.pop_back(); + } - if (requested > 0) // create new thread(s) - { - threads.push_back(new MainThread(0)); + if (requested > 0) // create new thread(s) + { + threads.push_back(new MainThread(0)); - while (threads.size() < requested) - threads.push_back(new Thread(threads.size())); - clear(); + while (threads.size() < requested) + threads.push_back(new Thread(threads.size())); + clear(); - // Reallocate the hash with the new threadpool size - TT.resize(size_t(Options["Hash"])); + // Reallocate the hash with the new threadpool size + TT.resize(size_t(Options["Hash"])); - // Init thread number dependent search params. - Search::init(); - } + // Init thread number dependent search params. + Search::init(); + } } @@ -162,77 +164,79 @@ void ThreadPool::set(size_t requested) { void ThreadPool::clear() { - for (Thread* th : threads) - th->clear(); + for (Thread* th : threads) + th->clear(); - main()->callsCnt = 0; - main()->bestPreviousScore = VALUE_INFINITE; - main()->bestPreviousAverageScore = VALUE_INFINITE; - main()->previousTimeReduction = 1.0; + main()->callsCnt = 0; + main()->bestPreviousScore = VALUE_INFINITE; + main()->bestPreviousAverageScore = VALUE_INFINITE; + main()->previousTimeReduction = 1.0; } // ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and // returns immediately. Main thread will wake up other threads and start the search. -void ThreadPool::start_thinking(Position& pos, StateListPtr& states, - const Search::LimitsType& limits, bool ponderMode) { +void ThreadPool::start_thinking(Position& pos, + StateListPtr& states, + const Search::LimitsType& limits, + bool ponderMode) { - main()->wait_for_search_finished(); + main()->wait_for_search_finished(); - main()->stopOnPonderhit = stop = false; - increaseDepth = true; - main()->ponder = ponderMode; - Search::Limits = limits; - Search::RootMoves rootMoves; + main()->stopOnPonderhit = stop = false; + increaseDepth = true; + main()->ponder = ponderMode; + Search::Limits = limits; + Search::RootMoves rootMoves; - for (const auto& m : MoveList(pos)) - if ( limits.searchmoves.empty() - || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) - rootMoves.emplace_back(m); + for (const auto& m : MoveList(pos)) + if (limits.searchmoves.empty() + || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + rootMoves.emplace_back(m); - if (!rootMoves.empty()) - Tablebases::rank_root_moves(pos, rootMoves); + if (!rootMoves.empty()) + Tablebases::rank_root_moves(pos, rootMoves); - // After ownership transfer 'states' becomes empty, so if we stop the search - // and call 'go' again without setting a new position states.get() == nullptr. - assert(states.get() || setupStates.get()); + // After ownership transfer 'states' becomes empty, so if we stop the search + // and call 'go' again without setting a new position states.get() == nullptr. + assert(states.get() || setupStates.get()); - if (states.get()) - setupStates = std::move(states); // Ownership transfer, states is now empty + if (states.get()) + setupStates = std::move(states); // Ownership transfer, states is now empty - // We use Position::set() to set root position across threads. But there are - // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot - // be deduced from a fen string, so set() clears them and they are set from - // setupStates->back() later. The rootState is per thread, earlier states are shared - // since they are read-only. - for (Thread* th : threads) - { - th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; - th->rootDepth = th->completedDepth = 0; - th->rootMoves = rootMoves; - th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); - th->rootState = setupStates->back(); - th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); - } + // We use Position::set() to set root position across threads. But there are + // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot + // be deduced from a fen string, so set() clears them and they are set from + // setupStates->back() later. The rootState is per thread, earlier states are shared + // since they are read-only. + for (Thread* th : threads) + { + th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; + th->rootDepth = th->completedDepth = 0; + th->rootMoves = rootMoves; + th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); + th->rootState = setupStates->back(); + th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); + } - main()->start_searching(); + main()->start_searching(); } Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = threads.front(); + Thread* bestThread = threads.front(); std::map votes; - Value minScore = VALUE_NONE; + Value minScore = VALUE_NONE; // Find the minimum score of all threads - for (Thread* th: threads) + for (Thread* th : threads) minScore = std::min(minScore, th->rootMoves[0].score); // Vote according to score and depth, and select the best thread auto thread_value = [minScore](Thread* th) { - return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); - }; + return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); + }; for (Thread* th : threads) votes[th->rootMoves[0].pv[0]] += thread_value(th); @@ -244,12 +248,13 @@ Thread* ThreadPool::get_best_thread() const { if (th->rootMoves[0].score > bestThread->rootMoves[0].score) bestThread = th; } - else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY - || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] - || ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] - && thread_value(th) * int(th->rootMoves[0].pv.size() > 2) - > thread_value(bestThread) * int(bestThread->rootMoves[0].pv.size() > 2))))) + else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY + && (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] + || (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] + && thread_value(th) * int(th->rootMoves[0].pv.size() > 2) + > thread_value(bestThread) + * int(bestThread->rootMoves[0].pv.size() > 2))))) bestThread = th; return bestThread; @@ -275,4 +280,4 @@ void ThreadPool::wait_for_search_finished() const { th->wait_for_search_finished(); } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/thread.h b/src/thread.h index 44cc5672..4a077962 100644 --- a/src/thread.h +++ b/src/thread.h @@ -41,56 +41,56 @@ namespace Stockfish { class Thread { - std::mutex mutex; - std::condition_variable cv; - size_t idx; - bool exit = false, searching = true; // Set before starting std::thread - NativeThread stdThread; + std::mutex mutex; + std::condition_variable cv; + size_t idx; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; -public: - explicit Thread(size_t); - virtual ~Thread(); - virtual void search(); - void clear(); - void idle_loop(); - void start_searching(); - void wait_for_search_finished(); - size_t id() const { return idx; } + public: + explicit Thread(size_t); + virtual ~Thread(); + virtual void search(); + void clear(); + void idle_loop(); + void start_searching(); + void wait_for_search_finished(); + size_t id() const { return idx; } - size_t pvIdx, pvLast; - std::atomic nodes, tbHits, bestMoveChanges; - int selDepth, nmpMinPly; - Value bestValue, optimism[COLOR_NB]; + size_t pvIdx, pvLast; + std::atomic nodes, tbHits, bestMoveChanges; + int selDepth, nmpMinPly; + Value bestValue, optimism[COLOR_NB]; - Position rootPos; - StateInfo rootState; - Search::RootMoves rootMoves; - Depth rootDepth, completedDepth; - Value rootDelta; - Value rootSimpleEval; - CounterMoveHistory counterMoves; - ButterflyHistory mainHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; + Position rootPos; + StateInfo rootState; + Search::RootMoves rootMoves; + Depth rootDepth, completedDepth; + Value rootDelta; + Value rootSimpleEval; + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; }; // MainThread is a derived class specific for main thread -struct MainThread : public Thread { +struct MainThread: public Thread { - using Thread::Thread; + using Thread::Thread; - void search() override; - void check_time(); + void search() override; + void check_time(); - double previousTimeReduction; - Value bestPreviousScore; - Value bestPreviousAverageScore; - Value iterValue[4]; - int callsCnt; - bool stopOnPonderhit; - std::atomic_bool ponder; + double previousTimeReduction; + Value bestPreviousScore; + Value bestPreviousAverageScore; + Value iterValue[4]; + int callsCnt; + bool stopOnPonderhit; + std::atomic_bool ponder; }; @@ -100,41 +100,41 @@ struct MainThread : public Thread { struct ThreadPool { - void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); - void clear(); - void set(size_t); + void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); + void set(size_t); - MainThread* main() const { return static_cast(threads.front()); } - uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } - uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; + MainThread* main() const { return static_cast(threads.front()); } + uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } + uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; - std::atomic_bool stop, increaseDepth; + std::atomic_bool stop, increaseDepth; - auto cbegin() const noexcept { return threads.cbegin(); } - auto begin() noexcept { return threads.begin(); } - auto end() noexcept { return threads.end(); } - auto cend() const noexcept { return threads.cend(); } - auto size() const noexcept { return threads.size(); } - auto empty() const noexcept { return threads.empty(); } + auto cbegin() const noexcept { return threads.cbegin(); } + auto begin() noexcept { return threads.begin(); } + auto end() noexcept { return threads.end(); } + auto cend() const noexcept { return threads.cend(); } + auto size() const noexcept { return threads.size(); } + auto empty() const noexcept { return threads.empty(); } -private: - StateListPtr setupStates; - std::vector threads; + private: + StateListPtr setupStates; + std::vector threads; - uint64_t accumulate(std::atomic Thread::* member) const { + uint64_t accumulate(std::atomic Thread::*member) const { - uint64_t sum = 0; - for (Thread* th : threads) - sum += (th->*member).load(std::memory_order_relaxed); - return sum; - } + uint64_t sum = 0; + for (Thread* th : threads) + sum += (th->*member).load(std::memory_order_relaxed); + return sum; + } }; extern ThreadPool Threads; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef THREAD_H_INCLUDED +#endif // #ifndef THREAD_H_INCLUDED diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 77352aa0..248e4a67 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -29,46 +29,45 @@ #if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) -#include + #include namespace Stockfish { static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; -template > -void* start_routine(void* ptr) -{ - P* p = reinterpret_cast(ptr); - (p->first->*(p->second))(); // Call member function pointer - delete p; - return nullptr; +template> +void* start_routine(void* ptr) { + P* p = reinterpret_cast(ptr); + (p->first->*(p->second))(); // Call member function pointer + delete p; + return nullptr; } class NativeThread { - pthread_t thread; + pthread_t thread; -public: - template> - explicit NativeThread(void(T::*fun)(), T* obj) { - pthread_attr_t attr_storage, *attr = &attr_storage; - pthread_attr_init(attr); - pthread_attr_setstacksize(attr, TH_STACK_SIZE); - pthread_create(&thread, attr, start_routine, new P(obj, fun)); - } - void join() { pthread_join(thread, nullptr); } + public: + template> + explicit NativeThread(void (T::*fun)(), T* obj) { + pthread_attr_t attr_storage, *attr = &attr_storage; + pthread_attr_init(attr); + pthread_attr_setstacksize(attr, TH_STACK_SIZE); + pthread_create(&thread, attr, start_routine, new P(obj, fun)); + } + void join() { pthread_join(thread, nullptr); } }; -} // namespace Stockfish +} // namespace Stockfish -#else // Default case: use STL classes +#else // Default case: use STL classes namespace Stockfish { using NativeThread = std::thread; -} // namespace Stockfish +} // namespace Stockfish #endif -#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED +#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED diff --git a/src/timeman.cpp b/src/timeman.cpp index 74f59d90..cf0e08ed 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -26,7 +26,7 @@ namespace Stockfish { -TimeManagement Time; // Our global time management object +TimeManagement Time; // Our global time management object // TimeManagement::init() is called at the beginning of the search and calculates @@ -36,74 +36,74 @@ TimeManagement Time; // Our global time management object void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - // If we have no time, no need to initialize TM, except for the start time, - // which is used by movetime. - startTime = limits.startTime; - if (limits.time[us] == 0) - return; + // If we have no time, no need to initialize TM, except for the start time, + // which is used by movetime. + startTime = limits.startTime; + if (limits.time[us] == 0) + return; - TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); - TimePoint slowMover = TimePoint(Options["Slow Mover"]); - TimePoint npmsec = TimePoint(Options["nodestime"]); + TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); + TimePoint slowMover = TimePoint(Options["Slow Mover"]); + TimePoint npmsec = TimePoint(Options["nodestime"]); - // optScale is a percentage of available time to use for the current move. - // maxScale is a multiplier applied to optimumTime. - double optScale, maxScale; + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; - // If we have to play in 'nodes as time' mode, then convert from time - // to nodes, and use resulting values in time management formulas. - // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) - // must be much lower than the real engine speed. - if (npmsec) - { - if (!availableNodes) // Only once at game start - availableNodes = npmsec * limits.time[us]; // Time is in msec + // If we have to play in 'nodes as time' mode, then convert from time + // to nodes, and use resulting values in time management formulas. + // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) + // must be much lower than the real engine speed. + if (npmsec) + { + if (!availableNodes) // Only once at game start + availableNodes = npmsec * limits.time[us]; // Time is in msec - // Convert from milliseconds to nodes - limits.time[us] = TimePoint(availableNodes); - limits.inc[us] *= npmsec; - limits.npmsec = npmsec; - } + // Convert from milliseconds to nodes + limits.time[us] = TimePoint(availableNodes); + limits.inc[us] *= npmsec; + limits.npmsec = npmsec; + } - // Maximum move horizon of 50 moves - int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + // Maximum move horizon of 50 moves + int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; - // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = std::max(TimePoint(1), - limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) + - moveOverhead * (2 + mtg)); - // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12); + // Use extra time with larger increments + double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12); - // A user may scale time usage by setting UCI option "Slow Mover" - // Default is 100 and changing this value will probably lose elo. - timeLeft = slowMover * timeLeft / 100; + // A user may scale time usage by setting UCI option "Slow Mover" + // Default is 100 and changing this value will probably lose elo. + timeLeft = slowMover * timeLeft / 100; - // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed actual available - // game time for the current move, so also cap to 20% of available game time. - if (limits.movestogo == 0) - { - optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039, - 0.2 * limits.time[us] / double(timeLeft)) + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed actual available + // game time for the current move, so also cap to 20% of available game time. + if (limits.movestogo == 0) + { + optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039, + 0.2 * limits.time[us] / double(timeLeft)) * optExtra; - maxScale = std::min(7.0, 4.0 + ply / 12.0); - } + maxScale = std::min(7.0, 4.0 + ply / 12.0); + } - // x moves in y seconds (+ z increment) - else - { - optScale = std::min((0.88 + ply / 116.4) / mtg, - 0.88 * limits.time[us] / double(timeLeft)); - maxScale = std::min(6.3, 1.5 + 0.11 * mtg); - } + // x moves in y seconds (+ z increment) + else + { + optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft)); + maxScale = std::min(6.3, 1.5 + 0.11 * mtg); + } - // Never use more than 80% of the available time for this move - optimumTime = TimePoint(optScale * timeLeft); - maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + // Never use more than 80% of the available time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = + TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; - if (Options["Ponder"]) - optimumTime += optimumTime / 4; + if (Options["Ponder"]) + optimumTime += optimumTime / 4; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/timeman.h b/src/timeman.h index 6acdf0ac..4b9b62bd 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -32,23 +32,24 @@ namespace Stockfish { // the maximum available time, the game move number, and other parameters. class TimeManagement { -public: - void init(Search::LimitsType& limits, Color us, int ply); - TimePoint optimum() const { return optimumTime; } - TimePoint maximum() const { return maximumTime; } - TimePoint elapsed() const { return Search::Limits.npmsec ? - TimePoint(Threads.nodes_searched()) : now() - startTime; } + public: + void init(Search::LimitsType& limits, Color us, int ply); + TimePoint optimum() const { return optimumTime; } + TimePoint maximum() const { return maximumTime; } + TimePoint elapsed() const { + return Search::Limits.npmsec ? TimePoint(Threads.nodes_searched()) : now() - startTime; + } - int64_t availableNodes; // When in 'nodes as time' mode + int64_t availableNodes; // When in 'nodes as time' mode -private: - TimePoint startTime; - TimePoint optimumTime; - TimePoint maximumTime; + private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; }; extern TimeManagement Time; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TIMEMAN_H_INCLUDED +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/src/tt.cpp b/src/tt.cpp index c3aec8d3..a3ad0a78 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -31,31 +31,29 @@ namespace Stockfish { -TranspositionTable TT; // Our global transposition table +TranspositionTable TT; // Our global transposition table // TTEntry::save() populates the TTEntry with a new node's data, possibly // overwriting an old position. The update is not atomic and can be racy. void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { - // Preserve any existing move for the same position - if (m || uint16_t(k) != key16) - move16 = uint16_t(m); + // Preserve any existing move for the same position + if (m || uint16_t(k) != key16) + move16 = uint16_t(m); - // Overwrite less valuable entries (cheapest checks first) - if ( b == BOUND_EXACT - || uint16_t(k) != key16 - || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) - { - assert(d > DEPTH_OFFSET); - assert(d < 256 + DEPTH_OFFSET); + // Overwrite less valuable entries (cheapest checks first) + if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) + { + assert(d > DEPTH_OFFSET); + assert(d < 256 + DEPTH_OFFSET); - key16 = uint16_t(k); - depth8 = uint8_t(d - DEPTH_OFFSET); - genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b); - value16 = int16_t(v); - eval16 = int16_t(ev); - } + key16 = uint16_t(k); + depth8 = uint8_t(d - DEPTH_OFFSET); + genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b); + value16 = int16_t(v); + eval16 = int16_t(ev); + } } @@ -65,21 +63,20 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) void TranspositionTable::resize(size_t mbSize) { - Threads.main()->wait_for_search_finished(); + Threads.main()->wait_for_search_finished(); - aligned_large_pages_free(table); + aligned_large_pages_free(table); - clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); + clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); - table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); - if (!table) - { - std::cerr << "Failed to allocate " << mbSize - << "MB for transposition table." << std::endl; - exit(EXIT_FAILURE); - } + table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); + if (!table) + { + std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } - clear(); + clear(); } @@ -88,28 +85,27 @@ void TranspositionTable::resize(size_t mbSize) { void TranspositionTable::clear() { - std::vector threads; + std::vector threads; - for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) - { - threads.emplace_back([this, idx]() { + for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) + { + threads.emplace_back([this, idx]() { + // Thread binding gives faster search on systems with a first-touch policy + if (Options["Threads"] > 8) + WinProcGroup::bindThisThread(idx); - // Thread binding gives faster search on systems with a first-touch policy - if (Options["Threads"] > 8) - WinProcGroup::bindThisThread(idx); + // Each thread will zero its part of the hash table + const size_t stride = size_t(clusterCount / Options["Threads"]), + start = size_t(stride * idx), + len = + idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start; - // Each thread will zero its part of the hash table - const size_t stride = size_t(clusterCount / Options["Threads"]), - start = size_t(stride * idx), - len = idx != size_t(Options["Threads"]) - 1 ? - stride : clusterCount - start; + std::memset(&table[start], 0, len * sizeof(Cluster)); + }); + } - std::memset(&table[start], 0, len * sizeof(Cluster)); - }); - } - - for (std::thread& th : threads) - th.join(); + for (std::thread& th : threads) + th.join(); } @@ -122,30 +118,33 @@ void TranspositionTable::clear() { TTEntry* TranspositionTable::probe(const Key key, bool& found) const { - TTEntry* const tte = first_entry(key); - const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster + TTEntry* const tte = first_entry(key); + const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster - for (int i = 0; i < ClusterSize; ++i) - if (tte[i].key16 == key16 || !tte[i].depth8) - { - tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh + for (int i = 0; i < ClusterSize; ++i) + if (tte[i].key16 == key16 || !tte[i].depth8) + { + tte[i].genBound8 = + uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh - return found = bool(tte[i].depth8), &tte[i]; - } + return found = bool(tte[i].depth8), &tte[i]; + } - // Find an entry to be replaced according to the replacement strategy - TTEntry* replace = tte; - for (int i = 1; i < ClusterSize; ++i) - // Due to our packed storage format for generation and its cyclic - // nature we add GENERATION_CYCLE (256 is the modulus, plus what - // is needed to keep the unrelated lowest n bits from affecting - // the result) to calculate the entry age correctly even after - // generation8 overflows into the next cycle. - if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) - > tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) - replace = &tte[i]; + // Find an entry to be replaced according to the replacement strategy + TTEntry* replace = tte; + for (int i = 1; i < ClusterSize; ++i) + // Due to our packed storage format for generation and its cyclic + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + if (replace->depth8 + - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) + > tte[i].depth8 + - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) + replace = &tte[i]; - return found = false, replace; + return found = false, replace; } @@ -154,12 +153,13 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { int TranspositionTable::hashfull() const { - int cnt = 0; - for (int i = 0; i < 1000; ++i) - for (int j = 0; j < ClusterSize; ++j) - cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; + int cnt = 0; + for (int i = 0; i < 1000; ++i) + for (int j = 0; j < ClusterSize; ++j) + cnt += table[i].entry[j].depth8 + && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; - return cnt / ClusterSize; + return cnt / ClusterSize; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/tt.h b/src/tt.h index fdea4933..628dfba2 100644 --- a/src/tt.h +++ b/src/tt.h @@ -40,23 +40,23 @@ namespace Stockfish { struct TTEntry { - Move move() const { return Move (move16); } - Value value() const { return Value(value16); } - Value eval() const { return Value(eval16); } - Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } - bool is_pv() const { return bool (genBound8 & 0x4); } - Bound bound() const { return Bound(genBound8 & 0x3); } - void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); + Move move() const { return Move(move16); } + Value value() const { return Value(value16); } + Value eval() const { return Value(eval16); } + Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } + bool is_pv() const { return bool(genBound8 & 0x4); } + Bound bound() const { return Bound(genBound8 & 0x3); } + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); -private: - friend class TranspositionTable; + private: + friend class TranspositionTable; - uint16_t key16; - uint8_t depth8; - uint8_t genBound8; - uint16_t move16; - int16_t value16; - int16_t eval16; + uint16_t key16; + uint8_t depth8; + uint8_t genBound8; + uint16_t move16; + int16_t value16; + int16_t eval16; }; @@ -68,43 +68,45 @@ private: class TranspositionTable { - static constexpr int ClusterSize = 3; + static constexpr int ClusterSize = 3; - struct Cluster { - TTEntry entry[ClusterSize]; - char padding[2]; // Pad to 32 bytes - }; + struct Cluster { + TTEntry entry[ClusterSize]; + char padding[2]; // Pad to 32 bytes + }; - static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); + static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); - // Constants used to refresh the hash table periodically - static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things - static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field - static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length - static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number + // Constants used to refresh the hash table periodically + static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things + static constexpr int GENERATION_DELTA = + (1 << GENERATION_BITS); // increment for generation field + static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length + static constexpr int GENERATION_MASK = + (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number -public: - ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things - TTEntry* probe(const Key key, bool& found) const; - int hashfull() const; - void resize(size_t mbSize); - void clear(); + public: + ~TranspositionTable() { aligned_large_pages_free(table); } + void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things + TTEntry* probe(const Key key, bool& found) const; + int hashfull() const; + void resize(size_t mbSize); + void clear(); - TTEntry* first_entry(const Key key) const { - return &table[mul_hi64(key, clusterCount)].entry[0]; - } + TTEntry* first_entry(const Key key) const { + return &table[mul_hi64(key, clusterCount)].entry[0]; + } -private: - friend struct TTEntry; + private: + friend struct TTEntry; - size_t clusterCount; - Cluster* table; - uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 + size_t clusterCount; + Cluster* table; + uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 }; extern TranspositionTable TT; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TT_H_INCLUDED +#endif // #ifndef TT_H_INCLUDED diff --git a/src/tune.cpp b/src/tune.cpp index 97baeb78..cf80b9d7 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -34,75 +34,84 @@ using std::string; namespace Stockfish { -bool Tune::update_on_last; -const UCI::Option* LastOption = nullptr; +bool Tune::update_on_last; +const UCI::Option* LastOption = nullptr; static std::map TuneResults; string Tune::next(string& names, bool pop) { - string name; + string name; - do { - string token = names.substr(0, names.find(',')); + do + { + string token = names.substr(0, names.find(',')); - if (pop) - names.erase(0, token.size() + 1); + if (pop) + names.erase(0, token.size() + 1); - std::stringstream ws(token); - name += (ws >> token, token); // Remove trailing whitespace + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace - } while ( std::count(name.begin(), name.end(), '(') - - std::count(name.begin(), name.end(), ')')); + } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); - return name; + return name; } static void on_tune(const UCI::Option& o) { - if (!Tune::update_on_last || LastOption == &o) - Tune::read_options(); + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); } static void make_option(const string& n, int v, const SetRange& r) { - // Do not generate option when there is nothing to tune (ie. min = max) - if (r(v).first == r(v).second) - return; + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; - if (TuneResults.count(n)) - v = TuneResults[n]; + if (TuneResults.count(n)) + v = TuneResults[n]; - Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); - LastOption = &Options[n]; + Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); + LastOption = &Options[n]; - // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," - << v << "," - << r(v).first << "," << r(v).second << "," - << (r(v).second - r(v).first) / 20.0 << "," - << "0.0020" - << std::endl; + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," + << (r(v).second - r(v).first) / 20.0 << "," + << "0.0020" << std::endl; } -template<> void Tune::Entry::init_option() { make_option(name, value, range); } - -template<> void Tune::Entry::read_option() { - if (Options.count(name)) - value = int(Options[name]); +template<> +void Tune::Entry::init_option() { + make_option(name, value, range); } -template<> void Tune::Entry::init_option() { make_option(name, value, range); } +template<> +void Tune::Entry::read_option() { + if (Options.count(name)) + value = int(Options[name]); +} -template<> void Tune::Entry::read_option() { - if (Options.count(name)) - value = Value(int(Options[name])); +template<> +void Tune::Entry::init_option() { + make_option(name, value, range); +} + +template<> +void Tune::Entry::read_option() { + if (Options.count(name)) + value = Value(int(Options[name])); } // Instead of a variable here we have a PostUpdate function: just call it -template<> void Tune::Entry::init_option() {} -template<> void Tune::Entry::read_option() { value(); } +template<> +void Tune::Entry::init_option() {} +template<> +void Tune::Entry::read_option() { + value(); +} -} // namespace Stockfish +} // namespace Stockfish // Init options with tuning session results instead of default values. Useful to @@ -117,9 +126,7 @@ template<> void Tune::Entry::read_option() { value(); } namespace Stockfish { -void Tune::read_results() { - - /* ...insert your values here... */ +void Tune::read_results() { /* ...insert your values here... */ } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/tune.h b/src/tune.h index a9a7331e..480aea16 100644 --- a/src/tune.h +++ b/src/tune.h @@ -22,28 +22,29 @@ #include #include #include -#include // IWYU pragma: keep +#include // IWYU pragma: keep #include #include namespace Stockfish { enum Value : int; -using Range = std::pair; // Option's min-max values -using RangeFun = Range (int); +using Range = std::pair; // Option's min-max values +using RangeFun = Range(int); // Default Range function, to calculate Option's min-max values -inline Range default_range(int v) { - return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); -} +inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } struct SetRange { - explicit SetRange(RangeFun f) : fun(f) {} - SetRange(int min, int max) : fun(nullptr), range(min, max) {} - Range operator()(int v) const { return fun ? fun(v) : range; } + explicit SetRange(RangeFun f) : + fun(f) {} + SetRange(int min, int max) : + fun(nullptr), + range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } - RangeFun* fun; - Range range; + RangeFun* fun; + Range range; }; #define SetDefaultRange SetRange(default_range) @@ -76,88 +77,102 @@ struct SetRange { class Tune { - using PostUpdate = void (); // Post-update function + using PostUpdate = void(); // Post-update function - Tune() { read_results(); } - Tune(const Tune&) = delete; - void operator=(const Tune&) = delete; - void read_results(); + Tune() { read_results(); } + Tune(const Tune&) = delete; + void operator=(const Tune&) = delete; + void read_results(); - static Tune& instance() { static Tune t; return t; } // Singleton + static Tune& instance() { + static Tune t; + return t; + } // Singleton - // Use polymorphism to accommodate Entry of different types in the same vector - struct EntryBase { - virtual ~EntryBase() = default; - virtual void init_option() = 0; - virtual void read_option() = 0; - }; + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; - template - struct Entry : public EntryBase { + template + struct Entry: public EntryBase { - static_assert(!std::is_const_v, "Parameter cannot be const!"); + static_assert(!std::is_const_v, "Parameter cannot be const!"); - static_assert( std::is_same_v - || std::is_same_v - || std::is_same_v, "Parameter type not supported!"); + static_assert(std::is_same_v || std::is_same_v + || std::is_same_v, + "Parameter type not supported!"); - Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} - void operator=(const Entry&) = delete; // Because 'value' is a reference - void init_option() override; - void read_option() override; + Entry(const std::string& n, T& v, const SetRange& r) : + name(n), + value(v), + range(r) {} + void operator=(const Entry&) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; - std::string name; - T& value; - SetRange range; - }; + std::string name; + T& value; + SetRange range; + }; - // Our facility to fill the container, each Entry corresponds to a parameter - // to tune. We use variadic templates to deal with an unspecified number of - // entries, each one of a possible different type. - static std::string next(std::string& names, bool pop = true); + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string& names, bool pop = true); - int add(const SetRange&, std::string&&) { return 0; } + int add(const SetRange&, std::string&&) { return 0; } - template - int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { - list.push_back(std::unique_ptr(new Entry(next(names), value, range))); - return add(range, std::move(names), args...); - } + template + int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } - // Template specialization for arrays: recursively handle multi-dimensional arrays - template - int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { - for (size_t i = 0; i < N; i++) - add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); - return add(range, std::move(names), args...); - } + // Template specialization for arrays: recursively handle multi-dimensional arrays + template + int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } - // Template specialization for SetRange - template - int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { - return add(value, (next(names), std::move(names)), args...); - } + // Template specialization for SetRange + template + int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { + return add(value, (next(names), std::move(names)), args...); + } - std::vector> list; + std::vector> list; -public: - template - static int add(const std::string& names, Args&&... args) { - return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis - } - static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access - static void read_options() { for (auto& e : instance().list) e->read_option(); } - static bool update_on_last; + public: + template + static int add(const std::string& names, Args&&... args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init() { + for (auto& e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCI::Options access + static void read_options() { + for (auto& e : instance().list) + e->read_option(); + } + static bool update_on_last; }; // Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() #define STRINGIFY(x) #x -#define UNIQUE2(x, y) x ## y -#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ +#define UNIQUE2(x, y) x##y +#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ #define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__) #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TUNE_H_INCLUDED +#endif // #ifndef TUNE_H_INCLUDED diff --git a/src/types.h b/src/types.h index 1fc4d33a..c76efd07 100644 --- a/src/types.h +++ b/src/types.h @@ -17,7 +17,7 @@ */ #ifndef TYPES_H_INCLUDED -#define TYPES_H_INCLUDED + #define TYPES_H_INCLUDED // When compiling with provided Makefile (e.g. for Linux and OSX), configuration // is done automatically. To get started type 'make help'. @@ -36,15 +36,15 @@ // -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works // | only in 64-bit mode and requires hardware with pext support. -#include -#include + #include + #include -#if defined(_MSC_VER) -// Disable some silly and noisy warnings from MSVC compiler -#pragma warning(disable: 4127) // Conditional expression is constant -#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type -#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' -#endif + #if defined(_MSC_VER) + // Disable some silly and noisy warnings from MSVC compiler + #pragma warning(disable: 4127) // Conditional expression is constant + #pragma warning(disable: 4146) // Unary minus operator applied to unsigned type + #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' + #endif // Predefined macros hell: // @@ -55,53 +55,54 @@ // _WIN32 Building on Windows (any) // _WIN64 Building on Windows 64 bit -#if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) -#define ALIGNAS_ON_STACK_VARIABLES_BROKEN -#endif + #if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \ + && defined(_WIN32) && !defined(__clang__) + #define ALIGNAS_ON_STACK_VARIABLES_BROKEN + #endif -#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) -#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used -# include // Microsoft header for _BitScanForward64() -# define IS_64BIT -#endif + #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used + #include // Microsoft header for _BitScanForward64() + #define IS_64BIT + #endif -#if defined(USE_POPCNT) && defined(_MSC_VER) -# include // Microsoft header for _mm_popcnt_u64() -#endif + #if defined(USE_POPCNT) && defined(_MSC_VER) + #include // Microsoft header for _mm_popcnt_u64() + #endif -#if !defined(NO_PREFETCH) && defined(_MSC_VER) -# include // Microsoft header for _mm_prefetch() -#endif + #if !defined(NO_PREFETCH) && defined(_MSC_VER) + #include // Microsoft header for _mm_prefetch() + #endif -#if defined(USE_PEXT) -# include // Header for _pext_u64() intrinsic -# define pext(b, m) _pext_u64(b, m) -#else -# define pext(b, m) 0 -#endif + #if defined(USE_PEXT) + #include // Header for _pext_u64() intrinsic + #define pext(b, m) _pext_u64(b, m) + #else + #define pext(b, m) 0 + #endif namespace Stockfish { -#ifdef USE_POPCNT + #ifdef USE_POPCNT constexpr bool HasPopCnt = true; -#else + #else constexpr bool HasPopCnt = false; -#endif + #endif -#ifdef USE_PEXT + #ifdef USE_PEXT constexpr bool HasPext = true; -#else + #else constexpr bool HasPext = false; -#endif + #endif -#ifdef IS_64BIT + #ifdef IS_64BIT constexpr bool Is64Bit = true; -#else + #else constexpr bool Is64Bit = false; -#endif + #endif -using Key = uint64_t; +using Key = uint64_t; using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; @@ -120,164 +121,187 @@ constexpr int MAX_PLY = 246; // while MOVE_NONE and MOVE_NULL have the same origin and destination square. enum Move : int { - MOVE_NONE, - MOVE_NULL = 65 + MOVE_NONE, + MOVE_NULL = 65 }; enum MoveType { - NORMAL, - PROMOTION = 1 << 14, - EN_PASSANT = 2 << 14, - CASTLING = 3 << 14 + NORMAL, + PROMOTION = 1 << 14, + EN_PASSANT = 2 << 14, + CASTLING = 3 << 14 }; enum Color { - WHITE, BLACK, COLOR_NB = 2 + WHITE, + BLACK, + COLOR_NB = 2 }; enum CastlingRights { - NO_CASTLING, - WHITE_OO, - WHITE_OOO = WHITE_OO << 1, - BLACK_OO = WHITE_OO << 2, - BLACK_OOO = WHITE_OO << 3, + NO_CASTLING, + WHITE_OO, + WHITE_OOO = WHITE_OO << 1, + BLACK_OO = WHITE_OO << 2, + BLACK_OOO = WHITE_OO << 3, - KING_SIDE = WHITE_OO | BLACK_OO, - QUEEN_SIDE = WHITE_OOO | BLACK_OOO, - WHITE_CASTLING = WHITE_OO | WHITE_OOO, - BLACK_CASTLING = BLACK_OO | BLACK_OOO, - ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, + KING_SIDE = WHITE_OO | BLACK_OO, + QUEEN_SIDE = WHITE_OOO | BLACK_OOO, + WHITE_CASTLING = WHITE_OO | WHITE_OOO, + BLACK_CASTLING = BLACK_OO | BLACK_OOO, + ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, - CASTLING_RIGHT_NB = 16 + CASTLING_RIGHT_NB = 16 }; enum Bound { - BOUND_NONE, - BOUND_UPPER, - BOUND_LOWER, - BOUND_EXACT = BOUND_UPPER | BOUND_LOWER + BOUND_NONE, + BOUND_UPPER, + BOUND_LOWER, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER }; enum Value : int { - VALUE_ZERO = 0, - VALUE_DRAW = 0, - VALUE_MATE = 32000, - VALUE_INFINITE = 32001, - VALUE_NONE = 32002, + VALUE_ZERO = 0, + VALUE_DRAW = 0, + VALUE_MATE = 32000, + VALUE_INFINITE = 32001, + VALUE_NONE = 32002, - VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, - VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, - VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, - VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, + VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, + VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, + VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, + VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, - // In the code, we make the assumption that these values - // are such that non_pawn_material() can be used to uniquely - // identify the material on the board. - PawnValue = 208, - KnightValue = 781, - BishopValue = 825, - RookValue = 1276, - QueenValue = 2538, + // In the code, we make the assumption that these values + // are such that non_pawn_material() can be used to uniquely + // identify the material on the board. + PawnValue = 208, + KnightValue = 781, + BishopValue = 825, + RookValue = 1276, + QueenValue = 2538, }; +// clang-format off enum PieceType { - NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, - ALL_PIECES = 0, - PIECE_TYPE_NB = 8 + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, + ALL_PIECES = 0, + PIECE_TYPE_NB = 8 }; enum Piece { - NO_PIECE, - W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, - PIECE_NB = 16 + NO_PIECE, + W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + PIECE_NB = 16 }; +// clang-format on -constexpr Value PieceValue[PIECE_NB] = { VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO }; +constexpr Value PieceValue[PIECE_NB] = { + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO}; using Depth = int; enum : int { - DEPTH_QS_CHECKS = 0, - DEPTH_QS_NO_CHECKS = -1, - DEPTH_QS_RECAPTURES = -5, + DEPTH_QS_CHECKS = 0, + DEPTH_QS_NO_CHECKS = -1, + DEPTH_QS_RECAPTURES = -5, - DEPTH_NONE = -6, + DEPTH_NONE = -6, - DEPTH_OFFSET = -7 // value used only for TT entry occupancy check + DEPTH_OFFSET = -7 // value used only for TT entry occupancy check }; +// clang-format off enum Square : int { - SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, - SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, - SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, - SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, - SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, - SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, - SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, - SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, - SQ_NONE, + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, + SQ_NONE, - SQUARE_ZERO = 0, - SQUARE_NB = 64 + SQUARE_ZERO = 0, + SQUARE_NB = 64 }; +// clang-format on enum Direction : int { - NORTH = 8, - EAST = 1, - SOUTH = -NORTH, - WEST = -EAST, + NORTH = 8, + EAST = 1, + SOUTH = -NORTH, + WEST = -EAST, - NORTH_EAST = NORTH + EAST, - SOUTH_EAST = SOUTH + EAST, - SOUTH_WEST = SOUTH + WEST, - NORTH_WEST = NORTH + WEST + NORTH_EAST = NORTH + EAST, + SOUTH_EAST = SOUTH + EAST, + SOUTH_WEST = SOUTH + WEST, + NORTH_WEST = NORTH + WEST }; enum File : int { - FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB + FILE_A, + FILE_B, + FILE_C, + FILE_D, + FILE_E, + FILE_F, + FILE_G, + FILE_H, + FILE_NB }; enum Rank : int { - RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB + RANK_1, + RANK_2, + RANK_3, + RANK_4, + RANK_5, + RANK_6, + RANK_7, + RANK_8, + RANK_NB }; // Keep track of what a move changes on the board (used by NNUE) struct DirtyPiece { - // Number of changed pieces - int dirty_num; + // Number of changed pieces + int dirty_num; - // Max 3 pieces can change in one move. A promotion with capture moves - // both the pawn and the captured piece to SQ_NONE and the piece promoted - // to from SQ_NONE to the capture square. - Piece piece[3]; + // Max 3 pieces can change in one move. A promotion with capture moves + // both the pawn and the captured piece to SQ_NONE and the piece promoted + // to from SQ_NONE to the capture square. + Piece piece[3]; - // From and to squares, which may be SQ_NONE - Square from[3]; - Square to[3]; + // From and to squares, which may be SQ_NONE + Square from[3]; + Square to[3]; }; -#define ENABLE_BASE_OPERATORS_ON(T) \ -constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ -constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ -constexpr T operator-(T d) { return T(-int(d)); } \ -inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ -inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } + #define ENABLE_BASE_OPERATORS_ON(T) \ + constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ + constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ + constexpr T operator-(T d) { return T(-int(d)); } \ + inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ + inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } -#define ENABLE_INCR_OPERATORS_ON(T) \ -inline T& operator++(T& d) { return d = T(int(d) + 1); } \ -inline T& operator--(T& d) { return d = T(int(d) - 1); } + #define ENABLE_INCR_OPERATORS_ON(T) \ + inline T& operator++(T& d) { return d = T(int(d) + 1); } \ + inline T& operator--(T& d) { return d = T(int(d) - 1); } -#define ENABLE_FULL_OPERATORS_ON(T) \ -ENABLE_BASE_OPERATORS_ON(T) \ -constexpr T operator*(int i, T d) { return T(i * int(d)); } \ -constexpr T operator*(T d, int i) { return T(int(d) * i); } \ -constexpr T operator/(T d, int i) { return T(int(d) / i); } \ -constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ -inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ -inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } + #define ENABLE_FULL_OPERATORS_ON(T) \ + ENABLE_BASE_OPERATORS_ON(T) \ + constexpr T operator*(int i, T d) { return T(i * int(d)); } \ + constexpr T operator*(T d, int i) { return T(int(d) * i); } \ + constexpr T operator/(T d, int i) { return T(int(d) / i); } \ + constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ + inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ + inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } ENABLE_FULL_OPERATORS_ON(Value) ENABLE_FULL_OPERATORS_ON(Direction) @@ -287,131 +311,97 @@ ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) ENABLE_INCR_OPERATORS_ON(Rank) -#undef ENABLE_FULL_OPERATORS_ON -#undef ENABLE_INCR_OPERATORS_ON -#undef ENABLE_BASE_OPERATORS_ON + #undef ENABLE_FULL_OPERATORS_ON + #undef ENABLE_INCR_OPERATORS_ON + #undef ENABLE_BASE_OPERATORS_ON // Additional operators to add a Direction to a Square constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } -inline Square& operator+=(Square& s, Direction d) { return s = s + d; } -inline Square& operator-=(Square& s, Direction d) { return s = s - d; } +inline Square& operator+=(Square& s, Direction d) { return s = s + d; } +inline Square& operator-=(Square& s, Direction d) { return s = s - d; } constexpr Color operator~(Color c) { - return Color(c ^ BLACK); // Toggle color + return Color(c ^ BLACK); // Toggle color } -constexpr Square flip_rank(Square s) { // Swap A1 <-> A8 - return Square(s ^ SQ_A8); +constexpr Square flip_rank(Square s) { // Swap A1 <-> A8 + return Square(s ^ SQ_A8); } -constexpr Square flip_file(Square s) { // Swap A1 <-> H1 - return Square(s ^ SQ_H1); +constexpr Square flip_file(Square s) { // Swap A1 <-> H1 + return Square(s ^ SQ_H1); } constexpr Piece operator~(Piece pc) { - return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT + return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT } constexpr CastlingRights operator&(Color c, CastlingRights cr) { - return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); + return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); } -constexpr Value mate_in(int ply) { - return VALUE_MATE - ply; -} +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } -constexpr Value mated_in(int ply) { - return -VALUE_MATE + ply; -} +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } -constexpr Square make_square(File f, Rank r) { - return Square((r << 3) + f); -} +constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); } -constexpr Piece make_piece(Color c, PieceType pt) { - return Piece((c << 3) + pt); -} +constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); } -constexpr PieceType type_of(Piece pc) { - return PieceType(pc & 7); -} +constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } inline Color color_of(Piece pc) { - assert(pc != NO_PIECE); - return Color(pc >> 3); + assert(pc != NO_PIECE); + return Color(pc >> 3); } -constexpr bool is_ok(Move m) { - return m != MOVE_NONE && m != MOVE_NULL; -} +constexpr bool is_ok(Move m) { return m != MOVE_NONE && m != MOVE_NULL; } -constexpr bool is_ok(Square s) { - return s >= SQ_A1 && s <= SQ_H8; -} +constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } -constexpr File file_of(Square s) { - return File(s & 7); -} +constexpr File file_of(Square s) { return File(s & 7); } -constexpr Rank rank_of(Square s) { - return Rank(s >> 3); -} +constexpr Rank rank_of(Square s) { return Rank(s >> 3); } -constexpr Square relative_square(Color c, Square s) { - return Square(s ^ (c * 56)); -} +constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } -constexpr Rank relative_rank(Color c, Rank r) { - return Rank(r ^ (c * 7)); -} +constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); } -constexpr Rank relative_rank(Color c, Square s) { - return relative_rank(c, rank_of(s)); -} +constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); } -constexpr Direction pawn_push(Color c) { - return c == WHITE ? NORTH : SOUTH; -} +constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } constexpr Square from_sq(Move m) { - assert(is_ok(m)); - return Square((m >> 6) & 0x3F); + assert(is_ok(m)); + return Square((m >> 6) & 0x3F); } constexpr Square to_sq(Move m) { - assert(is_ok(m)); - return Square(m & 0x3F); + assert(is_ok(m)); + return Square(m & 0x3F); } -constexpr int from_to(Move m) { - return m & 0xFFF; -} +constexpr int from_to(Move m) { return m & 0xFFF; } -constexpr MoveType type_of(Move m) { - return MoveType(m & (3 << 14)); -} +constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); } -constexpr PieceType promotion_type(Move m) { - return PieceType(((m >> 12) & 3) + KNIGHT); -} +constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); } -constexpr Move make_move(Square from, Square to) { - return Move((from << 6) + to); -} +constexpr Move make_move(Square from, Square to) { return Move((from << 6) + to); } template constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { - return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } // Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { - return seed * 6364136223846793005ULL + 1442695040888963407ULL; + return seed * 6364136223846793005ULL + 1442695040888963407ULL; } -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TYPES_H_INCLUDED +#endif // #ifndef TYPES_H_INCLUDED -#include "tune.h" // Global visibility to tuning setup +#include "tune.h" // Global visibility to tuning setup diff --git a/src/uci.cpp b/src/uci.cpp index 81bf7aff..0671cb5f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -45,18 +45,18 @@ namespace Stockfish { namespace { - // FEN string for the initial position in standard chess - const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +// FEN string for the initial position in standard chess +const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - // position() is called when the engine receives the "position" UCI command. - // It sets up the position that is described in the given FEN string ("fen") or - // the initial position ("startpos") and then makes the moves given in the following - // move list ("moves"). +// position() is called when the engine receives the "position" UCI command. +// It sets up the position that is described in the given FEN string ("fen") or +// the initial position ("startpos") and then makes the moves given in the following +// move list ("moves"). - void position(Position& pos, std::istringstream& is, StateListPtr& states) { +void position(Position& pos, std::istringstream& is, StateListPtr& states) { - Move m; + Move m; std::string token, fen; is >> token; @@ -64,7 +64,7 @@ namespace { if (token == "startpos") { fen = StartFEN; - is >> token; // Consume the "moves" token, if any + is >> token; // Consume the "moves" token, if any } else if (token == "fen") while (is >> token && token != "moves") @@ -72,7 +72,7 @@ namespace { else return; - states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one + states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse the move list, if any @@ -81,33 +81,33 @@ namespace { states->emplace_back(); pos.do_move(m, states->back()); } - } +} - // trace_eval() prints the evaluation of the current position, consistent with - // the UCI options set so far. +// trace_eval() prints the evaluation of the current position, consistent with +// the UCI options set so far. - void trace_eval(Position& pos) { +void trace_eval(Position& pos) { StateListPtr states(new std::deque(1)); - Position p; + Position p; p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); Eval::NNUE::verify(); sync_cout << "\n" << Eval::trace(p) << sync_endl; - } +} - // setoption() is called when the engine receives the "setoption" UCI command. - // The function updates the UCI option ("name") to the given value ("value"). +// setoption() is called when the engine receives the "setoption" UCI command. +// The function updates the UCI option ("name") to the given value ("value"). - void setoption(std::istringstream& is) { +void setoption(std::istringstream& is) { Threads.main()->wait_for_search_finished(); std::string token, name, value; - is >> token; // Consume the "name" token + is >> token; // Consume the "name" token // Read the option name (can contain spaces) while (is >> token && token != "value") @@ -121,54 +121,67 @@ namespace { Options[name] = value; else sync_cout << "No such option: " << name << sync_endl; - } +} - // go() is called when the engine receives the "go" UCI command. The function - // sets the thinking time and other parameters from the input string, then starts - // with a search. +// go() is called when the engine receives the "go" UCI command. The function +// sets the thinking time and other parameters from the input string, then starts +// with a search. - void go(Position& pos, std::istringstream& is, StateListPtr& states) { +void go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits; - std::string token; - bool ponderMode = false; + std::string token; + bool ponderMode = false; - limits.startTime = now(); // The search starts as early as possible + limits.startTime = now(); // The search starts as early as possible while (is >> token) - if (token == "searchmoves") // Needs to be the last command on the line + if (token == "searchmoves") // Needs to be the last command on the line while (is >> token) limits.searchmoves.push_back(UCI::to_move(pos, token)); - else if (token == "wtime") is >> limits.time[WHITE]; - else if (token == "btime") is >> limits.time[BLACK]; - else if (token == "winc") is >> limits.inc[WHITE]; - else if (token == "binc") is >> limits.inc[BLACK]; - else if (token == "movestogo") is >> limits.movestogo; - else if (token == "depth") is >> limits.depth; - else if (token == "nodes") is >> limits.nodes; - else if (token == "movetime") is >> limits.movetime; - else if (token == "mate") is >> limits.mate; - else if (token == "perft") is >> limits.perft; - else if (token == "infinite") limits.infinite = 1; - else if (token == "ponder") ponderMode = true; + else if (token == "wtime") + is >> limits.time[WHITE]; + else if (token == "btime") + is >> limits.time[BLACK]; + else if (token == "winc") + is >> limits.inc[WHITE]; + else if (token == "binc") + is >> limits.inc[BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder") + ponderMode = true; Threads.start_thinking(pos, states, limits, ponderMode); - } +} - // bench() is called when the engine receives the "bench" command. - // First, a list of UCI commands is set up according to the bench - // parameters, then it is run one by one, printing a summary at the end. +// bench() is called when the engine receives the "bench" command. +// First, a list of UCI commands is set up according to the bench +// parameters, then it is run one by one, printing a summary at the end. - void bench(Position& pos, std::istream& args, StateListPtr& states) { +void bench(Position& pos, std::istream& args, StateListPtr& states) { std::string token; - uint64_t num, nodes = 0, cnt = 1; + uint64_t num, nodes = 0, cnt = 1; std::vector list = setup_bench(pos, args); - num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + num = count_if(list.begin(), list.end(), + [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); TimePoint elapsed = now(); @@ -179,58 +192,64 @@ namespace { if (token == "go" || token == "eval") { - std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << std::endl; + std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" + << std::endl; if (token == "go") { - go(pos, is, states); - Threads.main()->wait_for_search_finished(); - nodes += Threads.nodes_searched(); + go(pos, is, states); + Threads.main()->wait_for_search_finished(); + nodes += Threads.nodes_searched(); } else - trace_eval(pos); + trace_eval(pos); } - else if (token == "setoption") setoption(is); - else if (token == "position") position(pos, is, states); - else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take a while + else if (token == "setoption") + setoption(is); + else if (token == "position") + position(pos, is, states); + else if (token == "ucinewgame") + { + Search::clear(); + elapsed = now(); + } // Search::clear() may take a while } - elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' dbg_print(); std::cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes + << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; - } +} - // The win rate model returns the probability of winning (in per mille units) given an - // eval and a game ply. It fits the LTC fishtest statistics rather accurately. - int win_rate_model(Value v, int ply) { +// The win rate model returns the probability of winning (in per mille units) given an +// eval and a game ply. It fits the LTC fishtest statistics rather accurately. +int win_rate_model(Value v, int ply) { - // The model only captures up to 240 plies, so limit the input and then rescale - double m = std::min(240, ply) / 64.0; + // The model only captures up to 240 plies, so limit the input and then rescale + double m = std::min(240, ply) / 64.0; - // The coefficients of a third-order polynomial fit is based on the fishtest data - // for two parameters that need to transform eval to the argument of a logistic - // function. - constexpr double as[] = { 0.38036525, -2.82015070, 23.17882135, 307.36768407}; - constexpr double bs[] = { -2.29434733, 13.27689788, -14.26828904, 63.45318330 }; + // The coefficients of a third-order polynomial fit is based on the fishtest data + // for two parameters that need to transform eval to the argument of a logistic + // function. + constexpr double as[] = {0.38036525, -2.82015070, 23.17882135, 307.36768407}; + constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330}; - // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 - static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); + // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 + static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); - double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; - double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - // Transform the eval to centipawns with limited range - double x = std::clamp(double(v), -4000.0, 4000.0); + // Transform the eval to centipawns with limited range + double x = std::clamp(double(v), -4000.0, 4000.0); - // Return the win rate in per mille units, rounded to the nearest integer - return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); - } + // Return the win rate in per mille units, rounded to the nearest integer + return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); +} -} // namespace +} // namespace // UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate @@ -241,81 +260,91 @@ namespace { void UCI::loop(int argc, char* argv[]) { - Position pos; - std::string token, cmd; - StateListPtr states(new std::deque(1)); + Position pos; + std::string token, cmd; + StateListPtr states(new std::deque(1)); - pos.set(StartFEN, false, &states->back(), Threads.main()); + pos.set(StartFEN, false, &states->back(), Threads.main()); - for (int i = 1; i < argc; ++i) - cmd += std::string(argv[i]) + " "; + for (int i = 1; i < argc; ++i) + cmd += std::string(argv[i]) + " "; - do { - if (argc == 1 && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication - cmd = "quit"; + do + { + if (argc == 1 + && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + cmd = "quit"; - std::istringstream is(cmd); + std::istringstream is(cmd); - token.clear(); // Avoid a stale if getline() returns nothing or a blank line - is >> std::skipws >> token; + token.clear(); // Avoid a stale if getline() returns nothing or a blank line + is >> std::skipws >> token; - if ( token == "quit" - || token == "stop") - Threads.stop = true; + if (token == "quit" || token == "stop") + Threads.stop = true; - // The GUI sends 'ponderhit' to tell that the user has played the expected move. - // So, 'ponderhit' is sent if pondering was done on the same move that the user - // has played. The search should continue, but should also switch from pondering - // to the normal search. - else if (token == "ponderhit") - Threads.main()->ponder = false; // Switch to the normal search + // The GUI sends 'ponderhit' to tell that the user has played the expected move. + // So, 'ponderhit' is sent if pondering was done on the same move that the user + // has played. The search should continue, but should also switch from pondering + // to the normal search. + else if (token == "ponderhit") + Threads.main()->ponder = false; // Switch to the normal search - else if (token == "uci") - sync_cout << "id name " << engine_info(true) - << "\n" << Options - << "\nuciok" << sync_endl; + else if (token == "uci") + sync_cout << "id name " << engine_info(true) << "\n" + << Options << "\nuciok" << sync_endl; - else if (token == "setoption") setoption(is); - else if (token == "go") go(pos, is, states); - else if (token == "position") position(pos, is, states); - else if (token == "ucinewgame") Search::clear(); - else if (token == "isready") sync_cout << "readyok" << sync_endl; + else if (token == "setoption") + setoption(is); + else if (token == "go") + go(pos, is, states); + else if (token == "position") + position(pos, is, states); + else if (token == "ucinewgame") + Search::clear(); + else if (token == "isready") + sync_cout << "readyok" << sync_endl; - // Add custom non-UCI commands, mainly for debugging purposes. - // These commands must not be used during a search! - else if (token == "flip") pos.flip(); - else if (token == "bench") bench(pos, is, states); - else if (token == "d") sync_cout << pos << sync_endl; - else if (token == "eval") trace_eval(pos); - else if (token == "compiler") sync_cout << compiler_info() << sync_endl; - else if (token == "export_net") - { - std::optional filename; - std::string f; - if (is >> std::skipws >> f) - filename = f; - Eval::NNUE::save_eval(filename); - } - else if (token == "--help" || token == "help" || token == "--license" || token == "license") - sync_cout << "\nStockfish is a powerful chess engine for playing and analyzing." - "\nIt is released as free software licensed under the GNU GPLv3 License." - "\nStockfish is normally used with a graphical user interface (GUI) and implements" - "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." - "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" - "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" << sync_endl; - else if (!token.empty() && token[0] != '#') - sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl; + // Add custom non-UCI commands, mainly for debugging purposes. + // These commands must not be used during a search! + else if (token == "flip") + pos.flip(); + else if (token == "bench") + bench(pos, is, states); + else if (token == "d") + sync_cout << pos << sync_endl; + else if (token == "eval") + trace_eval(pos); + else if (token == "compiler") + sync_cout << compiler_info() << sync_endl; + else if (token == "export_net") + { + std::optional filename; + std::string f; + if (is >> std::skipws >> f) + filename = f; + Eval::NNUE::save_eval(filename); + } + else if (token == "--help" || token == "help" || token == "--license" || token == "license") + sync_cout + << "\nStockfish is a powerful chess engine for playing and analyzing." + "\nIt is released as free software licensed under the GNU GPLv3 License." + "\nStockfish is normally used with a graphical user interface (GUI) and implements" + "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." + "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" + "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" + << sync_endl; + else if (!token.empty() && token[0] != '#') + sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." + << sync_endl; - } while (token != "quit" && argc == 1); // The command-line arguments are one-shot + } while (token != "quit" && argc == 1); // The command-line arguments are one-shot } // Turns a Value to an integer centipawn number, // without treatment of mate and similar special scores. -int UCI::to_cp(Value v) { - - return 100 * v / UCI::NormalizeToPawnValue; -} +int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; } // UCI::value() converts a Value to a string by adhering to the UCI protocol specification: // @@ -325,21 +354,21 @@ int UCI::to_cp(Value v) { std::string UCI::value(Value v) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - std::stringstream ss; + std::stringstream ss; - if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << UCI::to_cp(v); - else if (abs(v) < VALUE_MATE_IN_MAX_PLY) - { - const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply - ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); - } - else - ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; + if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + ss << "cp " << UCI::to_cp(v); + else if (abs(v) < VALUE_MATE_IN_MAX_PLY) + { + const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply + ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); + } + else + ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; - return ss.str(); + return ss.str(); } @@ -348,21 +377,21 @@ std::string UCI::value(Value v) { std::string UCI::wdl(Value v, int ply) { - std::stringstream ss; + std::stringstream ss; - int wdl_w = win_rate_model( v, ply); - int wdl_l = win_rate_model(-v, ply); - int wdl_d = 1000 - wdl_w - wdl_l; - ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + int wdl_w = win_rate_model(v, ply); + int wdl_l = win_rate_model(-v, ply); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; - return ss.str(); + return ss.str(); } // UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { - return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; + return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } @@ -373,24 +402,24 @@ std::string UCI::square(Square s) { std::string UCI::move(Move m, bool chess960) { - if (m == MOVE_NONE) - return "(none)"; + if (m == MOVE_NONE) + return "(none)"; - if (m == MOVE_NULL) - return "0000"; + if (m == MOVE_NULL) + return "0000"; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = from_sq(m); + Square to = to_sq(m); - if (type_of(m) == CASTLING && !chess960) - to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); + if (type_of(m) == CASTLING && !chess960) + to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - std::string move = UCI::square(from) + UCI::square(to); + std::string move = UCI::square(from) + UCI::square(to); - if (type_of(m) == PROMOTION) - move += " pnbrqk"[promotion_type(m)]; + if (type_of(m) == PROMOTION) + move += " pnbrqk"[promotion_type(m)]; - return move; + return move; } @@ -399,14 +428,14 @@ std::string UCI::move(Move m, bool chess960) { Move UCI::to_move(const Position& pos, std::string& str) { - if (str.length() == 5) - str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased + if (str.length() == 5) + str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased - for (const auto& m : MoveList(pos)) - if (str == UCI::move(m, pos.is_chess960())) - return m; + for (const auto& m : MoveList(pos)) + if (str == UCI::move(m, pos.is_chess960())) + return m; - return MOVE_NONE; + return MOVE_NONE; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/uci.h b/src/uci.h index 048f8c11..be5c70c5 100644 --- a/src/uci.h +++ b/src/uci.h @@ -43,7 +43,7 @@ class Option; // Define a custom comparator, because the UCI options should be case-insensitive struct CaseInsensitiveLess { - bool operator() (const std::string&, const std::string&) const; + bool operator()(const std::string&, const std::string&) const; }; // The options container is defined as a std::map @@ -52,44 +52,44 @@ using OptionsMap = std::map; // The Option class implements each option as specified by the UCI protocol class Option { - using OnChange = void (*)(const Option&); + using OnChange = void (*)(const Option&); -public: - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char* v, OnChange = nullptr); - Option(double v, int minv, int maxv, OnChange = nullptr); - Option(const char* v, const char* cur, OnChange = nullptr); + public: + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(double v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char* cur, OnChange = nullptr); - Option& operator=(const std::string&); - void operator<<(const Option&); - operator int() const; - operator std::string() const; - bool operator==(const char*) const; + Option& operator=(const std::string&); + void operator<<(const Option&); + operator int() const; + operator std::string() const; + bool operator==(const char*) const; -private: - friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + private: + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; }; -void init(OptionsMap&); -void loop(int argc, char* argv[]); -int to_cp(Value v); +void init(OptionsMap&); +void loop(int argc, char* argv[]); +int to_cp(Value v); std::string value(Value v); std::string square(Square s); std::string move(Move m, bool chess960); std::string pv(const Position& pos, Depth depth); std::string wdl(Value v, int ply); -Move to_move(const Position& pos, std::string& str); +Move to_move(const Position& pos, std::string& str); -} // namespace UCI +} // namespace UCI extern UCI::OptionsMap Options; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef UCI_H_INCLUDED +#endif // #ifndef UCI_H_INCLUDED diff --git a/src/ucioption.cpp b/src/ucioption.cpp index b822ccf9..8db4233a 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -40,7 +40,7 @@ using std::string; namespace Stockfish { -UCI::OptionsMap Options; // Global object +UCI::OptionsMap Options; // Global object namespace UCI { @@ -53,10 +53,10 @@ static void on_tb_path(const Option& o) { Tablebases::init(o); } static void on_eval_file(const Option&) { Eval::NNUE::init(); } // Our case insensitive less() function as required by UCI protocol -bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { +bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const { - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return tolower(c1) < tolower(c2); }); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return tolower(c1) < tolower(c2); }); } @@ -64,28 +64,28 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const void init(OptionsMap& o) { - constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; + constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; - o["Debug Log File"] << Option("", on_logger); - o["Threads"] << Option(1, 1, 1024, on_threads); - o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); - o["Clear Hash"] << Option(on_clear_hash); - o["Ponder"] << Option(false); - o["MultiPV"] << Option(1, 1, 500); - o["Skill Level"] << Option(20, 0, 20); - o["Move Overhead"] << Option(10, 0, 5000); - o["Slow Mover"] << Option(100, 10, 1000); - o["nodestime"] << Option(0, 0, 10000); - o["UCI_Chess960"] << Option(false); - o["UCI_AnalyseMode"] << Option(false); - o["UCI_LimitStrength"] << Option(false); - o["UCI_Elo"] << Option(1320, 1320, 3190); - o["UCI_ShowWDL"] << Option(false); - o["SyzygyPath"] << Option("", on_tb_path); - o["SyzygyProbeDepth"] << Option(1, 1, 100); - o["Syzygy50MoveRule"] << Option(true); - o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); + o["Debug Log File"] << Option("", on_logger); + o["Threads"] << Option(1, 1, 1024, on_threads); + o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); + o["Clear Hash"] << Option(on_clear_hash); + o["Ponder"] << Option(false); + o["MultiPV"] << Option(1, 1, 500); + o["Skill Level"] << Option(20, 0, 20); + o["Move Overhead"] << Option(10, 0, 5000); + o["Slow Mover"] << Option(100, 10, 1000); + o["nodestime"] << Option(0, 0, 10000); + o["UCI_Chess960"] << Option(false); + o["UCI_AnalyseMode"] << Option(false); + o["UCI_LimitStrength"] << Option(false); + o["UCI_Elo"] << Option(1320, 1320, 3190); + o["UCI_ShowWDL"] << Option(false); + o["SyzygyPath"] << Option("", on_tb_path); + o["SyzygyProbeDepth"] << Option(1, 1, 100); + o["Syzygy50MoveRule"] << Option(true); + o["SyzygyProbeLimit"] << Option(7, 0, 7); + o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); } @@ -94,59 +94,81 @@ void init(OptionsMap& o) { std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { - for (size_t idx = 0; idx < om.size(); ++idx) - for (const auto& it : om) - if (it.second.idx == idx) - { - const Option& o = it.second; - os << "\noption name " << it.first << " type " << o.type; + for (size_t idx = 0; idx < om.size(); ++idx) + for (const auto& it : om) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; - if (o.type == "string" || o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; + if (o.type == "string" || o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; - if (o.type == "spin") - os << " default " << int(stof(o.defaultValue)) - << " min " << o.min - << " max " << o.max; + if (o.type == "spin") + os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " + << o.max; - break; - } + break; + } - return os; + return os; } // Option class constructors and conversion operators -Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) -{ defaultValue = currentValue = v; } +Option::Option(const char* v, OnChange f) : + type("string"), + min(0), + max(0), + on_change(f) { + defaultValue = currentValue = v; +} -Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f) -{ defaultValue = currentValue = (v ? "true" : "false"); } +Option::Option(bool v, OnChange f) : + type("check"), + min(0), + max(0), + on_change(f) { + defaultValue = currentValue = (v ? "true" : "false"); +} -Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) -{} +Option::Option(OnChange f) : + type("button"), + min(0), + max(0), + on_change(f) {} -Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) -{ defaultValue = currentValue = std::to_string(v); } +Option::Option(double v, int minv, int maxv, OnChange f) : + type("spin"), + min(minv), + max(maxv), + on_change(f) { + defaultValue = currentValue = std::to_string(v); +} -Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) -{ defaultValue = v; currentValue = cur; } +Option::Option(const char* v, const char* cur, OnChange f) : + type("combo"), + min(0), + max(0), + on_change(f) { + defaultValue = v; + currentValue = cur; +} Option::operator int() const { - assert(type == "check" || type == "spin"); - return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { - assert(type == "string"); - return currentValue; + assert(type == "string"); + return currentValue; } bool Option::operator==(const char* s) const { - assert(type == "combo"); - return !CaseInsensitiveLess()(currentValue, s) - && !CaseInsensitiveLess()(s, currentValue); + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); } @@ -154,10 +176,10 @@ bool Option::operator==(const char* s) const { void Option::operator<<(const Option& o) { - static size_t insert_order = 0; + static size_t insert_order = 0; - *this = o; - idx = insert_order++; + *this = o; + idx = insert_order++; } @@ -167,33 +189,33 @@ void Option::operator<<(const Option& o) { Option& Option::operator=(const string& v) { - assert(!type.empty()); + assert(!type.empty()); - if ( (type != "button" && type != "string" && v.empty()) - || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (stof(v) < min || stof(v) > max))) - return *this; + if ((type != "button" && type != "string" && v.empty()) + || (type == "check" && v != "true" && v != "false") + || (type == "spin" && (stof(v) < min || stof(v) > max))) + return *this; - if (type == "combo") - { - OptionsMap comboMap; // To have case insensitive compare - string token; - std::istringstream ss(defaultValue); - while (ss >> token) - comboMap[token] << Option(); - if (!comboMap.count(v) || v == "var") - return *this; - } + if (type == "combo") + { + OptionsMap comboMap; // To have case insensitive compare + string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap[token] << Option(); + if (!comboMap.count(v) || v == "var") + return *this; + } - if (type != "button") - currentValue = v; + if (type != "button") + currentValue = v; - if (on_change) - on_change(*this); + if (on_change) + on_change(*this); - return *this; + return *this; } -} // namespace UCI +} // namespace UCI -} // namespace Stockfish +} // namespace Stockfish From b7b7800e2b752c93131e03c31d7456f18b392a7c Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Sun, 22 Oct 2023 08:36:43 +0200 Subject: [PATCH 440/678] Simplify futilityBase formula This patch replaces std::min(ss->staticEval, bestValue) with ss->staticEval in the futilityBase formula. Original idea by Vizvezdenec: https://tests.stockfishchess.org/tests/view/64ce66795b17f7c21c0d85f3 Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 116928 W: 29925 L: 29793 D: 57210 Ptnml(0-2): 399, 13558, 30446, 13634, 427 https://tests.stockfishchess.org/tests/view/653285aade6d262d08d385dd Passed LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 50868 W: 12947 L: 12757 D: 25164 Ptnml(0-2): 30, 5414, 14355, 5606, 29 https://tests.stockfishchess.org/tests/view/65336ffbde6d262d08d39ba0 closes https://github.com/official-stockfish/Stockfish/pull/4837 bench: 1241996 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 43f0c872..43d78892 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1451,7 +1451,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (bestValue > alpha) alpha = bestValue; - futilityBase = std::min(ss->staticEval, bestValue) + 200; + futilityBase = ss->staticEval + 200; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, From 40c6a84434ea4600b5ebdd1020b34516317be1a9 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 18 Oct 2023 04:03:39 +0900 Subject: [PATCH 441/678] Fix a compiler bug on Clang 15+ with AVX-512 fixes https://github.com/official-stockfish/Stockfish/issues/4450 closes https://github.com/official-stockfish/Stockfish/pull/4830 No functional change. --- src/syzygy/tbprobe.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index c8e60ab6..31597f83 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -690,6 +690,17 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { return value + 1; } +// A temporary fix for the compiler bug with AVX-512. (#4450) +#ifdef USE_AVX512 + #if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15 + #define CLANG_AVX512_BUG_FIX __attribute__((optnone)) + #endif +#endif + +#ifndef CLANG_AVX512_BUG_FIX + #define CLANG_AVX512_BUG_FIX +#endif + // Compute a unique index out of a position and use it to probe the TB file. To // encode k pieces of the same type and color, first sort the pieces by square in // ascending order s1 <= s2 <= ... <= sk then compute the unique index as: @@ -697,7 +708,8 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { // idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] // template -Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { +CLANG_AVX512_BUG_FIX Ret +do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { Square squares[TBPIECES]; Piece pieces[TBPIECES]; From b1876222335df6581777baadc68fb5b17e5fe656 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 22 Oct 2023 16:43:33 +0200 Subject: [PATCH 442/678] use expanded variables for shell commands Performance improvement for the shell commands in the Makefile. By using expanded variables, the shell commands are only evaluated once, instead of every time they are used. closes https://github.com/official-stockfish/Stockfish/pull/4838 No functional change --- src/Makefile | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Makefile b/src/Makefile index 7b7ee41b..76ef6fde 100644 --- a/src/Makefile +++ b/src/Makefile @@ -20,9 +20,9 @@ ### ========================================================================== ### Establish the operating system name -KERNEL = $(shell uname -s) +KERNEL := $(shell uname -s) ifeq ($(KERNEL),Linux) - OS = $(shell uname -o) + OS := $(shell uname -o) endif ### Target Windows OS @@ -33,7 +33,7 @@ ifeq ($(OS),Windows_NT) else ifeq ($(COMP),mingw) target_windows = yes ifeq ($(WINE_PATH),) - WINE_PATH = $(shell which wine) + WINE_PATH := $(shell which wine) endif endif @@ -116,7 +116,7 @@ ifeq ($(ARCH),) endif ifeq ($(ARCH), native) - override ARCH = $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1) + override ARCH := $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1) endif # explicitly check for the list of supported architectures (as listed with make help), @@ -542,8 +542,8 @@ endif ### Sometimes gcc is really clang ifeq ($(COMP),gcc) - gccversion = $(shell $(CXX) --version 2>/dev/null) - gccisclang = $(findstring clang,$(gccversion)) + gccversion := $(shell $(CXX) --version 2>/dev/null) + gccisclang := $(findstring clang,$(gccversion)) ifneq ($(gccisclang),) profile_make = clang-profile-make profile_use = clang-profile-use @@ -601,7 +601,7 @@ ifeq ($(optimize),yes) endif ifeq ($(comp),clang) - clangmajorversion = $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) + clangmajorversion := $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) ifeq ($(shell expr $(clangmajorversion) \< 16),1) CXXFLAGS += -fexperimental-new-pass-manager endif @@ -717,13 +717,13 @@ ifeq ($(pext),yes) endif ### 3.8.1 Try to include git commit sha for versioning -GIT_SHA = $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) +GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) ifneq ($(GIT_SHA), ) CXXFLAGS += -DGIT_SHA=$(GIT_SHA) endif ### 3.8.2 Try to include git commit date for versioning -GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) +GIT_DATE := $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) ifneq ($(GIT_DATE), ) CXXFLAGS += -DGIT_DATE=$(GIT_DATE) endif From a105978bbde04508389abad03bd121f817f91646 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 22 Oct 2023 20:20:53 +0200 Subject: [PATCH 443/678] remove blank line between function and it's description - remove the blank line between the declaration of the function and it's comment, leads to better IDE support when hovering over a function to see it's description - remove the unnecessary duplication of the function name in the functions description - slightly refactored code for lsb, msb in bitboard.h There are still a few things we can be improved later on, move the description of a function where it was declared (instead of implemented) and add descriptions to functions which are behind macros ifdefs closes https://github.com/official-stockfish/Stockfish/pull/4840 No functional change --- src/benchmark.cpp | 3 +- src/bitboard.cpp | 12 ++-- src/bitboard.h | 96 +++++++++++++---------------- src/evaluate.cpp | 14 ++--- src/misc.cpp | 19 ++---- src/misc.h | 25 +++++--- src/movegen.cpp | 1 - src/movepick.cpp | 12 ++-- src/nnue/evaluate_nnue.cpp | 6 +- src/nnue/features/half_ka_v2_hm.cpp | 2 +- src/nnue/nnue_common.h | 12 ++-- src/position.cpp | 75 ++++++++-------------- src/search.cpp | 47 +++++--------- src/search.h | 8 +-- src/syzygy/tbprobe.cpp | 2 +- src/thread.cpp | 26 +++----- src/thread.h | 3 - src/timeman.cpp | 3 +- src/timeman.h | 1 - src/tt.cpp | 16 ++--- src/tt.h | 2 - src/types.h | 20 +++--- src/uci.cpp | 30 ++++----- src/ucioption.cpp | 11 ++-- 24 files changed, 175 insertions(+), 271 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 63598e75..2270dcc3 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -97,7 +97,7 @@ const std::vector Defaults = { namespace Stockfish { -// setup_bench() builds a list of UCI commands to be run by bench. There +// Builds a list of UCI commands to be run by bench. There // are five parameters: TT size in MB, number of search threads that // should be used, the limit value spent for each position, a file name // where to look for positions in FEN format, and the type of the limit: @@ -108,7 +108,6 @@ namespace Stockfish { // bench 64 1 100000 default nodes : search default positions for 100K nodes each // bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec // bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" - std::vector setup_bench(const Position& current, std::istream& is) { std::vector fens, list; diff --git a/src/bitboard.cpp b/src/bitboard.cpp index fff7eba9..a8a10cbb 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -46,18 +46,16 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[]); } -// safe_destination() returns the bitboard of target square for the given step +// Returns the bitboard of target square for the given step // from the given square. If the step is off the board, returns empty bitboard. - inline Bitboard safe_destination(Square s, int step) { Square to = Square(s + step); return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); } -// Bitboards::pretty() returns an ASCII representation of a bitboard suitable +// Returns an ASCII representation of a bitboard suitable // to be printed to standard output. Useful for debugging. - std::string Bitboards::pretty(Bitboard b) { std::string s = "+---+---+---+---+---+---+---+---+\n"; @@ -75,9 +73,8 @@ std::string Bitboards::pretty(Bitboard b) { } -// Bitboards::init() initializes various bitboard tables. It is called at +// Initializes various bitboard tables. It is called at // startup and relies on global objects to be already zero-initialized. - void Bitboards::init() { for (unsigned i = 0; i < (1 << 16); ++i) @@ -137,11 +134,10 @@ Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { } -// init_magics() computes all rook and bishop attacks at startup. Magic +// Computes all rook and bishop attacks at startup. Magic // bitboards are used to look up attacks of sliding pieces. As a reference see // www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so // called "fancy" approach. - void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { // Optimal PRNG seeds to pick the correct magics in the shortest time diff --git a/src/bitboard.h b/src/bitboard.h index 03a51136..24f6deca 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -125,8 +125,7 @@ constexpr Bitboard file_bb(File f) { return FileABB << f; } constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); } -// shift() moves a bitboard one or two steps as specified by the direction D - +// Moves a bitboard one or two steps as specified by the direction D template constexpr Bitboard shift(Bitboard b) { return D == NORTH ? b << 8 @@ -143,9 +142,8 @@ constexpr Bitboard shift(Bitboard b) { } -// pawn_attacks_bb() returns the squares attacked by pawns of the given color +// Returns the squares attacked by pawns of the given color // from the squares in the given bitboard. - template constexpr Bitboard pawn_attacks_bb(Bitboard b) { return C == WHITE ? shift(b) | shift(b) @@ -158,11 +156,10 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { return PawnAttacks[c][s]; } -// line_bb() returns a bitboard representing an entire line (from board edge +// Returns a bitboard representing an entire line (from board edge // to board edge) that intersects the two given squares. If the given squares // are not on a same file/rank/diagonal, the function returns 0. For instance, // line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. - inline Bitboard line_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); @@ -171,14 +168,13 @@ inline Bitboard line_bb(Square s1, Square s2) { } -// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open +// Returns a bitboard representing the squares in the semi-open // segment between the squares s1 and s2 (excluding s1 but including s2). If the // given squares are not on a same file/rank/diagonal, it returns s2. For instance, // between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but // between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick // allows to generate non-king evasion moves faster: the defending piece must either // interpose itself to cover the check or capture the checking piece. - inline Bitboard between_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); @@ -186,9 +182,8 @@ inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } -// aligned() returns true if the squares s1, s2 and s3 are aligned either on a +// Returns true if the squares s1, s2 and s3 are aligned either on a // straight or on a diagonal line. - inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } @@ -197,14 +192,17 @@ inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & template inline int distance(Square x, Square y); + template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } + template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } + template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; @@ -212,9 +210,8 @@ inline int distance(Square x, Square y) { inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -// attacks_bb(Square) returns the pseudo attacks of the given piece type +// Returns the pseudo attacks of the given piece type // assuming an empty board. - template inline Bitboard attacks_bb(Square s) { @@ -224,10 +221,9 @@ inline Bitboard attacks_bb(Square s) { } -// attacks_bb(Square, Bitboard) returns the attacks by the given piece +// Returns the attacks by the given piece // assuming the board is occupied according to the passed Bitboard. // Sliding piece attacks do not continue passed an occupied square. - template inline Bitboard attacks_bb(Square s, Bitboard occupied) { @@ -246,6 +242,9 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) { } } +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { assert((pt != PAWN) && (is_ok(s))); @@ -264,8 +263,7 @@ inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { } -// popcount() counts the number of non-zero bits in a bitboard - +// Counts the number of non-zero bits in a bitboard. inline int popcount(Bitboard b) { #ifndef USE_POPCNT @@ -287,43 +285,22 @@ inline int popcount(Bitboard b) { #endif } - -// lsb() and msb() return the least/most significant bit in a non-zero bitboard +// Returns the least significant bit in a non-zero bitboard. +inline Square lsb(Bitboard b) { + assert(b); #if defined(__GNUC__) // GCC, Clang, ICX -inline Square lsb(Bitboard b) { - assert(b); return Square(__builtin_ctzll(b)); -} - -inline Square msb(Bitboard b) { - assert(b); - return Square(63 ^ __builtin_clzll(b)); -} - -#elif defined(_MSC_VER) // MSVC +#elif defined(_MSC_VER) #ifdef _WIN64 // MSVC, WIN64 -inline Square lsb(Bitboard b) { - assert(b); unsigned long idx; _BitScanForward64(&idx, b); return (Square) idx; -} - -inline Square msb(Bitboard b) { - assert(b); - unsigned long idx; - _BitScanReverse64(&idx, b); - return (Square) idx; -} #else // MSVC, WIN32 - -inline Square lsb(Bitboard b) { - assert(b); unsigned long idx; if (b & 0xffffffff) @@ -336,10 +313,29 @@ inline Square lsb(Bitboard b) { _BitScanForward(&idx, int32_t(b >> 32)); return Square(idx + 32); } + #endif +#else // Compiler is neither GCC nor MSVC compatible + #error "Compiler not supported." +#endif } +// Returns the most significant bit in a non-zero bitboard. inline Square msb(Bitboard b) { assert(b); + +#if defined(__GNUC__) // GCC, Clang, ICX + + return Square(63 ^ __builtin_clzll(b)); + +#elif defined(_MSC_VER) + #ifdef _WIN64 // MSVC, WIN64 + + unsigned long idx; + _BitScanReverse64(&idx, b); + return (Square) idx; + + #else // MSVC, WIN32 + unsigned long idx; if (b >> 32) @@ -352,26 +348,20 @@ inline Square msb(Bitboard b) { _BitScanReverse(&idx, int32_t(b)); return Square(idx); } + #endif +#else // Compiler is neither GCC nor MSVC compatible + #error "Compiler not supported." +#endif } - #endif - -#else // Compiler is neither GCC nor MSVC compatible - - #error "Compiler not supported." - -#endif - -// least_significant_square_bb() returns the bitboard of the least significant +// Returns the bitboard of the least significant // square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). - inline Bitboard least_significant_square_bb(Bitboard b) { assert(b); return b & -b; } -// pop_lsb() finds and clears the least significant bit in a non-zero bitboard - +// Finds and clears the least significant bit in a non-zero bitboard. inline Square pop_lsb(Bitboard& b) { assert(b); const Square s = lsb(b); diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 00498bf0..4ee3e6fd 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -57,14 +57,13 @@ namespace Eval { std::string currentEvalFileName = "None"; -// NNUE::init() tries to load a NNUE network at startup time, or when the engine +// Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" // The name of the NNUE network is always retrieved from the EvalFile option. // We search the given network in three locations: internally (the default // network may be embedded in the binary), in the active working directory and // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY // variable to have the engine search in a special directory in their distro. - void NNUE::init() { std::string eval_file = std::string(Options["EvalFile"]); @@ -111,7 +110,7 @@ void NNUE::init() { } } -// NNUE::verify() verifies that the last net used was loaded successfully +// Verifies that the last net used was loaded successfully void NNUE::verify() { std::string eval_file = std::string(Options["EvalFile"]); @@ -145,19 +144,17 @@ void NNUE::verify() { } -// simple_eval() returns a static, purely materialistic evaluation of the position +// Returns a static, purely materialistic evaluation of the position // from the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. - Value Eval::simple_eval(const Position& pos, Color c) { return PawnValue * (pos.count(c) - pos.count(~c)) + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } -// evaluate() is the evaluator for the outer world. It returns a static evaluation +// Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. - Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); @@ -197,11 +194,10 @@ Value Eval::evaluate(const Position& pos) { return v; } -// trace() is like evaluate(), but instead of returning a value, it returns +// Like evaluate(), but instead of returning a value, it returns // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view - std::string Eval::trace(Position& pos) { if (pos.checkers()) diff --git a/src/misc.cpp b/src/misc.cpp index 05181325..3e900615 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -148,7 +148,7 @@ class Logger { } // namespace -// engine_info() returns the full name of the current Stockfish version. +// Returns the full name of the current Stockfish version. // For local dev compiles we try to append the commit sha and commit date // from git if that fails only the local compilation date is set and "nogit" is specified: // Stockfish dev-YYYYMMDD-SHA @@ -157,7 +157,6 @@ class Logger { // // For releases (non-dev builds) we only include the version number: // Stockfish version - std::string engine_info(bool to_uci) { std::stringstream ss; ss << "Stockfish " << version << std::setfill('0'); @@ -192,8 +191,7 @@ std::string engine_info(bool to_uci) { } -// compiler_info() returns a string trying to describe the compiler we use - +// Returns a string trying to describe the compiler we use std::string compiler_info() { #define make_version_string(major, minor, patch) \ @@ -397,7 +395,6 @@ void dbg_print() { // Used to serialize access to std::cout to avoid multiple threads writing at // the same time. - std::ostream& operator<<(std::ostream& os, SyncCout sc) { static std::mutex m; @@ -416,9 +413,6 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) { void start_logger(const std::string& fname) { Logger::start(fname); } -// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking -// function that doesn't stall the CPU waiting for data to be loaded from memory, -// which can be quite slow. #ifdef NO_PREFETCH void prefetch(void*) {} @@ -437,10 +431,9 @@ void prefetch(void* addr) { #endif -// std_aligned_alloc() is our wrapper for systems where the c++17 implementation +// Wrapper for systems where the c++17 implementation // does not guarantee the availability of aligned_alloc(). Memory allocated with // std_aligned_alloc() must be freed with std_aligned_free(). - void* std_aligned_alloc(size_t alignment, size_t size) { #if defined(POSIXALIGNEDALLOC) @@ -607,10 +600,9 @@ void bindThisThread(size_t) {} #else -// best_node() retrieves logical processor information using Windows specific +// Retrieves logical processor information using Windows specific // API and returns the best node id for the thread with index idx. Original // code from Texel by Peter Österlund. - static int best_node(size_t idx) { int threads = 0; @@ -679,8 +671,7 @@ static int best_node(size_t idx) { } -// bindThisThread() sets the group affinity of the current thread - +// Sets the group affinity of the current thread void bindThisThread(size_t idx) { // Use only local variables to be thread-safe diff --git a/src/misc.h b/src/misc.h index 3cd3315a..91fdb72f 100644 --- a/src/misc.h +++ b/src/misc.h @@ -33,13 +33,19 @@ namespace Stockfish { std::string engine_info(bool to_uci = false); std::string compiler_info(); -void prefetch(void* addr); -void start_logger(const std::string& fname); -void* std_aligned_alloc(size_t alignment, size_t size); -void std_aligned_free(void* ptr); -void* aligned_large_pages_alloc( - size_t size); // memory aligned by page size, min alignment: 4096 bytes -void aligned_large_pages_free(void* mem); // nop if mem == nullptr + +// Preloads the given address in L1/L2 cache. This is a non-blocking +// function that doesn't stall the CPU waiting for data to be loaded from memory, +// which can be quite slow. +void prefetch(void* addr); + +void start_logger(const std::string& fname); +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); +// memory aligned by page size, min alignment: 4096 bytes +void* aligned_large_pages_alloc(size_t size); +// nop if mem == nullptr +void aligned_large_pages_free(void* mem); void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); @@ -66,7 +72,7 @@ std::ostream& operator<<(std::ostream&, SyncCout); #define sync_endl std::endl << IO_UNLOCK -// align_ptr_up() : get the first aligned element of an array. +// Get the first aligned element of an array. // ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, // where N is the number of elements in the array. template @@ -79,7 +85,7 @@ T* align_ptr_up(T* ptr) { } -// IsLittleEndian : true if and only if the binary is compiled on a little-endian machine +// True if and only if the binary is compiled on a little-endian machine static inline const union { uint32_t i; char c[4]; @@ -166,7 +172,6 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { // cores. To overcome this, some special platform-specific API should be // called to set group affinity for each thread. Original code from Texel by // Peter Österlund. - namespace WinProcGroup { void bindThisThread(size_t idx); } diff --git a/src/movegen.cpp b/src/movegen.cpp index cf457d11..16da659d 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -241,7 +241,6 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { // except castling and promotions // // Returns a pointer to the end of the move list. - template ExtMove* generate(const Position& pos, ExtMove* moveList) { diff --git a/src/movepick.cpp b/src/movepick.cpp index 41ad0dd6..ff282262 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -58,7 +58,7 @@ enum Stages { QCHECK }; -// partial_insertion_sort() sorts moves in descending order up to and including +// Sort moves in descending order up to and including // a given limit. The order of moves smaller than the limit is left unspecified. void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { @@ -103,7 +103,7 @@ MovePicker::MovePicker(const Position& p, stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); } -// MovePicker constructor for quiescence search +// Constructor for quiescence search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, @@ -123,7 +123,7 @@ MovePicker::MovePicker(const Position& p, stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); } -// MovePicker constructor for ProbCut: we generate captures with SEE greater +// Constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : pos(p), @@ -136,7 +136,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); } -// MovePicker::score() assigns a numerical value to each move in a list, used +// Assigns a numerical value to each move in a list, used // for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring // captures with a good history. Quiets moves are ordered using the history tables. template @@ -216,7 +216,7 @@ void MovePicker::score() { } } -// MovePicker::select() returns the next move satisfying a predicate function. +// Returns the next move satisfying a predicate function. // It never returns the TT move. template Move MovePicker::select(Pred filter) { @@ -234,7 +234,7 @@ Move MovePicker::select(Pred filter) { return MOVE_NONE; } -// MovePicker::next_move() is the most important method of the MovePicker class. It +// Most important method of the MovePicker class. It // returns a new pseudo-legal move every time it is called until there are no more // moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 679192d4..ea53a510 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -233,7 +233,7 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. +// Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. static void format_cp_compact(Value v, char* buffer) { @@ -270,7 +270,7 @@ static void format_cp_compact(Value v, char* buffer) { } -// format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals +// Converts a Value into pawns, always keeping two decimals static void format_cp_aligned_dot(Value v, std::stringstream& stream) { const double pawns = std::abs(0.01 * UCI::to_cp(v)); @@ -282,7 +282,7 @@ static void format_cp_aligned_dot(Value v, std::stringstream& stream) { } -// trace() returns a string with the value of each piece on a board, +// Returns a string with the value of each piece on a board, // and a table for (PSQT, Layers) values bucket by bucket. std::string trace(Position& pos) { diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 6c3fdfdb..6d1b60ce 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -50,7 +50,7 @@ void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -// append_changed_indices() : get a list of indices for recently changed features +// Get a list of indices for recently changed features template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index f4c55e00..cf908501 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -85,7 +85,7 @@ constexpr IntType ceil_to_multiple(IntType n, IntType base) { } -// read_little_endian() is our utility to read an integer (signed or unsigned, any size) +// Utility to read an integer (signed or unsigned, any size) // from a stream in little-endian order. We swap the byte order after the read if // necessary to return a result with the byte ordering of the compiling machine. template @@ -110,7 +110,7 @@ inline IntType read_little_endian(std::istream& stream) { } -// write_little_endian() is our utility to write an integer (signed or unsigned, any size) +// Utility to write an integer (signed or unsigned, any size) // to a stream in little-endian order. We swap the byte order before the write if // necessary to always write in little endian order, independently of the byte // ordering of the compiling machine. @@ -141,7 +141,7 @@ inline void write_little_endian(std::ostream& stream, IntType value) { } -// read_little_endian(s, out, N) : read integers in bulk from a little indian stream. +// Read integers in bulk from a little indian stream. // This reads N integers from stream s and put them in array out. template inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { @@ -153,7 +153,7 @@ inline void read_little_endian(std::istream& stream, IntType* out, std::size_t c } -// write_little_endian(s, values, N) : write integers in bulk to a little indian stream. +// Write integers in bulk to a little indian stream. // This takes N integers from array values and writes them on stream s. template inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { @@ -165,7 +165,7 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std } -// read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in +// Read N signed integers from the stream s, putting them in // the array out. The stream is assumed to be compressed using the signed LEB128 format. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template @@ -215,7 +215,7 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) } -// write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression. +// Write signed integers to a stream with LEB128 compression. // This takes N integers from array values, compress them with the LEB128 algorithm and // writes the result on the stream s. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. diff --git a/src/position.cpp b/src/position.cpp index f7354b3d..37c586ab 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -61,8 +61,7 @@ constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, } // namespace -// operator<<(Position) returns an ASCII representation of the position - +// Returns an ASCII representation of the position std::ostream& operator<<(std::ostream& os, const Position& pos) { os << "\n +---+---+---+---+---+---+---+---+\n"; @@ -114,8 +113,7 @@ Key cuckoo[8192]; Move cuckooMove[8192]; -// Position::init() initializes at startup the various arrays used to compute hash keys - +// Initializes at startup the various arrays used to compute hash keys void Position::init() { PRNG rng(1070372); @@ -158,10 +156,9 @@ void Position::init() { } -// Position::set() initializes the position object with the given FEN string. +// Initializes the position object with the given FEN string. // This function is not very robust - make sure that input FENs are correct, // this is assumed to be the responsibility of the GUI. - Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { /* A FEN string defines a particular position using only the ASCII character set. @@ -298,9 +295,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th } -// Position::set_castling_right() is a helper function used to set castling +// Helper function used to set castling // rights given the corresponding color and the rook starting square. - void Position::set_castling_right(Color c, Square rfrom) { Square kfrom = square(c); @@ -318,8 +314,7 @@ void Position::set_castling_right(Color c, Square rfrom) { } -// Position::set_check_info() sets king attacks to detect if a move gives check - +// Sets king attacks to detect if a move gives check void Position::set_check_info() const { update_slider_blockers(WHITE); @@ -336,10 +331,9 @@ void Position::set_check_info() const { } -// Position::set_state() computes the hash keys of the position, and other +// Computes the hash keys of the position, and other // data that once computed is updated incrementally as moves are made. // The function is only used when a new position is set up - void Position::set_state() const { st->key = st->materialKey = 0; @@ -372,10 +366,9 @@ void Position::set_state() const { } -// Position::set() is an overload to initialize the position object with +// Overload to initialize the position object with // the given endgame code string like "KBPKN". It is mainly a helper to // get the material key out of an endgame code. - Position& Position::set(const string& code, Color c, StateInfo* si) { assert(code[0] == 'K'); @@ -395,9 +388,8 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { } -// Position::fen() returns a FEN representation of the position. In case of +// Returns a FEN representation of the position. In case of // Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. - string Position::fen() const { int emptyCnt; @@ -444,7 +436,7 @@ string Position::fen() const { return ss.str(); } -// update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], +// Calculates st->blockersForKing[c] and st->pinners[~c], // which store respectively the pieces preventing king of color c from being in check // and the slider pieces of color ~c pinning pieces of color c to the king. void Position::update_slider_blockers(Color c) const { @@ -475,9 +467,8 @@ void Position::update_slider_blockers(Color c) const { } -// Position::attackers_to() computes a bitboard of all pieces which attack a +// Computes a bitboard of all pieces which attack a // given square. Slider attacks use the occupied bitboard to indicate occupancy. - Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) @@ -489,8 +480,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { } -// Position::legal() tests whether a pseudo-legal move is legal - +// Tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { assert(is_ok(m)); @@ -549,10 +539,9 @@ bool Position::legal(Move m) const { } -// Position::pseudo_legal() takes a random move and tests whether the move is +// Takes a random move and tests whether the move is // pseudo-legal. It is used to validate moves from TT that can be corrupted // due to SMP concurrent access or hash position key aliasing. - bool Position::pseudo_legal(const Move m) const { Color us = sideToMove; @@ -620,8 +609,7 @@ bool Position::pseudo_legal(const Move m) const { } -// Position::gives_check() tests whether a pseudo-legal move gives a check - +// Tests whether a pseudo-legal move gives a check bool Position::gives_check(Move m) const { assert(is_ok(m)); @@ -669,10 +657,9 @@ bool Position::gives_check(Move m) const { } -// Position::do_move() makes a move, and saves all information necessary +// Makes a move, and saves all information necessary // to a StateInfo object. The move is assumed to be legal. Pseudo-legal // moves should be filtered out before this function is called. - void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(is_ok(m)); @@ -867,9 +854,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } -// Position::undo_move() unmakes a move. When it returns, the position should +// Unmakes a move. When it returns, the position should // be restored to exactly the same state as before the move was made. - void Position::undo_move(Move m) { assert(is_ok(m)); @@ -931,7 +917,7 @@ void Position::undo_move(Move m) { } -// Position::do_castling() is a helper used to do/undo a castling move. This +// Helper used to do/undo a castling move. This // is a bit tricky in Chess960 where from/to squares can overlap. template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { @@ -963,9 +949,8 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ } -// Position::do_null_move() is used to do a "null move": it flips +// Used to do a "null move": it flips // the side to move without executing any move on the board. - void Position::do_null_move(StateInfo& newSt) { assert(!checkers()); @@ -1003,8 +988,7 @@ void Position::do_null_move(StateInfo& newSt) { } -// Position::undo_null_move() must be used to undo a "null move" - +// Must be used to undo a "null move" void Position::undo_null_move() { assert(!checkers()); @@ -1014,10 +998,9 @@ void Position::undo_null_move() { } -// Position::key_after() computes the new hash key after the given move. Needed +// Computes the new hash key after the given move. Needed // for speculative prefetch. It doesn't recognize special moves like castling, // en passant and promotions. - Key Position::key_after(Move m) const { Square from = from_sq(m); @@ -1035,10 +1018,9 @@ Key Position::key_after(Move m) const { } -// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the -// SEE value of move is greater or equal to the given threshold. We'll use an +// Tests if the SEE (Static Exchange Evaluation) +// value of move is greater or equal to the given threshold. We'll use an // algorithm similar to alpha-beta pruning with a null window. - bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); @@ -1140,9 +1122,8 @@ bool Position::see_ge(Move m, Value threshold) const { return bool(res); } -// Position::is_draw() tests whether the position is drawn by 50-move rule +// Tests whether the position is drawn by 50-move rule // or by repetition. It does not detect stalemates. - bool Position::is_draw(int ply) const { if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) @@ -1154,9 +1135,8 @@ bool Position::is_draw(int ply) const { } -// Position::has_repeated() tests whether there has been at least one repetition +// Tests whether there has been at least one repetition // of positions since the last capture or pawn move. - bool Position::has_repeated() const { StateInfo* stc = st; @@ -1172,9 +1152,8 @@ bool Position::has_repeated() const { } -// Position::has_game_cycle() tests if the position has a move which draws by repetition, +// Tests if the position has a move which draws by repetition, // or an earlier position has a move that directly reaches the current position. - bool Position::has_game_cycle(int ply) const { int j; @@ -1220,9 +1199,8 @@ bool Position::has_game_cycle(int ply) const { } -// Position::flip() flips position with the white and black sides reversed. This +// Flips position with the white and black sides reversed. This // is only useful for debugging e.g. for finding evaluation symmetry bugs. - void Position::flip() { string f, token; @@ -1255,10 +1233,9 @@ void Position::flip() { } -// Position::pos_is_ok() performs some consistency checks for the +// Performs some consistency checks for the // position object and raise an assert if something wrong is detected. // This is meant to be helpful when debugging. - bool Position::pos_is_ok() const { constexpr bool Fast = true; // Quick (default) or full check? diff --git a/src/search.cpp b/src/search.cpp index 43d78892..933cd154 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -148,7 +148,7 @@ void update_all_stats(const Position& pos, int captureCount, Depth depth); -// perft() is our utility to verify move generation. All the leaf nodes up +// Utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. template uint64_t perft(Position& pos, Depth depth) { @@ -179,8 +179,7 @@ uint64_t perft(Position& pos, Depth depth) { } // namespace -// Search::init() is called at startup to initialize various lookup tables - +// Called at startup to initialize various lookup tables void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) @@ -188,8 +187,7 @@ void Search::init() { } -// Search::clear() resets search state to its initial value - +// Resets search state to its initial value void Search::clear() { Threads.main()->wait_for_search_finished(); @@ -201,9 +199,8 @@ void Search::clear() { } -// MainThread::search() is started when the program receives the UCI 'go' +// Called when the program receives the UCI 'go' // command. It searches from the root position and outputs the "bestmove". - void MainThread::search() { if (Limits.perft) @@ -277,10 +274,9 @@ void MainThread::search() { } -// Thread::search() is the main iterative deepening loop. It calls search() +// Main iterative deepening loop. It calls search() // repeatedly with increasing depth until the allocated thinking time has been // consumed, the user stops the search, or the maximum search depth is reached. - void Thread::search() { // Allocate stack with extra size to allow access from (ss-7) to (ss+2): @@ -521,8 +517,7 @@ void Thread::search() { namespace { -// search<>() is the main search function for both PV and non-PV nodes - +// Main search function for both PV and non-PV nodes template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { @@ -1346,7 +1341,7 @@ moves_loop: // When in check, search starts here } -// qsearch() is the quiescence search function, which is called by the main search +// Quiescence search function, which is called by the main search // function with zero depth, or recursively with further decreasing depth per call. // (~155 Elo) template @@ -1593,10 +1588,9 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { } -// value_to_tt() adjusts a mate or TB score from "plies to mate from the root" +// Adjusts a mate or TB score from "plies to mate from the root" // to "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. - Value value_to_tt(Value v, int ply) { assert(v != VALUE_NONE); @@ -1605,12 +1599,11 @@ Value value_to_tt(Value v, int ply) { } -// value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score +// Inverse of value_to_tt(): it adjusts a mate or TB score // from the transposition table (which refers to the plies to mate/be mated from // current position) to "plies to mate/be mated (TB win/loss) from the root". // However, to avoid potentially false mate scores related to the 50 moves rule // and the graph history interaction problem, we return an optimal TB score instead. - Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) @@ -1636,8 +1629,7 @@ Value value_from_tt(Value v, int ply, int r50c) { } -// update_pv() adds current move and appends child pv[] - +// Adds current move and appends child pv[] void update_pv(Move* pv, Move move, const Move* childPv) { for (*pv++ = move; childPv && *childPv != MOVE_NONE;) @@ -1646,8 +1638,7 @@ void update_pv(Move* pv, Move move, const Move* childPv) { } -// update_all_stats() updates stats at the end of search() when a bestMove is found - +// Updates stats at the end of search() when a bestMove is found void update_all_stats(const Position& pos, Stack* ss, Move bestMove, @@ -1709,9 +1700,8 @@ void update_all_stats(const Position& pos, } -// update_continuation_histories() updates histories of the move pairs formed +// Updates histories of the move pairs formed // by moves at ply -1, -2, -4, and -6 with current move. - void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { for (int i : {1, 2, 3, 4, 6}) @@ -1725,8 +1715,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { } -// update_quiet_stats() updates move sorting heuristics - +// Updates move sorting heuristics void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { // Update killers @@ -1751,7 +1740,6 @@ void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { // When playing with strength handicap, choose the best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - Move Skill::pick_best(size_t multiPV) { const RootMoves& rootMoves = Threads.main()->rootMoves; @@ -1786,9 +1774,8 @@ Move Skill::pick_best(size_t multiPV) { } // namespace -// MainThread::check_time() is used to print debug info and, more importantly, +// Used to print debug info and, more importantly, // to detect when we are out of available time and thus stop the search. - void MainThread::check_time() { if (--callsCnt > 0) @@ -1819,9 +1806,8 @@ void MainThread::check_time() { } -// UCI::pv() formats PV information according to the UCI protocol. UCI requires +// Formats PV information according to the UCI protocol. UCI requires // that all (if any) unsearched PV lines are sent using a previous search score. - string UCI::pv(const Position& pos, Depth depth) { std::stringstream ss; @@ -1874,11 +1860,10 @@ string UCI::pv(const Position& pos, Depth depth) { } -// RootMove::extract_ponder_from_tt() is called in case we have no ponder move +// Called in case we have no ponder move // before exiting the search, for instance, in case we stop the search during a // fail high at root. We try hard to have a ponder move to return to the GUI, // otherwise in case of 'ponder on' we have nothing to think about. - bool RootMove::extract_ponder_from_tt(Position& pos) { StateInfo st; diff --git a/src/search.h b/src/search.h index 37cd5e5a..b2d22e61 100644 --- a/src/search.h +++ b/src/search.h @@ -36,7 +36,6 @@ namespace Search { // Stack struct keeps track of the information we need to remember from nodes // shallower and deeper in the tree during the search. Each search thread has // its own array of Stack objects, indexed by the current ply. - struct Stack { Move* pv; PieceToHistory* continuationHistory; @@ -58,14 +57,14 @@ struct Stack { // RootMove struct is used for moves at the root of the tree. For each root move // we store a score and a PV (really a refutation in the case of moves which // fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. - struct RootMove { explicit RootMove(Move m) : pv(1, m) {} bool extract_ponder_from_tt(Position& pos); bool operator==(const Move& m) const { return pv[0] == m; } - bool operator<(const RootMove& m) const { // Sort in descending order + // Sort in descending order + bool operator<(const RootMove& m) const { return m.score != score ? m.score < score : m.previousScore < previousScore; } @@ -89,7 +88,8 @@ using RootMoves = std::vector; struct LimitsType { - LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); movestogo = depth = mate = perft = infinite = 0; nodes = 0; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 31597f83..e2363157 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1317,7 +1317,7 @@ WDLScore search(Position& pos, ProbeState* result) { } // namespace -// Tablebases::init() is called at startup and after every change to +// Called at startup and after every change to // "SyzygyPath" UCI option to (re)create the various tables. It is not thread // safe, nor it needs to be. void Tablebases::init(const std::string& paths) { diff --git a/src/thread.cpp b/src/thread.cpp index 9f8a63bd..fdf89095 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -40,9 +40,8 @@ namespace Stockfish { ThreadPool Threads; // Global object -// Thread constructor launches the thread and waits until it goes to sleep +// Constructor launches the thread and waits until it goes to sleep // in idle_loop(). Note that 'searching' and 'exit' should be already set. - Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { @@ -51,9 +50,8 @@ Thread::Thread(size_t n) : } -// Thread destructor wakes up the thread in idle_loop() and waits +// Destructor wakes up the thread in idle_loop() and waits // for its termination. Thread should be already waiting. - Thread::~Thread() { assert(!searching); @@ -64,8 +62,7 @@ Thread::~Thread() { } -// Thread::clear() reset histories, usually before a new game - +// Reset histories, usually before a new game void Thread::clear() { counterMoves.fill(MOVE_NONE); @@ -80,8 +77,7 @@ void Thread::clear() { } -// Thread::start_searching() wakes up the thread that will start the search - +// Wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); searching = true; @@ -90,9 +86,8 @@ void Thread::start_searching() { } -// Thread::wait_for_search_finished() blocks on the condition variable +// Blocks on the condition variable // until the thread has finished searching. - void Thread::wait_for_search_finished() { std::unique_lock lk(mutex); @@ -100,7 +95,7 @@ void Thread::wait_for_search_finished() { } -// Thread::idle_loop() is where the thread is parked, blocked on the +// Thread gets parked here, blocked on the // condition variable, when it has no work to do. void Thread::idle_loop() { @@ -129,10 +124,9 @@ void Thread::idle_loop() { } } -// ThreadPool::set() creates/destroys threads to match the requested number. +// Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. - void ThreadPool::set(size_t requested) { if (threads.size() > 0) // destroy any existing thread(s) @@ -160,8 +154,7 @@ void ThreadPool::set(size_t requested) { } -// ThreadPool::clear() sets threadPool data to initial values - +// Sets threadPool data to initial values void ThreadPool::clear() { for (Thread* th : threads) @@ -174,9 +167,8 @@ void ThreadPool::clear() { } -// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and +// Wakes up main thread waiting in idle_loop() and // returns immediately. Main thread will wake up other threads and start the search. - void ThreadPool::start_thinking(Position& pos, StateListPtr& states, const Search::LimitsType& limits, diff --git a/src/thread.h b/src/thread.h index 4a077962..5f33b736 100644 --- a/src/thread.h +++ b/src/thread.h @@ -38,7 +38,6 @@ namespace Stockfish { // per-thread pawn and material hash tables so that once we get a // pointer to an entry its lifetime is unlimited and we don't have // to care about someone changing the entry under our feet. - class Thread { std::mutex mutex; @@ -76,7 +75,6 @@ class Thread { // MainThread is a derived class specific for main thread - struct MainThread: public Thread { using Thread::Thread; @@ -97,7 +95,6 @@ struct MainThread: public Thread { // ThreadPool struct handles all the threads-related stuff like init, starting, // parking and, most importantly, launching a thread. All the access to threads // is done through this class. - struct ThreadPool { void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); diff --git a/src/timeman.cpp b/src/timeman.cpp index cf0e08ed..7e77a4ad 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -29,11 +29,10 @@ namespace Stockfish { TimeManagement Time; // Our global time management object -// TimeManagement::init() is called at the beginning of the search and calculates +// Called at the beginning of the search and calculates // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) - void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // If we have no time, no need to initialize TM, except for the start time, diff --git a/src/timeman.h b/src/timeman.h index 4b9b62bd..6c56d506 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -30,7 +30,6 @@ namespace Stockfish { // The TimeManagement class computes the optimal time to think depending on // the maximum available time, the game move number, and other parameters. - class TimeManagement { public: void init(Search::LimitsType& limits, Color us, int ply); diff --git a/src/tt.cpp b/src/tt.cpp index a3ad0a78..816d43f8 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -33,9 +33,8 @@ namespace Stockfish { TranspositionTable TT; // Our global transposition table -// TTEntry::save() populates the TTEntry with a new node's data, possibly +// Populates the TTEntry with a new node's data, possibly // overwriting an old position. The update is not atomic and can be racy. - void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { // Preserve any existing move for the same position @@ -57,10 +56,9 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) } -// TranspositionTable::resize() sets the size of the transposition table, +// Sets the size of the transposition table, // measured in megabytes. Transposition table consists of a power of 2 number // of clusters and each cluster consists of ClusterSize number of TTEntry. - void TranspositionTable::resize(size_t mbSize) { Threads.main()->wait_for_search_finished(); @@ -80,9 +78,8 @@ void TranspositionTable::resize(size_t mbSize) { } -// TranspositionTable::clear() initializes the entire transposition table to zero, -// in a multi-threaded way. - +// Initializes the entire transposition table to zero, +// in a multi-threaded way. void TranspositionTable::clear() { std::vector threads; @@ -109,13 +106,12 @@ void TranspositionTable::clear() { } -// TranspositionTable::probe() looks up the current position in the transposition +// Looks up the current position in the transposition // table. It returns true and a pointer to the TTEntry if the position is found. // Otherwise, it returns false and a pointer to an empty or least valuable TTEntry // to be replaced later. The replace value of an entry is calculated as its depth // minus 8 times its relative age. TTEntry t1 is considered more valuable than // TTEntry t2 if its replace value is greater than that of t2. - TTEntry* TranspositionTable::probe(const Key key, bool& found) const { TTEntry* const tte = first_entry(key); @@ -148,7 +144,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { } -// TranspositionTable::hashfull() returns an approximation of the hashtable +// Returns an approximation of the hashtable // occupation during a search. The hash is x permill full, as per UCI protocol. int TranspositionTable::hashfull() const { diff --git a/src/tt.h b/src/tt.h index 628dfba2..12fedd2d 100644 --- a/src/tt.h +++ b/src/tt.h @@ -37,7 +37,6 @@ namespace Stockfish { // move 16 bit // value 16 bit // eval value 16 bit - struct TTEntry { Move move() const { return Move(move16); } @@ -65,7 +64,6 @@ struct TTEntry { // contains information on exactly one position. The size of a Cluster should // divide the size of a cache line for best performance, as the cacheline is // prefetched when possible. - class TranspositionTable { static constexpr int ClusterSize = 3; diff --git a/src/types.h b/src/types.h index c76efd07..7ac2f849 100644 --- a/src/types.h +++ b/src/types.h @@ -321,21 +321,17 @@ constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d inline Square& operator+=(Square& s, Direction d) { return s = s + d; } inline Square& operator-=(Square& s, Direction d) { return s = s - d; } -constexpr Color operator~(Color c) { - return Color(c ^ BLACK); // Toggle color -} +// Toggle color +constexpr Color operator~(Color c) { return Color(c ^ BLACK); } -constexpr Square flip_rank(Square s) { // Swap A1 <-> A8 - return Square(s ^ SQ_A8); -} +// Swap A1 <-> A8 +constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); } -constexpr Square flip_file(Square s) { // Swap A1 <-> H1 - return Square(s ^ SQ_H1); -} +// Swap A1 <-> H1 +constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); } -constexpr Piece operator~(Piece pc) { - return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT -} +// Swap color of piece B_KNIGHT <-> W_KNIGHT +constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); } constexpr CastlingRights operator&(Color c, CastlingRights cr) { return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); diff --git a/src/uci.cpp b/src/uci.cpp index 0671cb5f..1d8f5bdc 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -49,11 +49,10 @@ namespace { const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -// position() is called when the engine receives the "position" UCI command. +// Called when the engine receives the "position" UCI command. // It sets up the position that is described in the given FEN string ("fen") or // the initial position ("startpos") and then makes the moves given in the following // move list ("moves"). - void position(Position& pos, std::istringstream& is, StateListPtr& states) { Move m; @@ -83,9 +82,8 @@ void position(Position& pos, std::istringstream& is, StateListPtr& states) { } } -// trace_eval() prints the evaluation of the current position, consistent with +// Prints the evaluation of the current position, consistent with // the UCI options set so far. - void trace_eval(Position& pos) { StateListPtr states(new std::deque(1)); @@ -98,7 +96,7 @@ void trace_eval(Position& pos) { } -// setoption() is called when the engine receives the "setoption" UCI command. +// Called when the engine receives the "setoption" UCI command. // The function updates the UCI option ("name") to the given value ("value"). void setoption(std::istringstream& is) { @@ -124,7 +122,7 @@ void setoption(std::istringstream& is) { } -// go() is called when the engine receives the "go" UCI command. The function +// Called when the engine receives the "go" UCI command. The function // sets the thinking time and other parameters from the input string, then starts // with a search. @@ -170,7 +168,7 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) { } -// bench() is called when the engine receives the "bench" command. +// Called when the engine receives the "bench" command. // First, a list of UCI commands is set up according to the bench // parameters, then it is run one by one, printing a summary at the end. @@ -252,12 +250,11 @@ int win_rate_model(Value v, int ply) { } // namespace -// UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate +// Waits for a command from the stdin, parses it, and then calls the appropriate // function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a // graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, // like running 'bench', the function returns immediately after the command is executed. // In addition to the UCI ones, some additional debug commands are also supported. - void UCI::loop(int argc, char* argv[]) { Position pos; @@ -346,12 +343,11 @@ void UCI::loop(int argc, char* argv[]) { // without treatment of mate and similar special scores. int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; } -// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: +// Converts a Value to a string by adhering to the UCI protocol specification: // // cp The score from the engine's point of view in centipawns. // mate Mate in 'y' moves (not plies). If the engine is getting mated, // uses negative values for 'y'. - std::string UCI::value(Value v) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); @@ -372,9 +368,8 @@ std::string UCI::value(Value v) { } -// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation +// Reports the win-draw-loss (WDL) statistics given an evaluation // and a game ply based on the data gathered for fishtest LTC games. - std::string UCI::wdl(Value v, int ply) { std::stringstream ss; @@ -388,18 +383,16 @@ std::string UCI::wdl(Value v, int ply) { } -// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) - +// Converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } -// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). +// Converts a Move to a string in coordinate notation (g1f3, a7a8q). // The only special case is castling where the e1g1 notation is printed in // standard chess mode and in e1h1 notation it is printed in Chess960 mode. // Internally, all castling moves are always encoded as 'king captures rook'. - std::string UCI::move(Move m, bool chess960) { if (m == MOVE_NONE) @@ -423,9 +416,8 @@ std::string UCI::move(Move m, bool chess960) { } -// UCI::to_move() converts a string representing a move in coordinate notation +// Converts a string representing a move in coordinate notation // (g1f3, a7a8q) to the corresponding legal Move, if any. - Move UCI::to_move(const Position& pos, std::string& str) { if (str.length() == 5) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 8db4233a..d0db1c76 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -60,8 +60,7 @@ bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const { } -// UCI::init() initializes the UCI options to their hard-coded default values - +// Initializes the UCI options to their hard-coded default values void init(OptionsMap& o) { constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; @@ -89,9 +88,8 @@ void init(OptionsMap& o) { } -// operator<<() is used to print all the options default values in chronological +// Used to print all the options default values in chronological // insertion order (the idx field) and in the format defined by the UCI protocol. - std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { for (size_t idx = 0; idx < om.size(); ++idx) @@ -172,7 +170,7 @@ bool Option::operator==(const char* s) const { } -// operator<<() inits options and assigns idx in the correct printing order +// Inits options and assigns idx in the correct printing order void Option::operator<<(const Option& o) { @@ -183,10 +181,9 @@ void Option::operator<<(const Option& o) { } -// operator=() updates currentValue and triggers on_change() action. It's up to +// 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. - Option& Option::operator=(const string& v) { assert(!type.empty()); From cf3dbcb5acd66efaaa84fa1e24ce7afb062fba08 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 21 Oct 2023 17:01:45 +0800 Subject: [PATCH 444/678] Time management improvements 1. Tune time management parameters. 2. Scale the optimum time and maximum time parameters based on the amount of time left, using a logarithmic scale. Many acknowledgements to @FauziAkram for tuning the parameters and for the original idea (see https://tests.stockfishchess.org/tests/view/652f0356de6d262d08d333c5). STC: https://tests.stockfishchess.org/tests/view/6533938fde6d262d08d39e4d LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 44320 W: 11301 L: 10982 D: 22037 Ptnml(0-2): 146, 4810, 11920, 5147, 137 LTC: https://tests.stockfishchess.org/tests/view/653477e4de6d262d08d3ae06 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 146442 W: 37338 L: 36811 D: 72293 Ptnml(0-2): 60, 14975, 42645, 15460, 81 Verification runs: 3+0.03: https://tests.stockfishchess.org/tests/view/65364e7ef127f3553505178a 10+0: https://tests.stockfishchess.org/tests/view/65364e9ff127f3553505178f 180+1.8: https://tests.stockfishchess.org/tests/view/65364ec3f127f35535051794 closes https://github.com/official-stockfish/Stockfish/pull/4843 No functional change. Co-Authored-By: FauziAkram <11150271+FauziAkram@users.noreply.github.com> --- src/search.cpp | 10 +++++----- src/timeman.cpp | 14 +++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 933cd154..d5416e40 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -467,15 +467,15 @@ void Thread::search() { // Do we have time for the next iteration? Can we stop searching now? if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + 6 * (mainThread->iterValue[iterIdx] - bestValue)) - / 619.6; + / 583.0; fallingEval = std::clamp(fallingEval, 0.5, 1.5); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); - double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.03 * timeReduction); + double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / Threads.size(); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; diff --git a/src/timeman.cpp b/src/timeman.cpp index 7e77a4ad..1253d434 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -72,7 +72,11 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - moveOverhead * (2 + mtg)); // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12); + double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.12); + + // Calculate time constants based on current time left. + double optConstant = std::min(0.00335 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0048); + double maxConstant = std::max(3.6 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.7); // A user may scale time usage by setting UCI option "Slow Mover" // Default is 100 and changing this value will probably lose elo. @@ -83,10 +87,10 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // game time for the current move, so also cap to 20% of available game time. if (limits.movestogo == 0) { - optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039, + optScale = std::min(0.0120 + std::pow(ply + 3.3, 0.44) * optConstant, 0.2 * limits.time[us] / double(timeLeft)) * optExtra; - maxScale = std::min(7.0, 4.0 + ply / 12.0); + maxScale = std::min(6.8, maxConstant + ply / 12.2); } // x moves in y seconds (+ z increment) @@ -96,10 +100,10 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { maxScale = std::min(6.3, 1.5 + 0.11 * mtg); } - // Never use more than 80% of the available time for this move + // Limit the maximum possible time for this move optimumTime = TimePoint(optScale * timeLeft); maximumTime = - TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; if (Options["Ponder"]) optimumTime += optimumTime / 4; From 49ece9f791b84a261f2a8865d2de51c20a520bc6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 23 Oct 2023 19:42:08 +0200 Subject: [PATCH 445/678] Follow up Makefile changes for clang-format as reported on various OS, these changes help portability closes https://github.com/official-stockfish/Stockfish/pull/4844 No functional change. --- src/Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Makefile b/src/Makefile index 76ef6fde..59ea7bfe 100644 --- a/src/Makefile +++ b/src/Makefile @@ -153,7 +153,7 @@ dotprod = no arm_version = 0 STRIP = strip -ifneq ($(shell command -v clang-format-17),) +ifneq ($(shell which clang-format-17 2> /dev/null),) CLANG-FORMAT = clang-format-17 else CLANG-FORMAT = clang-format @@ -854,7 +854,8 @@ endif objclean profileclean config-sanity \ icx-profile-use icx-profile-make \ gcc-profile-use gcc-profile-make \ - clang-profile-use clang-profile-make FORCE + clang-profile-use clang-profile-make FORCE \ + format analyze analyze: net config-sanity objclean $(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS) @@ -951,7 +952,7 @@ net: netvariables fi; \ format: - $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file:../.clang-format + $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file # default target default: From d6a5c2b0852e0fe7efff1ef7e8bd6d94f962bf8e Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 24 Oct 2023 07:43:49 +0800 Subject: [PATCH 446/678] Small formatting improvements Changes some C style casts to C++ style, and fixes some incorrect comments and variable names. closes #4845 No functional change --- src/bitboard.h | 4 ++-- src/nnue/evaluate_nnue.cpp | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/nnue_common.h | 4 ++-- src/search.cpp | 20 ++++++++++---------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 24f6deca..7dbd5329 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -298,7 +298,7 @@ inline Square lsb(Bitboard b) { unsigned long idx; _BitScanForward64(&idx, b); - return (Square) idx; + return Square(idx); #else // MSVC, WIN32 unsigned long idx; @@ -332,7 +332,7 @@ inline Square msb(Bitboard b) { unsigned long idx; _BitScanReverse64(&idx, b); - return (Square) idx; + return Square(idx); #else // MSVC, WIN32 diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index ea53a510..e29d3c17 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -116,7 +116,7 @@ static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::str static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { write_little_endian(stream, Version); write_little_endian(stream, hashValue); - write_little_endian(stream, (std::uint32_t) desc.size()); + write_little_endian(stream, std::uint32_t(desc.size())); stream.write(&desc[0], desc.size()); return !stream.fail(); } diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 987de892..f8e2d497 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -93,7 +93,7 @@ class SqrClippedReLU { output[i] = static_cast( // Really should be /127 but we need to make it fast so we right shift // by an extra 7 bits instead. Needs to be accounted for in the trainer. - std::min(127ll, ((long long) input[i] * input[i]) >> (2 * WeightScaleBits + 7))); + std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7))); } } }; diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index cf908501..f9cd7fbb 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -130,11 +130,11 @@ inline void write_little_endian(std::ostream& stream, IntType value) { { for (; i + 1 < sizeof(IntType); ++i) { - u[i] = (std::uint8_t) v; + u[i] = std::uint8_t(v); v >>= 8; } } - u[i] = (std::uint8_t) v; + u[i] = std::uint8_t(v); stream.write(reinterpret_cast(u), sizeof(IntType)); } diff --git a/src/search.cpp b/src/search.cpp index d5416e40..4b390713 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -279,9 +279,9 @@ void MainThread::search() { // consumed, the user stops the search, or the maximum search depth is reached. void Thread::search() { - // Allocate stack with extra size to allow access from (ss-7) to (ss+2): - // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6), - // (ss+2) is needed for initialization of statScore and killers. + // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): + // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), + // (ss + 2) is needed for initialization of cutOffCnt and killers. Stack stack[MAX_PLY + 10], *ss = stack + 7; Move pv[MAX_PLY + 1]; Value alpha, beta, delta; @@ -363,13 +363,13 @@ void Thread::search() { selDepth = 0; // Reset aspiration window starting size - Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 17470; - alpha = std::max(prev - delta, -VALUE_INFINITE); - beta = std::min(prev + delta, VALUE_INFINITE); + Value avg = rootMoves[pvIdx].averageScore; + delta = Value(10) + int(avg) * avg / 17470; + alpha = std::max(avg - delta, -VALUE_INFINITE); + beta = std::min(avg + delta, VALUE_INFINITE); - // Adjust optimism based on root move's previousScore (~4 Elo) - int opt = 113 * prev / (std::abs(prev) + 109); + // Adjust optimism based on root move's averageScore (~4 Elo) + int opt = 113 * avg / (std::abs(avg) + 109); optimism[us] = Value(opt); optimism[~us] = -optimism[us]; @@ -582,7 +582,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo : value_draw(pos.this_thread()); // Step 3. Mate distance pruning. Even if we mate at the next move our score - // would be at best mate_in(ss->ply+1), but if alpha is already bigger because + // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because // a shorter mate was found upward in the tree then there is no need to search // because we will never beat the current alpha. Same logic but with reversed // signs apply also in the opposite condition of being mated instead of giving From ec02714b6262e26d6f96c45c4e2527f3d382a9f8 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 23 Oct 2023 22:49:37 +0200 Subject: [PATCH 447/678] Cleanup comments and some code reorg. passed STC: https://tests.stockfishchess.org/tests/view/6536dc7dcc309ae83955b04d LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 58048 W: 14693 L: 14501 D: 28854 Ptnml(0-2): 200, 6399, 15595, 6669, 161 closes https://github.com/official-stockfish/Stockfish/pull/4846 No functional change --- src/evaluate.cpp | 10 ++++---- src/movepick.cpp | 8 +++--- src/nnue/evaluate_nnue.cpp | 4 +-- src/nnue/layers/affine_transform.h | 3 ++- src/nnue/nnue_architecture.h | 4 +-- src/nnue/nnue_feature_transformer.h | 6 +++-- src/search.cpp | 39 ++++++++++++++++------------- src/uci.cpp | 5 ++-- 8 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4ee3e6fd..c405cfb5 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -123,11 +123,11 @@ void NNUE::verify() { std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; - std::string msg3 = - "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - std::string msg4 = - "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" - + std::string(EvalFileDefaultName); + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + std::string(EvalFileDefaultName); std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; diff --git a/src/movepick.cpp b/src/movepick.cpp index ff282262..d2a49706 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -263,11 +263,9 @@ top: case GOOD_CAPTURE : if (select([&]() { - return pos.see_ge(*cur, Value(-cur->value)) - ? - // Move losing capture to endBadCaptures to be tried later - true - : (*endBadCaptures++ = *cur, false); + // Move losing capture to endBadCaptures to be tried later + return pos.see_ge(*cur, Value(-cur->value)) ? true + : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index e29d3c17..ef6b7e91 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -407,8 +407,8 @@ bool save_eval(const std::optional& filename) { { if (currentEvalFileName != EvalFileDefaultName) { - msg = - "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; sync_cout << msg << sync_endl; return false; diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 3fba45ed..44fa5d00 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -256,7 +256,8 @@ class AffineTransform { else if constexpr (OutputDimensions == 1) { - // We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements. + // We cannot use AVX512 for the last layer because there are only 32 inputs + // and the buffer is not padded to 64 elements. #if defined(USE_AVX2) using vec_t = __m256i; #define vec_setzero _mm256_setzero_si256 diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index be0138f1..e4c308cb 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -113,8 +113,8 @@ struct Network { ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); - // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<previous from states_to_update[i+1] or states_to_update[i] == nullptr. - // computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr. + // by repeatedly applying ->previous from states_to_update[i+1] or + // states_to_update[i] == nullptr. + // computed_st must be reachable by repeatedly applying ->previous on + // states_to_update[0], if not nullptr. template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, diff --git a/src/search.cpp b/src/search.cpp index 4b390713..ad4b458e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -379,8 +379,8 @@ void Thread::search() { int failedHighCnt = 0; while (true) { - // Adjust the effective depth searched, but ensure at least one effective increment for every - // four searchAgain steps (see issue #2717). + // Adjust the effective depth searched, but ensure at least one effective increment + // for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); @@ -633,7 +633,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (!ttCapture) update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); - // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) + // Extra penalty for early quiet moves of + // the previous ply (~0 Elo on STC, ~2 Elo on LTC). if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); @@ -715,7 +716,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } else if (excludedMove) { - // Providing the hint that this node's accumulator will be used often brings significant Elo gain (~13 Elo) + // 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); eval = ss->staticEval; } @@ -817,8 +819,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } } - // Step 10. If the position doesn't have a ttMove, decrease depth by 2 - // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). + // Step 10. If the position doesn't have a ttMove, decrease depth by 2, + // or by 4 if the TT entry for the current position was hit and + // the stored depth is greater than or equal to the current depth. // Use qsearch if depth is equal or below zero (~9 Elo) if (PvNode && !ttMove) depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); @@ -967,13 +970,15 @@ moves_loop: // When in check, search starts here if (capture || givesCheck) { // Futility pruning for captures (~2 Elo) - if (!givesCheck && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)] - [type_of(pos.piece_on(to_sq(move)))] - / 7 - < alpha) - continue; + if (!givesCheck && lmrDepth < 7 && !ss->inCheck) + { + Piece capturedPiece = pos.piece_on(to_sq(move)); + int futilityEval = + ss->staticEval + 188 + 206 * lmrDepth + PieceValue[capturedPiece] + + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; + if (futilityEval < alpha) + continue; + } // SEE based pruning for captures and checks (~11 Elo) if (!pos.see_ge(move, Value(-185) * depth)) @@ -1018,9 +1023,9 @@ moves_loop: // When in check, search starts here // that 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 this type of time controls. - if (!rootNode + // Recursive singular search is avoided. + if (!rootNode && move == ttMove && !excludedMove && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) - && move == ttMove && !excludedMove // Avoid recursive singular search && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { @@ -1053,7 +1058,7 @@ moves_loop: // When in check, search starts here else if (singularBeta >= beta) return singularBeta; - // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) + // If the eval of ttMove is greater than beta, reduce it (negative extension) (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; @@ -1061,7 +1066,7 @@ moves_loop: // When in check, search starts here else if (cutNode) extension = depth < 19 ? -2 : -1; - // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) + // If the eval of ttMove is less than value, reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; } diff --git a/src/uci.cpp b/src/uci.cpp index 1d8f5bdc..8139fae4 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -178,8 +178,9 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) { uint64_t num, nodes = 0, cnt = 1; std::vector list = setup_bench(pos, args); - num = count_if(list.begin(), list.end(), - [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + + num = count_if(list.begin(), list.end(), + [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); TimePoint elapsed = now(); From 0024133b08777b578c1036eb1e4a36343b7fa1bb Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 22 Oct 2023 12:59:21 -0400 Subject: [PATCH 448/678] Update 5 search params for pruning at shallow depth Found by spsa tuning at 45+0.45 with: ``` int fpcEvalOffset = 188; int fpcLmrDepthMult = 206; int histDepthMult = -3232; int histDenom = 5793; int fpEvalOffset = 115; int negSeeDepthMultSq = -27; TUNE(SetRange(0, 394), fpcEvalOffset); TUNE(SetRange(0, 412), fpcLmrDepthMult); TUNE(SetRange(-6464, -1616), histDepthMult); TUNE(SetRange(2896, 11586), histDenom); TUNE(SetRange(0, 230), fpEvalOffset); TUNE(SetRange(-54, 0), negSeeDepthMultSq); ``` Passed STC: https://tests.stockfishchess.org/tests/view/6535551de746e058e6c0165d LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 109056 W: 28025 L: 27599 D: 53432 Ptnml(0-2): 357, 12669, 28038, 13119, 345 Passed LTC: https://tests.stockfishchess.org/tests/view/65364c6ff127f3553505175d LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 61290 W: 15316 L: 14941 D: 31033 Ptnml(0-2): 34, 6849, 16498, 7236, 28 closes https://github.com/official-stockfish/Stockfish/pull/4847 bench 1167412 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ad4b458e..9cef7e5f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -974,7 +974,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(to_sq(move)); int futilityEval = - ss->staticEval + 188 + 206 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 239 + 291 * lmrDepth + PieceValue[capturedPiece] + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; if (futilityEval < alpha) continue; @@ -991,16 +991,16 @@ moves_loop: // When in check, search starts here + (*contHist[3])[movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3232 * depth) + if (lmrDepth < 6 && history < -3498 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 5793; + lmrDepth += history / 7815; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 115 + 122 * lmrDepth <= alpha) + if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 80 + 122 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); From 871ab55f01f844bd24ed768c5e734ed2c956ef78 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Wed, 25 Oct 2023 16:00:58 +0200 Subject: [PATCH 449/678] Simplify futility pruning formula closes https://github.com/official-stockfish/Stockfish/pull/4848 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9cef7e5f..24f0d994 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -775,7 +775,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo - (ss - 1)->statScore / 321 >= beta && eval >= beta && eval < 29462 // smaller than TB wins - && !(!ttCapture && ttMove)) + && (!ttMove || ttCapture)) return eval; // Step 9. Null move search with verification search (~35 Elo) From b0658f09b93185e2b43d4b2d6f0daa30c36ebcc2 Mon Sep 17 00:00:00 2001 From: Michael Chaly <26898827+Vizvezdenec@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:19:31 +0200 Subject: [PATCH 450/678] Introduce pawn structure based history Original idea by Seer chess engine https://github.com/connormcmonigle/seer-nnue, coding done by @Disservin, code refactoring done by @locutus2 to match the style of other histories. This patch introduces pawn structure based history, which assings moves values based on last digits of pawn structure hash and piece type of moved piece and landing square of the move. Idea is that good places for pieces are quite often determined by pawn structure of position. Used in 3 different places - sorting of quiet moves, sorting of quiet check evasions and in history based pruning in search. Passed STC: https://tests.stockfishchess.org/tests/view/65391d08cc309ae83955dbaf LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 155488 W: 39408 L: 38913 D: 77167 Ptnml(0-2): 500, 18427, 39408, 18896, 513 Passed LTC: https://tests.stockfishchess.org/tests/view/653a36a2cc309ae83955f181 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 70110 W: 17548 L: 17155 D: 35407 Ptnml(0-2): 33, 7859, 18889, 8230, 44 closes https://github.com/official-stockfish/Stockfish/pull/4849 Bench: 1257882 Co-Authored-By: Disservin Co-Authored-By: Stefan Geschwentner --- src/movepick.cpp | 13 +++++++++++-- src/movepick.h | 13 +++++++++++-- src/position.cpp | 17 ++++++++++++++--- src/position.h | 4 ++++ src/search.cpp | 15 +++++++++++---- src/thread.cpp | 1 + src/thread.h | 1 + 7 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index d2a49706..444477cf 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -89,12 +89,14 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, + const PawnHistory& ph, Move cm, const Move* killers) : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), + pawnHistory(ph), ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) { @@ -110,11 +112,13 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, + const PawnHistory& ph, Square rs) : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), + pawnHistory(ph), ttMove(ttm), recaptureSquare(rs), depth(d) { @@ -125,9 +129,11 @@ MovePicker::MovePicker(const Position& p, // Constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : +MovePicker::MovePicker( + const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph, const PawnHistory& ph) : pos(p), captureHistory(cph), + pawnHistory(ph), ttMove(ttm), threshold(th) { assert(!pos.checkers()); @@ -203,6 +209,8 @@ void MovePicker::score() { : pt != PAWN ? bool(to & threatenedByPawn) * 15000 : 0) : 0; + + m.value += pawnHistory[pawn_structure(pos)][pc][to]; } else // Type == EVASIONS @@ -212,7 +220,8 @@ void MovePicker::score() { + (1 << 28); else m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]; + + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + + pawnHistory[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)]; } } diff --git a/src/movepick.h b/src/movepick.h index 65e93dda..f210f538 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -28,9 +28,13 @@ #include "movegen.h" #include "types.h" +#include "position.h" namespace Stockfish { -class Position; + +constexpr int PAWN_HISTORY_SIZE = 512; + +inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } // StatsEntry stores the stat table value. It is usually a number but could // be a move or even a nested history. We use a class instead of a naked value @@ -112,6 +116,8 @@ using PieceToHistory = Stats; // (~63 elo) using ContinuationHistory = Stats; +// PawnStructureHistory is addressed by the pawn structure and a move's [piece][to] +using PawnHistory = Stats; // MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which returns a @@ -135,6 +141,7 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, + const PawnHistory&, Move, const Move*); MovePicker(const Position&, @@ -143,8 +150,9 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, + const PawnHistory&, Square); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*, const PawnHistory&); Move next_move(bool skipQuiets = false); private: @@ -159,6 +167,7 @@ class MovePicker { const ButterflyHistory* mainHistory; const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; + const PawnHistory& pawnHistory; Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; diff --git a/src/position.cpp b/src/position.cpp index 37c586ab..2bb47871 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -49,7 +49,7 @@ namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; Key enpassant[FILE_NB]; Key castling[CASTLING_RIGHT_NB]; -Key side; +Key side, noPawns; } namespace { @@ -128,7 +128,8 @@ void Position::init() { for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) Zobrist::castling[cr] = rng.rand(); - Zobrist::side = rng.rand(); + Zobrist::side = rng.rand(); + Zobrist::noPawns = rng.rand(); // Prepare the cuckoo tables std::memset(cuckoo, 0, sizeof(cuckoo)); @@ -337,6 +338,7 @@ void Position::set_check_info() const { void Position::set_state() const { st->key = st->materialKey = 0; + st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); @@ -348,7 +350,10 @@ void Position::set_state() const { Piece pc = piece_on(s); st->key ^= Zobrist::psq[pc][s]; - if (type_of(pc) != KING && type_of(pc) != PAWN) + if (type_of(pc) == PAWN) + st->pawnKey ^= Zobrist::psq[pc][s]; + + else if (type_of(pc) != KING) st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; } @@ -728,6 +733,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(piece_on(to) == NO_PIECE); assert(piece_on(capsq) == make_piece(them, PAWN)); } + + st->pawnKey ^= Zobrist::psq[captured][capsq]; } else st->nonPawnMaterial[them] -= PieceValue[captured]; @@ -806,6 +813,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // 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]]; @@ -813,6 +821,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[us] += PieceValue[promotion]; } + // Update pawn hash key + st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + // Reset rule 50 draw counter st->rule50 = 0; } diff --git a/src/position.h b/src/position.h index 2aeb8fcd..ce03c34f 100644 --- a/src/position.h +++ b/src/position.h @@ -39,6 +39,7 @@ struct StateInfo { // Copied when making a move Key materialKey; + Key pawnKey; Value nonPawnMaterial[COLOR_NB]; int castlingRights; int rule50; @@ -146,6 +147,7 @@ class Position { Key key() const; Key key_after(Move m) const; Key material_key() const; + Key pawn_key() const; // Other properties of the position Color side_to_move() const; @@ -293,6 +295,8 @@ inline Key Position::adjust_key50(Key k) const { return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); } +inline Key Position::pawn_key() const { return st->pawnKey; } + inline Key Position::material_key() const { return st->materialKey; } inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } diff --git a/src/search.cpp b/src/search.cpp index 24f0d994..0ffca247 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -848,7 +848,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { assert(probCutBeta < VALUE_INFINITE); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory, + thisThread->pawnHistory); while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) @@ -904,7 +905,7 @@ moves_loop: // When in check, search starts here prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, - countermove, ss->killers); + thisThread->pawnHistory, countermove, ss->killers); value = bestValue; moveCountPruning = singularQuietLMR = false; @@ -988,7 +989,8 @@ moves_loop: // When in check, search starts here { int history = (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)]; + + (*contHist[3])[movedPiece][to_sq(move)] + + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) if (lmrDepth < 6 && history < -3498 * depth) @@ -1463,7 +1465,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // will be generated. Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, prevSq); + contHist, thisThread->pawnHistory, prevSq); int quietCheckEvasions = 0; @@ -1671,10 +1673,15 @@ void update_all_stats(const Position& pos, // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bestMoveBonus); + thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)] + << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { + thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])] + [to_sq(quietsSearched[i])] + << -bestMoveBonus; thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bestMoveBonus); diff --git a/src/thread.cpp b/src/thread.cpp index fdf89095..bc884ded 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -68,6 +68,7 @@ void Thread::clear() { counterMoves.fill(MOVE_NONE); mainHistory.fill(0); captureHistory.fill(0); + pawnHistory.fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) diff --git a/src/thread.h b/src/thread.h index 5f33b736..37a4a6ca 100644 --- a/src/thread.h +++ b/src/thread.h @@ -71,6 +71,7 @@ class Thread { ButterflyHistory mainHistory; CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; }; From d30af4f669fa9a47e26a54967c571ffa7987d660 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 27 Oct 2023 13:28:55 +0300 Subject: [PATCH 451/678] Rewarding Quiet Moves that Enable Razoring The main idea of the patch comes from @peregrineshahin : https://tests.stockfishchess.org/tests/view/65205363ac57711436728781 Another small idea (tuning) comes from @Vizvezdenec https://tests.stockfishchess.org/tests/view/652e071ade6d262d08d318f4 And a long phases of tuning and tests was done by me in order to make the patch be able to pass both tests. The idea, as mentioned by Peregrine is that in our standard code, if no best move found after searching all moves, we give a bonus to the previous move that caused the fail high. So in razoring we assume no bestmove will be found so we might as well do the same. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 82336 W: 20997 L: 20610 D: 40729 Ptnml(0-2): 288, 9710, 20753, 10161, 256 https://tests.stockfishchess.org/tests/view/6538fafbcc309ae83955d8f0 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 46584 W: 11753 L: 11411 D: 23420 Ptnml(0-2): 21, 5133, 12642, 5475, 21 https://tests.stockfishchess.org/tests/view/653a3f2ccc309ae83955f223 closes https://github.com/official-stockfish/Stockfish/pull/4850 Bench: 1258079 Co-Authored-By: Shahin M. Shahin <41402573+peregrineshahin@users.noreply.github.com> --- src/evaluate.cpp | 4 ++-- src/search.cpp | 34 +++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c405cfb5..9c39d4c0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -144,8 +144,8 @@ void NNUE::verify() { } -// Returns a static, purely materialistic evaluation of the position -// from the point of view of the given color. It can be divided by PawnValue to get +// Returns a static, purely materialistic evaluation of the position from +// the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. Value Eval::simple_eval(const Position& pos, Color c) { return PawnValue * (pos.count(c) - pos.count(~c)) diff --git a/src/search.cpp b/src/search.cpp index 0ffca247..65b27d16 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,7 +77,7 @@ enum NodeType { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return Value((126 - 42 * noTtCutNode) * (d - improving)); + return Value((125 - 43 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup @@ -85,8 +85,8 @@ int Reductions[MAX_MOVES]; // [depth or moveNumber] Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024 - + (!i && reductionScale > 791); + return (reductionScale + 1487 - int(delta) * 976 / int(rootDelta)) / 1024 + + (!i && reductionScale > 808); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -94,7 +94,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(334 * d - 531, 1538); } +int stat_bonus(Depth d) { return std::min(357 * d - 483, 1511); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(const Thread* thisThread) { @@ -761,11 +761,19 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 492 - (257 - 200 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 474 - (270 - 174 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) + { + if (!priorCapture && prevSq != SQ_NONE) + { + int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658) + ((ss-1)->moveCount > 11); + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus * 57 / 100; + } return value; + } } // Step 8. Futility pruning: child node (~40 Elo) @@ -993,22 +1001,22 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3498 * depth) + if (lmrDepth < 6 && history < -3645 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7815; - lmrDepth = std::max(lmrDepth, -2); + lmrDepth += history / 7836; + lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 80 + 122 * lmrDepth <= alpha) + if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 77 + 124 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth))) + if (!pos.see_ge(move, Value(-26 * lmrDepth * lmrDepth))) continue; } } @@ -1318,12 +1326,12 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653) - + ((ss - 1)->moveCount > 11); + int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 657) + + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] - << stat_bonus(depth) * bonus / 2; + << stat_bonus(depth) * bonus * 61 / 100; } if (PvNode) From 08ed4c90db31959521b7ef3186c026edd1e90307 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 27 Oct 2023 17:33:41 +0200 Subject: [PATCH 452/678] Format Code No functional change --- src/search.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 65b27d16..b7b51e0b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -768,9 +768,12 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658) + ((ss-1)->moveCount > 11); - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus * 57 / 100; + int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658) + + ((ss - 1)->moveCount > 11); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] + << stat_bonus(depth) * bonus * 57 / 100; } return value; } From 347d613b0e2c47f90cbf1c5a5affe97303f1ac3d Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 27 Oct 2023 18:35:52 +0200 Subject: [PATCH 453/678] remove outdated comment Bench: 1258079 --- src/thread.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/thread.h b/src/thread.h index 37a4a6ca..cb2f6db1 100644 --- a/src/thread.h +++ b/src/thread.h @@ -34,10 +34,7 @@ namespace Stockfish { -// Thread class keeps together all the thread-related stuff. We use -// per-thread pawn and material hash tables so that once we get a -// pointer to an entry its lifetime is unlimited and we don't have -// to care about someone changing the entry under our feet. +// Thread class keeps together all the thread-related stuff. class Thread { std::mutex mutex; From 38aa70adcfa767387da91c8df1180fb11ad89ac7 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 29 Oct 2023 09:18:10 +0800 Subject: [PATCH 454/678] Small cleanups Corrects some incorrect or outdated comments. Credit is shared with yaneurao (see 38e830a#commitcomment-131131500) and locutus2 closes #4852 No functional change. --- src/movepick.h | 5 ++++- src/search.cpp | 15 +++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index f210f538..dc171c9f 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -32,7 +32,10 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 + +static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, + "PAWN_HISTORY_SIZE has to be a power of 2"); inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } diff --git a/src/search.cpp b/src/search.cpp index b7b51e0b..ef98d862 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -830,16 +830,19 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } } - // Step 10. If the position doesn't have a ttMove, decrease depth by 2, - // or by 4 if the TT entry for the current position was hit and + // Step 10. Internal iterative reductions (~9 Elo) + // For PV nodes without a ttMove, we decrease depth by 2, + // or by 4 if the current position is present in the TT and // the stored depth is greater than or equal to the current depth. - // Use qsearch if depth is equal or below zero (~9 Elo) + // Use qsearch if depth <= 0. if (PvNode && !ttMove) depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); + // For cutNodes without a ttMove, we decrease depth by 2 + // if current depth >= 8. if (cutNode && depth >= 8 && !ttMove) depth -= 2; @@ -1129,7 +1132,7 @@ moves_loop: // When in check, search starts here if (PvNode) r--; - // Decrease reduction if ttMove has been singularly extended (~1 Elo) + // Decrease reduction if a quiet ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) r--; @@ -1194,7 +1197,7 @@ moves_loop: // When in check, search starts here // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { - // Increase reduction for cut nodes and not ttMove (~1 Elo) + // Increase reduction for cut nodes without ttMove (~1 Elo) if (!ttMove && cutNode) r += 2; @@ -1724,7 +1727,7 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed -// by moves at ply -1, -2, -4, and -6 with current move. +// by moves at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { for (int i : {1, 2, 3, 4, 6}) From 908811c24ab53d2cb1bebc1138427e21fefa8054 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:04:15 +0800 Subject: [PATCH 455/678] Introduce asymmetric optimism Introduce asymmetric optimism for both side to move and opponent. Parameter tuning was done with 200k LTC games. STC: https://tests.stockfishchess.org/tests/view/653cc08fcc309ae8395622b3 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 98336 W: 25074 L: 24661 D: 48601 Ptnml(0-2): 339, 11612, 24890, 11951, 376 LTC: https://tests.stockfishchess.org/tests/view/653db595cc309ae839563140 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 83244 W: 20760 L: 20339 D: 42145 Ptnml(0-2): 51, 9306, 22498, 9705, 62 closes https://github.com/official-stockfish/Stockfish/pull/4853 Bench: 1371690 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ef98d862..daab1eb1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -364,14 +364,13 @@ void Thread::search() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(avg) * avg / 17470; + delta = Value(10) + int(avg) * avg / 15335; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - int opt = 113 * avg / (std::abs(avg) + 109); - optimism[us] = Value(opt); - optimism[~us] = -optimism[us]; + optimism[us] = 103 * (avg + 33) / (std::abs(avg + 34) + 119); + optimism[~us] = -116 * (avg + 40) / (std::abs(avg + 12) + 123); // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail From e277dda7166992536124891e212d6d6a866f8a12 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 31 Oct 2023 08:25:06 +0800 Subject: [PATCH 456/678] Prefetch TT entries in probcut Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 101344 W: 25893 L: 25491 D: 49960 Ptnml(0-2): 303, 11350, 26991, 11698, 330 https://tests.stockfishchess.org/tests/view/6540daa6cc309ae83956669b slight speedup: ``` Result of 100 runs ================== base (./stockfish.master ) = 1170705 +/- 3133 test (./stockfish.patch ) = 1174545 +/- 2895 diff = +3841 +/- 3196 speedup = +0.0033 P(speedup > 0) = 0.9907 ``` closes https://github.com/official-stockfish/Stockfish/pull/4856 No functional change --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index daab1eb1..6e719be8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -869,6 +869,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { assert(pos.capture_stage(move)); + // Prefetch the TT entry for the resulting position + prefetch(TT.first_entry(pos.key_after(move))); + ss->currentMove = move; ss->continuationHistory = &thisThread From 101d2bb8eae2c93c94013f56eae3af4faf8886f9 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 1 Nov 2023 13:42:06 +0300 Subject: [PATCH 457/678] Simplifying two formulas by eliminating two multiplication operations. Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60000 W: 15193 L: 14996 D: 29811 Ptnml(0-2): 199, 7100, 15215, 7277, 209 https://tests.stockfishchess.org/tests/view/653beb69cc309ae83956129d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 122910 W: 30471 L: 30353 D: 62086 Ptnml(0-2): 68, 13961, 33271, 14095, 60 https://tests.stockfishchess.org/tests/view/653c5848cc309ae839561ae7 closes https://github.com/official-stockfish/Stockfish/pull/4857 bench: 1216779 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6e719be8..3cd3b555 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -772,7 +772,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] - << stat_bonus(depth) * bonus * 57 / 100; + << stat_bonus(depth) * bonus / 2; } return value; } @@ -1339,7 +1339,7 @@ moves_loop: // When in check, search starts here update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] - << stat_bonus(depth) * bonus * 61 / 100; + << stat_bonus(depth) * bonus / 2; } if (PvNode) From 1cb9afbdc04fbb864ee48babe56c1e88867d11e9 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 28 Oct 2023 07:43:12 +0200 Subject: [PATCH 458/678] Remove razoring history update. The recently commit 'Rewarding Quiet Moves that Enable Razoring' add a history update if razoring. But its contains also many tuned values all over the search. Following tests shows that the tuned values and not the added history update is responsible for the elo gain. So remove later. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 29376 W: 7641 L: 7410 D: 14325 Ptnml(0-2): 100, 3411, 7451, 3610, 116 https://tests.stockfishchess.org/tests/view/653c9fe1cc309ae839562070 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 242922 W: 59879 L: 59885 D: 123158 Ptnml(0-2): 129, 27764, 65688, 27744, 136 https://tests.stockfishchess.org/tests/view/653d06cbcc309ae839562735 closes https://github.com/official-stockfish/Stockfish/pull/4858 Bench: 1286104 --- src/search.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3cd3b555..aaf545d2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -764,18 +764,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) - { - if (!priorCapture && prevSq != SQ_NONE) - { - int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658) - + ((ss - 1)->moveCount > 11); - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] - << stat_bonus(depth) * bonus / 2; - } return value; - } } // Step 8. Futility pruning: child node (~40 Elo) From b4b704e6866bde21c69cd53457a6a91a15182b36 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 3 Nov 2023 14:03:12 +0300 Subject: [PATCH 459/678] Update pawn history based on static eval difference Use the same logic as in main history but with 1/4 multiplier. Passed STC: https://tests.stockfishchess.org/tests/view/653c1282cc309ae8395615bf LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 306624 W: 77811 L: 77094 D: 151719 Ptnml(0-2): 975, 36411, 77830, 37114, 982 Passed LTC: https://tests.stockfishchess.org/tests/view/654258e2cc309ae83956818d LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 99150 W: 24681 L: 24228 D: 50241 Ptnml(0-2): 56, 11107, 26792, 11568, 52 closes https://github.com/official-stockfish/Stockfish/pull/4859 bench 1330590 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index aaf545d2..55d11003 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -745,6 +745,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { int bonus = std::clamp(-18 * int((ss - 1)->staticEval + ss->staticEval), -1812, 1812); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; + if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) + thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; } // Set up the improving flag, which is true if current static evaluation is From 7f97ba775ece910402108d7a7b11ce645185d300 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 4 Nov 2023 17:19:08 +0300 Subject: [PATCH 460/678] Tweaking the futility pruning formula Huge credit goes also to candirufish, as the idea was first tried by him, and then tuned by me at multiple phases. Tweaking the futility pruning formula to be a bit more selective about when pruning is applied. Adjust the value added to the static eval based on the bestValue relative to ss->staticEval. If bestValue is significantly lower, we add a larger value. Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 37120 W: 9590 L: 9266 D: 18264 Ptnml(0-2): 130, 4301, 9385, 4603, 141 https://tests.stockfishchess.org/tests/view/6544cf90136acbc573523247 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 49632 W: 12381 L: 12033 D: 25218 Ptnml(0-2): 30, 5429, 13549, 5779, 29 https://tests.stockfishchess.org/tests/view/65453bc1136acbc573523a3c closes https://github.com/official-stockfish/Stockfish/pull/4861 bench: 1107118 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 55d11003..27f0f987 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1009,7 +1009,10 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 77 + 124 * lmrDepth <= alpha) + if (!ss->inCheck && lmrDepth < 13 + && ss->staticEval + (bestValue < ss->staticEval - 62 ? 123 : 77) + + 127 * lmrDepth + <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); From 791419aab529e714271ebc03d1d84ad4e7e9879a Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Sun, 5 Nov 2023 12:01:06 +0300 Subject: [PATCH 461/678] Enhance some comments This documents some code and makes some hard code easier to understand for new developers. closes https://github.com/official-stockfish/Stockfish/pull/4862 No functional change --- src/movepick.h | 2 +- src/search.cpp | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index dc171c9f..f058ff0e 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -119,7 +119,7 @@ using PieceToHistory = Stats; // (~63 elo) using ContinuationHistory = Stats; -// PawnStructureHistory is addressed by the pawn structure and a move's [piece][to] +// PawnHistory is addressed by the pawn structure and a move's [piece][to] using PawnHistory = Stats; // MovePicker class is used to pick one pseudo-legal move at a time from the diff --git a/src/search.cpp b/src/search.cpp index 27f0f987..483412c5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1030,9 +1030,10 @@ moves_loop: // When in check, search starts here // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the result - // is lower than ttValue minus a margin, then we will extend the ttMove. Note - // that depth margin and singularBeta margin are known for having non-linear + // 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. + + // 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 this type of time controls. // Recursive singular search is avoided. @@ -1063,22 +1064,28 @@ moves_loop: // When in check, search starts here } // Multi-cut pruning - // Our ttMove is assumed to fail high, and now we failed high also on a - // reduced search without the ttMove. So we assume this expected cut-node - // is not singular, that multiple moves fail high, and we can prune the - // whole subtree by returning a softbound. + // Our ttMove is assumed to fail high based on the bound of the TT entry, + // and if after excluding the ttMove with a reduced search we fail high over the original beta, + // we assume this expected cut-node is not singular (multiple moves fail high), + // and we can prune the whole subtree by returning a softbound. else if (singularBeta >= beta) return singularBeta; - // If the eval of ttMove is greater than beta, reduce it (negative extension) (~7 Elo) + // Negative extensions + // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, + // but we cannot do multi-cut because (ttValue - margin) is lower than the original beta, + // we do not know 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 currnet beta (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; - // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) + // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) extension = depth < 19 ? -2 : -1; - // If the eval of ttMove is less than value, reduce it (negative extension) (~1 Elo) + // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo) else if (ttValue <= value) extension = -1; } @@ -1411,9 +1418,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { assert(0 <= ss->ply && ss->ply < MAX_PLY); - // Decide whether or not to include checks: this fixes also the type of - // TT entry depth that we are going to use. Note that in qsearch we use - // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. + // Decide the replacement and cutoff priority of the qsearch TT entries ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; // Step 3. Transposition table lookup From d4b46ea6db7caf71cad3c36d0ef8c0162a743aba Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 5 Nov 2023 13:52:20 +0300 Subject: [PATCH 462/678] Set reduction to 0 if the move is a TT move The reduction formula currently decreases by 1 if the move is a TT move. This changes this by just setting the reduction to 0 instead. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 83136 W: 21145 L: 20758 D: 41233 Ptnml(0-2): 279, 9772, 21090, 10137, 290 https://tests.stockfishchess.org/tests/view/653c0fbacc309ae839561584 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 273150 W: 67987 L: 67171 D: 137992 Ptnml(0-2): 155, 30730, 73966, 31592, 132 https://tests.stockfishchess.org/tests/view/653d9d02cc309ae839562fdf closes https://github.com/official-stockfish/Stockfish/pull/4863 bench: 1110556 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 483412c5..8b2cc572 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1147,9 +1147,10 @@ moves_loop: // When in check, search starts here if ((ss + 1)->cutoffCnt > 3) r++; - // Decrease reduction for first generated move (ttMove) + // Set reduction to 0 for first generated move (ttMove) + // Nullifies all previous reduction adjustments to ttMove and leaves only history to do them else if (move == ttMove) - r--; + r = 0; ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] From 442c294a07836e9e32ad8b3bad49a853cc6f47de Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Sun, 5 Nov 2023 16:01:52 +0100 Subject: [PATCH 463/678] Use stat_malus when decreasing stats This patch applies stat_bonus when increasing and stat_malus when decreasing stats. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 133792 W: 34221 L: 33758 D: 65813 Ptnml(0-2): 477, 15764, 33959, 16211, 485 https://tests.stockfishchess.org/tests/view/654699f3136acbc5735256b2 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 67374 W: 16912 L: 16523 D: 33939 Ptnml(0-2): 42, 7528, 18171, 7891, 55 https://tests.stockfishchess.org/tests/view/65474558136acbc5735263ab closes https://github.com/official-stockfish/Stockfish/pull/4864 bench: 1114417 --- src/search.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8b2cc572..a8f178a3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,7 +94,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(357 * d - 483, 1511); } +int stat_bonus(Depth d) { return std::min(364 * d - 438, 1501); } + +// History and stats update malus, based on depth +int stat_malus(Depth d) { return std::min(452 * d - 452, 1478); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(const Thread* thisThread) { @@ -636,12 +639,12 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // the previous ply (~0 Elo on STC, ~2 Elo on LTC). if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_bonus(depth + 1)); + -stat_malus(depth + 1)); } // Penalty for a quiet ttMove that fails low (~1 Elo) else if (!ttCapture) { - int penalty = -stat_bonus(depth); + int penalty = -stat_malus(depth); thisThread->mainHistory[us][from_to(ttMove)] << penalty; update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); } @@ -1190,7 +1193,7 @@ moves_loop: // When in check, search starts here if (newDepth > d) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); - int bonus = value <= alpha ? -stat_bonus(newDepth) + int bonus = value <= alpha ? -stat_malus(newDepth) : value >= beta ? stat_bonus(newDepth) : 0; @@ -1681,6 +1684,7 @@ void update_all_stats(const Position& pos, PieceType captured; int quietMoveBonus = stat_bonus(depth + 1); + int quietMoveMalus = stat_malus(depth + 1); if (!pos.capture_stage(bestMove)) { @@ -1692,15 +1696,18 @@ void update_all_stats(const Position& pos, thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)] << quietMoveBonus; + int moveMalus = bestValue > beta + 168 ? quietMoveMalus // larger malus + : stat_malus(depth); // smaller malus + // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])] [to_sq(quietsSearched[i])] - << -bestMoveBonus; - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus; + << -moveMalus; + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -moveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - to_sq(quietsSearched[i]), -bestMoveBonus); + to_sq(quietsSearched[i]), -moveMalus); } } else @@ -1716,14 +1723,14 @@ void update_all_stats(const Position& pos, && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit || ((ss - 1)->currentMove == (ss - 1)->killers[0])) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveBonus); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus); // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveBonus; + captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveMalus; } } From d0e87104aa782176735442e1f6668f91014f07eb Mon Sep 17 00:00:00 2001 From: Taras Vuk Date: Mon, 6 Nov 2023 15:00:06 +0100 Subject: [PATCH 464/678] Remove pawn history from ProbCut constructor use same style as other history tables Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 184672 W: 46953 L: 46896 D: 90823 Ptnml(0-2): 604, 21095, 48887, 21140, 610 https://tests.stockfishchess.org/tests/view/654766b4136acbc573526602 closes https://github.com/official-stockfish/Stockfish/pull/4865 No functional change --- src/movepick.cpp | 13 +++++-------- src/movepick.h | 8 ++++---- src/search.cpp | 7 +++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 444477cf..0aba9a55 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -89,7 +89,7 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory& ph, + const PawnHistory* ph, Move cm, const Move* killers) : pos(p), @@ -112,7 +112,7 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory& ph, + const PawnHistory* ph, Square rs) : pos(p), mainHistory(mh), @@ -129,11 +129,9 @@ MovePicker::MovePicker(const Position& p, // Constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. -MovePicker::MovePicker( - const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph, const PawnHistory& ph) : +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : pos(p), captureHistory(cph), - pawnHistory(ph), ttMove(ttm), threshold(th) { assert(!pos.checkers()); @@ -188,6 +186,7 @@ void MovePicker::score() { m.value += (*continuationHistory[2])[pc][to] / 4; m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; + m.value += (*pawnHistory)[pawn_structure(pos)][pc][to]; // bonus for checks m.value += bool(pos.check_squares(pt) & to) * 16384; @@ -209,8 +208,6 @@ void MovePicker::score() { : pt != PAWN ? bool(to & threatenedByPawn) * 15000 : 0) : 0; - - m.value += pawnHistory[pawn_structure(pos)][pc][to]; } else // Type == EVASIONS @@ -221,7 +218,7 @@ void MovePicker::score() { else m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + pawnHistory[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)]; + + (*pawnHistory)[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)]; } } diff --git a/src/movepick.h b/src/movepick.h index f058ff0e..9f189974 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -144,7 +144,7 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory&, + const PawnHistory*, Move, const Move*); MovePicker(const Position&, @@ -153,9 +153,9 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory&, + const PawnHistory*, Square); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*, const PawnHistory&); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); private: @@ -170,7 +170,7 @@ class MovePicker { const ButterflyHistory* mainHistory; const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; - const PawnHistory& pawnHistory; + const PawnHistory* pawnHistory; Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; diff --git a/src/search.cpp b/src/search.cpp index a8f178a3..b947fc5f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -855,8 +855,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { assert(probCutBeta < VALUE_INFINITE); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory, - thisThread->pawnHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) @@ -915,7 +914,7 @@ moves_loop: // When in check, search starts here prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, - thisThread->pawnHistory, countermove, ss->killers); + &thisThread->pawnHistory, countermove, ss->killers); value = bestValue; moveCountPruning = singularQuietLMR = false; @@ -1484,7 +1483,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // will be generated. Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, thisThread->pawnHistory, prevSq); + contHist, &thisThread->pawnHistory, prevSq); int quietCheckEvasions = 0; From 80b0e3754303c44bdcc53c01339a955d5677cd64 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Mon, 6 Nov 2023 16:12:30 +0100 Subject: [PATCH 465/678] Double weight of pawn history for quiet move ordering. I measured on my 1000 position bench the average additional added pawn history per depth. This shows on average negative value with even smaller values with increaing depth. A linear regression against depth get following formula: -1960 - 130 * depth For compensation add this to the used sort limit to maintain roughly the same proportion of sorted quiet moves. Remarks: 1. using no compensation failed here https://tests.stockfishchess.org/tests/view/6547664f136acbc5735265f0 2. using only the compensation failed at LTC: passed STC: https://tests.stockfishchess.org/tests/view/65477457136acbc5735266f8 failed LTC: https://tests.stockfishchess.org/tests/view/65487fc8136acbc573527d1c STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 98528 W: 25109 L: 24699 D: 48720 Ptnml(0-2): 334, 11586, 25009, 12006, 329 https://tests.stockfishchess.org/tests/view/65475873136acbc5735264f7 LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 69726 W: 17467 L: 17073 D: 35186 Ptnml(0-2): 39, 7814, 18769, 8196, 45 https://tests.stockfishchess.org/tests/view/6547e759136acbc573527071 closes https://github.com/official-stockfish/Stockfish/pull/4866 Bench: 1379422 --- src/movepick.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 0aba9a55..798f51dd 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -181,12 +181,12 @@ void MovePicker::score() { // histories m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value += 2 * (*pawnHistory)[pawn_structure(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to] / 4; m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; - m.value += (*pawnHistory)[pawn_structure(pos)][pc][to]; // bonus for checks m.value += bool(pos.check_squares(pt) & to) * 16384; @@ -302,7 +302,7 @@ top: endMoves = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, -3000 * depth); + partial_insertion_sort(cur, endMoves, -1960 - 3130 * depth); } ++stage; From fbc6b275051773b491c1c180fd7ff331194ca0f1 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 7 Nov 2023 13:03:05 -0500 Subject: [PATCH 466/678] Simplify away optimism average score offset params Passed non-regression STC: https://tests.stockfishchess.org/tests/view/654abf6b136acbc57352ac4b LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 49664 W: 12687 L: 12477 D: 24500 Ptnml(0-2): 138, 5840, 12703, 5976, 175 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/654b638e136acbc57352b961 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 347166 W: 85561 L: 85676 D: 175929 Ptnml(0-2): 206, 39569, 94150, 39450, 208 closes https://github.com/official-stockfish/Stockfish/pull/4871 bench 1257641 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b947fc5f..3ce74126 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -372,8 +372,8 @@ void Thread::search() { beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 103 * (avg + 33) / (std::abs(avg + 34) + 119); - optimism[~us] = -116 * (avg + 40) / (std::abs(avg + 12) + 123); + optimism[us] = 103 * avg / (std::abs(avg) + 119); + optimism[~us] = -116 * avg / (std::abs(avg) + 123); // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail From 863a1f2b4cb233be3126b244cbd8f6c8b9b4d13c Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:45:36 +0100 Subject: [PATCH 467/678] Introduce recapture extensions When in a PV-node this patch extends ttMove if it is a recapture and has a good history. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 83840 W: 21560 L: 21166 D: 41114 Ptnml(0-2): 343, 9905, 21027, 10305, 340 https://tests.stockfishchess.org/tests/view/654f4b02136acbc5735308ab Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 165318 W: 41068 L: 40476 D: 83774 Ptnml(0-2): 98, 18670, 44517, 19290, 84 https://tests.stockfishchess.org/tests/view/654fde04136acbc5735314e0 closes https://github.com/official-stockfish/Stockfish/pull/4872 Bench: 1393911 --- src/search.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 3ce74126..5c53c0da 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1100,6 +1100,12 @@ moves_loop: // When in check, search starts here else if (PvNode && move == ttMove && move == ss->killers[0] && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) extension = 1; + + // Recapture extensions (~1 Elo) + else if (PvNode && move == ttMove && to_sq(move) == prevSq + && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] + > 4000) + extension = 1; } // Add extension to new depth From f9d8717844643e4ea3723f5ea240bf5d22800df7 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 12 Nov 2023 15:51:38 +0100 Subject: [PATCH 468/678] Symmetrize optimism Removes some additional parameters, making the term more logical at the same time. Passed STC: https://tests.stockfishchess.org/tests/view/6550e896136acbc5735328ed LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 271104 W: 68441 L: 68480 D: 134183 Ptnml(0-2): 827, 32590, 68816, 32433, 886 Passed LTC: https://tests.stockfishchess.org/tests/view/65523858136acbc5735344f7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 198954 W: 49250 L: 49211 D: 100493 Ptnml(0-2): 93, 22565, 54117, 22614, 88 closes https://github.com/official-stockfish/Stockfish/pull/4874 Bench: 1334248 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5c53c0da..5bea5945 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -372,8 +372,8 @@ void Thread::search() { beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 103 * avg / (std::abs(avg) + 119); - optimism[~us] = -116 * avg / (std::abs(avg) + 123); + optimism[us] = 110 * avg / (std::abs(avg) + 121); + optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail From d89217766b748eccd08f58f35209d762d8bf0600 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 15 Nov 2023 21:00:37 +0100 Subject: [PATCH 469/678] CI updates - updates the SDE action to v2.2 - removes the linux x86-32 builds, which were almost unused, and the build process under SDE started failing recently, possibly related to glibc update (The futex facility returned an unexpected error code.) closes https://github.com/official-stockfish/Stockfish/pull/4875 No functional change --- .github/workflows/stockfish_binaries.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index fadfbcfc..6da576e4 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -13,6 +13,7 @@ jobs: NAME: ${{ matrix.config.simple_name }} BINARY: ${{ matrix.binaries }} strategy: + fail-fast: false matrix: config: - name: Ubuntu 20.04 GCC @@ -42,7 +43,6 @@ jobs: sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future -- archive_ext: zip binaries: - - x86-32 - x86-64 - x86-64-sse41-popcnt - x86-64-avx2 @@ -54,10 +54,6 @@ jobs: exclude: - binaries: x86-64-avxvnni config: { ubuntu-20.04 } - - binaries: x86-32 - config: { os: windows-2022} - - binaries: x86-32 - config: { os: macos-13 } - binaries: x86-64-avxvnni config: { os: macos-13 } - binaries: x86-64-avx512 @@ -75,12 +71,6 @@ jobs: with: fetch-depth: 0 - - name: Download required Linux packages - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install g++-multilib g++-11-multilib - - name: Download required macOS packages if: runner.os == 'macOS' run: brew install coreutils @@ -100,7 +90,7 @@ jobs: - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@v2.1 + uses: petarpetrovt/setup-sde@6f4926100f31791716b11d25c0f3f35809d44f84 with: environmentVariableName: SDE_DIR sdeVersion: 9.14.0 From 7970236e9ea64796d5c7597cb1aedde737751f07 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 16 Nov 2023 08:40:25 +0100 Subject: [PATCH 470/678] Fix undefined behavior in search. We use following line to clamp the search depth in some range: Depth d = std::clamp(newDepth - r, 1, newDepth + 1); Through negative extension its possible that the maximum value becomes smaller than the minimum value but then the behavior is undefined (see https://en.cppreference.com/w/cpp/algorithm/clamp). So replace this line with a safe implementation. Remark: We have in recent master already one line where up to 3 negative extensions are possible which could trigger this undefined behavior but this can only be happen for completed depth > 24 so its not discovered by our default bench. Recent negative extension tests by @fauzi shows then this undefined behavior with wrong bench numbers. closes https://github.com/official-stockfish/Stockfish/pull/4877 No functional change --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5bea5945..fa479c4b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1178,7 +1178,9 @@ moves_loop: // When in check, search starts here // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension // beyond the first move depth. This may lead to hidden double extensions. - Depth d = std::clamp(newDepth - r, 1, newDepth + 1); + // To prevent problems when the max value is less than the min value, + // std::clamp has been replaced by a more robust implementation. + Depth d = std::max(1, std::min(newDepth - r, newDepth + 1)); value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From 504bf0e8b8cf2dd818deb623c5ad7e428e504cd8 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 20 Nov 2023 18:56:34 +0100 Subject: [PATCH 471/678] Change depth - 1 to newDepth Replacing 'depth - 1' with 'newDepth' in the singularbeta formula utilizes existing variables more succinctly. closes https://github.com/official-stockfish/Stockfish/pull/4876 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fa479c4b..7d567b8a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1045,7 +1045,7 @@ moves_loop: // When in check, search starts here && tte->depth() >= depth - 3) { Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; - Depth singularDepth = (depth - 1) / 2; + Depth singularDepth = newDepth / 2; ss->excludedMove = move; value = From b59786e750a59d3d7cff2630cf284553f607ed29 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 16 Nov 2023 14:00:11 +0300 Subject: [PATCH 472/678] Remove doEvenDeeperSearch Passed STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 51040 W: 13014 L: 12804 D: 25222 Ptnml(0-2): 166, 6032, 12917, 6236, 169 https://tests.stockfishchess.org/tests/view/65525aa1136acbc573534801 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 165168 W: 40863 L: 40789 D: 83516 Ptnml(0-2): 73, 18783, 44792, 18869, 67 https://tests.stockfishchess.org/tests/view/65535af5136acbc573535c84 closes https://github.com/official-stockfish/Stockfish/pull/4880 Bench: 1477007 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7d567b8a..ae83ab34 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1190,12 +1190,9 @@ 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 + 51 + 10 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; - ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; - - newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; + newDepth += doDeeperSearch - doShallowerSearch; if (newDepth > d) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); From b4e9ee72e36aadd0e653ac4ab5c07a9e3d639aca Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 20 Nov 2023 19:09:48 +0100 Subject: [PATCH 473/678] Reformat some comments Tests used to derive some Elo worth comments: https://tests.stockfishchess.org/tests/view/653cf6b7cc309ae83956263a https://tests.stockfishchess.org/tests/view/655250b7136acbc573534711 https://tests.stockfishchess.org/tests/view/65525767136acbc5735347b9 https://tests.stockfishchess.org/tests/view/65525aa1136acbc573534801 closes https://github.com/official-stockfish/Stockfish/pull/4879 No functional change --- src/misc.cpp | 19 +++++++++---------- src/movepick.h | 9 ++++----- src/position.cpp | 27 ++++++++++++--------------- src/search.cpp | 16 ++++++++-------- src/uci.cpp | 9 ++++----- src/uci.h | 2 +- 6 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 3e900615..4193f8d2 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -393,8 +393,8 @@ void dbg_print() { } -// Used to serialize access to std::cout to avoid multiple threads writing at -// the same time. +// Used to serialize access to std::cout +// to avoid multiple threads writing at the same time. std::ostream& operator<<(std::ostream& os, SyncCout sc) { static std::mutex m; @@ -558,7 +558,7 @@ void* aligned_large_pages_alloc(size_t allocSize) { constexpr size_t alignment = 4096; // assumed small page size #endif - // round up to multiples of alignment + // Round up to multiples of alignment size_t size = ((allocSize + alignment - 1) / alignment) * alignment; void* mem = std_aligned_alloc(alignment, size); #if defined(MADV_HUGEPAGE) @@ -600,7 +600,7 @@ void bindThisThread(size_t) {} #else -// Retrieves logical processor information using Windows specific +// Retrieves logical processor information using Windows-specific // API and returns the best node id for the thread with index idx. Original // code from Texel by Peter Österlund. static int best_node(size_t idx) { @@ -660,8 +660,7 @@ static int best_node(size_t idx) { groups.push_back(n); // In case a core has more than one logical processor (we assume 2) and we - // have still threads to allocate, then spread them evenly across available - // nodes. + // still have threads to allocate, spread them evenly across available nodes. for (int t = 0; t < threads - cores; t++) groups.push_back(t % nodes); @@ -731,7 +730,7 @@ std::string workingDirectory; // path of the working directory void init([[maybe_unused]] int argc, char* argv[]) { std::string pathSeparator; - // extract the path+name of the executable binary + // Extract the path+name of the executable binary argv0 = argv[0]; #ifdef _WIN32 @@ -747,14 +746,14 @@ void init([[maybe_unused]] int argc, char* argv[]) { pathSeparator = "/"; #endif - // extract the working directory + // Extract the working directory workingDirectory = ""; char buff[40000]; char* cwd = GETCWD(buff, 40000); if (cwd) workingDirectory = cwd; - // extract the binary directory path from argv0 + // Extract the binary directory path from argv0 binaryDirectory = argv0; size_t pos = binaryDirectory.find_last_of("\\/"); if (pos == std::string::npos) @@ -762,7 +761,7 @@ void init([[maybe_unused]] int argc, char* argv[]) { else binaryDirectory.resize(pos + 1); - // pattern replacement: "./" at the start of path is replaced by the working directory + // Pattern replacement: "./" at the start of path is replaced by the working directory if (binaryDirectory.find("." + pathSeparator) == 0) binaryDirectory.replace(0, 1, workingDirectory); } diff --git a/src/movepick.h b/src/movepick.h index 9f189974..299925a5 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -96,11 +96,10 @@ enum StatsType { Captures }; -// ButterflyHistory records how often quiet moves have been successful or -// unsuccessful during the current search, and is used for reduction and move -// ordering decisions. It uses 2 tables (one for each color) indexed by -// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards -// (~11 elo) +// ButterflyHistory records how often quiet moves have been successful or unsuccessful +// during the current search, and is used for reduction and move ordering decisions. +// It uses 2 tables (one for each color) indexed by the move's from and to squares, +// see www.chessprogramming.org/Butterfly_Boards (~11 elo) using ButterflyHistory = Stats; // CounterMoveHistory stores counter moves indexed by [piece][to] of the previous diff --git a/src/position.cpp b/src/position.cpp index 2bb47871..c45dd7b2 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -371,9 +371,8 @@ void Position::set_state() const { } -// Overload to initialize the position object with -// the given endgame code string like "KBPKN". It is mainly a helper to -// get the material key out of an endgame code. +// Overload to initialize the position object with the given endgame code string +// like "KBPKN". It's mainly a helper to get the material key out of an endgame code. Position& Position::set(const string& code, Color c, StateInfo* si) { assert(code[0] == 'K'); @@ -472,8 +471,8 @@ void Position::update_slider_blockers(Color c) const { } -// Computes a bitboard of all pieces which attack a -// given square. Slider attacks use the occupied bitboard to indicate occupancy. +// Computes a bitboard of all pieces which attack a given square. +// Slider attacks use the occupied bitboard to indicate occupancy. Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) @@ -575,8 +574,7 @@ bool Position::pseudo_legal(const Move m) const { // Handle the special case of a pawn move if (type_of(pc) == PAWN) { - // We have already handled promotion moves, so destination - // cannot be on the 8th/1st rank. + // We have already handled promotion moves, so destination cannot be on the 8th/1st rank if ((Rank8BB | Rank1BB) & to) return false; @@ -639,10 +637,9 @@ bool Position::gives_check(Move m) const { case PROMOTION : return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); - // En passant capture with check? We have already handled the case - // of direct checks and ordinary discovered check, so the only case we - // need to handle is the unusual case of a discovered check through - // the captured pawn. + // En passant capture with check? We have already handled the case of direct + // checks and ordinary discovered check, so the only case we need to handle + // is the unusual case of a discovered check through the captured pawn. case EN_PASSANT : { Square capsq = make_square(file_of(to), rank_of(from)); Bitboard b = (pieces() ^ from ^ capsq) | to; @@ -928,8 +925,8 @@ void Position::undo_move(Move m) { } -// Helper used to do/undo a castling move. This -// is a bit tricky in Chess960 where from/to squares can overlap. +// Helper used to do/undo a castling move. This is a bit +// tricky in Chess960 where from/to squares can overlap. template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { @@ -1244,8 +1241,8 @@ void Position::flip() { } -// Performs some consistency checks for the -// position object and raise an assert if something wrong is detected. +// Performs some consistency checks for the position object +// and raise an assert if something wrong is detected. // This is meant to be helpful when debugging. bool Position::pos_is_ok() const { diff --git a/src/search.cpp b/src/search.cpp index ae83ab34..c878b0ab 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -834,8 +834,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (depth <= 0) return qsearch(pos, ss, alpha, beta); - // For cutNodes without a ttMove, we decrease depth by 2 - // if current depth >= 8. + // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough. if (cutNode && depth >= 8 && !ttMove) depth -= 2; @@ -1037,7 +1036,7 @@ moves_loop: // When in check, search starts here // 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 this type of time controls. + // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) @@ -1079,7 +1078,7 @@ moves_loop: // When in check, search starts here // we do not know 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 currnet beta (~7 Elo) + // If the ttMove is assumed to fail high over current beta (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; @@ -1155,7 +1154,7 @@ moves_loop: // When in check, search starts here if ((ss + 1)->cutoffCnt > 3) r++; - // Set reduction to 0 for first generated move (ttMove) + // Set reduction to 0 for first picked move (ttMove) (~2 Elo) // Nullifies all previous reduction adjustments to ttMove and leaves only history to do them else if (move == ttMove) r = 0; @@ -1189,8 +1188,9 @@ 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 + 51 + 10 * (newDepth - d)); - const bool doShallowerSearch = value < bestValue + newDepth; + const bool doDeeperSearch = + value > (bestValue + 51 + 10 * (newDepth - d)); // (~1 Elo) + const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1459,7 +1459,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { bestValue = ttValue; } else - // In case of null move search use previous static eval with a different sign + // In case of null move search, use previous static eval with a different sign ss->staticEval = bestValue = (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; diff --git a/src/uci.cpp b/src/uci.cpp index 8139fae4..95f6f349 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -82,8 +82,8 @@ void position(Position& pos, std::istringstream& is, StateListPtr& states) { } } -// Prints the evaluation of the current position, consistent with -// the UCI options set so far. +// Prints the evaluation of the current position, +// consistent with the UCI options set so far. void trace_eval(Position& pos) { StateListPtr states(new std::deque(1)); @@ -122,9 +122,8 @@ void setoption(std::istringstream& is) { } -// Called when the engine receives the "go" UCI command. The function -// sets the thinking time and other parameters from the input string, then starts -// with a search. +// Called when the engine receives the "go" UCI command. The function sets the +// thinking time and other parameters from the input string then stars with a search void go(Position& pos, std::istringstream& is, StateListPtr& states) { diff --git a/src/uci.h b/src/uci.h index be5c70c5..55fb47c2 100644 --- a/src/uci.h +++ b/src/uci.h @@ -36,7 +36,7 @@ namespace UCI { // to the UCI centipawn result used in output. This value is derived from // the win_rate_model() such that Stockfish outputs an advantage of // "100 centipawns" for a position if the engine has a 50% probability to win -// from this position in selfplay at fishtest LTC time control. +// from this position in self-play at fishtest LTC time control. const int NormalizeToPawnValue = 328; class Option; From 13426a93c187c4953388a4484b8da69ee6f26fa3 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 23 Nov 2023 22:13:11 +0100 Subject: [PATCH 474/678] Simplify history update. Removal of the slowdown factor from the history update formula with corresponding adjustment of the stat bonus used in the search. Passed STC: https://tests.stockfishchess.org/tests/view/655e1079136acbc573544744 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 128096 W: 32355 L: 32235 D: 63506 Ptnml(0-2): 466, 15187, 32573, 15405, 417 Passed LTC: https://tests.stockfishchess.org/tests/view/655f4e60136acbc573546266 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 50652 W: 12653 L: 12459 D: 25540 Ptnml(0-2): 28, 5666, 13751, 5846, 35 closes https://github.com/official-stockfish/Stockfish/pull/4883 Bench: 1303857 --- src/movepick.h | 2 +- src/search.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 299925a5..e032b0c7 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -58,7 +58,7 @@ class StatsEntry { assert(abs(bonus) <= D); // Ensure range is [-D, D] static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += (bonus * D - entry * abs(bonus)) / (D * 5 / 4); + entry += bonus - entry * abs(bonus) / D; assert(abs(entry) <= D); } diff --git a/src/search.cpp b/src/search.cpp index c878b0ab..55be43b3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,10 +94,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(364 * d - 438, 1501); } +int stat_bonus(Depth d) { return std::min(291 * d - 350, 1200); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(452 * d - 452, 1478); } +int stat_malus(Depth d) { return std::min(361 * d - 361, 1182); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(const Thread* thisThread) { @@ -746,7 +746,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-18 * int((ss - 1)->staticEval + ss->staticEval), -1812, 1812); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1449, 1449); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; From 757ae2ff53714b975066cd9eb3b518611bc06b11 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:16:56 +0800 Subject: [PATCH 475/678] Simplify move history reduction Recent VLTC search tuning has suggested that the depth limit can be increased by a lot. This patch simplifies away the depth-based bonus from statScore reduction, making the divisor a constant. Passed STC: https://tests.stockfishchess.org/tests/view/656201f5136acbc573549791 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 91520 W: 23130 L: 22967 D: 45423 Ptnml(0-2): 282, 10947, 23141, 11106, 284 Passed LTC: https://tests.stockfishchess.org/tests/view/6562b43a136acbc57354a581 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 352902 W: 86796 L: 86917 D: 179189 Ptnml(0-2): 190, 40227, 95741, 40100, 193 closes https://github.com/official-stockfish/Stockfish/pull/4886 Bench: 1297179 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 55be43b3..6580f520 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1165,7 +1165,7 @@ moves_loop: // When in check, search starts here + (*contHist[3])[movedPiece][to_sq(move)] - 3848; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23)); + r -= ss->statScore / 14200; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has From f17db4641e0ec3a3d633cff6abc83e980a04ac4c Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 2 Dec 2023 11:33:11 +0100 Subject: [PATCH 476/678] Simplify doDeeperSearch Removing dependence on d simplifies the doDeeperSearch formula and eliminates a variable that is not necessary in this context. Passed STC: https://tests.stockfishchess.org/tests/view/65647980136acbc57354c9f6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 37440 W: 9558 L: 9334 D: 18548 Ptnml(0-2): 127, 4439, 9375, 4641, 138 Passed LTC: https://tests.stockfishchess.org/tests/view/6564c3f0136acbc57354d126 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 113946 W: 27993 L: 27864 D: 58089 Ptnml(0-2): 67, 12975, 30783, 13058, 90 closes https://github.com/official-stockfish/Stockfish/pull/4888 Bench: 1427733 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6580f520..0a12c85b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1188,9 +1188,8 @@ 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 + 51 + 10 * (newDepth - d)); // (~1 Elo) - const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) + const bool doDeeperSearch = value > (bestValue + 50 + 2 * newDepth); // (~1 Elo) + const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; From 883163395ed464d17c6732e227a2d2c3c0b26f1e Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sat, 2 Dec 2023 11:37:52 +0100 Subject: [PATCH 477/678] Simplify promotion move generation closes https://github.com/official-stockfish/Stockfish/pull/4892 No functional change --- src/movegen.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 16da659d..7d6856bb 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -31,18 +31,12 @@ namespace { template ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { - if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) - { - *moveList++ = make(to - D, to, QUEEN); - if constexpr (Enemy && Type == CAPTURES) - { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); - } - } + constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS; - if constexpr ((Type == QUIETS && !Enemy) || Type == EVASIONS || Type == NON_EVASIONS) + if constexpr (Type == CAPTURES || all) + *moveList++ = make(to - D, to, QUEEN); + + if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all) { *moveList++ = make(to - D, to, ROOK); *moveList++ = make(to - D, to, BISHOP); From 7dc40ac6437ec96d312e387b76573e5c496bd0b6 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 29 Nov 2023 13:47:25 +0300 Subject: [PATCH 478/678] Simplify quietMoveMalus malus Use a simple depth instead of depth + 1 in the quietMoveMalus formula. Passed STC: https://tests.stockfishchess.org/tests/view/65636bf0136acbc57354b662 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 105248 W: 26680 L: 26532 D: 52036 Ptnml(0-2): 409, 12590, 26481, 12732, 412 Passed LTC: https://tests.stockfishchess.org/tests/view/6563b5db136acbc57354bcab LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 204204 W: 50200 L: 50166 D: 103838 Ptnml(0-2): 123, 23331, 55145, 23395, 108 closes https://github.com/official-stockfish/Stockfish/pull/4893 Bench: 1717495 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0a12c85b..786d25c6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1687,7 +1687,7 @@ void update_all_stats(const Position& pos, PieceType captured; int quietMoveBonus = stat_bonus(depth + 1); - int quietMoveMalus = stat_malus(depth + 1); + int quietMoveMalus = stat_malus(depth); if (!pos.capture_stage(bestMove)) { @@ -1898,9 +1898,9 @@ string UCI::pv(const Position& pos, Depth depth) { } -// Called in case we have no ponder move -// before exiting the search, for instance, in case we stop the search during a -// fail high at root. We try hard to have a ponder move to return to the GUI, +// Called in case we have no ponder move before exiting the search, +// for instance, in case we stop the search during a fail high at root. +// We try hard to have a ponder move to return to the GUI, // otherwise in case of 'ponder on' we have nothing to think about. bool RootMove::extract_ponder_from_tt(Position& pos) { From 85403a89bac9fe3538ae410fe651364abf78c504 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:20:43 +0100 Subject: [PATCH 479/678] Skip LMR for 2nd move at the root only This patch reverts commit by Vizvezdenec: https://github.com/official-stockfish/Stockfish/commit/27139dedac14af400f5b18e2ab50aca3f8cf0e33 Passed STC: https://tests.stockfishchess.org/tests/view/65660b4a136acbc57354f13d LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 301952 W: 76271 L: 76344 D: 149337 Ptnml(0-2): 1053, 36293, 76348, 36238, 1044 Passed LTC: https://tests.stockfishchess.org/tests/view/656738ab136acbc573550e39 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 25050 W: 6283 L: 6063 D: 12704 Ptnml(0-2): 10, 2756, 6775, 2972, 12 closes https://github.com/official-stockfish/Stockfish/pull/4895 Bench: 1722961 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 786d25c6..409a3c1d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1171,7 +1171,7 @@ moves_loop: // When in check, search starts here // We use various heuristics for the sons of a node after the first son has // been searched. In general, we would like to reduce them, but there are many // cases where we extend a son if it has good chances to be "interesting". - if (depth >= 2 && moveCount > 1 + (PvNode && ss->ply <= 1) + if (depth >= 2 && moveCount > 1 + rootNode && (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1))) { // In general we want to cap the LMR depth search at newDepth, but when From 15d47a2b3821b92c4d048f39f7f43c301299d365 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:25:01 +0800 Subject: [PATCH 480/678] Remove recaptures stage in qsearch Simplify an old commit https://github.com/official-stockfish/Stockfish/commit/72760c05c64d1fb2bb71c2ac54acfbeecf513b87. Search is not stuck on the test position given r1n1n1b1/1P1P1P1P/1N1N1N2/2RnQrRq/2pKp3/3BNQbQ/k7/4Bq2 w - - 0 1 Passed STC: https://tests.stockfishchess.org/tests/view/6567050d136acbc573550919 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 236160 W: 59475 L: 59475 D: 117210 Ptnml(0-2): 841, 28266, 59816, 28366, 791 Passed LTC: https://tests.stockfishchess.org/tests/view/6567d133136acbc573551c78 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 201690 W: 49630 L: 49593 D: 102467 Ptnml(0-2): 128, 23214, 54122, 23255, 126 closes https://github.com/official-stockfish/Stockfish/pull/4896 Bench: 1604361 --- src/movepick.cpp | 7 ++----- src/movepick.h | 4 +--- src/search.cpp | 2 +- src/types.h | 5 ++--- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 798f51dd..0267a8e2 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -112,15 +112,13 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory* ph, - Square rs) : + const PawnHistory* ph) : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), pawnHistory(ph), ttMove(ttm), - recaptureSquare(rs), depth(d) { assert(d <= 0); @@ -340,8 +338,7 @@ top: return select([&]() { return pos.see_ge(*cur, threshold); }); case QCAPTURE : - if (select( - [&]() { return depth > DEPTH_QS_RECAPTURES || to_sq(*cur) == recaptureSquare; })) + if (select([]() { return true; })) return *(cur - 1); // If we did not find any move and we do not try checks, we have finished diff --git a/src/movepick.h b/src/movepick.h index e032b0c7..7828fa19 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -152,8 +152,7 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory*, - Square); + const PawnHistory*); MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); @@ -173,7 +172,6 @@ class MovePicker { Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; - Square recaptureSquare; Value threshold; Depth depth; ExtMove moves[MAX_MOVES]; diff --git a/src/search.cpp b/src/search.cpp index 409a3c1d..16003138 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1487,7 +1487,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // will be generated. Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, &thisThread->pawnHistory, prevSq); + contHist, &thisThread->pawnHistory); int quietCheckEvasions = 0; diff --git a/src/types.h b/src/types.h index 7ac2f849..0575f1d4 100644 --- a/src/types.h +++ b/src/types.h @@ -205,9 +205,8 @@ constexpr Value PieceValue[PIECE_NB] = { using Depth = int; enum : int { - DEPTH_QS_CHECKS = 0, - DEPTH_QS_NO_CHECKS = -1, - DEPTH_QS_RECAPTURES = -5, + DEPTH_QS_CHECKS = 0, + DEPTH_QS_NO_CHECKS = -1, DEPTH_NONE = -6, From 08cdbca56fac98513481683a92eb1ecdc00d3f6e Mon Sep 17 00:00:00 2001 From: lonfom169 <50217346+lonfom169@users.noreply.github.com> Date: Thu, 30 Nov 2023 01:12:19 -0300 Subject: [PATCH 481/678] Tweak return value in futility pruning In futility pruning, return the average between eval and beta. Passed STC: https://tests.stockfishchess.org/tests/view/65680bb6136acbc5735521d7 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 15200 W: 3926 L: 3642 D: 7632 Ptnml(0-2): 36, 1699, 3867, 1941, 57 Passed LTC: https://tests.stockfishchess.org/tests/view/656817fc136acbc573552304 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 200376 W: 49700 L: 49036 D: 101640 Ptnml(0-2): 110, 22584, 54137, 23246, 111 closes https://github.com/official-stockfish/Stockfish/pull/4897 Bench: 1403703 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 16003138..ba5ea2e5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -780,7 +780,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo >= beta && eval >= beta && eval < 29462 // smaller than TB wins && (!ttMove || ttCapture)) - return eval; + return (eval + beta) / 2; // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17257 && eval >= beta From 8f65953583a2abc34041b087120a378e22d0509d Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 3 Dec 2023 13:37:59 +0100 Subject: [PATCH 482/678] Temporarily disable CI include checks The include checks currently fail because of broken LLVM nightly packages: https://github.com/llvm/llvm-project/issues/73402. closes https://github.com/official-stockfish/Stockfish/pull/4899 No functional change --- .github/workflows/stockfish.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 1ed4b92d..83dd1b9c 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -33,8 +33,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - Analyzers: - uses: ./.github/workflows/stockfish_analyzers.yml + # The include checks currently fail because of broken LLVM nightly packages: https://github.com/llvm/llvm-project/issues/73402 + #Analyzers: + # uses: ./.github/workflows/stockfish_analyzers.yml Sanitizers: uses: ./.github/workflows/stockfish_sanitizers.yml Tests: From 0ff2ea654971445f4e8955840bcb974dc62e5106 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Sun, 3 Dec 2023 13:39:52 +0100 Subject: [PATCH 483/678] Update GitHub workflows - Use the latest version of the actions - Use commit hash for actions from little providers - Update Intel SDE to 9.27 closes https://github.com/official-stockfish/Stockfish/pull/4900 No functional change --- .github/workflows/codeql.yml | 2 +- .github/workflows/stockfish.yml | 2 +- .github/workflows/stockfish_analyzers.yml | 4 ++-- .github/workflows/stockfish_arm_binaries.yml | 6 +++--- .github/workflows/stockfish_binaries.yml | 16 ++++++++-------- .github/workflows/stockfish_compile_test.yml | 2 +- .github/workflows/stockfish_format_check.yml | 8 ++++---- .github/workflows/stockfish_sanitizers.yml | 2 +- .github/workflows/stockfish_test.yml | 6 +++--- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 863f219c..054be900 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 83dd1b9c..6d71fef5 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -26,7 +26,7 @@ jobs: echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV # delete old previous pre-release and tag - - uses: dev-drprasad/delete-tag-and-release@v0.2.1 + - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1 if: env.COMMIT_SHA != 'null' with: tag_name: ${{ env.COMMIT_SHA }} diff --git a/.github/workflows/stockfish_analyzers.yml b/.github/workflows/stockfish_analyzers.yml index 5f985cc8..f54cdd7c 100644 --- a/.github/workflows/stockfish_analyzers.yml +++ b/.github/workflows/stockfish_analyzers.yml @@ -11,12 +11,12 @@ jobs: shell: bash steps: - name: Checkout Stockfish - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: Stockfish - name: Checkout include-what-you-use - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: include-what-you-use/include-what-you-use ref: f25caa280dc3277c4086ec345ad279a2463fea0f diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 4d7f3d55..203c00f2 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -46,7 +46,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -139,7 +139,7 @@ jobs: - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: files: stockfish-android-${{ matrix.binaries }}.tar @@ -162,7 +162,7 @@ jobs: - name: Prerelease if: github.ref_name == 'master' && env.CHANGES == '0' continue-on-error: true - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 6da576e4..5b3a5226 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -23,7 +23,7 @@ jobs: comp: gcc shell: bash archive_ext: tar - sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future -- + sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future -- - name: MacOS 13 Apple Clang os: macos-13 simple_name: macos @@ -40,7 +40,7 @@ jobs: msys_env: x86_64-gcc shell: msys2 {0} ext: .exe - sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future -- + sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future -- archive_ext: zip binaries: - x86-64 @@ -67,7 +67,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -77,7 +77,7 @@ jobs: - name: Install fixed GCC on Linux if: runner.os == 'Linux' - uses: egor-tensin/setup-gcc@v1 + uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 with: version: 11 @@ -90,10 +90,10 @@ jobs: - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@6f4926100f31791716b11d25c0f3f35809d44f84 + uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 with: environmentVariableName: SDE_DIR - sdeVersion: 9.14.0 + sdeVersion: 9.27.0 - name: Download the used network from the fishtest framework run: make net @@ -183,7 +183,7 @@ jobs: - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} @@ -206,7 +206,7 @@ jobs: - name: Prerelease if: github.ref_name == 'master' && env.CHANGES == '0' continue-on-error: true - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 808fcb55..1adc3e34 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -51,7 +51,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup msys and install required packages if: runner.os == 'Windows' diff --git a/.github/workflows/stockfish_format_check.yml b/.github/workflows/stockfish_format_check.yml index cb16b327..7a47ab6f 100644 --- a/.github/workflows/stockfish_format_check.yml +++ b/.github/workflows/stockfish_format_check.yml @@ -16,12 +16,12 @@ jobs: name: clang-format check runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - name: Run clang-format style check - uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e + uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 id: clang-format continue-on-error: true with: @@ -30,7 +30,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome == 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 with: message: | clang-format 17 needs to be run on this PR. @@ -42,7 +42,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome != 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 with: message: | _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index b137f50e..e3f04617 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -35,7 +35,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download required linux packages run: | diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 72f0c22e..cff3ef1b 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -95,7 +95,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -121,11 +121,11 @@ jobs: - name: Set up QEMU if: matrix.config.base_image - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx if: matrix.config.base_image - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build Docker container if: matrix.config.base_image From 7a8bcfc229ca6e4e44c0b284b7609a3aa26fa1ee Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 4 Dec 2023 11:31:16 +0100 Subject: [PATCH 484/678] Remove cutNode condition cutNode condition seems to be irrelevant. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 24224 W: 6206 L: 5970 D: 12048 Ptnml(0-2): 69, 2818, 6122, 3014, 89 https://tests.stockfishchess.org/tests/view/65686910136acbc5735529ec Passed LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 236538 W: 58624 L: 58622 D: 119292 Ptnml(0-2): 136, 26955, 64091, 26945, 142 https://tests.stockfishchess.org/tests/view/6568925a136acbc573552d8f closes https://github.com/official-stockfish/Stockfish/pull/4901 Bench: 1244386 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ba5ea2e5..b3ca8c9a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1207,8 +1207,8 @@ moves_loop: // When in check, search starts here // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { - // Increase reduction for cut nodes without ttMove (~1 Elo) - if (!ttMove && cutNode) + // Increase reduction if ttMove is not present (~1 Elo) + if (!ttMove) r += 2; // Note that if expected reduction is high, we reduce search depth by 1 here From dadff4698651336ebd07c414f74c1e707cd9bd15 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 5 Dec 2023 11:49:12 +0100 Subject: [PATCH 485/678] Revert "Temporarily disable CI include checks" This reverts commit 8f65953583a2abc34041b087120a378e22d0509d. closes https://github.com/official-stockfish/Stockfish/pull/4904 No functional change --- .github/workflows/stockfish.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 6d71fef5..7bbb53d5 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -33,9 +33,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # The include checks currently fail because of broken LLVM nightly packages: https://github.com/llvm/llvm-project/issues/73402 - #Analyzers: - # uses: ./.github/workflows/stockfish_analyzers.yml + Analyzers: + uses: ./.github/workflows/stockfish_analyzers.yml Sanitizers: uses: ./.github/workflows/stockfish_sanitizers.yml Tests: From 53ad6d23b0e7ec2814579d4acba7c02c2b12008f Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:41:10 +0100 Subject: [PATCH 486/678] Remove moveMalus Passed STC: https://tests.stockfishchess.org/tests/view/656e0bb86980e15f69c763fa LLR: 3.15 (-2.94,2.94) <-1.75,0.25> Total: 123008 W: 30973 L: 30831 D: 61204 Ptnml(0-2): 368, 14032, 32568, 14162, 374 closes https://github.com/official-stockfish/Stockfish/pull/4905 No functional change --- src/search.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b3ca8c9a..39711f3c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1699,18 +1699,15 @@ void update_all_stats(const Position& pos, thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)] << quietMoveBonus; - int moveMalus = bestValue > beta + 168 ? quietMoveMalus // larger malus - : stat_malus(depth); // smaller malus - // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])] [to_sq(quietsSearched[i])] - << -moveMalus; - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -moveMalus; + << -quietMoveMalus; + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - to_sq(quietsSearched[i]), -moveMalus); + to_sq(quietsSearched[i]), -quietMoveMalus); } } else From 8724503d9c8f15f9185bd4394e425e809f3992c1 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Wed, 6 Dec 2023 18:01:58 +0100 Subject: [PATCH 487/678] Simplify the code to get the native flags closes https://github.com/official-stockfish/Stockfish/pull/4908 No functional change --- scripts/get_native_properties.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index cffb0ce2..ae23c3bb 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -14,10 +14,9 @@ check_flags() { } # Set the CPU flags list +# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 get_flags() { - flags="$(awk '/^flags[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*/, ""); line=$0} END{print line}' /proc/cpuinfo) $(awk '/^Features[ \t]*:/{gsub(/^Features[ \t]*:[ \t]*/, ""); line=$0} END{print line}' /proc/cpuinfo)" - # remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 - flags=$(printf '%s' "$flags" | sed "s/[_.]//g") + flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo) } # Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid @@ -55,7 +54,7 @@ case $uname_s in file_arch='x86-64-sse41-popcnt' # Supported by Rosetta 2 ;; 'x86_64') - flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | sed "s/[_.]//g") + flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') set_arch_x86_64 if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then file_arch='x86-64-bmi2' From 36db936e769a2e7a95fc4032eec3b79251bbaef5 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:39:57 +0800 Subject: [PATCH 488/678] VLTC Search parameters tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SPSA tuning was done for 44k games at 120+1.2. https://tests.stockfishchess.org/tests/view/656ee2a76980e15f69c7767f. Note that the tune was originally done in combination with the recent dual NNUE idea (see #4910). VLTC: https://tests.stockfishchess.org/tests/view/65731ccbf09ce1261f12246e LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 52806 W: 13069 L: 12760 D: 26977 Ptnml(0-2): 19, 5498, 15056, 5815, 15 VLTC SMP: https://tests.stockfishchess.org/tests/view/65740ffaf09ce1261f1239ba LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 27630 W: 6934 L: 6651 D: 14045 Ptnml(0-2): 1, 2643, 8243, 2928, 0 Estimated close to neutral at LTC: https://tests.stockfishchess.org/tests/view/6575485a8ec68176cf7d9423 Elo: -0.59 ± 1.8 (95%) LOS: 26.6% Total: 32060 W: 7859 L: 7913 D: 16288 Ptnml(0-2): 20, 3679, 8676, 3645, 10 nElo: -1.21 ± 3.8 (95%) PairsRatio: 0.99 closes https://github.com/official-stockfish/Stockfish/pull/4912 Bench: 1283323 --- src/search.cpp | 76 +++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 39711f3c..f3980740 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,7 +77,7 @@ enum NodeType { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return Value((125 - 43 * noTtCutNode) * (d - improving)); + return Value((116 - 44 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup @@ -85,8 +85,8 @@ int Reductions[MAX_MOVES]; // [depth or moveNumber] Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1487 - int(delta) * 976 / int(rootDelta)) / 1024 - + (!i && reductionScale > 808); + return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 + + (!i && reductionScale > 880); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -94,10 +94,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(291 * d - 350, 1200); } +int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(361 * d - 361, 1182); } +int stat_malus(Depth d) { return std::min(400 * d - 354, 1201); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(const Thread* thisThread) { @@ -367,12 +367,12 @@ void Thread::search() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(avg) * avg / 15335; + delta = Value(9) + int(avg) * avg / 14847; 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] = 110 * avg / (std::abs(avg) + 121); + optimism[us] = 121 * avg / (std::abs(avg) + 109); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -746,7 +746,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1449, 1449); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1555, 1452); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; @@ -765,7 +765,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 474 - (270 - 174 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 472 - (284 - 165 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -776,22 +776,22 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // The depth condition is important for mate finding. if (!ss->ttPv && depth < 9 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - - (ss - 1)->statScore / 321 + - (ss - 1)->statScore / 337 >= beta - && eval >= beta && eval < 29462 // smaller than TB wins + && eval >= beta && eval < 29008 // smaller than TB wins && (!ttMove || ttCapture)) return (eval + beta) / 2; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17257 && eval >= beta - && eval >= ss->staticEval && ss->staticEval >= beta - 24 * depth + 281 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17496 && eval >= beta + && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -805,7 +805,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Do not return unproven mate or TB scores if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { - if (thisThread->nmpMinPly || depth < 14) + if (thisThread->nmpMinPly || depth < 15) return nullValue; assert(!thisThread->nmpMinPly); // Recursive verification is not allowed @@ -838,7 +838,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 168 - 70 * improving; + probCutBeta = beta + 163 - 67 * improving; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -896,7 +896,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 416; + probCutBeta = beta + 425; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -983,14 +983,14 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(to_sq(move)); int futilityEval = - ss->staticEval + 239 + 291 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; if (futilityEval < alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, Value(-185) * depth)) + if (!pos.see_ge(move, Value(-187) * depth)) continue; } else @@ -1001,18 +1001,18 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3645 * depth) + if (lmrDepth < 6 && history < -3752 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7836; + lmrDepth += history / 7838; lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 - && ss->staticEval + (bestValue < ss->staticEval - 62 ? 123 : 77) - + 127 * lmrDepth + if (!ss->inCheck && lmrDepth < 14 + && ss->staticEval + (bestValue < ss->staticEval - 57 ? 124 : 71) + + 118 * lmrDepth <= alpha) continue; @@ -1039,11 +1039,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 27) + 2 * (PvNode && tte->is_pv()) && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (66 + 58 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1057,7 +1057,7 @@ moves_loop: // When in check, search starts here singularQuietLMR = !ttCapture; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 18 && ss->doubleExtensions <= 11) + if (!PvNode && value < singularBeta - 17 && ss->doubleExtensions <= 11) { extension = 2; depth += depth < 15; @@ -1092,18 +1092,18 @@ moves_loop: // When in check, search starts here } // Check extensions (~1 Elo) - else if (givesCheck && depth > 9) + else if (givesCheck && depth > 10) extension = 1; // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) + && (*contHist[0])[movedPiece][to_sq(move)] >= 4325) extension = 1; // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && to_sq(move) == prevSq && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] - > 4000) + > 4146) extension = 1; } @@ -1162,10 +1162,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - 3848; + + (*contHist[3])[movedPiece][to_sq(move)] - 3817; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / 14200; + r -= ss->statScore / 14767; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1188,7 +1188,7 @@ 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 + 50 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 53 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1303,7 +1303,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13828 && value > -11369) + if (depth > 2 && depth < 12 && beta < 13782 && value > -11541) depth -= 2; assert(depth > 0); @@ -1342,7 +1342,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 657) + int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 656) + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1475,7 +1475,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 200; + futilityBase = ss->staticEval + 182; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1555,7 +1555,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-90))) + if (!pos.see_ge(move, Value(-77))) continue; } @@ -1691,7 +1691,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 282e15bf75bd1142de96306b22424f0cd2bb8dfa Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Fri, 17 Nov 2023 18:25:50 +0300 Subject: [PATCH 489/678] Fix TB score output in UCI without using TB This is a rewrite of the fix introduced for https://github.com/official-stockfish/Stockfish/issues/4413 in https://github.com/official-stockfish/Stockfish/pull/4591 by @windfishballad it targets only the relevant part of this issue that returns TB scores (CP 20000) without using TB due to the downgrading of potentially false mates from the TT to an optimal TB score. the difference is that it is a much clearer code that introduces a separate TB_VALUE constant to account for a correct distance from the TB_VALUE with MAX_PLY. the originally posted position in the issue does not trigger the problem anymore, so here is a new position to test: ``` position fen 3k4/8/8/8/8/8/3BN3/3K4 w - - 0 1 go infinite ``` Passed non-regression STC: https://tests.stockfishchess.org/tests/view/65578994136acbc57353b258 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 119264 W: 29993 L: 29863 D: 59408 Ptnml(0-2): 372, 13692, 31379, 13812, 377 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6558323f136acbc57353c1ca LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 237834 W: 58791 L: 58792 D: 120251 Ptnml(0-2): 193, 26200, 66111, 26241, 172 fixes https://github.com/official-stockfish/Stockfish/issues/4413 closes https://github.com/official-stockfish/Stockfish/pull/4591 closes https://github.com/official-stockfish/Stockfish/pull/4882 Bench: 1305821 --- src/search.cpp | 39 +++++++++++++++++++++++++++------------ src/types.h | 12 +++++++----- src/uci.cpp | 4 ++-- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f3980740..89879374 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -678,9 +678,11 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo int drawScore = TB::UseRule50 ? 1 : 0; - // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score - value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 - : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 + Value tbValue = VALUE_TB - ss->ply; + + // use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score + value = wdl < -drawScore ? -tbValue + : wdl > drawScore ? tbValue : VALUE_DRAW + 2 * wdl * drawScore; Bound b = wdl < -drawScore ? BOUND_UPPER @@ -1631,25 +1633,38 @@ Value value_to_tt(Value v, int ply) { // Inverse of value_to_tt(): it adjusts a mate or TB score // from the transposition table (which refers to the plies to mate/be mated from // current position) to "plies to mate/be mated (TB win/loss) from the root". -// However, to avoid potentially false mate scores related to the 50 moves rule -// and the graph history interaction problem, we return an optimal TB score instead. +// However, to avoid potentially false mate or TB scores related to the 50 moves rule +// and the graph history interaction, we return highest non-TB score instead. + Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) return VALUE_NONE; - if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better + // handle TB win or better + if (v >= VALUE_TB_WIN_IN_MAX_PLY) { - if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c) - return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score + // Downgrade a potentially false mate score + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; return v - ply; } - if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse + // handle TB loss or worse + if (v <= VALUE_TB_LOSS_IN_MAX_PLY) { - if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c) - return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score + // Downgrade a potentially false mate score. + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; return v + ply; } @@ -1866,7 +1881,7 @@ string UCI::pv(const Position& pos, Depth depth) { if (v == -VALUE_INFINITE) v = VALUE_ZERO; - bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; + bool tb = TB::RootInTB && abs(v) <= VALUE_TB; v = tb ? rootMoves[i].tbScore : v; if (ss.rdbuf()->in_avail()) // Not at first line diff --git a/src/types.h b/src/types.h index 0575f1d4..3e00d68d 100644 --- a/src/types.h +++ b/src/types.h @@ -164,14 +164,16 @@ enum Bound { enum Value : int { VALUE_ZERO = 0, VALUE_DRAW = 0, - VALUE_MATE = 32000, - VALUE_INFINITE = 32001, VALUE_NONE = 32002, + VALUE_INFINITE = 32001, - VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, + VALUE_MATE = 32000, + VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, + VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, + + VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1, + VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY, VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, - VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, - VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, // In the code, we make the assumption that these values // are such that non_pawn_material() can be used to uniquely diff --git a/src/uci.cpp b/src/uci.cpp index 95f6f349..d0341bd7 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -356,9 +356,9 @@ std::string UCI::value(Value v) { if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) ss << "cp " << UCI::to_cp(v); - else if (abs(v) < VALUE_MATE_IN_MAX_PLY) + else if (abs(v) <= VALUE_TB) { - const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply + const int ply = VALUE_TB - std::abs(v); // recompute ss->ply ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); } else From 7885fa5bd3c8aae1e992ec80cbaaab1177502426 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Mon, 4 Dec 2023 00:39:41 +0300 Subject: [PATCH 490/678] Track seldepth in qsearch too Sometimes if we count the reported PV length, it turns out to be longer than the selective depth reported. This fixes this behavior by applying the selective depth to qsearch since we do report PVs from it as well. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/656cf5b66980e15f69c7499d LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 223648 W: 56372 L: 56356 D: 110920 Ptnml(0-2): 710, 25580, 59231, 25590, 713 closes https://github.com/official-stockfish/Stockfish/pull/4903 No functional change --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 89879374..10a36cbf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1421,6 +1421,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->inCheck = pos.checkers(); moveCount = 0; + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; + // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; From cdfafb3426cdf3a6c60fe2e20eb52c72d2777e51 Mon Sep 17 00:00:00 2001 From: WangXiang Date: Sun, 10 Dec 2023 23:04:32 +0800 Subject: [PATCH 491/678] Add loongarch64 support Adding support for LoongArch64 architecture. Tested on Loongson 3A6000 EVB Board. Since Loongson's SIMD extended instruction set ([LSX](https://gcc.gnu.org/onlinedocs/gcc/LoongArch-SX-Vector-Intrinsics.html), [LASX](https://gcc.gnu.org/onlinedocs/gcc/LoongArch-ASX-Vector-Intrinsics.html)) is already supported by GCC, more optimizations are being developed. Here's the benchmark result for Loongson 3A6000 (4c8t, 2.5Ghz) without SIMD optimizations. ``` Total time (ms) : 17903 Nodes searched : 1244386 Nodes/second : 69507 ``` closes https://github.com/official-stockfish/Stockfish/pull/4913 No functional change --- AUTHORS | 1 + src/Makefile | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index d7b64b62..f6a10288 100644 --- a/AUTHORS +++ b/AUTHORS @@ -227,6 +227,7 @@ Vince Negri (cuddlestmonkey) Viren windfishballad xefoci7612 +Xiang Wang (KatyushaScarlet) zz4032 # Additionally, we acknowledge the authors and maintainers of fishtest, diff --git a/src/Makefile b/src/Makefile index 59ea7bfe..761b4086 100644 --- a/src/Makefile +++ b/src/Makefile @@ -125,7 +125,7 @@ ifeq ($(ARCH), $(filter $(ARCH), \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \ - armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64)) + armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 loongarch64)) SUPPORTED_ARCH=true else SUPPORTED_ARCH=false @@ -369,6 +369,10 @@ endif ifeq ($(ARCH),riscv64) arch = riscv64 endif + +ifeq ($(ARCH),loongarch64) + arch = loongarch64 +endif endif @@ -404,6 +408,8 @@ ifeq ($(COMP),gcc) ifeq ($(ARCH),riscv64) CXXFLAGS += -latomic endif + else ifeq ($(ARCH),loongarch64) + CXXFLAGS += -latomic else CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) @@ -474,6 +480,8 @@ ifeq ($(COMP),clang) ifeq ($(ARCH),riscv64) CXXFLAGS += -latomic endif + else ifeq ($(ARCH),loongarch64) + CXXFLAGS += -latomic else CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) @@ -823,6 +831,7 @@ help: @echo "general-64 > unspecified 64-bit" @echo "general-32 > unspecified 32-bit" @echo "riscv64 > RISC-V 64-bit" + @echo "loongarch64 > LoongArch 64-bit" @echo "" @echo "Supported compilers:" @echo "" @@ -1004,7 +1013,7 @@ config-sanity: net @test "$(SUPPORTED_ARCH)" = "true" @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \ - test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" + test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64" @test "$(bits)" = "32" || test "$(bits)" = "64" @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" From 9fc064e872e772f941e6fb5d303d827174003ce7 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 10 Dec 2023 23:40:45 +0100 Subject: [PATCH 492/678] Fix action deprecation warning for dev-drprasad closes https://github.com/official-stockfish/Stockfish/pull/4914 No functional change --- .github/workflows/stockfish.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 7bbb53d5..e8db5235 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -30,8 +30,7 @@ jobs: if: env.COMMIT_SHA != 'null' with: tag_name: ${{ env.COMMIT_SHA }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} Analyzers: uses: ./.github/workflows/stockfish_analyzers.yml From 536d692a3082cc86afcf1a48b0cf25ac73fa7074 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 12 Dec 2023 16:38:24 +0300 Subject: [PATCH 493/678] Remove SlowMover Option The SlowMover option allows users to modify the timeLeft variant, impacting the engine's time management. However, this feature, while theoretically flexible, doesn't offer substantial benefits. Instead, it introduces the risk of non-experienced users altering values without a clear understanding of the effects, potentially leading to a weaker engine. The vast majority of SF users don't use it anyway, and based on tests conducted by fauzi several months ago suggest that changing it would only lose Elo. Examples: https://tests.stockfishchess.org/tests/view/651f309bac57711436726bba https://tests.stockfishchess.org/tests/view/651fea29ac57711436727d85 https://tests.stockfishchess.org/tests/view/65257c343125598fc7eb68a1 https://tests.stockfishchess.org/tests/view/652296c83125598fc7eb2ad7 Tune: https://tests.stockfishchess.org/tests/view/652a70313125598fc7ebd706 (keeping the value at 100, zz2) closes https://github.com/official-stockfish/Stockfish/pull/4917 No functional change --- src/timeman.cpp | 5 ----- src/ucioption.cpp | 1 - 2 files changed, 6 deletions(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 1253d434..9ff422fe 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -42,7 +42,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { return; TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); - TimePoint slowMover = TimePoint(Options["Slow Mover"]); TimePoint npmsec = TimePoint(Options["nodestime"]); // optScale is a percentage of available time to use for the current move. @@ -78,10 +77,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { double optConstant = std::min(0.00335 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0048); double maxConstant = std::max(3.6 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.7); - // A user may scale time usage by setting UCI option "Slow Mover" - // Default is 100 and changing this value will probably lose elo. - timeLeft = slowMover * timeLeft / 100; - // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed actual available // game time for the current move, so also cap to 20% of available game time. diff --git a/src/ucioption.cpp b/src/ucioption.cpp index d0db1c76..233602ca 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -73,7 +73,6 @@ void init(OptionsMap& o) { o["MultiPV"] << Option(1, 1, 500); o["Skill Level"] << Option(20, 0, 20); o["Move Overhead"] << Option(10, 0, 5000); - o["Slow Mover"] << Option(100, 10, 1000); o["nodestime"] << Option(0, 0, 10000); o["UCI_Chess960"] << Option(false); o["UCI_AnalyseMode"] << Option(false); From c53d2ec253557d8a679197becb7ba9a6aa393ecc Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 13 Dec 2023 19:05:37 +0800 Subject: [PATCH 494/678] Remove UCI_AnalyseMode Option Simplify away the useless option, as documented: "An option handled by your GUI. This currently doesn't do anything." The option was originally added with the introduction of contempt (https://github.com/official-stockfish/Stockfish/commit/e9aeaad05266ca557a9496b5a17b4c5f82f0e946), but it is now no longer used. closes https://github.com/official-stockfish/Stockfish/pull/4918 No functional change --- src/ucioption.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 233602ca..1dc9b89b 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -75,7 +75,6 @@ void init(OptionsMap& o) { o["Move Overhead"] << Option(10, 0, 5000); o["nodestime"] << Option(0, 0, 10000); o["UCI_Chess960"] << Option(false); - o["UCI_AnalyseMode"] << Option(false); o["UCI_LimitStrength"] << Option(false); o["UCI_Elo"] << Option(1320, 1320, 3190); o["UCI_ShowWDL"] << Option(false); From d9ec82e7438716671168d78eee26fae327249e8c Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 15 Dec 2023 01:51:52 +0300 Subject: [PATCH 495/678] Adjust stand pat in qsearch on pv nodes Instead of immediately returning a fail high do this only at non-pv nodes, for pv nodes adjust bestValue to value between alpha and beta and continue searching. Idea is to do it the same way as it's done in search where we don't return positive beta cutoffs after ttHits / zero window search at PvNodes and instead fully search lines. Passed STC: https://tests.stockfishchess.org/tests/view/65739b0af09ce1261f122f33 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 189216 W: 48142 L: 47598 D: 93476 Ptnml(0-2): 584, 22463, 48051, 22845, 665 Passed LTC: https://tests.stockfishchess.org/tests/view/657701214d789acf40aac194 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 82506 W: 20689 L: 20269 D: 41548 Ptnml(0-2): 56, 9236, 22268, 9618, 75 Two issues had to be resolved: - in rare cases it set alpha to the same value as beta and thus broke some asserts; - messed up with returning tb win values. Fix passed non-regression LTC vs this patch: https://tests.stockfishchess.org/tests/view/6578113b4d789acf40aad544 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 277308 W: 68839 L: 68880 D: 139589 Ptnml(0-2): 167, 31580, 75212, 31517, 178 closes https://github.com/official-stockfish/Stockfish/pull/4922 Bench: 1069503 Co-Authored-By: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Co-Authored-By: Shahin M. Shahin <41402573+peregrineshahin@users.noreply.github.com> Co-Authored-By: fffelix-huang <72808219+fffelix-huang@users.noreply.github.com> --- src/search.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 10a36cbf..27c2c84e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1468,14 +1468,19 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->staticEval = bestValue = (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; - // Stand pat. Return immediately if static value is at least beta + // Stand pat. Return immediately if bestValue is at least beta at non-Pv nodes. + // At PvNodes set bestValue between alpha and beta instead if (bestValue >= beta) { - if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - MOVE_NONE, ss->staticEval); + if (!PvNode || abs(bestValue) >= VALUE_TB_WIN_IN_MAX_PLY) + { + if (!ss->ttHit) + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, + DEPTH_NONE, MOVE_NONE, ss->staticEval); - return bestValue; + return bestValue; + } + bestValue = std::min((alpha + beta) / 2, beta - 1); } if (bestValue > alpha) From 07a2619b62a25910a32ad8a4e9912f748338580f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 15 Dec 2023 14:29:44 +0300 Subject: [PATCH 496/678] Improvement of Time Management Parameters Passed STC: https://tests.stockfishchess.org/tests/view/6579c5574d789acf40aaf914 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 44672 W: 11354 L: 11030 D: 22288 Ptnml(0-2): 140, 5033, 11685, 5319, 159 Passed LTC: https://tests.stockfishchess.org/tests/view/657ad7f44d789acf40ab105e LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 40932 W: 10275 L: 9950 D: 20707 Ptnml(0-2): 21, 4316, 11473, 4629, 27 Passed non-regression Sudden death 10+0: https://tests.stockfishchess.org/tests/view/657b9b9e393ac02e7911f1a8 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 21384 W: 5171 L: 4925 D: 11288 Ptnml(0-2): 112, 2420, 5409, 2612, 139 closes https://github.com/official-stockfish/Stockfish/pull/4923 No functional change --- src/search.cpp | 6 +++--- src/timeman.cpp | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 27c2c84e..ce71c788 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -471,12 +471,12 @@ void Thread::search() { { double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + 6 * (mainThread->iterValue[iterIdx] - bestValue)) - / 583.0; - fallingEval = std::clamp(fallingEval, 0.5, 1.5); + / 616.6; + fallingEval = std::clamp(fallingEval, 0.51, 1.51); // If the bestMove is stable over several iterations, reduce time accordingly timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.03 * timeReduction); + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.17 * timeReduction); double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / Threads.size(); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; diff --git a/src/timeman.cpp b/src/timeman.cpp index 9ff422fe..f404ee0c 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -71,21 +71,21 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - moveOverhead * (2 + mtg)); // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.12); + double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); // Calculate time constants based on current time left. - double optConstant = std::min(0.00335 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0048); - double maxConstant = std::max(3.6 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.7); + double optConstant = std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); + double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed actual available // game time for the current move, so also cap to 20% of available game time. if (limits.movestogo == 0) { - optScale = std::min(0.0120 + std::pow(ply + 3.3, 0.44) * optConstant, - 0.2 * limits.time[us] / double(timeLeft)) + optScale = std::min(0.0120 + std::pow(ply + 3.1, 0.44) * optConstant, + 0.21 * limits.time[us] / double(timeLeft)) * optExtra; - maxScale = std::min(6.8, maxConstant + ply / 12.2); + maxScale = std::min(6.9, maxConstant + ply / 12.2); } // x moves in y seconds (+ z increment) From a069a1bbbfb60abddbe3fe5276b06f35f783f41c Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 18 Dec 2023 16:20:41 +0300 Subject: [PATCH 497/678] Use std::abs over abs closes https://github.com/official-stockfish/Stockfish/pull/4926 closes https://github.com/official-stockfish/Stockfish/pull/4909 No functional change Co-Authored-By: fffelix-huang <72808219+fffelix-huang@users.noreply.github.com> --- AUTHORS | 1 + src/evaluate.cpp | 11 ++++++----- src/movepick.h | 7 ++++--- src/nnue/evaluate_nnue.cpp | 2 +- src/search.cpp | 8 ++++---- src/thread.cpp | 3 ++- src/uci.cpp | 4 ++-- 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/AUTHORS b/AUTHORS index f6a10288..cedee2f3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -214,6 +214,7 @@ Taras Vuk (TarasVuk) Thanar2 thaspel theo77186 +Ting-Hsuan Huang (fffelix-huang) Tomasz Sobczyk (Sopel97) Tom Truscott Tom Vijlbrief (tomtor) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9c39d4c0..586cadc0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -164,9 +165,9 @@ Value Eval::evaluate(const Position& pos) { int shuffling = pos.rule50_count(); int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); - bool lazy = abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling - + abs(pos.this_thread()->bestValue) - + abs(pos.this_thread()->rootSimpleEval); + bool lazy = std::abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling + + std::abs(pos.this_thread()->bestValue) + + std::abs(pos.this_thread()->rootSimpleEval); if (lazy) v = Value(simpleEval); @@ -178,8 +179,8 @@ Value Eval::evaluate(const Position& pos) { Value optimism = pos.this_thread()->optimism[stm]; // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512; - nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768; + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; int npm = pos.non_pawn_material() / 64; v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; diff --git a/src/movepick.h b/src/movepick.h index 7828fa19..5077f4e3 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -55,12 +56,12 @@ class StatsEntry { operator const T&() const { return entry; } void operator<<(int bonus) { - assert(abs(bonus) <= D); // Ensure range is [-D, D] + assert(std::abs(bonus) <= D); // Ensure range is [-D, D] static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += bonus - entry * abs(bonus) / D; + entry += bonus - entry * std::abs(bonus) / D; - assert(abs(entry) <= D); + assert(std::abs(entry) <= D); } }; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index ef6b7e91..e7339c10 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -180,7 +180,7 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { const auto positional = network[bucket]->propagate(transformedFeatures); if (complexity) - *complexity = abs(psqt - positional) / OutputScale; + *complexity = std::abs(psqt - positional) / OutputScale; // Give more value to positional evaluation when adjusted flag is set if (adjusted) diff --git a/src/search.cpp b/src/search.cpp index ce71c788..bd3da5a2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -847,7 +847,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // much above beta, we can (almost) safely prune the previous move. if ( !PvNode && depth > 3 - && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY // 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 @@ -901,7 +901,7 @@ moves_loop: // When in check, search starts here probCutBeta = beta + 425; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta - && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1042,7 +1042,7 @@ moves_loop: // When in check, search starts here // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove && depth >= 4 - (thisThread->completedDepth > 27) + 2 * (PvNode && tte->is_pv()) - && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) + && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { Value singularBeta = ttValue - (66 + 58 * (ss->ttPv && !PvNode)) * depth / 64; @@ -1890,7 +1890,7 @@ string UCI::pv(const Position& pos, Depth depth) { if (v == -VALUE_INFINITE) v = VALUE_ZERO; - bool tb = TB::RootInTB && abs(v) <= VALUE_TB; + bool tb = TB::RootInTB && std::abs(v) <= VALUE_TB; v = tb ? rootMoves[i].tbScore : v; if (ss.rdbuf()->in_avail()) // Not at first line diff --git a/src/thread.cpp b/src/thread.cpp index bc884ded..de8de87d 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -235,7 +236,7 @@ Thread* ThreadPool::get_best_thread() const { votes[th->rootMoves[0].pv[0]] += thread_value(th); for (Thread* th : threads) - if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + if (std::abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) { // Make sure we pick the shortest mate / TB conversion or stave off mate the longest if (th->rootMoves[0].score > bestThread->rootMoves[0].score) diff --git a/src/uci.cpp b/src/uci.cpp index d0341bd7..5f250a36 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -354,9 +354,9 @@ std::string UCI::value(Value v) { std::stringstream ss; - if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) ss << "cp " << UCI::to_cp(v); - else if (abs(v) <= VALUE_TB) + else if (std::abs(v) <= VALUE_TB) { const int ply = VALUE_TB - std::abs(v); // recompute ss->ply ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); From 9be0360aa414556f231873ce2348f9c1f00d1713 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 18 Dec 2023 20:34:35 +0300 Subject: [PATCH 498/678] Adjust return value in qsearch after fail high Instead of returning strict fail soft fail high return value between value from search and beta (somewhat by analogy to futility pruning and probcut). This seems to be somewhat depth sensitive heuristic which performed much worse at LTC while performing much better at STC if it is more aggressive, passed version is the least aggressive one. Passed STC: https://tests.stockfishchess.org/tests/view/657b06414d789acf40ab1475 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 212352 W: 53900 L: 53315 D: 105137 Ptnml(0-2): 809, 25236, 53520, 25783, 828 Passed LTC: https://tests.stockfishchess.org/tests/view/657ce36f393ac02e79120a7c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 319362 W: 79541 L: 78630 D: 161191 Ptnml(0-2): 202, 35839, 86709, 36708, 223 closes https://github.com/official-stockfish/Stockfish/pull/4928 Bench: 974739 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index bd3da5a2..fad43b62 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1618,6 +1618,9 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { return mated_in(ss->ply); // Plies to mate from the root } + if (abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) + bestValue = bestValue >= beta ? (3 * bestValue + beta) / 4 : bestValue; + // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, ss->staticEval); From 358a85379094cbffa9d80b443ba63f3066c4cd33 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 22 Dec 2023 11:46:28 +0100 Subject: [PATCH 499/678] Revert "Adjust stand pat in qsearch on pv nodes" This reverts commit d9ec82e7438716671168d78eee26fae327249e8c. Bench: 1249544 --- src/search.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index fad43b62..235b35c1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1468,19 +1468,14 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->staticEval = bestValue = (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; - // Stand pat. Return immediately if bestValue is at least beta at non-Pv nodes. - // At PvNodes set bestValue between alpha and beta instead + // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { - if (!PvNode || abs(bestValue) >= VALUE_TB_WIN_IN_MAX_PLY) - { - if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, - DEPTH_NONE, MOVE_NONE, ss->staticEval); + if (!ss->ttHit) + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, + MOVE_NONE, ss->staticEval); - return bestValue; - } - bestValue = std::min((alpha + beta) / 2, beta - 1); + return bestValue; } if (bestValue > alpha) From fbdf5d94a9a42acb92720a5896b16c92931ec3de Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 20 Dec 2023 14:26:11 +0300 Subject: [PATCH 500/678] Tweak quiet move bonus Improving quiet move bonus by replacing bestvalue and alpha comparison, with checking the statScore of the previous search step instead. Inspired by @locutus2 Passed STC: https://tests.stockfishchess.org/tests/view/657f22fb893104ee25b614e8 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 51296 W: 13121 L: 12774 D: 25401 Ptnml(0-2): 225, 5986, 12868, 6355, 214 Passed LTC: https://tests.stockfishchess.org/tests/view/658024a2893104ee25b62587 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 82758 W: 20606 L: 20189 D: 41963 Ptnml(0-2): 51, 9149, 22555, 9580, 44 closes https://github.com/official-stockfish/Stockfish/pull/4930 Bench: 1312822 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 235b35c1..3c61ea2f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -748,7 +748,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1555, 1452); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; @@ -1344,7 +1344,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 656) + int bonus = (depth > 6) + (PvNode || cutNode) + ((ss - 1)->statScore < -18782) + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); From 3f5adc037e14e40d1e0e1380e3e7c5884ca528ed Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Thu, 21 Dec 2023 07:44:32 +0300 Subject: [PATCH 501/678] Fix wrong mate/tb scores from probCut This fixes returning wrong mated-in scores, or losing a proven mate-in score from probCut after recent tweaks. The issue reported by @cj5716 on discord. Passed non-reg STC: https://tests.stockfishchess.org/tests/view/6583c36b5457644dc9843afe LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 295936 W: 75011 L: 75075 D: 145850 Ptnml(0-2): 978, 33947, 78146, 33955, 942 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/658513075457644dc98451cd LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 55932 W: 13970 L: 13786 D: 28176 Ptnml(0-2): 33, 5933, 15837, 6143, 20 closes https://github.com/official-stockfish/Stockfish/pull/4933 Bench: 1308739 --- src/search.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3c61ea2f..25fc30ba 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -854,7 +854,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // So effective depth is equal to depth - 3 && !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) { - assert(probCutBeta < VALUE_INFINITE); + assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); @@ -888,7 +888,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Save ProbCut data into transposition table tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); - return value - (probCutBeta - beta); + return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) + : value; } } @@ -1613,7 +1614,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { return mated_in(ss->ply); // Plies to mate from the root } - if (abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) + if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) bestValue = bestValue >= beta ? (3 * bestValue + beta) / 4 : bestValue; // Save gathered info in transposition table From f388e4180950833a1f79e023c88ff2521c9583b2 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 24 Dec 2023 20:36:52 +0300 Subject: [PATCH 502/678] Adjust value returned after TT cutoff Instead of returning value from TT in case of a fail high return mix between it and beta. Passed STC: https://tests.stockfishchess.org/tests/view/658465395457644dc98446c7 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 220704 W: 56404 L: 55811 D: 108489 Ptnml(0-2): 750, 26214, 55921, 26627, 840 Passed LTC: https://tests.stockfishchess.org/tests/view/6585c3f55457644dc9845db9 LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 124980 W: 31169 L: 30658 D: 63153 Ptnml(0-2): 57, 14147, 33603, 14594, 89 closes https://github.com/official-stockfish/Stockfish/pull/4934 Bench: 1191093 --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 25fc30ba..bc196ec4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -653,7 +653,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) - return ttValue; + return ttValue >= beta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY + ? (ttValue * 3 + beta) / 4 + : ttValue; } // Step 5. Tablebases probe From bab1cc300cbf1929fe42bdbce786a22dd97c8e1b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 26 Dec 2023 20:27:45 +0300 Subject: [PATCH 503/678] Refactor bestvalue adjustment in qsearch closes https://github.com/official-stockfish/Stockfish/pull/4935 No functional change --- AUTHORS | 2 +- src/search.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index cedee2f3..28586eec 100644 --- a/AUTHORS +++ b/AUTHORS @@ -72,7 +72,7 @@ Fabian Beuke (madnight) Fabian Fichter (ianfab) Fanael Linithien (Fanael) fanon -Fauzi Akram Dabat (FauziAkram) +Fauzi Akram Dabat (fauzi2) Felix Wittmann gamander Gabriele Lombardo (gabe) diff --git a/src/search.cpp b/src/search.cpp index bc196ec4..7709af35 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1616,8 +1616,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { return mated_in(ss->ply); // Plies to mate from the root } - if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) - bestValue = bestValue >= beta ? (3 * bestValue + beta) / 4 : bestValue; + if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && bestValue >= beta) + bestValue = (3 * bestValue + beta) / 4; // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, From f12035c88c58a5fd568d26cde9868f73a8d7b839 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 25 Dec 2023 10:14:15 -0500 Subject: [PATCH 504/678] Update default net to nn-b1e55edbea57.nnue Created by retraining the master big net `nn-0000000000a0.nnue` on the same dataset with the ranger21 optimizer and more WDL skipping at training time. More WDL skipping is meant to increase lambda accuracy and train on fewer misevaluated positions where position scores are unlikely to correlate with game outcomes. Inspired by: - repeated reports in discord #events-discuss about SF misplaying due to wrong endgame evals, possibly due to Leela's endgame weaknesses reflected in training data - an attempt to reduce the skewed dataset piece count distribution where there are much more positions with less than 16 pieces, since the target piece count distribution in the trainer is symmetric around 16 The faster convergence seen with ranger21 is meant to: - prune experiment ideas more quickly since fewer epochs are needed to reach elo maxima - research faster potential trainings by shortening each run ```yaml experiment-name: 2560-S7-Re-514G-ranger21-more-wdl-skip training-dataset: /data/S6-514G.binpack early-fen-skipping: 28 start-from-engine-test-net: True nnue-pytorch-branch: linrock/nnue-pytorch/r21-more-wdl-skip num-epochs: 1200 lr: 4.375e-4 gamma: 0.995 start-lambda: 1.0 end-lambda: 0.7 ``` Experiment yaml configs converted to easy_train.sh commands with: https://github.com/linrock/nnue-tools/blob/4339954/yaml_easy_train.py Implementations based off of Sopel's NNUE training & experimentation log: https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY - Experiment 336 - ranger21 https://github.com/Sopel97/nnue-pytorch/tree/experiment_336 - Experiment 351 - more WDL skipping The version of the ranger21 optimizer used is: https://github.com/lessw2020/Ranger21/blob/b507df6/ranger21/ranger21.py The dataset is the exact same as in: https://github.com/official-stockfish/Stockfish/pull/4782 Local elo at 25k nodes per move: nn-epoch619.nnue : 6.2 +/- 4.2 Passed STC: https://tests.stockfishchess.org/tests/view/658a029779aa8af82b94fbe6 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 46528 W: 11985 L: 11650 D: 22893 Ptnml(0-2): 154, 5489, 11688, 5734, 199 Passed LTC: https://tests.stockfishchess.org/tests/view/658a448979aa8af82b95010f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 265326 W: 66378 L: 65574 D: 133374 Ptnml(0-2): 153, 30175, 71254, 30877, 204 This was additionally tested with the latest DualNNUE and passed SPRTs: Passed STC vs. https://github.com/official-stockfish/Stockfish/pull/4919 https://tests.stockfishchess.org/tests/view/658bcd5c79aa8af82b951846 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 296128 W: 76273 L: 75554 D: 144301 Ptnml(0-2): 1223, 35768, 73617, 35979, 1477 Passed LTC vs. https://github.com/official-stockfish/Stockfish/pull/4919 https://tests.stockfishchess.org/tests/view/658c988d79aa8af82b95240f LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 75618 W: 19085 L: 18680 D: 37853 Ptnml(0-2): 45, 8420, 20497, 8779, 68 closes https://github.com/official-stockfish/Stockfish/pull/4942 Bench: 1304666 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 2ab477ec..33df1308 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ extern std::string currentEvalFileName; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultName "nn-0000000000a0.nnue" +#define EvalFileDefaultName "nn-b1e55edbea57.nnue" namespace NNUE { From 1a69efbb404fd4389651ab9f45127fb012c0cf94 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Sat, 30 Dec 2023 00:28:13 +0300 Subject: [PATCH 505/678] Fix scores from reverse futility pruning This fixes futility pruning return values after recent tweaks, `eval` is guaranteed to be less than the mate-in range but it can be as low value such that the average between eval and beta can still fall in the mated-in range when beta is as low in mated range. i.e. (eval + beta) / 2 being at mated-range which can break mates. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/658f3eed79aa8af82b955139 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 117408 W: 29891 L: 29761 D: 57756 Ptnml(0-2): 386, 13355, 31120, 13429, 414 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/658f8b7a79aa8af82b9557bd LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60240 W: 14962 L: 14786 D: 30492 Ptnml(0-2): 22, 6257, 17390, 6425, 26 changes signature at higher depth e.g. `128 1 15` closes https://github.com/official-stockfish/Stockfish/pull/4944 Bench: 1304666 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 7709af35..4e12a6c9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -784,7 +784,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo >= beta && eval >= beta && eval < 29008 // smaller than TB wins && (!ttMove || ttCapture)) - return (eval + beta) / 2; + return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17496 && eval >= beta From 4f99dfcae2dd8e9a4b163ade623084888655ed46 Mon Sep 17 00:00:00 2001 From: Tobias Steinmann Date: Mon, 18 Dec 2023 15:07:14 +0100 Subject: [PATCH 506/678] Update Makefile for android x86-64 builds For developing an Android GUI it can be helpful to use the Emulator on Windows. Therefor an android_x86-64 library of Stockfish is needed. It would be nice to compile it "out-of-the-box". This change is originally suggested by Craftyawesome closes https://github.com/official-stockfish/Stockfish/pull/4927 No functional change --- AUTHORS | 1 + src/Makefile | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/AUTHORS b/AUTHORS index 28586eec..6f518ec2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -215,6 +215,7 @@ Thanar2 thaspel theo77186 Ting-Hsuan Huang (fffelix-huang) +Tobias Steinmann Tomasz Sobczyk (Sopel97) Tom Truscott Tom Vijlbrief (tomtor) diff --git a/src/Makefile b/src/Makefile index 761b4086..ac354c7b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -521,6 +521,14 @@ ifeq ($(COMP),ndk) STRIP=llvm-strip endif endif + ifeq ($(arch),x86_64) + CXX=x86_64-linux-android21-clang++ + ifneq ($(shell which x86_64-linux-android-strip 2>/dev/null),) + STRIP=x86_64-linux-android-strip + else + STRIP=llvm-strip + endif + endif LDFLAGS += -static-libstdc++ -pie -lm -latomic endif From 833a2e2bc09e3640440766683043134d72bffd51 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 30 Dec 2023 15:22:17 +0300 Subject: [PATCH 507/678] Cleanup comments Tests used to derive some Elo worth comments: https://tests.stockfishchess.org/tests/view/656a7f4e136acbc573555a31 https://tests.stockfishchess.org/tests/view/6585fb455457644dc984620f closes https://github.com/official-stockfish/Stockfish/pull/4945 No functional change --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/stockfish_binaries.yml | 6 +++--- src/incbin/incbin.h | 10 +++++----- src/nnue/features/half_ka_v2_hm.h | 14 +++++++------- src/nnue/layers/affine_transform_sparse_input.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/nnue_common.h | 16 ++++++++-------- src/nnue/nnue_feature_transformer.h | 6 +++--- src/search.cpp | 8 ++++---- tests/instrumented.sh | 6 +++--- 11 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1f8694d2..0666eb32 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Discord server url: https://discord.gg/GWDRS3kU6R - about: Feel free to ask for support or have a chat with us in our Discord server! + about: Feel free to ask for support or have a chat with us on our Discord server! - name: Discussions, Q&A, ideas, show us something... url: https://github.com/official-stockfish/Stockfish/discussions/new about: Do you have an idea for Stockfish? Do you want to show something that you made? Please open a discussion about it! diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 054be900..d6da8a1c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,7 +23,7 @@ jobs: matrix: language: [ 'cpp' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'java' to analyze code written in Java, Kotlin, or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 5b3a5226..eff2c2c9 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -172,8 +172,8 @@ jobs: name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar - # Artifacts automatically get zipped - # to avoid double zipping, we use the unzipped directory + # Artifacts automatically get zipped. + # To avoid double-zipping, we use the unzipped directory - name: Upload binaries if: runner.os == 'Windows' uses: actions/upload-artifact@v3 @@ -195,7 +195,7 @@ jobs: id: commit_date run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV - # Make sure that an old ci which still runs on master doesn't recreate a prerelease + # Make sure that an old ci that still runs on master doesn't recreate a prerelease - name: Check Pullable Commits id: check_commits run: | diff --git a/src/incbin/incbin.h b/src/incbin/incbin.h index c19684d7..18718b95 100644 --- a/src/incbin/incbin.h +++ b/src/incbin/incbin.h @@ -3,8 +3,8 @@ * @author Dale Weiler * @brief Utility for including binary files * - * Facilities for including binary files into the current translation unit and - * making use from them externally in other translation units. + * Facilities for including binary files into the current translation unit + * and making use of them externally in other translation units. */ #ifndef INCBIN_HDR #define INCBIN_HDR @@ -139,7 +139,7 @@ #endif #if defined(__APPLE__) -/* The directives are different for Apple branded compilers */ +/* The directives are different for Apple-branded compilers */ # define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" # define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" # define INCBIN_INT ".long " @@ -261,8 +261,8 @@ INCBIN_STRINGIZE( \ INCBIN_STYLE_IDENT(TYPE)) \ -/* Generate the global labels by indirectly invoking the macro with our style - * type and concatenating the name against them. */ +/* Generate the global labels by indirectly invoking the macro + * with our style type and concatenate the name against them. */ #define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ INCBIN_INVOKE( \ INCBIN_GLOBAL, \ diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 540ff895..c208e38d 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -34,11 +34,11 @@ class Position; namespace Stockfish::Eval::NNUE::Features { -// Feature HalfKAv2_hm: Combination of the position of own king -// and the position of pieces. Position mirrored such that king always on e..h files. +// Feature HalfKAv2_hm: Combination of the position of own king and the +// position of pieces. Position mirrored such that king is always on e..h files. class HalfKAv2_hm { - // unique number for each piece type on each square + // Unique number for each piece type on each square enum { PS_NONE = 0, PS_W_PAWN = 0, @@ -56,8 +56,8 @@ class HalfKAv2_hm { }; static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { - // convention: W - us, B - them - // viewed from other side, W and B are reversed + // Convention: W - us, B - them + // Viewed from other side, W and B are reversed {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE}, {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, @@ -140,8 +140,8 @@ class HalfKAv2_hm { static int update_cost(const StateInfo* st); static int refresh_cost(const Position& pos); - // Returns whether the change stored in this StateInfo means that - // a full accumulator refresh is required. + // Returns whether the change stored in this StateInfo means + // that a full accumulator refresh is required. static bool requires_refresh(const StateInfo* st, Color perspective); }; diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 6cb4d1a9..70dbd790 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -235,7 +235,7 @@ class AffineTransformSparseInput { const auto input32 = reinterpret_cast(input); - // Find indices of nonzero 32bit blocks + // Find indices of nonzero 32-bit blocks find_nnz(input32, nnz, count); const outvec_t* biasvec = reinterpret_cast(biases); diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index f8e2d497..b9d8f030 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -91,7 +91,7 @@ class SqrClippedReLU { for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( - // Really should be /127 but we need to make it fast so we right shift + // Really should be /127 but we need to make it fast so we right-shift // by an extra 7 bits instead. Needs to be accounted for in the trainer. std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7))); } diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index f9cd7fbb..d4bd0028 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -112,7 +112,7 @@ inline IntType read_little_endian(std::istream& stream) { // Utility to write an integer (signed or unsigned, any size) // to a stream in little-endian order. We swap the byte order before the write if -// necessary to always write in little endian order, independently of the byte +// necessary to always write in little-endian order, independently of the byte // ordering of the compiling machine. template inline void write_little_endian(std::ostream& stream, IntType value) { @@ -141,8 +141,8 @@ inline void write_little_endian(std::ostream& stream, IntType value) { } -// Read integers in bulk from a little indian stream. -// This reads N integers from stream s and put them in array out. +// Read integers in bulk from a little-endian stream. +// This reads N integers from stream s and puts them in array out. template inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { if (IsLittleEndian) @@ -153,7 +153,7 @@ inline void read_little_endian(std::istream& stream, IntType* out, std::size_t c } -// Write integers in bulk to a little indian stream. +// Write integers in bulk to a little-endian stream. // This takes N integers from array values and writes them on stream s. template inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { @@ -165,8 +165,8 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std } -// Read N signed integers from the stream s, putting them in -// the array out. The stream is assumed to be compressed using the signed LEB128 format. +// Read N signed integers from the stream s, putting them in the array out. +// The stream is assumed to be compressed using the signed LEB128 format. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { @@ -216,8 +216,8 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) // Write signed integers to a stream with LEB128 compression. -// This takes N integers from array values, compress them with the LEB128 algorithm and -// writes the result on the stream s. +// This takes N integers from array values, compresses them with +// the LEB128 algorithm and writes the result on the stream s. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 2af80f07..a83a77c9 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -366,14 +366,14 @@ class FeatureTransformer { // 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. + // feature set's update cost calculation to be correct and never allow + // updates with more added/removed features than MaxActiveDimensions. FeatureSet::IndexList removed[N - 1], added[N - 1]; { int i = N - - 2; // last potential state to update. Skip last element because it must be nullptr. + - 2; // Last potential state to update. Skip last element because it must be nullptr. while (states_to_update[i] == nullptr) --i; diff --git a/src/search.cpp b/src/search.cpp index 4e12a6c9..eb63ec90 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -747,7 +747,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } - // Use static evaluation difference to improve quiet move ordering (~4 Elo) + // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); @@ -1201,6 +1201,7 @@ moves_loop: // When in check, search starts here if (newDepth > d) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); + // Post LMR continuation history updates (~1 Elo) int bonus = value <= alpha ? -stat_malus(newDepth) : value >= beta ? stat_bonus(newDepth) : 0; @@ -1216,7 +1217,7 @@ moves_loop: // When in check, search starts here if (!ttMove) r += 2; - // Note that if expected reduction is high, we reduce search depth by 1 here + // Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3), !cutNode); } @@ -1644,8 +1645,7 @@ Value value_to_tt(Value v, int ply) { // from the transposition table (which refers to the plies to mate/be mated from // current position) to "plies to mate/be mated (TB win/loss) from the root". // However, to avoid potentially false mate or TB scores related to the 50 moves rule -// and the graph history interaction, we return highest non-TB score instead. - +// and the graph history interaction, we return the highest non-TB score instead. Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 637d19f9..2a3eadc0 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -1,5 +1,5 @@ #!/bin/bash -# check for errors under valgrind or sanitizers. +# check for errors under Valgrind or sanitizers. error() { @@ -151,7 +151,7 @@ cat << EOF > game.exp send "quit\n" expect eof - # return error code of the spawned program, useful for valgrind + # return error code of the spawned program, useful for Valgrind lassign [wait] pid spawnid os_error_flag value exit \$value EOF @@ -179,7 +179,7 @@ cat << EOF > syzygy.exp send "quit\n" expect eof - # return error code of the spawned program, useful for valgrind + # return error code of the spawned program, useful for Valgrind lassign [wait] pid spawnid os_error_flag value exit \$value EOF From 1fe562fdf32c153f82929660197f8b97469f76b4 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 30 Dec 2023 19:26:41 +0300 Subject: [PATCH 508/678] Simplify the improving flag calculation Passed STC: https://tests.stockfishchess.org/tests/view/658ec29979aa8af82b9547f6 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 93408 W: 23747 L: 23587 D: 46074 Ptnml(0-2): 340, 11178, 23527, 11300, 359 Passed LTC: https://tests.stockfishchess.org/tests/view/658f73e479aa8af82b9555b6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 64026 W: 15984 L: 15806 D: 32236 Ptnml(0-2): 31, 7113, 17552, 7281, 36 closes https://github.com/official-stockfish/Stockfish/pull/4948 Bench: 1143749 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index eb63ec90..cb6b450d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -761,9 +761,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // check at our previous move we look at static evaluation at move prior to it // and if we were in check at move prior to it flag is set to true) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval - : (ss - 4)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 4)->staticEval - : true; + improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval + : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, From 4ff297a6dfae199571a4f24631a8e970924c8d63 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 31 Dec 2023 03:43:19 +0300 Subject: [PATCH 509/678] Mark square_bb() as constexpr closes https://github.com/official-stockfish/Stockfish/pull/4949 No functional change --- src/bitboard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitboard.h b/src/bitboard.h index 7dbd5329..8b9c2918 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -89,7 +89,7 @@ struct Magic { extern Magic RookMagics[SQUARE_NB]; extern Magic BishopMagics[SQUARE_NB]; -inline Bitboard square_bb(Square s) { +constexpr Bitboard square_bb(Square s) { assert(is_ok(s)); return (1ULL << s); } From b4d995d0d910044cf4ea2ad3ee30fd1d21070cd8 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 31 Dec 2023 10:13:03 +0300 Subject: [PATCH 510/678] Introduce static evaluation correction history Idea from Caissa (https://github.com/Witek902/Caissa) chess engine. With given pawn structure collect data with how often search result and by how much it was better / worse than static evalution of position and use it to adjust static evaluation of positions with given pawn structure. Details: 1. excludes positions with fail highs and moves producing it being a capture; 2. update value is function of not only difference between best value and static evaluation but also is multiplied by linear function of depth; 3. maximum update value is maximum value of correction history divided by 2; 4. correction history itself is divided by 32 when applied so maximum value of static evaluation adjustment is 32 internal units. Passed STC: https://tests.stockfishchess.org/tests/view/658fc7b679aa8af82b955cac LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 128672 W: 32757 L: 32299 D: 63616 Ptnml(0-2): 441, 15241, 32543, 15641, 470 Passed LTC: https://tests.stockfishchess.org/tests/view/65903f6979aa8af82b9566f1 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 97422 W: 24626 L: 24178 D: 48618 Ptnml(0-2): 41, 10837, 26527, 11245, 61 closes https://github.com/official-stockfish/Stockfish/pull/4950 Bench: 1157852 --- src/movepick.cpp | 4 +- src/movepick.h | 21 ++++++++++- src/search.cpp | 97 +++++++++++++++++++++++++++++++++++++----------- src/thread.cpp | 1 + src/thread.h | 1 + 5 files changed, 98 insertions(+), 26 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 0267a8e2..ab37ff68 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -179,7 +179,7 @@ void MovePicker::score() { // histories m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; - m.value += 2 * (*pawnHistory)[pawn_structure(pos)][pc][to]; + m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to] / 4; @@ -216,7 +216,7 @@ void MovePicker::score() { else m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + (*pawnHistory)[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)]; + + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][to_sq(m)]; } } diff --git a/src/movepick.h b/src/movepick.h index 5077f4e3..eefc0d50 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -33,12 +33,25 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } +static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, + "CORRECTION_HISTORY_SIZE has to be a power of 2"); + +enum PawnHistoryType { + Normal, + Correction +}; + +template +inline int pawn_structure_index(const Position& pos) { + return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); +} // StatsEntry stores the stat table value. It is usually a number but could // be a move or even a nested history. We use a class instead of a naked value @@ -122,6 +135,10 @@ using ContinuationHistory = Stats // PawnHistory is addressed by the pawn structure and a move's [piece][to] using PawnHistory = Stats; +// CorrectionHistory is addressed by color and pawn structure +using CorrectionHistory = + Stats; + // MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which returns a // new pseudo-legal move each time it is called, until there are no moves left, diff --git a/src/search.cpp b/src/search.cpp index cb6b450d..1ec77bcd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -93,6 +93,11 @@ constexpr int futility_move_count(bool improving, Depth depth) { return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; } +// Guarantee evaluation does not hit the tablebase range +constexpr Value to_static_eval(const Value v) { + return std::clamp(v, 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(268 * d - 352, 1153); } @@ -712,6 +717,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo CapturePieceToHistory& captureHistory = thisThread->captureHistory; + Value unadjustedStaticEval = VALUE_NONE; + // Step 6. Static evaluation of the position if (ss->inCheck) { @@ -725,26 +732,40 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // 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); - eval = ss->staticEval; + unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) { // Never assume anything about values stored in TT - ss->staticEval = eval = tte->eval(); + unadjustedStaticEval = ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - ss->staticEval = eval = evaluate(pos); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); + Value newEval = + ss->staticEval + + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + ss->staticEval = eval = to_static_eval(newEval); + // ttValue can be used as a better position evaluation (~7 Elo) if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttValue; } else { - ss->staticEval = eval = evaluate(pos); - // Save static evaluation into the transposition table - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos); + + Value newEval = + ss->staticEval + + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + ss->staticEval = eval = to_static_eval(newEval); + + // Static evaluation is saved as it was before adjustment by correction history + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, + unadjustedStaticEval); } // Use static evaluation difference to improve quiet move ordering (~9 Elo) @@ -753,7 +774,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) - thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; + thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] + << bonus / 4; } // Set up the improving flag, which is true if current static evaluation is @@ -888,7 +910,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // Save ProbCut data into transposition table tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, - move, ss->staticEval); + move, unadjustedStaticEval); return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) : value; } @@ -999,10 +1021,10 @@ moves_loop: // When in check, search starts here } else { - int history = (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)]; + int history = + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] + + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) if (lmrDepth < 6 && history < -3752 * depth) @@ -1364,12 +1386,23 @@ moves_loop: // When in check, search starts here ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); // Write gathered information in transposition table + // Static evaluation is saved as it was before correction history if (!excludedMove && !(rootNode && thisThread->pvIdx)) tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, ss->staticEval); + depth, bestMove, unadjustedStaticEval); + + // Adjust correction history + if (!ss->inCheck && (!bestMove || !pos.capture(bestMove)) + && !(bestValue >= beta && bestValue <= ss->staticEval) + && !(!bestMove && bestValue >= ss->staticEval)) + { + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + thisThread->correctionHistory[us][pawn_structure_index(pos)] << bonus; + } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1450,6 +1483,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; + Value unadjustedStaticEval = VALUE_NONE; + // Step 4. Static evaluation of the position if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; @@ -1458,8 +1493,14 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (ss->ttHit) { // Never assume anything about values stored in TT - if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - ss->staticEval = bestValue = evaluate(pos); + if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) + unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos); + + Value newEval = + ss->staticEval + + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + ss->staticEval = bestValue = to_static_eval(newEval); // ttValue can be used as a better position evaluation (~13 Elo) if (ttValue != VALUE_NONE @@ -1467,16 +1508,24 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { bestValue = ttValue; } else + { // In case of null move search, use previous static eval with a different sign - ss->staticEval = bestValue = + unadjustedStaticEval = ss->staticEval = bestValue = (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; + Value newEval = + ss->staticEval + + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + ss->staticEval = bestValue = to_static_eval(newEval); + } + // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - MOVE_NONE, ss->staticEval); + MOVE_NONE, unadjustedStaticEval); return bestValue; } @@ -1620,8 +1669,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { bestValue = (3 * bestValue + beta) / 4; // Save gathered info in transposition table + // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, - bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, ss->staticEval); + bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, + unadjustedStaticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1720,15 +1771,17 @@ void update_all_stats(const Position& pos, // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bestMoveBonus); - thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)] - << quietMoveBonus; + + int pIndex = pawn_structure_index(pos); + thisThread->pawnHistory[pIndex][moved_piece][to_sq(bestMove)] << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])] - [to_sq(quietsSearched[i])] + thisThread + ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][to_sq(quietsSearched[i])] << -quietMoveMalus; + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -quietMoveMalus); diff --git a/src/thread.cpp b/src/thread.cpp index de8de87d..eeab1882 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -70,6 +70,7 @@ void Thread::clear() { mainHistory.fill(0); captureHistory.fill(0); pawnHistory.fill(0); + correctionHistory.fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) diff --git a/src/thread.h b/src/thread.h index cb2f6db1..1edc9cc9 100644 --- a/src/thread.h +++ b/src/thread.h @@ -69,6 +69,7 @@ class Thread { CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; PawnHistory pawnHistory; + CorrectionHistory correctionHistory; }; From 3cfaef74311e943298a9a82bce5717d272338e66 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 31 Dec 2023 18:45:48 +0100 Subject: [PATCH 511/678] Tweak static eval history update Modify the applied static eval bonus for main and pawn history with different factors for positive and negative values. Passed STC: https://tests.stockfishchess.org/tests/view/659132e179aa8af82b957bb0 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 12512 W: 3308 L: 3027 D: 6177 Ptnml(0-2): 32, 1372, 3189, 1609, 54 Passed LTC: https://tests.stockfishchess.org/tests/view/65913e3d79aa8af82b957cd2 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 35946 W: 9128 L: 8809 D: 18009 Ptnml(0-2): 19, 3879, 9862, 4190, 23 closes https://github.com/official-stockfish/Stockfish/pull/4952 Bench: 1392883 --- src/search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/search.cpp b/src/search.cpp index 1ec77bcd..8e48b164 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -772,6 +772,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); + bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] From 0fca5605fa2e5e7240fde5e1aae50952b2612231 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 02:29:28 +0100 Subject: [PATCH 512/678] Fix formatting in search.cpp fixes the formatting for 1fe562fdf32c153f82929660197f8b97469f76b4 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8e48b164..c45e9f20 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -784,8 +784,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // check at our previous move we look at static evaluation at move prior to it // and if we were in check at move prior to it flag is set to true) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval - : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; + improving = (ss - 2)->staticEval != VALUE_NONE + ? ss->staticEval > (ss - 2)->staticEval + : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, From 154abb337e8737aedd6def4e7c0ca18bd4737252 Mon Sep 17 00:00:00 2001 From: Joseph Huang Date: Sun, 31 Dec 2023 03:11:04 -0500 Subject: [PATCH 513/678] Lower MultiPV max to MAX_MOVES Link max value of MultiPV to that of MAX_MOVES which is 256 closes https://github.com/official-stockfish/Stockfish/pull/4951 No functional change --- src/ucioption.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 1dc9b89b..43392e9a 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -70,7 +70,7 @@ void init(OptionsMap& o) { o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Clear Hash"] << Option(on_clear_hash); o["Ponder"] << Option(false); - o["MultiPV"] << Option(1, 1, 500); + o["MultiPV"] << Option(1, 1, MAX_MOVES); o["Skill Level"] << Option(20, 0, 20); o["Move Overhead"] << Option(10, 0, 5000); o["nodestime"] << Option(0, 0, 10000); From a25f48a23671b0e18d1eae58e95d1a28fc389221 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 02:19:23 +0100 Subject: [PATCH 514/678] Silence security alert warning about possible infinite loop As some have noticed, a security alert has been complaining about a for loop in our TB code for quite some now. Though it was never a real issue, so not of high importance. A few lines earlier the symlen vector is resized `d->symlen.resize(number(data));` while this code seems odd at first, it resizes the array to at most (2 << 16) - 1 elements, basically making the infinite loop issue impossible to occur. closes https://github.com/official-stockfish/Stockfish/pull/4953 No functional change --- src/syzygy/tbprobe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index e2363157..5fe28fd2 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1074,7 +1074,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf std::vector visited(d->symlen.size()); - for (Sym sym = 0; sym < d->symlen.size(); ++sym) + for (std::size_t sym = 0; sym < d->symlen.size(); ++sym) if (!visited[sym]) d->symlen[sym] = set_symlen(d, sym, visited); From 444f03ee95fcde4cf9014d82cae72c644357a31d Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 12:41:20 +0100 Subject: [PATCH 515/678] Update copyright year closes https://github.com/official-stockfish/Stockfish/pull/4954 No functional change --- src/Makefile | 2 +- src/benchmark.cpp | 2 +- src/benchmark.h | 2 +- src/bitboard.cpp | 2 +- src/bitboard.h | 2 +- src/evaluate.cpp | 2 +- src/evaluate.h | 2 +- src/main.cpp | 2 +- src/misc.cpp | 2 +- src/misc.h | 2 +- src/movegen.cpp | 2 +- src/movegen.h | 2 +- src/movepick.cpp | 2 +- src/movepick.h | 2 +- src/nnue/evaluate_nnue.cpp | 2 +- src/nnue/evaluate_nnue.h | 2 +- src/nnue/features/half_ka_v2_hm.cpp | 2 +- src/nnue/features/half_ka_v2_hm.h | 2 +- src/nnue/layers/affine_transform.h | 2 +- src/nnue/layers/affine_transform_sparse_input.h | 2 +- src/nnue/layers/clipped_relu.h | 2 +- src/nnue/layers/simd.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/nnue_accumulator.h | 2 +- src/nnue/nnue_architecture.h | 2 +- src/nnue/nnue_common.h | 2 +- src/nnue/nnue_feature_transformer.h | 2 +- src/position.cpp | 2 +- src/position.h | 2 +- src/search.cpp | 2 +- src/search.h | 2 +- src/syzygy/tbprobe.cpp | 2 +- src/syzygy/tbprobe.h | 2 +- src/thread.cpp | 2 +- src/thread.h | 2 +- src/thread_win32_osx.h | 2 +- src/timeman.cpp | 2 +- src/timeman.h | 2 +- src/tt.cpp | 2 +- src/tt.h | 2 +- src/tune.cpp | 2 +- src/tune.h | 2 +- src/types.h | 2 +- src/uci.cpp | 2 +- src/uci.h | 2 +- src/ucioption.cpp | 2 +- 46 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Makefile b/src/Makefile index ac354c7b..660b41e7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 -# Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) +# Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 2270dcc3..50f8612d 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.h b/src/benchmark.h index e6206d19..86f8a0ad 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.cpp b/src/bitboard.cpp index a8a10cbb..72afabb6 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.h b/src/bitboard.h index 8b9c2918..d028be02 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 586cadc0..b6342f18 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.h b/src/evaluate.h index 33df1308..c2b08aaf 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/main.cpp b/src/main.cpp index 04879cc4..78b3f54d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.cpp b/src/misc.cpp index 4193f8d2..9350a483 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.h b/src/misc.h index 91fdb72f..ca6cc166 100644 --- a/src/misc.h +++ b/src/misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.cpp b/src/movegen.cpp index 7d6856bb..750a07e8 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.h b/src/movegen.h index 9a39d1c5..3ae84c4c 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.cpp b/src/movepick.cpp index ab37ff68..aa577541 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.h b/src/movepick.h index eefc0d50..24252433 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index e7339c10..14e2fec1 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 6edc212f..05c98bc5 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 6d1b60ce..5789db48 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index c208e38d..8363184f 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 44fa5d00..e6852236 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 70dbd790..0ac557ab 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index a3a0c1ed..813234c5 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 5425ca19..6f4c9d20 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index b9d8f030..9c20df9d 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 2f1b1d35..f6d70524 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index e4c308cb..6c0e52b7 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index d4bd0028..4bc3408f 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index a83a77c9..2008cf25 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.cpp b/src/position.cpp index c45dd7b2..32823bd0 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.h b/src/position.h index ce03c34f..46956afc 100644 --- a/src/position.h +++ b/src/position.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.cpp b/src/search.cpp index c45e9f20..3553065f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.h b/src/search.h index b2d22e61..72e275d3 100644 --- a/src/search.h +++ b/src/search.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 5fe28fd2..c1275cf5 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 3b7c8aa7..cc8eb0d4 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.cpp b/src/thread.cpp index eeab1882..e900a9ac 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.h b/src/thread.h index 1edc9cc9..22fe32c3 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 248e4a67..4bc62d67 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.cpp b/src/timeman.cpp index f404ee0c..77db2f62 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.h b/src/timeman.h index 6c56d506..0509158c 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.cpp b/src/tt.cpp index 816d43f8..5c4e6d53 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.h b/src/tt.h index 12fedd2d..82a66863 100644 --- a/src/tt.h +++ b/src/tt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.cpp b/src/tune.cpp index cf80b9d7..44bfa682 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.h b/src/tune.h index 480aea16..3d45e51c 100644 --- a/src/tune.h +++ b/src/tune.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/types.h b/src/types.h index 3e00d68d..dde1a52c 100644 --- a/src/types.h +++ b/src/types.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.cpp b/src/uci.cpp index 5f250a36..5dc9b2b0 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.h b/src/uci.h index 55fb47c2..d249da74 100644 --- a/src/uci.h +++ b/src/uci.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 43392e9a..087882f1 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 5546bc0a260d9bd01b1ef9d5b6b10fbbcb3a24b0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 1 Jan 2024 14:52:05 +0300 Subject: [PATCH 516/678] Simplification of partial_insertion_sort formula. Passed STC: https://tests.stockfishchess.org/tests/view/6590110879aa8af82b9562e9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 134880 W: 34468 L: 34355 D: 66057 Ptnml(0-2): 476, 16060, 34220, 16243, 441 Passed LTC: https://tests.stockfishchess.org/tests/view/659156ca79aa8af82b957f07 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60780 W: 15179 L: 14996 D: 30605 Ptnml(0-2): 27, 6847, 16464, 7020, 32 closes https://github.com/official-stockfish/Stockfish/pull/4955 Bench: 1338331 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index aa577541..f33839cd 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -300,7 +300,7 @@ top: endMoves = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, -1960 - 3130 * depth); + partial_insertion_sort(cur, endMoves, -3330 * depth); } ++stage; From 28f8663f3947e716fefe392a463060dc12e39849 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Tue, 2 Jan 2024 02:54:45 +0000 Subject: [PATCH 517/678] Modify ttPV reduction This patch modifies ttPV reduction by reducing 1 more unless ttValue is above alpha. Inspired from @pb00068 https://tests.stockfishchess.org/tests/view/658060796a3b4f6202215f1f Passed STC: https://tests.stockfishchess.org/tests/view/6591867679aa8af82b958328 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 37856 W: 9727 L: 9407 D: 18722 Ptnml(0-2): 99, 4444, 9568, 4672, 145 Passed LTC: https://tests.stockfishchess.org/tests/view/6591d9b679aa8af82b958a6c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 128256 W: 32152 L: 31639 D: 64465 Ptnml(0-2): 64, 14364, 34772, 14851, 77 closes https://github.com/official-stockfish/Stockfish/pull/4957 Bench: 1176235 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 3553065f..aae3625a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1152,7 +1152,7 @@ moves_loop: // When in check, search starts here // Decrease reduction if position is or has been on the PV (~4 Elo) if (ss->ttPv && !likelyFailLow) - r -= cutNode && tte->depth() >= depth ? 3 : 2; + r -= 1 + (cutNode && tte->depth() >= depth) + (ttValue > alpha); // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss - 1)->moveCount > 7) From cafbe8e8e8c26594dd7040788e6f72bc4bc8cfd9 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 23:13:18 +0100 Subject: [PATCH 518/678] Change the Move enum to a class This changes the Move enum to a class, this way all move related functions can be moved into the class and be more self contained. closes https://github.com/official-stockfish/Stockfish/pull/4958 No functional change --- src/movegen.cpp | 28 ++++----- src/movegen.h | 8 +-- src/movepick.cpp | 32 +++++----- src/movepick.h | 2 +- src/position.cpp | 86 +++++++++++++-------------- src/position.h | 10 ++-- src/search.cpp | 148 ++++++++++++++++++++++++----------------------- src/thread.cpp | 10 ++-- src/tt.cpp | 2 +- src/tt.h | 2 +- src/types.h | 117 ++++++++++++++++++++++--------------- src/uci.cpp | 18 +++--- 12 files changed, 241 insertions(+), 222 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 750a07e8..e6923067 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -34,13 +34,13 @@ ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS; if constexpr (Type == CAPTURES || all) - *moveList++ = make(to - D, to, QUEEN); + *moveList++ = Move::make(to - D, to, QUEEN); if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all) { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); + *moveList++ = Move::make(to - D, to, ROOK); + *moveList++ = Move::make(to - D, to, BISHOP); + *moveList++ = Move::make(to - D, to, KNIGHT); } return moveList; @@ -89,13 +89,13 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta while (b1) { Square to = pop_lsb(b1); - *moveList++ = make_move(to - Up, to); + *moveList++ = Move(to - Up, to); } while (b2) { Square to = pop_lsb(b2); - *moveList++ = make_move(to - Up - Up, to); + *moveList++ = Move(to - Up - Up, to); } } @@ -128,13 +128,13 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta while (b1) { Square to = pop_lsb(b1); - *moveList++ = make_move(to - UpRight, to); + *moveList++ = Move(to - UpRight, to); } while (b2) { Square to = pop_lsb(b2); - *moveList++ = make_move(to - UpLeft, to); + *moveList++ = Move(to - UpLeft, to); } if (pos.ep_square() != SQ_NONE) @@ -150,7 +150,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta assert(b1); while (b1) - *moveList++ = make(pop_lsb(b1), pos.ep_square()); + *moveList++ = Move::make(pop_lsb(b1), pos.ep_square()); } } @@ -175,7 +175,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) b &= pos.check_squares(Pt); while (b) - *moveList++ = make_move(from, pop_lsb(b)); + *moveList++ = Move(from, pop_lsb(b)); } return moveList; @@ -213,12 +213,12 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { b &= ~attacks_bb(pos.square(~Us)); while (b) - *moveList++ = make_move(ksq, pop_lsb(b)); + *moveList++ = Move(ksq, pop_lsb(b)); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) - *moveList++ = make(ksq, pos.castling_rook_square(cr)); + *moveList++ = Move::make(ksq, pos.castling_rook_square(cr)); } return moveList; @@ -268,9 +268,9 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) - if (((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) + if (((pinned & cur->from_sq()) || cur->from_sq() == ksq || cur->type_of() == EN_PASSANT) && !pos.legal(*cur)) - *cur = (--moveList)->move; + *cur = *(--moveList); else ++cur; diff --git a/src/movegen.h b/src/movegen.h index 3ae84c4c..5f650d2e 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -37,12 +37,10 @@ enum GenType { LEGAL }; -struct ExtMove { - Move move; - int value; +struct ExtMove: public Move { + int value; - operator Move() const { return move; } - void operator=(Move m) { move = m; } + void operator=(Move m) { data = m.raw(); } // Inhibit unwanted implicit conversions to Move // with an ambiguity that yields to a compile error. diff --git a/src/movepick.cpp b/src/movepick.cpp index f33839cd..cae01891 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -166,19 +166,19 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = - (7 * int(PieceValue[pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) + (7 * int(PieceValue[pos.piece_on(m.to_sq())]) + + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]) / 16; else if constexpr (Type == QUIETS) { Piece pc = pos.moved_piece(m); PieceType pt = type_of(pos.moved_piece(m)); - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); // histories - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()]; m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; @@ -211,12 +211,12 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + m.value = PieceValue[pos.piece_on(m.to_sq())] - Value(type_of(pos.moved_piece(m))) + (1 << 28); else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][to_sq(m)]; + m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] + + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][m.to_sq()]; } } @@ -235,7 +235,7 @@ Move MovePicker::select(Pred filter) { cur++; } - return MOVE_NONE; + return Move::none(); } // Most important method of the MovePicker class. It @@ -278,8 +278,7 @@ top: endMoves = std::end(refutations); // If the countermove is the same as a killer, skip it - if (refutations[0].move == refutations[2].move - || refutations[1].move == refutations[2].move) + if (refutations[0] == refutations[2] || refutations[1] == refutations[2]) --endMoves; ++stage; @@ -287,7 +286,7 @@ top: case REFUTATION : if (select([&]() { - return *cur != MOVE_NONE && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); + return *cur != Move::none() && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); })) return *(cur - 1); ++stage; @@ -308,8 +307,7 @@ top: case QUIET : if (!skipQuiets && select([&]() { - return *cur != refutations[0].move && *cur != refutations[1].move - && *cur != refutations[2].move; + return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) return *(cur - 1); @@ -343,7 +341,7 @@ top: // If we did not find any move and we do not try checks, we have finished if (depth != DEPTH_QS_CHECKS) - return MOVE_NONE; + return Move::none(); ++stage; [[fallthrough]]; @@ -360,7 +358,7 @@ top: } assert(false); - return MOVE_NONE; // Silence warning + return Move::none(); // Silence warning } } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 24252433..ad4be8e9 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -142,7 +142,7 @@ using CorrectionHistory = // MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which returns a // new pseudo-legal move each time it is called, until there are no moves left, -// when MOVE_NONE is returned. In order to improve the efficiency of the +// when Move::none() is returned. In order to improve the efficiency of the // alpha-beta algorithm, MovePicker attempts to return the moves which are most // likely to get a cut-off first. class MovePicker { diff --git a/src/position.cpp b/src/position.cpp index 32823bd0..810bba57 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -140,14 +140,14 @@ void Position::init() { for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) { - Move move = make_move(s1, s2); + Move move = Move(s1, s2); Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; int i = H1(key); while (true) { std::swap(cuckoo[i], key); std::swap(cuckooMove[i], move); - if (move == MOVE_NONE) // Arrived at empty slot? + if (move == Move::none()) // Arrived at empty slot? break; i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot } @@ -487,11 +487,11 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { // Tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { - assert(is_ok(m)); + assert(m.is_ok()); Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); assert(color_of(moved_piece(m)) == us); assert(piece_on(square(us)) == make_piece(us, KING)); @@ -499,7 +499,7 @@ bool Position::legal(Move m) const { // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { Square ksq = square(us); Square capsq = to - pawn_push(us); @@ -516,7 +516,7 @@ bool Position::legal(Move m) const { // Castling moves generation does not check if the castling path is clear of // enemy attacks, it is delayed at a later time: now! - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { // After castling, the rook and king final positions are the same in // Chess960 as they would be in standard chess. @@ -529,7 +529,7 @@ bool Position::legal(Move m) const { // In case of Chess960, verify if the Rook blocks some checks. // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 || !(blockers_for_king(us) & to_sq(m)); + return !chess960 || !(blockers_for_king(us) & m.to_sq()); } // If the moving piece is a king, check whether the destination square is @@ -549,18 +549,18 @@ bool Position::legal(Move m) const { bool Position::pseudo_legal(const Move m) const { Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = moved_piece(m); // Use a slower but simpler function for uncommon cases // yet we skip the legality check of MoveList(). - if (type_of(m) != NORMAL) + if (m.type_of() != NORMAL) return checkers() ? MoveList(*this).contains(m) : MoveList(*this).contains(m); // Is not a promotion, so the promotion piece must be empty - assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); + assert(m.promotion_type() - KNIGHT == NO_PIECE_TYPE); // If the 'from' square is not occupied by a piece belonging to the side to // move, the move is obviously not legal. @@ -615,11 +615,11 @@ bool Position::pseudo_legal(const Move m) const { // Tests whether a pseudo-legal move gives a check bool Position::gives_check(Move m) const { - assert(is_ok(m)); + assert(m.is_ok()); assert(color_of(moved_piece(m)) == sideToMove); - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); // Is there a direct check? if (check_squares(type_of(piece_on(from))) & to) @@ -627,15 +627,15 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || type_of(m) == CASTLING; + return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; - switch (type_of(m)) + switch (m.type_of()) { case NORMAL : return false; case PROMOTION : - return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle @@ -664,7 +664,7 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { - assert(is_ok(m)); + assert(m.is_ok()); assert(&newSt != st); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); @@ -691,16 +691,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Color us = sideToMove; Color them = ~us; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(from); - Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); assert(color_of(pc) == us); - assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); + assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us)); assert(type_of(captured) != KING); - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { assert(pc == make_piece(us, KING)); assert(captured == make_piece(us, ROOK)); @@ -720,7 +720,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // update non-pawn material. if (type_of(captured) == PAWN) { - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { capsq -= pawn_push(us); @@ -771,7 +771,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) + if (m.type_of() != CASTLING) { dp.piece[0] = pc; dp.from[0] = from; @@ -791,9 +791,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { k ^= Zobrist::enpassant[file_of(st->epSquare)]; } - else if (type_of(m) == PROMOTION) + else if (m.type_of() == PROMOTION) { - Piece promotion = make_piece(us, promotion_type(m)); + Piece promotion = make_piece(us, m.promotion_type()); assert(relative_rank(us, to) == RANK_8); assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); @@ -866,22 +866,22 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // be restored to exactly the same state as before the move was made. void Position::undo_move(Move m) { - assert(is_ok(m)); + assert(m.is_ok()); sideToMove = ~sideToMove; Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(to); - assert(empty(from) || type_of(m) == CASTLING); + assert(empty(from) || m.type_of() == CASTLING); assert(type_of(st->capturedPiece) != KING); - if (type_of(m) == PROMOTION) + if (m.type_of() == PROMOTION) { assert(relative_rank(us, to) == RANK_8); - assert(type_of(pc) == promotion_type(m)); + assert(type_of(pc) == m.promotion_type()); assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); remove_piece(to); @@ -889,7 +889,7 @@ void Position::undo_move(Move m) { put_piece(pc, to); } - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { Square rfrom, rto; do_castling(us, from, to, rfrom, rto); @@ -902,7 +902,7 @@ void Position::undo_move(Move m) { { Square capsq = to; - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { capsq -= pawn_push(us); @@ -1011,8 +1011,8 @@ void Position::undo_null_move() { // en passant and promotions. Key Position::key_after(Move m) const { - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(from); Piece captured = piece_on(to); Key k = st->key ^ Zobrist::side; @@ -1031,13 +1031,13 @@ Key Position::key_after(Move m) const { // algorithm similar to alpha-beta pruning with a null window. bool Position::see_ge(Move m, Value threshold) const { - assert(is_ok(m)); + assert(m.is_ok()); // Only deal with normal moves, assume others pass a simple SEE - if (type_of(m) != NORMAL) + if (m.type_of() != NORMAL) return VALUE_ZERO >= threshold; - Square from = from_sq(m), to = to_sq(m); + Square from = m.from_sq(), to = m.to_sq(); int swap = PieceValue[piece_on(to)] - threshold; if (swap < 0) @@ -1182,8 +1182,8 @@ bool Position::has_game_cycle(int ply) const { if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) { Move move = cuckooMove[j]; - Square s1 = from_sq(move); - Square s2 = to_sq(move); + Square s1 = move.from_sq(); + Square s2 = move.to_sq(); if (!((between_bb(s1, s2) ^ s2) & pieces())) { diff --git a/src/position.h b/src/position.h index 46956afc..3e932759 100644 --- a/src/position.h +++ b/src/position.h @@ -210,7 +210,7 @@ inline Piece Position::piece_on(Square s) const { inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } -inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); } +inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } @@ -312,16 +312,16 @@ inline int Position::rule50_count() const { return st->rule50; } inline bool Position::is_chess960() const { return chess960; } inline bool Position::capture(Move m) const { - assert(is_ok(m)); - return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; + assert(m.is_ok()); + return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT; } // Returns true if a move is generated from the capture stage, having also // queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { - assert(is_ok(m)); - return capture(m) || promotion_type(m) == QUEEN; + assert(m.is_ok()); + return capture(m) || m.promotion_type() == QUEEN; } inline Piece Position::captured_piece() const { return st->capturedPiece; } diff --git a/src/search.cpp b/src/search.cpp index aae3625a..0d41f48d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -130,7 +130,7 @@ struct Skill { Move pick_best(size_t multiPV); double level; - Move best = MOVE_NONE; + Move best = Move::none(); }; template @@ -226,7 +226,7 @@ void MainThread::search() { if (rootMoves.empty()) { - rootMoves.emplace_back(MOVE_NONE); + rootMoves.emplace_back(Move::none()); sync_cout << "info depth 0 score " << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; } @@ -262,7 +262,7 @@ void MainThread::search() { Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); if (int(Options["MultiPV"]) == 1 && !Limits.depth && !skill.enabled() - && rootMoves[0].pv[0] != MOVE_NONE) + && rootMoves[0].pv[0] != Move::none()) bestThread = Threads.get_best_thread(); bestPreviousScore = bestThread->rootMoves[0].score; @@ -293,7 +293,7 @@ void Thread::search() { Stack stack[MAX_PLY + 10], *ss = stack + 7; Move pv[MAX_PLY + 1]; Value alpha, beta, delta; - Move lastBestMove = MOVE_NONE; + Move lastBestMove = Move::none(); Depth lastBestMoveDepth = 0; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); double timeReduction = 1, totBestMoveChanges = 0; @@ -604,11 +604,11 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss + 1)->excludedMove = bestMove = MOVE_NONE; - (ss + 2)->killers[0] = (ss + 2)->killers[1] = MOVE_NONE; + (ss + 1)->excludedMove = bestMove = Move::none(); + (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; ss->doubleExtensions = (ss - 1)->doubleExtensions; - Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; // Step 4. Transposition table lookup. @@ -618,7 +618,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() - : MOVE_NONE; + : Move::none(); ttCapture = ttMove && pos.capture_stage(ttMove); // At this point, if excluded, skip straight to step 6, static eval. However, @@ -650,8 +650,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else if (!ttCapture) { int penalty = -stat_malus(depth); - thisThread->mainHistory[us][from_to(ttMove)] << penalty; - update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); + thisThread->mainHistory[us][ttMove.from_to()] << penalty; + update_continuation_histories(ss, pos.moved_piece(ttMove), ttMove.to_sq(), penalty); } } @@ -699,7 +699,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), MOVE_NONE, VALUE_NONE); + std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE); return value; } @@ -764,17 +764,17 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->staticEval = eval = to_static_eval(newEval); // Static evaluation is saved as it was before adjustment by correction history - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), unadjustedStaticEval); } // Use static evaluation difference to improve quiet move ordering (~9 Elo) - if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); bonus = bonus > 0 ? 2 * bonus : bonus / 2; - thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; - if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; + 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 / 4; } @@ -810,9 +810,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17496 && eval >= beta - && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 && !excludedMove - && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17496 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 + && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); @@ -820,7 +820,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Null move dynamic reduction based on depth and eval Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; - ss->currentMove = MOVE_NULL; + ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; pos.do_null_move(st); @@ -883,7 +883,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != Move::none()) if (move != excludedMove && pos.legal(move)) { assert(pos.capture_stage(move)); @@ -894,7 +894,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->currentMove = move; ss->continuationHistory = &thisThread - ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][to_sq(move)]; + ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; pos.do_move(move, st); @@ -938,7 +938,7 @@ moves_loop: // When in check, search starts here (ss - 6)->continuationHistory}; Move countermove = - prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; + prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none(); MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, &thisThread->pawnHistory, countermove, ss->killers); @@ -953,9 +953,9 @@ moves_loop: // When in check, search starts here // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) + while ((move = mp.next_move(moveCountPruning)) != Move::none()) { - assert(is_ok(move)); + assert(move.is_ok()); if (move == excludedMove) continue; @@ -1009,10 +1009,10 @@ moves_loop: // When in check, search starts here // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Piece capturedPiece = pos.piece_on(to_sq(move)); + Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] - + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; + + captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) continue; } @@ -1024,15 +1024,16 @@ moves_loop: // When in check, search starts here else { int history = - (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][to_sq(move)]; + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) if (lmrDepth < 6 && history < -3752 * depth) continue; - history += 2 * thisThread->mainHistory[us][from_to(move)]; + history += 2 * thisThread->mainHistory[us][move.from_to()]; lmrDepth += history / 7838; lmrDepth = std::max(lmrDepth, -1); @@ -1077,7 +1078,7 @@ moves_loop: // When in check, search starts here ss->excludedMove = move; value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - ss->excludedMove = MOVE_NONE; + ss->excludedMove = Move::none(); if (value < singularBeta) { @@ -1125,12 +1126,13 @@ moves_loop: // When in check, search starts here // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 4325) + && (*contHist[0])[movedPiece][move.to_sq()] >= 4325) extension = 1; // Recapture extensions (~1 Elo) - else if (PvNode && move == ttMove && to_sq(move) == prevSq - && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] + else if (PvNode && move == ttMove && move.to_sq() == prevSq + && captureHistory[movedPiece][move.to_sq()] + [type_of(pos.piece_on(move.to_sq()))] > 4146) extension = 1; } @@ -1145,7 +1147,7 @@ moves_loop: // When in check, search starts here // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = - &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][to_sq(move)]; + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; // Step 16. Make the move pos.do_move(move, st, givesCheck); @@ -1187,10 +1189,10 @@ moves_loop: // When in check, search starts here else if (move == ttMove) r = 0; - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - 3817; + ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] - 3817; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) r -= ss->statScore / 14767; @@ -1229,7 +1231,7 @@ moves_loop: // When in check, search starts here : value >= beta ? stat_bonus(newDepth) : 0; - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); + update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } @@ -1249,7 +1251,7 @@ moves_loop: // When in check, search starts here if (PvNode && (moveCount == 1 || value > alpha)) { (ss + 1)->pv = pv; - (ss + 1)->pv[0] = MOVE_NONE; + (ss + 1)->pv[0] = Move::none(); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); } @@ -1296,7 +1298,7 @@ moves_loop: // When in check, search starts here assert((ss + 1)->pv); - for (Move* m = (ss + 1)->pv; *m != MOVE_NONE; ++m) + for (Move* m = (ss + 1)->pv; *m != Move::none(); ++m) rm.pv.push_back(*m); // We record how often the best move has been changed in each iteration. @@ -1375,7 +1377,7 @@ moves_loop: // When in check, search starts here + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << stat_bonus(depth) * bonus / 2; } @@ -1451,11 +1453,11 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (PvNode) { (ss + 1)->pv = pv; - ss->pv[0] = MOVE_NONE; + ss->pv[0] = Move::none(); } Thread* thisThread = pos.this_thread(); - bestMove = MOVE_NONE; + bestMove = Move::none(); ss->inCheck = pos.checkers(); moveCount = 0; @@ -1476,7 +1478,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { posKey = pos.key(); tte = TT.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = ss->ttHit ? tte->move() : MOVE_NONE; + ttMove = ss->ttHit ? tte->move() : Move::none(); pvHit = ss->ttHit && tte->is_pv(); // At non-PV nodes we check for an early TT cutoff @@ -1513,7 +1515,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; + (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; Value newEval = ss->staticEval @@ -1527,7 +1529,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - MOVE_NONE, unadjustedStaticEval); + Move::none(), unadjustedStaticEval); return bestValue; } @@ -1545,7 +1547,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); @@ -1553,9 +1555,9 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != Move::none()) { - assert(is_ok(move)); + assert(move.is_ok()); // Check for legality if (!pos.legal(move)) @@ -1570,13 +1572,13 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) - if (!givesCheck && to_sq(move) != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY - && type_of(move) != PROMOTION) + if (!givesCheck && move.to_sq() != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY + && move.type_of() != PROMOTION) { if (moveCount > 2) continue; - futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; + 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. @@ -1610,8 +1612,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { break; // Continuation history based pruning (~3 Elo) - if (!capture && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) + if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] < 0 + && (*contHist[1])[pos.moved_piece(move)][move.to_sq()] < 0) continue; // Do not search moves with bad enough SEE values (~5 Elo) @@ -1626,7 +1628,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->currentMove = move; ss->continuationHistory = &thisThread - ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][to_sq(move)]; + ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; quietCheckEvasions += !capture && ss->inCheck; @@ -1738,9 +1740,9 @@ Value value_from_tt(Value v, int ply, int r50c) { // Adds current move and appends child pv[] void update_pv(Move* pv, Move move, const Move* childPv) { - for (*pv++ = move; childPv && *childPv != MOVE_NONE;) + for (*pv++ = move; childPv && *childPv != Move::none();) *pv++ = *childPv++; - *pv = MOVE_NONE; + *pv = Move::none(); } @@ -1775,25 +1777,25 @@ void update_all_stats(const Position& pos, update_quiet_stats(pos, ss, bestMove, bestMoveBonus); int pIndex = pawn_structure_index(pos); - thisThread->pawnHistory[pIndex][moved_piece][to_sq(bestMove)] << quietMoveBonus; + thisThread->pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread - ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][to_sq(quietsSearched[i])] + ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] << -quietMoveMalus; - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus; + thisThread->mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - to_sq(quietsSearched[i]), -quietMoveMalus); + quietsSearched[i].to_sq(), -quietMoveMalus); } } else { // Increase stats for the best move in case it was a capture move - captured = type_of(pos.piece_on(to_sq(bestMove))); - captureHistory[moved_piece][to_sq(bestMove)][captured] << quietMoveBonus; + captured = type_of(pos.piece_on(bestMove.to_sq())); + captureHistory[moved_piece][bestMove.to_sq()][captured] << quietMoveBonus; } // Extra penalty for a quiet early move that was not a TT move or @@ -1808,8 +1810,8 @@ void update_all_stats(const Position& pos, for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); - captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveMalus; + captured = type_of(pos.piece_on(capturesSearched[i].to_sq())); + captureHistory[moved_piece][capturesSearched[i].to_sq()][captured] << -quietMoveMalus; } } @@ -1823,7 +1825,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; - if (is_ok((ss - i)->currentMove)) + if (((ss - i)->currentMove).is_ok()) (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); } } @@ -1841,13 +1843,13 @@ void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { Color us = pos.side_to_move(); Thread* thisThread = pos.this_thread(); - thisThread->mainHistory[us][from_to(move)] << bonus; - update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); + thisThread->mainHistory[us][move.from_to()] << bonus; + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); // Update countermove history - if (is_ok((ss - 1)->currentMove)) + if (((ss - 1)->currentMove).is_ok()) { - Square prevSq = to_sq((ss - 1)->currentMove); + Square prevSq = ((ss - 1)->currentMove).to_sq(); thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } } @@ -1987,7 +1989,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { assert(pv.size() == 1); - if (pv[0] == MOVE_NONE) + if (pv[0] == Move::none()) return false; pos.do_move(pv[0], st); diff --git a/src/thread.cpp b/src/thread.cpp index e900a9ac..01ccd4fc 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -66,7 +66,7 @@ Thread::~Thread() { // Reset histories, usually before a new game void Thread::clear() { - counterMoves.fill(MOVE_NONE); + counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(0); pawnHistory.fill(0); @@ -220,9 +220,9 @@ void ThreadPool::start_thinking(Position& pos, Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = threads.front(); - std::map votes; - Value minScore = VALUE_NONE; + Thread* bestThread = threads.front(); + std::unordered_map votes; + Value minScore = VALUE_NONE; // Find the minimum score of all threads for (Thread* th : threads) diff --git a/src/tt.cpp b/src/tt.cpp index 5c4e6d53..2e3f7d32 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -39,7 +39,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) // Preserve any existing move for the same position if (m || uint16_t(k) != key16) - move16 = uint16_t(m); + move16 = m; // Overwrite less valuable entries (cheapest checks first) if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) diff --git a/src/tt.h b/src/tt.h index 82a66863..61e854c1 100644 --- a/src/tt.h +++ b/src/tt.h @@ -53,7 +53,7 @@ struct TTEntry { uint16_t key16; uint8_t depth8; uint8_t genBound8; - uint16_t move16; + Move move16; int16_t value16; int16_t eval16; }; diff --git a/src/types.h b/src/types.h index dde1a52c..2970d1e0 100644 --- a/src/types.h +++ b/src/types.h @@ -108,30 +108,6 @@ using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; -// A move needs 16 bits to be stored -// -// bit 0- 5: destination square (from 0 to 63) -// bit 6-11: origin square (from 0 to 63) -// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) -// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) -// NOTE: en passant bit is set only when a pawn can be captured -// -// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in -// any normal move destination square is always different from origin square -// while MOVE_NONE and MOVE_NULL have the same origin and destination square. - -enum Move : int { - MOVE_NONE, - MOVE_NULL = 65 -}; - -enum MoveType { - NORMAL, - PROMOTION = 1 << 14, - EN_PASSANT = 2 << 14, - CASTLING = 3 << 14 -}; - enum Color { WHITE, BLACK, @@ -353,8 +329,6 @@ inline Color color_of(Piece pc) { return Color(pc >> 3); } -constexpr bool is_ok(Move m) { return m != MOVE_NONE && m != MOVE_NULL; } - constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } constexpr File file_of(Square s) { return File(s & 7); } @@ -369,34 +343,81 @@ constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_o constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } -constexpr Square from_sq(Move m) { - assert(is_ok(m)); - return Square((m >> 6) & 0x3F); -} - -constexpr Square to_sq(Move m) { - assert(is_ok(m)); - return Square(m & 0x3F); -} - -constexpr int from_to(Move m) { return m & 0xFFF; } - -constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); } - -constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); } - -constexpr Move make_move(Square from, Square to) { return Move((from << 6) + to); } - -template -constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { - return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); -} // Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } + +enum MoveType { + NORMAL, + PROMOTION = 1 << 14, + EN_PASSANT = 2 << 14, + CASTLING = 3 << 14 +}; + +// A move needs 16 bits to be stored +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +// NOTE: en passant bit is set only when a pawn can be captured +// +// Special cases are Move::none() and Move::null(). We can sneak these in because in +// any normal move destination square is always different from origin square +// while Move::none() and Move::null() have the same origin and destination square. +class Move { + public: + Move() = default; + constexpr explicit Move(std::uint16_t d) : + data(d) {} + + constexpr Move(Square from, Square to) : + data((from << 6) + to) {} + + template + static constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); + } + + constexpr Square from_sq() const { + assert(is_ok()); + return Square((data >> 6) & 0x3F); + } + + constexpr Square to_sq() const { + assert(is_ok()); + return Square(data & 0x3F); + } + + constexpr int from_to() const { return data & 0xFFF; } + + constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } + + constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } + + constexpr bool is_ok() const { return none().data != data && null().data != data; } + + static constexpr Move null() { return Move(65); } + static constexpr Move none() { return Move(0); } + + constexpr bool operator==(const Move& m) const { return data == m.data; } + constexpr bool operator!=(const Move& m) const { return data != m.data; } + + constexpr explicit operator bool() const { return data != 0; } + + constexpr std::uint16_t raw() const { return data; } + + struct MoveHash { + std::size_t operator()(const Move& m) const { return m.data; } + }; + + protected: + std::uint16_t data; +}; + } // namespace Stockfish #endif // #ifndef TYPES_H_INCLUDED diff --git a/src/uci.cpp b/src/uci.cpp index 5dc9b2b0..8e93eee6 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -75,7 +75,7 @@ void position(Position& pos, std::istringstream& is, StateListPtr& states) { pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse the move list, if any - while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) + while (is >> token && (m = UCI::to_move(pos, token)) != Move::none()) { states->emplace_back(); pos.do_move(m, states->back()); @@ -395,22 +395,22 @@ std::string UCI::square(Square s) { // Internally, all castling moves are always encoded as 'king captures rook'. std::string UCI::move(Move m, bool chess960) { - if (m == MOVE_NONE) + if (m == Move::none()) return "(none)"; - if (m == MOVE_NULL) + if (m == Move::null()) return "0000"; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); - if (type_of(m) == CASTLING && !chess960) + if (m.type_of() == CASTLING && !chess960) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); std::string move = UCI::square(from) + UCI::square(to); - if (type_of(m) == PROMOTION) - move += " pnbrqk"[promotion_type(m)]; + if (m.type_of() == PROMOTION) + move += " pnbrqk"[m.promotion_type()]; return move; } @@ -427,7 +427,7 @@ Move UCI::to_move(const Position& pos, std::string& str) { if (str == UCI::move(m, pos.is_chess960())) return m; - return MOVE_NONE; + return Move::none(); } } // namespace Stockfish From 49308929852731e118b2c6d5d4232e930182cc11 Mon Sep 17 00:00:00 2001 From: RainRat Date: Tue, 2 Jan 2024 23:01:24 -0800 Subject: [PATCH 519/678] Fix typo in tbprobe.cpp closes https://github.com/official-stockfish/Stockfish/pull/4959 No functional change --- src/syzygy/tbprobe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index c1275cf5..91013dca 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -863,7 +863,7 @@ do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 - // triangle to 0...5. There are 63 squares for second piece and and 62 + // triangle to 0...5. There are 63 squares for second piece and 62 // (mapped to 0...61) for the third. if (off_A1H8(squares[0])) idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2; From b987d4f0332f57a58157641bf3a6e437133e7879 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 20:45:14 +0100 Subject: [PATCH 520/678] Use type aliases instead of enums for Value types The primary rationale behind this lies in the fact that enums were not originally designed to be employed in the manner we currently utilize them. The Value enum was used like a type alias throughout the code and was often misused. Furthermore, changing the underlying size of the enum to int16_t broke everything, mostly because of the operator overloads for the Value enum, were causing data to be truncated. Since Value is now a type alias, the operator overloads are no longer required. Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/6593b8bb79aa8af82b95b401 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 235296 W: 59919 L: 59917 D: 115460 Ptnml(0-2): 743, 27085, 62054, 26959, 807 closes https://github.com/official-stockfish/Stockfish/pull/4960 No functional change --- src/evaluate.cpp | 10 ++++----- src/evaluate.h | 2 +- src/movepick.cpp | 9 ++++---- src/movepick.h | 4 ++-- src/nnue/evaluate_nnue.h | 2 +- src/position.cpp | 2 +- src/position.h | 2 +- src/search.cpp | 26 +++++++++++------------ src/thread.h | 6 ++++-- src/tune.cpp | 15 -------------- src/tune.h | 4 +--- src/types.h | 45 +++++++++++++++++++++------------------- 12 files changed, 57 insertions(+), 70 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index b6342f18..bda7132a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -148,7 +148,7 @@ void NNUE::verify() { // Returns a static, purely materialistic evaluation of the position from // the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. -Value Eval::simple_eval(const Position& pos, Color c) { +int Eval::simple_eval(const Position& pos, Color c) { return PawnValue * (pos.count(c) - pos.count(~c)) + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } @@ -160,7 +160,7 @@ Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); - Value v; + int v; Color stm = pos.side_to_move(); int shuffling = pos.rule50_count(); int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); @@ -170,13 +170,13 @@ Value Eval::evaluate(const Position& pos) { + std::abs(pos.this_thread()->rootSimpleEval); if (lazy) - v = Value(simpleEval); + v = simpleEval; else { int nnueComplexity; Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - Value optimism = pos.this_thread()->optimism[stm]; + int optimism = pos.this_thread()->optimism[stm]; // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; @@ -190,7 +190,7 @@ Value Eval::evaluate(const Position& pos) { v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range - v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + v = std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); return v; } diff --git a/src/evaluate.h b/src/evaluate.h index c2b08aaf..0a7ec61a 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -31,7 +31,7 @@ namespace Eval { std::string trace(Position& pos); -Value simple_eval(const Position& pos, Color c); +int simple_eval(const Position& pos, Color c); Value evaluate(const Position& pos); extern std::string currentEvalFileName; diff --git a/src/movepick.cpp b/src/movepick.cpp index cae01891..14b6c87a 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -127,7 +127,7 @@ MovePicker::MovePicker(const Position& p, // Constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : +MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) : pos(p), captureHistory(cph), ttMove(ttm), @@ -211,8 +211,8 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = PieceValue[pos.piece_on(m.to_sq())] - Value(type_of(pos.moved_piece(m))) - + (1 << 28); + m.value = + PieceValue[pos.piece_on(m.to_sq())] - type_of(pos.moved_piece(m)) + (1 << 28); else m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] @@ -268,8 +268,7 @@ top: case GOOD_CAPTURE : if (select([&]() { // Move losing capture to endBadCaptures to be tried later - return pos.see_ge(*cur, Value(-cur->value)) ? true - : (*endBadCaptures++ = *cur, false); + return pos.see_ge(*cur, -cur->value) ? true : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); diff --git a/src/movepick.h b/src/movepick.h index ad4be8e9..c429f8ae 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -171,7 +171,7 @@ class MovePicker { const CapturePieceToHistory*, const PieceToHistory**, const PawnHistory*); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); private: @@ -190,7 +190,7 @@ class MovePicker { Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; - Value threshold; + int threshold; Depth depth; ExtMove moves[MAX_MOVES]; }; diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 05c98bc5..f80aa398 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -30,10 +30,10 @@ #include "../misc.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" +#include "../types.h" namespace Stockfish { class Position; -enum Value : int; } namespace Stockfish::Eval::NNUE { diff --git a/src/position.cpp b/src/position.cpp index 810bba57..4fba3c23 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1029,7 +1029,7 @@ Key Position::key_after(Move m) const { // Tests if the SEE (Static Exchange Evaluation) // value of move is greater or equal to the given threshold. We'll use an // algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value threshold) const { +bool Position::see_ge(Move m, int threshold) const { assert(m.is_ok()); diff --git a/src/position.h b/src/position.h index 3e932759..7e0c3eef 100644 --- a/src/position.h +++ b/src/position.h @@ -141,7 +141,7 @@ class Position { void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, int threshold = 0) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index 0d41f48d..9dc4ee98 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,13 +77,13 @@ enum NodeType { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return Value((116 - 44 * noTtCutNode) * (d - improving)); + return ((116 - 44 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup int Reductions[MAX_MOVES]; // [depth or moveNumber] -Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { +Depth reduction(bool i, Depth d, int mn, int delta, int rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 + (!i && reductionScale > 880); @@ -95,7 +95,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Guarantee evaluation does not hit the tablebase range constexpr Value to_static_eval(const Value v) { - return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + return std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth @@ -292,13 +292,13 @@ void Thread::search() { // (ss + 2) is needed for initialization of cutOffCnt and killers. Stack stack[MAX_PLY + 10], *ss = stack + 7; Move pv[MAX_PLY + 1]; - Value alpha, beta, delta; + Value alpha, beta; Move lastBestMove = Move::none(); Depth lastBestMoveDepth = 0; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int iterIdx = 0; + Color us = rootPos.side_to_move(); + int delta, iterIdx = 0; std::memset(ss - 7, 0, 10 * sizeof(Stack)); for (int i = 7; i > 0; --i) @@ -374,7 +374,7 @@ void Thread::search() { Value avg = rootMoves[pvIdx].averageScore; delta = Value(9) + int(avg) * avg / 14847; alpha = std::max(avg - delta, -VALUE_INFINITE); - beta = std::min(avg + delta, VALUE_INFINITE); + beta = std::min(avg + delta, int(VALUE_INFINITE)); // Adjust optimism based on root move's averageScore (~4 Elo) optimism[us] = 121 * avg / (std::abs(avg) + 109); @@ -425,7 +425,7 @@ void Thread::search() { } else if (bestValue >= beta) { - beta = std::min(bestValue + delta, VALUE_INFINITE); + beta = std::min(bestValue + delta, int(VALUE_INFINITE)); ++failedHighCnt; } else @@ -989,7 +989,7 @@ moves_loop: // When in check, search starts here // Calculate new depth for this move newDepth = depth - 1; - Value delta = beta - alpha; + int delta = beta - alpha; Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); @@ -1018,7 +1018,7 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, Value(-187) * depth)) + if (!pos.see_ge(move, -187 * depth)) continue; } else @@ -1048,7 +1048,7 @@ 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, Value(-26 * lmrDepth * lmrDepth))) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1617,7 +1617,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-77))) + if (!pos.see_ge(move, -77)) continue; } @@ -1863,7 +1863,7 @@ Move Skill::pick_best(size_t multiPV) { // RootMoves are already sorted by score in descending order Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue); + int delta = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue)); int maxScore = -VALUE_INFINITE; double weakness = 120 - 2 * level; diff --git a/src/thread.h b/src/thread.h index 22fe32c3..7db7c159 100644 --- a/src/thread.h +++ b/src/thread.h @@ -56,13 +56,15 @@ class Thread { size_t pvIdx, pvLast; std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; - Value bestValue, optimism[COLOR_NB]; + Value bestValue; + + int optimism[COLOR_NB]; Position rootPos; StateInfo rootState; Search::RootMoves rootMoves; Depth rootDepth, completedDepth; - Value rootDelta; + int rootDelta; Value rootSimpleEval; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; diff --git a/src/tune.cpp b/src/tune.cpp index 44bfa682..1dddca0c 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -26,10 +26,6 @@ #include "uci.h" -namespace Stockfish { -enum Value : int; -} - using std::string; namespace Stockfish { @@ -92,17 +88,6 @@ void Tune::Entry::read_option() { value = int(Options[name]); } -template<> -void Tune::Entry::init_option() { - make_option(name, value, range); -} - -template<> -void Tune::Entry::read_option() { - if (Options.count(name)) - value = Value(int(Options[name])); -} - // Instead of a variable here we have a PostUpdate function: just call it template<> void Tune::Entry::init_option() {} diff --git a/src/tune.h b/src/tune.h index 3d45e51c..17057001 100644 --- a/src/tune.h +++ b/src/tune.h @@ -27,7 +27,6 @@ #include namespace Stockfish { -enum Value : int; using Range = std::pair; // Option's min-max values using RangeFun = Range(int); @@ -101,8 +100,7 @@ class Tune { static_assert(!std::is_const_v, "Parameter cannot be const!"); - static_assert(std::is_same_v || std::is_same_v - || std::is_same_v, + static_assert(std::is_same_v || std::is_same_v, "Parameter type not supported!"); Entry(const std::string& n, T& v, const SetRange& r) : diff --git a/src/types.h b/src/types.h index 2970d1e0..ca9ef615 100644 --- a/src/types.h +++ b/src/types.h @@ -137,29 +137,33 @@ enum Bound { BOUND_EXACT = BOUND_UPPER | BOUND_LOWER }; -enum Value : int { - VALUE_ZERO = 0, - VALUE_DRAW = 0, - VALUE_NONE = 32002, - VALUE_INFINITE = 32001, +// Value is used as an alias for int16_t, this is done to differentiate between +// a search value and any other integer value. The values used in search are always +// supposed to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range. +using Value = int; - VALUE_MATE = 32000, - VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, - VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, +constexpr Value VALUE_ZERO = 0; +constexpr Value VALUE_DRAW = 0; +constexpr Value VALUE_NONE = 32002; +constexpr Value VALUE_INFINITE = 32001; - VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1, - VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY, - VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, +constexpr Value VALUE_MATE = 32000; +constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY; +constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY; + +constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; +constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; +constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; + +// In the code, we make the assumption that these values +// are such that non_pawn_material() can be used to uniquely +// identify the material on the board. +constexpr Value PawnValue = 208; +constexpr Value KnightValue = 781; +constexpr Value BishopValue = 825; +constexpr Value RookValue = 1276; +constexpr Value QueenValue = 2538; - // In the code, we make the assumption that these values - // are such that non_pawn_material() can be used to uniquely - // identify the material on the board. - PawnValue = 208, - KnightValue = 781, - BishopValue = 825, - RookValue = 1276, - QueenValue = 2538, -}; // clang-format off enum PieceType { @@ -280,7 +284,6 @@ struct DirtyPiece { inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } -ENABLE_FULL_OPERATORS_ON(Value) ENABLE_FULL_OPERATORS_ON(Direction) ENABLE_INCR_OPERATORS_ON(PieceType) From 8b4583bce76da7d27aaa565e6302d2e540cd496a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 3 Jan 2024 17:32:57 +0300 Subject: [PATCH 521/678] Remove redundant int cast Remove a redundant int cast in the calculation of fwdOut. The variable OutputType is already defined as std::int32_t, which is an integer type, making the cast unnecessary. closes https://github.com/official-stockfish/Stockfish/pull/4961 No functional change --- src/nnue/nnue_architecture.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 6c0e52b7..92445704 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -116,7 +116,7 @@ struct Network { // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1< Date: Fri, 5 Jan 2024 21:03:19 +0800 Subject: [PATCH 522/678] Remove unneeded operator overload macros Only Direction type is using two of the enable overload macros. Aside from this, only two of the overloads are even being used. Therefore, we can just define the needed overloads and remove the macros. closes https://github.com/official-stockfish/Stockfish/pull/4966 No functional change. --- src/types.h | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/types.h b/src/types.h index ca9ef615..e83b306d 100644 --- a/src/types.h +++ b/src/types.h @@ -264,36 +264,19 @@ struct DirtyPiece { Square to[3]; }; - #define ENABLE_BASE_OPERATORS_ON(T) \ - constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ - constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ - constexpr T operator-(T d) { return T(-int(d)); } \ - inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ - inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } - #define ENABLE_INCR_OPERATORS_ON(T) \ inline T& operator++(T& d) { return d = T(int(d) + 1); } \ inline T& operator--(T& d) { return d = T(int(d) - 1); } - #define ENABLE_FULL_OPERATORS_ON(T) \ - ENABLE_BASE_OPERATORS_ON(T) \ - constexpr T operator*(int i, T d) { return T(i * int(d)); } \ - constexpr T operator*(T d, int i) { return T(int(d) * i); } \ - constexpr T operator/(T d, int i) { return T(int(d) / i); } \ - constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ - inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ - inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } - -ENABLE_FULL_OPERATORS_ON(Direction) - ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) ENABLE_INCR_OPERATORS_ON(Rank) - #undef ENABLE_FULL_OPERATORS_ON #undef ENABLE_INCR_OPERATORS_ON - #undef ENABLE_BASE_OPERATORS_ON + +constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int(d1) + int(d2)); } +constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); } // Additional operators to add a Direction to a Square constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } From 6f9071c64354a34970e7b5669701d0ad15b7a694 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 6 Jan 2024 07:42:53 +0300 Subject: [PATCH 523/678] Tweak usage of correction history Instead of using linear formula use quadratic one. Maximum impact of correction history is doubled this way, it breaks even with previous formula on half of maximum value. Passed STC: https://tests.stockfishchess.org/tests/view/659591e579aa8af82b95d7e8 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 225216 W: 57616 L: 57019 D: 110581 Ptnml(0-2): 747, 26677, 57201, 27198, 785 Passed LTC: https://tests.stockfishchess.org/tests/view/6596ee0b79aa8af82b95f08a LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 73314 W: 18524 L: 18125 D: 36665 Ptnml(0-2): 41, 8159, 19875, 8524, 58 closes https://github.com/official-stockfish/Stockfish/pull/4967 Bench: 1464785 --- src/search.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9dc4ee98..e93b12d1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -745,7 +745,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo Value newEval = ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + thisThread->correctionHistory[us][pawn_structure_index(pos)] + * std::abs(thisThread->correctionHistory[us][pawn_structure_index(pos)]) + / 16384; ss->staticEval = eval = to_static_eval(newEval); @@ -759,7 +761,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo Value newEval = ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + thisThread->correctionHistory[us][pawn_structure_index(pos)] + * std::abs(thisThread->correctionHistory[us][pawn_structure_index(pos)]) + / 16384; ss->staticEval = eval = to_static_eval(newEval); @@ -1502,7 +1506,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { Value newEval = ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + thisThread->correctionHistory[us][pawn_structure_index(pos)] + * std::abs( + thisThread->correctionHistory[us][pawn_structure_index(pos)]) + / 16384; ss->staticEval = bestValue = to_static_eval(newEval); @@ -1519,7 +1526,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { Value newEval = ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + thisThread->correctionHistory[us][pawn_structure_index(pos)] + * std::abs( + thisThread->correctionHistory[us][pawn_structure_index(pos)]) + / 16384; ss->staticEval = bestValue = to_static_eval(newEval); } From 19f9a197be95395f761304310f9792d40b05307c Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 6 Jan 2024 19:36:17 +0100 Subject: [PATCH 524/678] Add .git-blame-ignore-revs Add a `.git-blame-ignore-revs` file which can be used to skip specified commits when blaming, this is useful to ignore formatting commits, like clang-format #4790. Github blame automatically supports this file format, as well as other third party tools. Git itself needs to be told about the file name to work, the following command will add it to the current git repo. `git config blame.ignoreRevsFile .git-blame-ignore-revs`, alternatively one has to specify it with every blame. `git blame --ignore-revs-file .git-blame-ignore-revs search.cpp` Supported since git 2.23. closes https://github.com/official-stockfish/Stockfish/pull/4969 No functional change --- .git-blame-ignore-revs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..d2d6cfe6 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,7 @@ +# .git-blame-ignore-revs +# Ignore commit which added clang-format +2d0237db3f0e596fb06e3ffbadba84dcc4e018f6 + +# Post commit formatting fixes +0fca5605fa2e5e7240fde5e1aae50952b2612231 +08ed4c90db31959521b7ef3186c026edd1e90307 \ No newline at end of file From a5a76a63704009d35997725558dfafd90f5d616f Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 6 Jan 2024 19:29:27 -0800 Subject: [PATCH 525/678] Introduce BAD_QUIET movepicker stage Split quiets into good and bad as we do with captures. When we find the first quiet move below a certain threshold that has been sorted we consider all subsequent quiets bad. Inspired by @locutus2 idea to skip bad captures. Passed STC: https://tests.stockfishchess.org/tests/view/6597759f79aa8af82b95fa17 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 138688 W: 35566 L: 35096 D: 68026 Ptnml(0-2): 476, 16367, 35183, 16847, 471 Passed LTC: https://tests.stockfishchess.org/tests/view/6598583c79aa8af82b960ad0 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 84108 W: 21468 L: 21048 D: 41592 Ptnml(0-2): 38, 9355, 22858, 9755, 48 closes https://github.com/official-stockfish/Stockfish/pull/4970 Bench: 1336907 --- src/movepick.cpp | 45 +++++++++++++++++++++++++++++++++++++++------ src/movepick.h | 10 +++++----- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 14b6c87a..6a562996 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -37,8 +37,9 @@ enum Stages { GOOD_CAPTURE, REFUTATION, QUIET_INIT, - QUIET, + GOOD_QUIET, BAD_CAPTURE, + BAD_QUIET, // generate evasion moves EVASION_TT, @@ -243,6 +244,8 @@ Move MovePicker::select(Pred filter) { // moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { + auto quiet_threshold = [](Depth d) { return -3330 * d; }; + top: switch (stage) { @@ -295,20 +298,34 @@ top: if (!skipQuiets) { cur = endBadCaptures; - endMoves = generate(pos, cur); + endMoves = beginBadQuiets = endBadQuiets = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, -3330 * depth); + partial_insertion_sort(cur, endMoves, quiet_threshold(depth)); } ++stage; [[fallthrough]]; - case QUIET : + case GOOD_QUIET : if (!skipQuiets && select([&]() { return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) - return *(cur - 1); + { + Move tmp = *(cur - 1); + if ((cur - 1)->value < -7500 && (cur - 1)->value > quiet_threshold(depth)) + { + // Remaining quiets are bad + beginBadQuiets = cur; + + // Prepare the pointers to loop over the bad captures + cur = moves; + endMoves = endBadCaptures; + + ++stage; + } + return tmp; + } // Prepare the pointers to loop over the bad captures cur = moves; @@ -318,7 +335,23 @@ top: [[fallthrough]]; case BAD_CAPTURE : - return select([]() { return true; }); + if (select([]() { return true; })) + return *(cur - 1); + + // Prepare the pointers to loop over the bad quiets + cur = beginBadQuiets; + endMoves = endBadQuiets; + + ++stage; + [[fallthrough]]; + + case BAD_QUIET : + if (!skipQuiets) + return select([&]() { + return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; + }); + + return Move::none(); case EVASION_INIT : cur = moves; diff --git a/src/movepick.h b/src/movepick.h index c429f8ae..357918a9 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -188,11 +188,11 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; - int stage; - int threshold; - Depth depth; - ExtMove moves[MAX_MOVES]; + ExtMove refutations[3], *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; + int stage; + int threshold; + Depth depth; + ExtMove moves[MAX_MOVES]; }; } // namespace Stockfish From 584d9efedcde330eeb96a99215552ddfb06f52ba Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 2 Dec 2023 17:50:32 -0800 Subject: [PATCH 526/678] Dual NNUE with L1-128 smallnet Credit goes to @mstembera for: - writing the code enabling dual NNUE: https://github.com/official-stockfish/Stockfish/pull/4898 - the idea of trying L1-128 trained exclusively on high simple eval positions The L1-128 smallnet is: - epoch 399 of a single-stage training from scratch - trained only on positions from filtered data with high material difference - defined by abs(simple_eval) > 1000 ```yaml experiment-name: 128--S1-only-hse-v2 training-dataset: - /data/hse/S3/dfrc99-16tb7p-eval-filt-v2.min.high-simple-eval-1k.binpack - /data/hse/S3/leela96-filt-v2.min.high-simple-eval-1k.binpack - /data/hse/S3/test80-apr2022-16tb7p.min.high-simple-eval-1k.binpack - /data/hse/S7/test60-2020-2tb7p.v6-3072.high-simple-eval-1k.binpack - /data/hse/S7/test60-novdec2021-12tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test77-nov2021-2tb7p.v6-3072.min.high-simple-eval-1k.binpack - /data/hse/S7/test77-dec2021-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test77-jan2022-2tb7p.high-simple-eval-1k.binpack - /data/hse/S7/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test79-apr2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test79-may2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack # T80 2022 - /data/hse/S7/test80-may2022-16tb7p.high-simple-eval-1k.binpack - /data/hse/S7/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-jul2022-16tb7p.v6-dd.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-aug2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-sep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-oct2022-16tb7p.v6-dd.high-simple-eval-1k.binpack - /data/hse/S7/test80-nov2022-16tb7p-v6-dd.min.high-simple-eval-1k.binpack # T80 2023 - /data/hse/S7/test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-mar2023-2tb7p.v6-sk16.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-apr2023-2tb7p-filter-v6-sk16.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-may2023-2tb7p.v6.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-jun2023-2tb7p.v6-3072.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-jul2023-2tb7p.v6-3072.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-aug2023-2tb7p.v6.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-sep2023-2tb7p.high-simple-eval-1k.binpack - /data/hse/S7/test80-oct2023-2tb7p.high-simple-eval-1k.binpack start-from-engine-test-net: False nnue-pytorch-branch: linrock/nnue-pytorch/L1-128 engine-test-branch: linrock/Stockfish/L1-128-nolazy engine-base-branch: linrock/Stockfish/L1-128 num-epochs: 500 lambda: 1.0 ``` Experiment yaml configs converted to easy_train.sh commands with: https://github.com/linrock/nnue-tools/blob/4339954/yaml_easy_train.py Binpacks interleaved at training time with: https://github.com/official-stockfish/nnue-pytorch/pull/259 Data filtered for high simple eval positions with: https://github.com/linrock/nnue-data/blob/32d6a68/filter_high_simple_eval_plain.py https://github.com/linrock/Stockfish/blob/61dbfe/src/tools/transform.cpp#L626-L655 Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move of L1-128 smallnet (nnue-only eval) vs. L1-128 trained on standard S1 data: nn-epoch399.nnue : -318.1 +/- 2.1 Passed STC: https://tests.stockfishchess.org/tests/view/6574cb9d95ea6ba1fcd49e3b LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 62432 W: 15875 L: 15521 D: 31036 Ptnml(0-2): 177, 7331, 15872, 7633, 203 Passed LTC: https://tests.stockfishchess.org/tests/view/6575da2d4d789acf40aaac6e LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 64830 W: 16118 L: 15738 D: 32974 Ptnml(0-2): 43, 7129, 17697, 7497, 49 closes https://github.com/official-stockfish/Stockfish/pulls Bench: 1330050 Co-Authored-By: mstembera <5421953+mstembera@users.noreply.github.com> --- src/Makefile | 32 +++--- src/evaluate.cpp | 153 ++++++++++++++++------------ src/evaluate.h | 5 +- src/nnue/evaluate_nnue.cpp | 139 ++++++++++++++++--------- src/nnue/evaluate_nnue.h | 19 ++-- src/nnue/nnue_accumulator.h | 3 +- src/nnue/nnue_architecture.h | 40 +++++--- src/nnue/nnue_feature_transformer.h | 65 ++++++------ src/position.cpp | 18 ++-- src/position.h | 6 +- src/uci.cpp | 3 +- src/ucioption.cpp | 4 +- 12 files changed, 293 insertions(+), 194 deletions(-) diff --git a/src/Makefile b/src/Makefile index 660b41e7..e6de514e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -806,7 +806,7 @@ help: @echo "help > Display architecture details" @echo "profile-build > standard build with profile-guided optimization" @echo "build > skip profile-guided optimization" - @echo "net > Download the default nnue net" + @echo "net > Download the default nnue nets" @echo "strip > Strip executable" @echo "install > Install executable" @echo "clean > Clean up" @@ -922,16 +922,7 @@ profileclean: @rm -f stockfish.res @rm -f ./-lstdc++.res -# set up shell variables for the net stuff -netvariables: - $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) - $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) - $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) - $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) - $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) - -# evaluation network (nnue) -net: netvariables +define fetch_network @echo "Default net: $(nnuenet)" @if [ "x$(curl_or_wget)" = "x" ]; then \ echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ @@ -966,7 +957,24 @@ net: netvariables if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ echo "Network validated"; break; \ fi; \ - fi; \ + fi; +endef + +# set up shell variables for the net stuff +define netvariables +$(eval nnuenet := $(shell grep $(1) evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) +$(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) +$(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) +$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) +$(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) +endef + +# evaluation network (nnue) +net: + $(call netvariables, EvalFileDefaultNameBig) + $(call fetch_network) + $(call netvariables, EvalFileDefaultNameSmall) + $(call fetch_network) format: $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bda7132a..deeb9e67 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include "incbin/incbin.h" #include "misc.h" #include "nnue/evaluate_nnue.h" +#include "nnue/nnue_architecture.h" #include "position.h" #include "thread.h" #include "types.h" @@ -44,11 +46,15 @@ // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) -INCBIN(EmbeddedNNUE, EvalFileDefaultName); +INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); +INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); #else -const unsigned char gEmbeddedNNUEData[1] = {0x0}; -const unsigned char* const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; -const unsigned int gEmbeddedNNUESize = 1; +const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; +const unsigned int gEmbeddedNNUEBigSize = 1; +const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; +const unsigned int gEmbeddedNNUESmallSize = 1; #endif @@ -56,7 +62,9 @@ namespace Stockfish { namespace Eval { -std::string currentEvalFileName = "None"; +std::string currentEvalFileName[2] = {"None", "None"}; +const std::string EvFiles[2] = {"EvalFile", "EvalFileSmall"}; +const std::string EvFileNames[2] = {EvalFileDefaultNameBig, EvalFileDefaultNameSmall}; // Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -67,84 +75,96 @@ std::string currentEvalFileName = "None"; // variable to have the engine search in a special directory in their distro. void NNUE::init() { - std::string eval_file = std::string(Options["EvalFile"]); - if (eval_file.empty()) - eval_file = EvalFileDefaultName; + for (NetSize netSize : {Big, Small}) + { + // change after fishtest supports EvalFileSmall + std::string eval_file = + std::string(netSize == Small ? EvalFileDefaultNameSmall : Options[EvFiles[netSize]]); + if (eval_file.empty()) + eval_file = EvFileNames[netSize]; #if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = {"", "", CommandLine::binaryDirectory, - stringify(DEFAULT_NNUE_DIRECTORY)}; + std::vector dirs = {"", "", CommandLine::binaryDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; #else - std::vector dirs = {"", "", CommandLine::binaryDirectory}; + std::vector dirs = {"", "", CommandLine::binaryDirectory}; #endif - for (const std::string& directory : dirs) - if (currentEvalFileName != eval_file) + for (const std::string& directory : dirs) { - if (directory != "") + if (currentEvalFileName[netSize] != eval_file) { - std::ifstream stream(directory + eval_file, std::ios::binary); - if (NNUE::load_eval(eval_file, stream)) - currentEvalFileName = eval_file; - } + if (directory != "") + { + std::ifstream stream(directory + eval_file, std::ios::binary); + if (NNUE::load_eval(eval_file, stream, netSize)) + currentEvalFileName[netSize] = eval_file; + } - if (directory == "" && eval_file == EvalFileDefaultName) - { - // C++ way to prepare a buffer for a memory stream - class MemoryBuffer: public std::basic_streambuf { - public: - MemoryBuffer(char* p, size_t n) { - setg(p, p, p + n); - setp(p, p + n); - } - }; + if (directory == "" && eval_file == EvFileNames[netSize]) + { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } + }; - MemoryBuffer buffer( - const_cast(reinterpret_cast(gEmbeddedNNUEData)), - size_t(gEmbeddedNNUESize)); - (void) gEmbeddedNNUEEnd; // Silence warning on unused variable + MemoryBuffer buffer( + const_cast(reinterpret_cast( + netSize == Small ? gEmbeddedNNUESmallData : gEmbeddedNNUEBigData)), + size_t(netSize == Small ? gEmbeddedNNUESmallSize : gEmbeddedNNUEBigSize)); + (void) gEmbeddedNNUEBigEnd; // Silence warning on unused variable + (void) gEmbeddedNNUESmallEnd; - std::istream stream(&buffer); - if (NNUE::load_eval(eval_file, stream)) - currentEvalFileName = eval_file; + std::istream stream(&buffer); + if (NNUE::load_eval(eval_file, stream, netSize)) + currentEvalFileName[netSize] = eval_file; + } } } + } } // Verifies that the last net used was loaded successfully void NNUE::verify() { - std::string eval_file = std::string(Options["EvalFile"]); - if (eval_file.empty()) - eval_file = EvalFileDefaultName; - - if (currentEvalFileName != eval_file) + for (NetSize netSize : {Big, Small}) { + // change after fishtest supports EvalFileSmall + std::string eval_file = + std::string(netSize == Small ? EvalFileDefaultNameSmall : Options[EvFiles[netSize]]); + if (eval_file.empty()) + eval_file = EvFileNames[netSize]; - std::string msg1 = - "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, " - "including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: " - "https://tests.stockfishchess.org/api/nn/" - + std::string(EvalFileDefaultName); - std::string msg5 = "The engine will be terminated now."; + if (currentEvalFileName[netSize] != eval_file) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + std::string(EvFileNames[netSize]); + std::string msg5 = "The engine will be terminated now."; - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); + } + + sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; } - - sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; } } - // Returns a static, purely materialistic evaluation of the position from // the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. @@ -163,18 +183,19 @@ Value Eval::evaluate(const Position& pos) { int v; Color stm = pos.side_to_move(); int shuffling = pos.rule50_count(); - int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); - - bool lazy = std::abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling - + std::abs(pos.this_thread()->bestValue) - + std::abs(pos.this_thread()->rootSimpleEval); + int simpleEval = simple_eval(pos, stm); + bool lazy = std::abs(simpleEval) > 2300; if (lazy) v = simpleEval; else { - int nnueComplexity; - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + bool smallNet = std::abs(simpleEval) > 1100; + + int nnueComplexity; + + Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) + : NNUE::evaluate(pos, true, &nnueComplexity); int optimism = pos.this_thread()->optimism[stm]; @@ -217,7 +238,7 @@ std::string Eval::trace(Position& pos) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); Value v; - v = NNUE::evaluate(pos, false); + v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; diff --git a/src/evaluate.h b/src/evaluate.h index 0a7ec61a..3ead6b76 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -34,12 +34,13 @@ std::string trace(Position& pos); int simple_eval(const Position& pos, Color c); Value evaluate(const Position& pos); -extern std::string currentEvalFileName; +extern std::string currentEvalFileName[2]; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultName "nn-b1e55edbea57.nnue" +#define EvalFileDefaultNameBig "nn-b1e55edbea57.nnue" +#define EvalFileDefaultNameSmall "nn-c01dc0ffeede.nnue" namespace NNUE { diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 14e2fec1..004e28df 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -40,14 +40,18 @@ namespace Stockfish::Eval::NNUE { // Input feature converter -LargePagePtr featureTransformer; +LargePagePtr> + featureTransformerBig; +LargePagePtr> + featureTransformerSmall; // Evaluation function -AlignedPtr network[LayerStacks]; +AlignedPtr> networkBig[LayerStacks]; +AlignedPtr> networkSmall[LayerStacks]; -// Evaluation function file name -std::string fileName; -std::string netDescription; +// Evaluation function file names +std::string fileName[2]; +std::string netDescription[2]; namespace Detail { @@ -91,11 +95,20 @@ bool write_parameters(std::ostream& stream, const T& reference) { // Initialize the evaluation function parameters -static void initialize() { +static void initialize(NetSize netSize) { - Detail::initialize(featureTransformer); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(network[i]); + if (netSize == Small) + { + Detail::initialize(featureTransformerSmall); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(networkSmall[i]); + } + else + { + Detail::initialize(featureTransformerBig); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(networkBig[i]); + } } // Read network header @@ -122,39 +135,57 @@ static bool write_header(std::ostream& stream, std::uint32_t hashValue, const st } // Read network parameters -static bool read_parameters(std::istream& stream) { +static bool read_parameters(std::istream& stream, NetSize netSize) { std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) + if (!read_header(stream, &hashValue, &netDescription[netSize])) return false; - if (hashValue != HashValue) + if (hashValue != HashValue[netSize]) return false; - if (!Detail::read_parameters(stream, *featureTransformer)) + if (netSize == Big && !Detail::read_parameters(stream, *featureTransformerBig)) + return false; + if (netSize == Small && !Detail::read_parameters(stream, *featureTransformerSmall)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::read_parameters(stream, *(network[i]))) + { + if (netSize == Big && !Detail::read_parameters(stream, *(networkBig[i]))) return false; + if (netSize == Small && !Detail::read_parameters(stream, *(networkSmall[i]))) + return false; + } return stream && stream.peek() == std::ios::traits_type::eof(); } // Write network parameters -static bool write_parameters(std::ostream& stream) { +static bool write_parameters(std::ostream& stream, NetSize netSize) { - if (!write_header(stream, HashValue, netDescription)) + if (!write_header(stream, HashValue[netSize], netDescription[netSize])) return false; - if (!Detail::write_parameters(stream, *featureTransformer)) + if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig)) + return false; + if (netSize == Small && !Detail::write_parameters(stream, *featureTransformerSmall)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::write_parameters(stream, *(network[i]))) + { + if (netSize == Big && !Detail::write_parameters(stream, *(networkBig[i]))) return false; + if (netSize == Small && !Detail::write_parameters(stream, *(networkSmall[i]))) + return false; + } return bool(stream); } void hint_common_parent_position(const Position& pos) { - featureTransformer->hint_common_access(pos); + + int simpleEval = simple_eval(pos, pos.side_to_move()); + if (abs(simpleEval) > 1100) + featureTransformerSmall->hint_common_access(pos); + else + featureTransformerBig->hint_common_access(pos); } // Evaluation function. Perform differential calculation. +template Value evaluate(const Position& pos, bool adjusted, int* complexity) { // We manually align the arrays on the stack because with gcc < 9.3 @@ -165,19 +196,28 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + transformedFeaturesUnaligned[FeatureTransformer < Small ? TransformedFeatureDimensionsSmall + : TransformedFeatureDimensionsBig, + nullptr + > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall + : TransformedFeatureDimensionsBig, + nullptr > ::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + const auto psqt = Net_Size == Small + ? featureTransformerSmall->transform(pos, transformedFeatures, bucket) + : featureTransformerBig->transform(pos, transformedFeatures, bucket); + const auto positional = Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) + : networkBig[bucket]->propagate(transformedFeatures); if (complexity) *complexity = std::abs(psqt - positional) / OutputScale; @@ -190,6 +230,9 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { return static_cast((psqt + positional) / OutputScale); } +template Value evaluate(const Position& pos, bool adjusted, int* complexity); +template Value evaluate(const Position& pos, bool adjusted, int* complexity); + struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); @@ -205,13 +248,14 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); @@ -220,8 +264,8 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { t.correctBucket = (pos.count() - 1) / 4; for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { - const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + const auto materialist = featureTransformerBig->transform(pos, transformedFeatures, bucket); + const auto positional = networkBig[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); t.positional[bucket] = static_cast(positional / OutputScale); @@ -310,7 +354,7 @@ std::string trace(Position& pos) { // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - Value base = evaluate(pos); + Value base = evaluate(pos); base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= FILE_H; ++f) @@ -325,16 +369,16 @@ std::string trace(Position& pos) { auto st = pos.state(); pos.remove_piece(sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + st->accumulatorBig.computed[WHITE] = false; + st->accumulatorBig.computed[BLACK] = false; - Value eval = evaluate(pos); + Value eval = evaluate(pos); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + st->accumulatorBig.computed[WHITE] = false; + st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); @@ -379,24 +423,24 @@ std::string trace(Position& pos) { // Load eval, from a file stream or a memory stream -bool load_eval(std::string name, std::istream& stream) { +bool load_eval(const std::string name, std::istream& stream, NetSize netSize) { - initialize(); - fileName = name; - return read_parameters(stream); + initialize(netSize); + fileName[netSize] = name; + return read_parameters(stream, netSize); } // Save eval, to a file stream or a memory stream -bool save_eval(std::ostream& stream) { +bool save_eval(std::ostream& stream, NetSize netSize) { - if (fileName.empty()) + if (fileName[netSize].empty()) return false; - return write_parameters(stream); + return write_parameters(stream, netSize); } // Save eval, to a file given by its name -bool save_eval(const std::optional& filename) { +bool save_eval(const std::optional& filename, NetSize netSize) { std::string actualFilename; std::string msg; @@ -405,7 +449,8 @@ bool save_eval(const std::optional& filename) { actualFilename = filename.value(); else { - if (currentEvalFileName != EvalFileDefaultName) + if (currentEvalFileName[netSize] + != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) { msg = "Failed to export a net. " "A non-embedded net can only be saved if the filename is specified"; @@ -413,11 +458,11 @@ bool save_eval(const std::optional& filename) { sync_cout << msg << sync_endl; return false; } - actualFilename = EvalFileDefaultName; + actualFilename = (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig); } std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream); + bool saved = save_eval(stream, netSize); msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index f80aa398..fabfb569 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -39,9 +39,11 @@ class Position; namespace Stockfish::Eval::NNUE { // Hash value of evaluation function structure -constexpr std::uint32_t HashValue = - FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); - +constexpr std::uint32_t HashValue[2] = { + FeatureTransformer::get_hash_value() + ^ Network::get_hash_value(), + FeatureTransformer::get_hash_value() + ^ Network::get_hash_value()}; // Deleter for automating release of memory area template @@ -67,12 +69,13 @@ template using LargePagePtr = std::unique_ptr>; std::string trace(Position& pos); -Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); -void hint_common_parent_position(const Position& pos); +template +Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); +void hint_common_parent_position(const Position& pos); -bool load_eval(std::string name, std::istream& stream); -bool save_eval(std::ostream& stream); -bool save_eval(const std::optional& filename); +bool load_eval(const std::string name, std::istream& stream, NetSize netSize); +bool save_eval(std::ostream& stream, NetSize netSize); +bool save_eval(const std::optional& filename, NetSize netSize); } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index f6d70524..0b05d00d 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -29,8 +29,9 @@ namespace Stockfish::Eval::NNUE { // Class that holds the result of affine transformation of input features +template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[2][TransformedFeatureDimensions]; + std::int16_t accumulation[2][Size]; std::int32_t psqtAccumulation[2][PSQTBuckets]; bool computed[2]; }; diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 92445704..949f2d86 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -37,14 +37,28 @@ namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2_hm; -// Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 2560; -constexpr IndexType PSQTBuckets = 8; -constexpr IndexType LayerStacks = 8; +enum NetSize { + Big, + Small +}; +// Number of input feature dimensions after conversion +constexpr IndexType TransformedFeatureDimensionsBig = 2560; +constexpr int L2Big = 15; +constexpr int L3Big = 32; + +constexpr IndexType TransformedFeatureDimensionsSmall = 128; +constexpr int L2Small = 15; +constexpr int L3Small = 32; + +constexpr IndexType PSQTBuckets = 8; +constexpr IndexType LayerStacks = 8; + +template struct Network { - static constexpr int FC_0_OUTPUTS = 15; - static constexpr int FC_1_OUTPUTS = 32; + static constexpr IndexType TransformedFeatureDimensions = L1; + static constexpr int FC_0_OUTPUTS = L2; + static constexpr int FC_1_OUTPUTS = L3; Layers::AffineTransformSparseInput fc_0; Layers::SqrClippedReLU ac_sqr_0; @@ -84,13 +98,13 @@ struct Network { std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { struct alignas(CacheLineSize) Buffer { - alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; - alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType + alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out; + alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; - alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; - alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; - alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; - alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out; + alignas(CacheLineSize) typename decltype(ac_0)::OutputBuffer ac_0_out; + alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out; + alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out; + alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out; Buffer() { std::memset(this, 0, sizeof(*this)); } }; @@ -108,7 +122,7 @@ struct Network { ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, - FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType)); + FC_0_OUTPUTS * sizeof(typename decltype(ac_0)::OutputType)); fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 2008cf25..9a162ac9 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -186,11 +186,6 @@ static constexpr int BestRegisterCount() { return 1; } - -static constexpr int NumRegs = - BestRegisterCount(); -static constexpr int NumPsqtRegs = - BestRegisterCount(); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif @@ -198,6 +193,8 @@ static constexpr int NumPsqtRegs = // Input feature converter +template StateInfo::*accPtr> class FeatureTransformer { private: @@ -205,6 +202,11 @@ class FeatureTransformer { static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; #ifdef VECTOR + static constexpr int NumRegs = + BestRegisterCount(); + static constexpr int NumPsqtRegs = + BestRegisterCount(); + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); @@ -253,8 +255,8 @@ class FeatureTransformer { update_accumulator(pos); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& accumulation = pos.state()->accumulator.accumulation; - const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; + const auto& accumulation = (pos.state()->*accPtr).accumulation; + const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; const auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) @@ -323,7 +325,7 @@ class FeatureTransformer { // of the estimated gain in terms of features to be added/subtracted. StateInfo *st = pos.state(), *next = nullptr; int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !st->accumulator.computed[Perspective]) + 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. @@ -381,7 +383,7 @@ class FeatureTransformer { for (; i >= 0; --i) { - states_to_update[i]->accumulator.computed[Perspective] = true; + (states_to_update[i]->*accPtr).computed[Perspective] = true; const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; @@ -402,9 +404,9 @@ class FeatureTransformer { assert(states_to_update[0]); auto accIn = - reinterpret_cast(&st->accumulator.accumulation[Perspective][0]); + reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); auto accOut = reinterpret_cast( - &states_to_update[0]->accumulator.accumulation[Perspective][0]); + &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); const IndexType offsetR0 = HalfDimensions * removed[0][0]; auto columnR0 = reinterpret_cast(&weights[offsetR0]); @@ -428,10 +430,10 @@ class FeatureTransformer { vec_add_16(columnR0[k], columnR1[k])); } - auto accPsqtIn = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][0]); + auto accPsqtIn = + reinterpret_cast(&(st->*accPtr).psqtAccumulation[Perspective][0]); auto accPsqtOut = reinterpret_cast( - &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]); + &(states_to_update[0]->*accPtr).psqtAccumulation[Perspective][0]); const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); @@ -463,7 +465,7 @@ class FeatureTransformer { { // Load accumulator auto accTileIn = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); + &(st->*accPtr).accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_load(&accTileIn[k]); @@ -489,7 +491,7 @@ class FeatureTransformer { // Store accumulator auto accTileOut = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); + &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) vec_store(&accTileOut[k], acc[k]); } @@ -499,7 +501,7 @@ class FeatureTransformer { { // Load accumulator auto accTilePsqtIn = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + &(st->*accPtr).psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); @@ -525,8 +527,8 @@ class FeatureTransformer { // Store accumulator auto accTilePsqtOut = reinterpret_cast( - &states_to_update[i] - ->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + &(states_to_update[i]->*accPtr) + .psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqtOut[k], psqt[k]); } @@ -535,13 +537,12 @@ class FeatureTransformer { #else for (IndexType i = 0; states_to_update[i]; ++i) { - std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], - st->accumulator.accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); + std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], + (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) - states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = - st->accumulator.psqtAccumulation[Perspective][k]; + (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = + (st->*accPtr).psqtAccumulation[Perspective][k]; st = states_to_update[i]; @@ -551,10 +552,10 @@ class FeatureTransformer { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; + (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] -= + (st->*accPtr).psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; } @@ -564,10 +565,10 @@ class FeatureTransformer { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] += weights[offset + j]; + (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] += + (st->*accPtr).psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } } @@ -586,7 +587,7 @@ class FeatureTransformer { // Refresh the accumulator // Could be extracted to a separate function because it's done in 2 places, // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->accumulator; + auto& accumulator = pos.state()->*accPtr; accumulator.computed[Perspective] = true; FeatureSet::IndexList active; FeatureSet::append_active_indices(pos, active); @@ -663,12 +664,12 @@ class FeatureTransformer { // Look for a usable accumulator of an earlier position. We keep track // of the estimated gain in terms of features to be added/subtracted. // Fast early exit. - if (pos.state()->accumulator.computed[Perspective]) + if ((pos.state()->*accPtr).computed[Perspective]) return; auto [oldest_st, _] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) + if ((oldest_st->*accPtr).computed[Perspective]) { // Only update current position accumulator to minimize work. StateInfo* states_to_update[2] = {pos.state(), nullptr}; @@ -685,7 +686,7 @@ class FeatureTransformer { auto [oldest_st, next] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) + if ((oldest_st->*accPtr).computed[Perspective]) { if (next == nullptr) return; diff --git a/src/position.cpp b/src/position.cpp index 4fba3c23..ddc31888 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -684,10 +684,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; - auto& dp = st->dirtyPiece; - dp.dirty_num = 1; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; Color us = sideToMove; Color them = ~us; @@ -964,15 +964,15 @@ void Position::do_null_move(StateInfo& newSt) { assert(!checkers()); assert(&newSt != st); - std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); + std::memcpy(&newSt, st, offsetof(StateInfo, accumulatorBig)); newSt.previous = st; st = &newSt; - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + 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) { diff --git a/src/position.h b/src/position.h index 7e0c3eef..34b53f4a 100644 --- a/src/position.h +++ b/src/position.h @@ -27,6 +27,7 @@ #include "bitboard.h" #include "nnue/nnue_accumulator.h" +#include "nnue/nnue_architecture.h" #include "types.h" namespace Stockfish { @@ -57,8 +58,9 @@ struct StateInfo { int repetition; // Used by NNUE - Eval::NNUE::Accumulator accumulator; - DirtyPiece dirtyPiece; + Eval::NNUE::Accumulator accumulatorBig; + Eval::NNUE::Accumulator accumulatorSmall; + DirtyPiece dirtyPiece; }; diff --git a/src/uci.cpp b/src/uci.cpp index 8e93eee6..be902277 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -37,6 +37,7 @@ #include "misc.h" #include "movegen.h" #include "nnue/evaluate_nnue.h" +#include "nnue/nnue_architecture.h" #include "position.h" #include "search.h" #include "thread.h" @@ -320,7 +321,7 @@ void UCI::loop(int argc, char* argv[]) { std::string f; if (is >> std::skipws >> f) filename = f; - Eval::NNUE::save_eval(filename); + Eval::NNUE::save_eval(filename, Eval::NNUE::Big); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 087882f1..f8cbcc53 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -82,7 +82,9 @@ void init(OptionsMap& o) { o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); + o["EvalFile"] << Option(EvalFileDefaultNameBig, on_eval_file); + // Enable this after fishtest workers support EvalFileSmall + // o["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, on_eval_file); } From f09adaa4a4c3cbb44e1ca8cc687a08dc3d58076e Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 13 Dec 2023 13:07:36 -0500 Subject: [PATCH 527/678] Update smallnet to nn-baff1ede1f90.nnue with wider eval range Created by training an L1-128 net from scratch with a wider range of evals in the training data and wld-fen-skipping disabled during training. The differences in this training data compared to the first dual nnue PR are: - removal of all positions with 3 pieces - when piece count >= 16, keep positions with simple eval above 750 - when piece count < 16, remove positions with simple eval above 3000 The asymmetric data filtering was meant to flatten the training data piece count distribution, which was previously heavily skewed towards positions with low piece counts. Additionally, the simple eval range where the smallnet is used was widened to cover more positions previously evaluated by the big net and simple eval. ```yaml experiment-name: 128--S1-hse-S7-v4-S3-v1-no-wld-skip training-dataset: - /data/hse/S3/leela96-filt-v2.min.high-simple-eval-1k.binpack - /data/hse/S3/dfrc99-16tb7p-eval-filt-v2.min.high-simple-eval-1k.binpack - /data/hse/S3/test80-apr2022-16tb7p.min.high-simple-eval-1k.binpack - /data/hse/S7/test60-2020-2tb7p.v6-3072.high-simple-eval-v4.binpack - /data/hse/S7/test60-novdec2021-12tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test77-nov2021-2tb7p.v6-3072.min.high-simple-eval-v4.binpack - /data/hse/S7/test77-dec2021-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test77-jan2022-2tb7p.high-simple-eval-v4.binpack - /data/hse/S7/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test79-apr2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test79-may2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-may2022-16tb7p.high-simple-eval-v4.binpack - /data/hse/S7/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-jul2022-16tb7p.v6-dd.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-aug2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-sep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-oct2022-16tb7p.v6-dd.high-simple-eval-v4.binpack - /data/hse/S7/test80-nov2022-16tb7p-v6-dd.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-mar2023-2tb7p.v6-sk16.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-apr2023-2tb7p-filter-v6-sk16.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-may2023-2tb7p.v6.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-jun2023-2tb7p.v6-3072.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-jul2023-2tb7p.v6-3072.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-aug2023-2tb7p.v6.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-sep2023-2tb7p.high-simple-eval-v4.binpack - /data/hse/S7/test80-oct2023-2tb7p.high-simple-eval-v4.binpack wld-fen-skipping: False start-from-engine-test-net: False nnue-pytorch-branch: linrock/nnue-pytorch/L1-128 engine-test-branch: linrock/Stockfish/L1-128-nolazy engine-base-branch: linrock/Stockfish/L1-128 num-epochs: 500 start-lambda: 1.0 end-lambda: 1.0 ``` Experiment yaml configs converted to easy_train.sh commands with: https://github.com/linrock/nnue-tools/blob/4339954/yaml_easy_train.py Binpacks interleaved at training time with: https://github.com/official-stockfish/nnue-pytorch/pull/259 FT weights permuted with 10k positions from fishpack32.binpack with: https://github.com/official-stockfish/nnue-pytorch/pull/254 Data filtered for high simple eval positions (v4) with: https://github.com/linrock/Stockfish/blob/b9c8440/src/tools/transform.cpp#L640-L675 Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move of L1-128 smallnet (nnue-only eval) vs. L1-128 trained on standard S1 data: nn-epoch319.nnue : -241.7 +/- 3.2 Passed STC vs. 36db936: https://tests.stockfishchess.org/tests/view/6576b3484d789acf40aabbfe LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 21920 W: 5680 L: 5381 D: 10859 Ptnml(0-2): 82, 2488, 5520, 2789, 81 Passed LTC vs. DualNNUE #4915: https://tests.stockfishchess.org/tests/view/65775c034d789acf40aac7e3 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 147606 W: 36619 L: 36063 D: 74924 Ptnml(0-2): 98, 16591, 39891, 17103, 120 closes https://github.com/official-stockfish/Stockfish/pull/4919 Bench: 1438336 --- src/evaluate.cpp | 4 ++-- src/evaluate.h | 2 +- src/nnue/evaluate_nnue.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index deeb9e67..e3f60f9c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -185,12 +185,12 @@ Value Eval::evaluate(const Position& pos) { int shuffling = pos.rule50_count(); int simpleEval = simple_eval(pos, stm); - bool lazy = std::abs(simpleEval) > 2300; + bool lazy = std::abs(simpleEval) > 2550; if (lazy) v = simpleEval; else { - bool smallNet = std::abs(simpleEval) > 1100; + bool smallNet = std::abs(simpleEval) > 1050; int nnueComplexity; diff --git a/src/evaluate.h b/src/evaluate.h index 3ead6b76..ce608735 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -40,7 +40,7 @@ extern std::string currentEvalFileName[2]; // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. #define EvalFileDefaultNameBig "nn-b1e55edbea57.nnue" -#define EvalFileDefaultNameSmall "nn-c01dc0ffeede.nnue" +#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" namespace NNUE { diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 004e28df..7566d849 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -178,7 +178,7 @@ static bool write_parameters(std::ostream& stream, NetSize netSize) { void hint_common_parent_position(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - if (abs(simpleEval) > 1100) + if (abs(simpleEval) > 1050) featureTransformerSmall->hint_common_access(pos); else featureTransformerBig->hint_common_access(pos); From 7c5e3f28655607288a980645e6b2ce600a627b11 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 7 Jan 2024 21:41:52 +0100 Subject: [PATCH 528/678] Prefix abs with std:: --- src/nnue/evaluate_nnue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 7566d849..7a3f6877 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -178,7 +178,7 @@ static bool write_parameters(std::ostream& stream, NetSize netSize) { void hint_common_parent_position(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - if (abs(simpleEval) > 1050) + if (std::abs(simpleEval) > 1050) featureTransformerSmall->hint_common_access(pos); else featureTransformerBig->hint_common_access(pos); From 99cdb920fcee4cb09cbe273eef9deb85b5a1af2c Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 7 Jan 2024 23:08:33 +0100 Subject: [PATCH 529/678] Cleanup Evalfile handling This cleans up the EvalFile handling after the merge of #4915, which has become a bit confusing on what it is actually doing. closes https://github.com/official-stockfish/Stockfish/pull/4971 No functional change --- src/evaluate.cpp | 61 ++++++++++++++++++++---------------- src/evaluate.h | 13 ++++++-- src/nnue/evaluate_nnue.cpp | 3 +- src/nnue/nnue_architecture.h | 2 +- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e3f60f9c..e220b92a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -23,10 +23,10 @@ #include #include #include -#include #include #include #include +#include #include #include "incbin/incbin.h" @@ -62,9 +62,10 @@ namespace Stockfish { namespace Eval { -std::string currentEvalFileName[2] = {"None", "None"}; -const std::string EvFiles[2] = {"EvalFile", "EvalFileSmall"}; -const std::string EvFileNames[2] = {EvalFileDefaultNameBig, EvalFileDefaultNameSmall}; +std::unordered_map EvalFiles = { + {NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None"}}, + {NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None"}}}; + // Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -75,13 +76,16 @@ const std::string EvFileNames[2] = {EvalFileDefaultNameBig, EvalFileDefa // variable to have the engine search in a special directory in their distro. void NNUE::init() { - for (NetSize netSize : {Big, Small}) + for (auto& [netSize, evalFile] : EvalFiles) { - // change after fishtest supports EvalFileSmall - std::string eval_file = - std::string(netSize == Small ? EvalFileDefaultNameSmall : Options[EvFiles[netSize]]); - if (eval_file.empty()) - eval_file = EvFileNames[netSize]; + // Replace with + // Options[evalFile.option_name] + // once fishtest supports the uci option EvalFileSmall + std::string user_eval_file = + netSize == Small ? evalFile.default_name : Options[evalFile.option_name]; + + if (user_eval_file.empty()) + user_eval_file = evalFile.default_name; #if defined(DEFAULT_NNUE_DIRECTORY) std::vector dirs = {"", "", CommandLine::binaryDirectory, @@ -92,16 +96,16 @@ void NNUE::init() { for (const std::string& directory : dirs) { - if (currentEvalFileName[netSize] != eval_file) + if (evalFile.selected_name != user_eval_file) { if (directory != "") { - std::ifstream stream(directory + eval_file, std::ios::binary); - if (NNUE::load_eval(eval_file, stream, netSize)) - currentEvalFileName[netSize] = eval_file; + std::ifstream stream(directory + user_eval_file, std::ios::binary); + if (NNUE::load_eval(user_eval_file, stream, netSize)) + evalFile.selected_name = user_eval_file; } - if (directory == "" && eval_file == EvFileNames[netSize]) + if (directory == "" && user_eval_file == evalFile.default_name) { // C++ way to prepare a buffer for a memory stream class MemoryBuffer: public std::basic_streambuf { @@ -120,8 +124,8 @@ void NNUE::init() { (void) gEmbeddedNNUESmallEnd; std::istream stream(&buffer); - if (NNUE::load_eval(eval_file, stream, netSize)) - currentEvalFileName[netSize] = eval_file; + if (NNUE::load_eval(user_eval_file, stream, netSize)) + evalFile.selected_name = user_eval_file; } } } @@ -131,24 +135,27 @@ void NNUE::init() { // Verifies that the last net used was loaded successfully void NNUE::verify() { - for (NetSize netSize : {Big, Small}) + for (const auto& [netSize, evalFile] : EvalFiles) { - // change after fishtest supports EvalFileSmall - std::string eval_file = - std::string(netSize == Small ? EvalFileDefaultNameSmall : Options[EvFiles[netSize]]); - if (eval_file.empty()) - eval_file = EvFileNames[netSize]; + // Replace with + // Options[evalFile.option_name] + // once fishtest supports the uci option EvalFileSmall + std::string user_eval_file = + netSize == Small ? evalFile.default_name : Options[evalFile.option_name]; + if (user_eval_file.empty()) + user_eval_file = evalFile.default_name; - if (currentEvalFileName[netSize] != eval_file) + if (evalFile.selected_name != user_eval_file) { std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; + std::string msg2 = + "The network file " + user_eval_file + " was not loaded successfully."; std::string msg3 = "The UCI option EvalFile might need to specify the full path, " "including the directory name, to the network file."; std::string msg4 = "The default net can be downloaded from: " "https://tests.stockfishchess.org/api/nn/" - + std::string(EvFileNames[netSize]); + + evalFile.default_name; std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; @@ -160,7 +167,7 @@ void NNUE::verify() { exit(EXIT_FAILURE); } - sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; + sync_cout << "info string NNUE evaluation using " << user_eval_file << sync_endl; } } } diff --git a/src/evaluate.h b/src/evaluate.h index ce608735..f712d8e6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -20,6 +20,7 @@ #define EVALUATE_H_INCLUDED #include +#include #include "types.h" @@ -34,8 +35,6 @@ std::string trace(Position& pos); int simple_eval(const Position& pos, Color c); Value evaluate(const Position& pos); -extern std::string currentEvalFileName[2]; - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. @@ -44,11 +43,21 @@ extern std::string currentEvalFileName[2]; namespace NNUE { +enum NetSize : int; + void init(); void verify(); } // namespace NNUE +struct EvalFile { + std::string option_name; + std::string default_name; + std::string selected_name; +}; + +extern std::unordered_map EvalFiles; + } // namespace Eval } // namespace Stockfish diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 7a3f6877..86fe5230 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "../evaluate.h" #include "../misc.h" @@ -449,7 +450,7 @@ bool save_eval(const std::optional& filename, NetSize netSize) { actualFilename = filename.value(); else { - if (currentEvalFileName[netSize] + if (EvalFiles.at(netSize).selected_name != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) { msg = "Failed to export a net. " diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 949f2d86..b222ab99 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -37,7 +37,7 @@ namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2_hm; -enum NetSize { +enum NetSize : int { Big, Small }; From 6deb88728fb141e853243c2873ad0cda4dd19320 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 3 Jan 2024 00:58:16 -0500 Subject: [PATCH 530/678] Update default main net to nn-baff1edbea57.nnue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created by retraining the previous main net nn-b1e55edbea57.nnue with: - some of the same options as before: ranger21 optimizer, more WDL skipping - adding T80 aug filter-v6, sep, and oct 2023 data to the previous best dataset - increasing training loss for positions where predicted win rates were higher than estimated match results from training data position scores ```yaml experiment-name: 2560--S8-r21-more-wdl-skip-10p-more-loss-high-q-sk28 training-dataset: # https://github.com/official-stockfish/Stockfish/pull/4782 - /data/S6-1ee1aba5ed.binpack - /data/test80-aug2023-2tb7p.v6.min.binpack - /data/test80-sep2023-2tb7p.binpack - /data/test80-oct2023-2tb7p.binpack early-fen-skipping: 28 start-from-engine-test-net: True nnue-pytorch-branch: linrock/nnue-pytorch/r21-more-wdl-skip-10p-more-loss-high-q num-epochs: 1000 lr: 4.375e-4 gamma: 0.995 start-lambda: 1.0 end-lambda: 0.7 ``` Training data can be found at: https://robotmoon.com/nnue-training-data/ Training loss was increased by 10% for positions where predicted win rates were higher than suggested by the win rate model based on the training data, by multiplying with: ((qf > pt) * 0.1 + 1). This was a variant of experiments from Sopel's NNUE training & experimentation log: https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY Experiment 302 - increase loss when prediction too high, vondele’s idea Experiment 309 - increase loss when prediction too high, normalize in a batch Passed STC: https://tests.stockfishchess.org/tests/view/6597a21c79aa8af82b95fd5c LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 148320 W: 37960 L: 37475 D: 72885 Ptnml(0-2): 542, 17565, 37383, 18206, 464 Passed LTC: https://tests.stockfishchess.org/tests/view/659834a679aa8af82b960845 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 55188 W: 13955 L: 13592 D: 27641 Ptnml(0-2): 34, 6162, 14834, 6535, 29 closes https://github.com/official-stockfish/Stockfish/pull/4972 Bench: 1219824 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index f712d8e6..79b77192 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -38,7 +38,7 @@ Value evaluate(const Position& pos); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultNameBig "nn-b1e55edbea57.nnue" +#define EvalFileDefaultNameBig "nn-baff1edbea57.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" namespace NNUE { From a10791095150bf7c020b92be0f55566fe34e9bf2 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 8 Jan 2024 19:48:46 +0100 Subject: [PATCH 531/678] Refactor global variables This aims to remove some of the annoying global structure which Stockfish has. Overall there is no major elo regression to be expected. Non regression SMP STC (paused, early version): https://tests.stockfishchess.org/tests/view/65983d7979aa8af82b9608f1 LLR: 0.23 (-2.94,2.94) <-1.75,0.25> Total: 76232 W: 19035 L: 19096 D: 38101 Ptnml(0-2): 92, 8735, 20515, 8690, 84 Non regression STC (early version): https://tests.stockfishchess.org/tests/view/6595b3a479aa8af82b95da7f LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 185344 W: 47027 L: 46972 D: 91345 Ptnml(0-2): 571, 21285, 48943, 21264, 609 Non regression SMP STC: https://tests.stockfishchess.org/tests/view/65a0715c79aa8af82b96b7e4 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142936 W: 35761 L: 35662 D: 71513 Ptnml(0-2): 209, 16400, 38135, 16531, 193 These global structures/variables add hidden dependencies and allow data to be mutable from where it shouldn't it be (i.e. options). They also prevent Stockfish from internal selfplay, which would be a nice thing to be able to do, i.e. instantiate two Stockfish instances and let them play against each other. It will also allow us to make Stockfish a library, which can be easier used on other platforms. For consistency with the old search code, `thisThread` has been kept, even though it is not strictly necessary anymore. This the first major refactor of this kind (in recent time), and future changes are required, to achieve the previously described goals. This includes cleaning up the dependencies, transforming the network to be self contained and coming up with a plan to deal with proper tablebase memory management (see comments for more information on this). The removal of these global structures has been discussed in parts with Vondele and Sopel. closes https://github.com/official-stockfish/Stockfish/pull/4968 No functional change --- src/Makefile | 2 +- src/evaluate.cpp | 79 +++--- src/evaluate.h | 34 ++- src/main.cpp | 19 +- src/misc.cpp | 15 +- src/misc.h | 15 +- src/nnue/evaluate_nnue.cpp | 38 +-- src/nnue/evaluate_nnue.h | 19 +- src/position.cpp | 18 +- src/position.h | 30 +-- src/search.cpp | 506 ++++++++++++++++------------------- src/search.h | 155 ++++++++++- src/syzygy/tbprobe.cpp | 8 +- src/syzygy/tbprobe.h | 8 +- src/thread.cpp | 134 +++++----- src/thread.h | 115 ++++---- src/thread_win32_osx.h | 26 +- src/timeman.cpp | 33 ++- src/timeman.h | 28 +- src/tt.cpp | 31 +-- src/tt.h | 14 +- src/tune.cpp | 19 +- src/tune.h | 9 +- src/uci.cpp | 527 ++++++++++++++++++++----------------- src/uci.h | 105 ++++---- src/ucioption.cpp | 133 ++++------ src/ucioption.h | 81 ++++++ 27 files changed, 1200 insertions(+), 1001 deletions(-) create mode 100644 src/ucioption.h diff --git a/src/Makefile b/src/Makefile index e6de514e..9680ca7f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h + tt.h tune.h types.h uci.h ucioption.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e220b92a..3e067e4c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -34,9 +35,10 @@ #include "nnue/evaluate_nnue.h" #include "nnue/nnue_architecture.h" #include "position.h" -#include "thread.h" +#include "search.h" #include "types.h" #include "uci.h" +#include "ucioption.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). @@ -62,10 +64,6 @@ namespace Stockfish { namespace Eval { -std::unordered_map EvalFiles = { - {NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None"}}, - {NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None"}}}; - // Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -74,38 +72,45 @@ std::unordered_map EvalFiles = { // network may be embedded in the binary), in the active working directory and // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY // variable to have the engine search in a special directory in their distro. -void NNUE::init() { +NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, + const OptionsMap& options, + NNUE::EvalFiles evalFiles) { - for (auto& [netSize, evalFile] : EvalFiles) + for (auto& [netSize, evalFile] : evalFiles) { // Replace with - // Options[evalFile.option_name] + // options[evalFile.optionName] // once fishtest supports the uci option EvalFileSmall std::string user_eval_file = - netSize == Small ? evalFile.default_name : Options[evalFile.option_name]; + netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; if (user_eval_file.empty()) - user_eval_file = evalFile.default_name; + user_eval_file = evalFile.defaultName; #if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = {"", "", CommandLine::binaryDirectory, + std::vector dirs = {"", "", rootDirectory, stringify(DEFAULT_NNUE_DIRECTORY)}; #else - std::vector dirs = {"", "", CommandLine::binaryDirectory}; + std::vector dirs = {"", "", rootDirectory}; #endif for (const std::string& directory : dirs) { - if (evalFile.selected_name != user_eval_file) + if (evalFile.current != user_eval_file) { if (directory != "") { std::ifstream stream(directory + user_eval_file, std::ios::binary); - if (NNUE::load_eval(user_eval_file, stream, netSize)) - evalFile.selected_name = user_eval_file; + auto description = NNUE::load_eval(stream, netSize); + + if (description.has_value()) + { + evalFile.current = user_eval_file; + evalFile.netDescription = description.value(); + } } - if (directory == "" && user_eval_file == evalFile.default_name) + if (directory == "" && user_eval_file == evalFile.defaultName) { // C++ way to prepare a buffer for a memory stream class MemoryBuffer: public std::basic_streambuf { @@ -124,28 +129,36 @@ void NNUE::init() { (void) gEmbeddedNNUESmallEnd; std::istream stream(&buffer); - if (NNUE::load_eval(user_eval_file, stream, netSize)) - evalFile.selected_name = user_eval_file; + auto description = NNUE::load_eval(stream, netSize); + + if (description.has_value()) + { + evalFile.current = user_eval_file; + evalFile.netDescription = description.value(); + } } } } } + + return evalFiles; } // Verifies that the last net used was loaded successfully -void NNUE::verify() { +void NNUE::verify(const OptionsMap& options, + const std::unordered_map& evalFiles) { - for (const auto& [netSize, evalFile] : EvalFiles) + for (const auto& [netSize, evalFile] : evalFiles) { // Replace with - // Options[evalFile.option_name] + // options[evalFile.optionName] // once fishtest supports the uci option EvalFileSmall std::string user_eval_file = - netSize == Small ? evalFile.default_name : Options[evalFile.option_name]; + netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; if (user_eval_file.empty()) - user_eval_file = evalFile.default_name; + user_eval_file = evalFile.defaultName; - if (evalFile.selected_name != user_eval_file) + if (evalFile.current != user_eval_file) { std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; @@ -155,7 +168,7 @@ void NNUE::verify() { "including the directory name, to the network file."; std::string msg4 = "The default net can be downloaded from: " "https://tests.stockfishchess.org/api/nn/" - + evalFile.default_name; + + evalFile.defaultName; std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; @@ -183,7 +196,7 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos) { +Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) { assert(!pos.checkers()); @@ -204,7 +217,7 @@ Value Eval::evaluate(const Position& pos) { Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) : NNUE::evaluate(pos, true, &nnueComplexity); - int optimism = pos.this_thread()->optimism[stm]; + int optimism = workerThread.optimism[stm]; // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; @@ -227,16 +240,16 @@ Value Eval::evaluate(const Position& pos) { // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos) { +std::string Eval::trace(Position& pos, Search::Worker& workerThread) { if (pos.checkers()) return "Final evaluation: none (in check)"; // Reset any global variable used in eval - pos.this_thread()->bestValue = VALUE_ZERO; - pos.this_thread()->rootSimpleEval = VALUE_ZERO; - pos.this_thread()->optimism[WHITE] = VALUE_ZERO; - pos.this_thread()->optimism[BLACK] = VALUE_ZERO; + workerThread.iterBestValue = VALUE_ZERO; + workerThread.rootSimpleEval = VALUE_ZERO; + workerThread.optimism[WHITE] = VALUE_ZERO; + workerThread.optimism[BLACK] = VALUE_ZERO; std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); @@ -249,7 +262,7 @@ std::string Eval::trace(Position& pos) { v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos); + v = evaluate(pos, workerThread); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index 79b77192..8a9d6fc7 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -27,13 +27,18 @@ namespace Stockfish { class Position; +class OptionsMap; + +namespace Search { +class Worker; +} namespace Eval { -std::string trace(Position& pos); +std::string trace(Position& pos, Search::Worker& workerThread); int simple_eval(const Position& pos, Color c); -Value evaluate(const Position& pos); +Value evaluate(const Position& pos, const Search::Worker& workerThread); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the @@ -41,23 +46,28 @@ Value evaluate(const Position& pos); #define EvalFileDefaultNameBig "nn-baff1edbea57.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" +struct EvalFile { + // UCI option name + std::string optionName; + // Default net name, will use one of the macros above + std::string defaultName; + // Selected net name, either via uci option or default + std::string current; + // Net description extracted from the net file + std::string netDescription; +}; + namespace NNUE { enum NetSize : int; -void init(); -void verify(); +using EvalFiles = std::unordered_map; + +EvalFiles load_networks(const std::string&, const OptionsMap&, EvalFiles); +void verify(const OptionsMap&, const EvalFiles&); } // namespace NNUE -struct EvalFile { - std::string option_name; - std::string default_name; - std::string selected_name; -}; - -extern std::unordered_map EvalFiles; - } // namespace Eval } // namespace Stockfish diff --git a/src/main.cpp b/src/main.cpp index 78b3f54d..de07d6a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,15 +16,13 @@ along with this program. If not, see . */ -#include #include +#include #include "bitboard.h" #include "evaluate.h" #include "misc.h" #include "position.h" -#include "search.h" -#include "thread.h" #include "tune.h" #include "types.h" #include "uci.h" @@ -35,17 +33,16 @@ int main(int argc, char* argv[]) { std::cout << engine_info() << std::endl; - CommandLine::init(argc, argv); - UCI::init(Options); - Tune::init(); Bitboards::init(); Position::init(); - Threads.set(size_t(Options["Threads"])); - Search::clear(); // After threads are up - Eval::NNUE::init(); - UCI::loop(argc, argv); + UCI uci(argc, argv); + + Tune::init(uci.options); + + uci.evalFiles = Eval::NNUE::load_networks(uci.workingDirectory(), uci.options, uci.evalFiles); + + uci.loop(); - Threads.set(0); return 0; } diff --git a/src/misc.cpp b/src/misc.cpp index 9350a483..4885a5cd 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -721,17 +721,13 @@ void bindThisThread(size_t idx) { #define GETCWD getcwd #endif -namespace CommandLine { - -std::string argv0; // path+name of the executable binary, as given by argv[0] -std::string binaryDirectory; // path of the executable directory -std::string workingDirectory; // path of the working directory - -void init([[maybe_unused]] int argc, char* argv[]) { +CommandLine::CommandLine(int _argc, char** _argv) : + argc(_argc), + argv(_argv) { std::string pathSeparator; // Extract the path+name of the executable binary - argv0 = argv[0]; + std::string argv0 = argv[0]; #ifdef _WIN32 pathSeparator = "\\"; @@ -766,7 +762,4 @@ void init([[maybe_unused]] int argc, char* argv[]) { binaryDirectory.replace(0, 1, workingDirectory); } - -} // namespace CommandLine - } // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index ca6cc166..994f551d 100644 --- a/src/misc.h +++ b/src/misc.h @@ -176,12 +176,17 @@ namespace WinProcGroup { void bindThisThread(size_t idx); } -namespace CommandLine { -void init(int argc, char* argv[]); -extern std::string binaryDirectory; // path of the executable directory -extern std::string workingDirectory; // path of the working directory -} +struct CommandLine { + public: + CommandLine(int, char**); + + int argc; + char** argv; + + std::string binaryDirectory; // path of the executable directory + std::string workingDirectory; // path of the working directory +}; } // namespace Stockfish diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 86fe5230..d4a4dbe4 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -26,8 +26,10 @@ #include #include #include +#include #include #include +#include #include #include "../evaluate.h" @@ -51,8 +53,6 @@ AlignedPtr> network AlignedPtr> networkSmall[LayerStacks]; // Evaluation function file names -std::string fileName[2]; -std::string netDescription[2]; namespace Detail { @@ -136,10 +136,10 @@ static bool write_header(std::ostream& stream, std::uint32_t hashValue, const st } // Read network parameters -static bool read_parameters(std::istream& stream, NetSize netSize) { +static bool read_parameters(std::istream& stream, NetSize netSize, std::string& netDescription) { std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription[netSize])) + if (!read_header(stream, &hashValue, &netDescription)) return false; if (hashValue != HashValue[netSize]) return false; @@ -158,9 +158,10 @@ static bool read_parameters(std::istream& stream, NetSize netSize) { } // Write network parameters -static bool write_parameters(std::ostream& stream, NetSize netSize) { +static bool +write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDescription) { - if (!write_header(stream, HashValue[netSize], netDescription[netSize])) + if (!write_header(stream, HashValue[netSize], netDescription)) return false; if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig)) return false; @@ -424,24 +425,30 @@ std::string trace(Position& pos) { // Load eval, from a file stream or a memory stream -bool load_eval(const std::string name, std::istream& stream, NetSize netSize) { +std::optional load_eval(std::istream& stream, NetSize netSize) { initialize(netSize); - fileName[netSize] = name; - return read_parameters(stream, netSize); + std::string netDescription; + return read_parameters(stream, netSize, netDescription) ? std::make_optional(netDescription) + : std::nullopt; } // Save eval, to a file stream or a memory stream -bool save_eval(std::ostream& stream, NetSize netSize) { +bool save_eval(std::ostream& stream, + NetSize netSize, + const std::string& name, + const std::string& netDescription) { - if (fileName[netSize].empty()) + if (name.empty() || name == "None") return false; - return write_parameters(stream, netSize); + return write_parameters(stream, netSize, netDescription); } // Save eval, to a file given by its name -bool save_eval(const std::optional& filename, NetSize netSize) { +bool save_eval(const std::optional& filename, + NetSize netSize, + const std::unordered_map& evalFiles) { std::string actualFilename; std::string msg; @@ -450,7 +457,7 @@ bool save_eval(const std::optional& filename, NetSize netSize) { actualFilename = filename.value(); else { - if (EvalFiles.at(netSize).selected_name + if (evalFiles.at(netSize).current != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) { msg = "Failed to export a net. " @@ -463,7 +470,8 @@ bool save_eval(const std::optional& filename, NetSize netSize) { } std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream, netSize); + bool saved = save_eval(stream, netSize, evalFiles.at(netSize).current, + evalFiles.at(netSize).netDescription); msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index fabfb569..ea88f890 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -26,14 +26,20 @@ #include #include #include +#include #include "../misc.h" +#include "../types.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" -#include "../types.h" namespace Stockfish { class Position; + +namespace Eval { +struct EvalFile; +} + } namespace Stockfish::Eval::NNUE { @@ -73,9 +79,14 @@ template Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); void hint_common_parent_position(const Position& pos); -bool load_eval(const std::string name, std::istream& stream, NetSize netSize); -bool save_eval(std::ostream& stream, NetSize netSize); -bool save_eval(const std::optional& filename, NetSize netSize); +std::optional load_eval(std::istream& stream, NetSize netSize); +bool save_eval(std::ostream& stream, + NetSize netSize, + const std::string& name, + const std::string& netDescription); +bool save_eval(const std::optional& filename, + NetSize netSize, + const std::unordered_map&); } // namespace Stockfish::Eval::NNUE diff --git a/src/position.cpp b/src/position.cpp index ddc31888..6202381d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -19,7 +19,6 @@ #include "position.h" #include -#include #include #include #include @@ -36,7 +35,6 @@ #include "movegen.h" #include "nnue/nnue_common.h" #include "syzygy/tbprobe.h" -#include "thread.h" #include "tt.h" #include "uci.h" @@ -87,7 +85,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Position p; - p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + p.set(pos.fen(), pos.is_chess960(), &st); Tablebases::ProbeState s1, s2; Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); int dtz = Tablebases::probe_dtz(p, &s2); @@ -160,7 +158,7 @@ void Position::init() { // Initializes the position object with the given FEN string. // This function is not very robust - make sure that input FENs are correct, // this is assumed to be the responsibility of the GUI. -Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { +Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { /* A FEN string defines a particular position using only the ASCII character set. @@ -286,8 +284,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // handle also common incorrect FEN with fullmove = 0. gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); - chess960 = isChess960; - thisThread = th; + chess960 = isChess960; set_state(); assert(pos_is_ok()); @@ -388,7 +385,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; - return set(fenStr, false, si, nullptr); + return set(fenStr, false, si); } @@ -667,7 +664,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(m.is_ok()); assert(&newSt != st); - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); Key k = st->key ^ Zobrist::side; // Copy some fields of the old state to our new StateInfo object except the @@ -959,7 +955,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ // Used to do a "null move": it flips // the side to move without executing any move on the board. -void Position::do_null_move(StateInfo& newSt) { +void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { assert(!checkers()); assert(&newSt != st); @@ -982,7 +978,7 @@ void Position::do_null_move(StateInfo& newSt) { st->key ^= Zobrist::side; ++st->rule50; - prefetch(TT.first_entry(key())); + prefetch(tt.first_entry(key())); st->pliesFromNull = 0; @@ -1235,7 +1231,7 @@ void Position::flip() { std::getline(ss, token); // Half and full moves f += token; - set(f, is_chess960(), st, this_thread()); + set(f, is_chess960(), st); assert(pos_is_ok()); } diff --git a/src/position.h b/src/position.h index 34b53f4a..7ce3556f 100644 --- a/src/position.h +++ b/src/position.h @@ -32,6 +32,8 @@ namespace Stockfish { +class TranspositionTable; + // StateInfo struct stores information needed to restore a Position object to // its previous state when we retract a move. Whenever a move is made on the // board (by calling Position::do_move), a StateInfo object must be passed. @@ -75,8 +77,6 @@ using StateListPtr = std::unique_ptr>; // pieces, side to move, hash keys, castling info, etc. Important methods are // do_move() and undo_move(), used by the search to update node info when // traversing the search tree. -class Thread; - class Position { public: static void init(); @@ -86,7 +86,7 @@ class Position { Position& operator=(const Position&) = delete; // FEN string input/output - Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + Position& set(const std::string& fenStr, bool isChess960, StateInfo* si); Position& set(const std::string& code, Color c, StateInfo* si); std::string fen() const; @@ -139,7 +139,7 @@ class Position { void do_move(Move m, StateInfo& newSt); void do_move(Move m, StateInfo& newSt, bool givesCheck); void undo_move(Move m); - void do_null_move(StateInfo& newSt); + void do_null_move(StateInfo& newSt, TranspositionTable& tt); void undo_null_move(); // Static Exchange Evaluation @@ -152,16 +152,15 @@ class Position { Key pawn_key() const; // Other properties of the position - Color side_to_move() const; - int game_ply() const; - bool is_chess960() const; - Thread* this_thread() const; - bool is_draw(int ply) const; - bool has_game_cycle(int ply) const; - bool has_repeated() const; - int rule50_count() const; - Value non_pawn_material(Color c) const; - Value non_pawn_material() const; + Color side_to_move() const; + int game_ply() const; + bool is_chess960() const; + bool is_draw(int ply) const; + bool has_game_cycle(int ply) const; + bool has_repeated() const; + int rule50_count() const; + Value non_pawn_material(Color c) const; + Value non_pawn_material() const; // Position consistency check, for debugging bool pos_is_ok() const; @@ -194,7 +193,6 @@ class Position { int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB]; - Thread* thisThread; StateInfo* st; int gamePly; Color sideToMove; @@ -328,8 +326,6 @@ inline bool Position::capture_stage(Move m) const { inline Piece Position::captured_piece() const { return st->capturedPiece; } -inline Thread* Position::this_thread() const { return thisThread; } - inline void Position::put_piece(Piece pc, Square s) { board[s] = pc; diff --git a/src/search.cpp b/src/search.cpp index e93b12d1..5530d125 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -27,8 +27,6 @@ #include #include #include -#include -#include #include #include "bitboard.h" @@ -44,14 +42,10 @@ #include "timeman.h" #include "tt.h" #include "uci.h" +#include "ucioption.h" namespace Stockfish { -namespace Search { - -LimitsType Limits; -} - namespace Tablebases { int Cardinality; @@ -62,33 +56,17 @@ Depth ProbeDepth; namespace TB = Tablebases; -using std::string; using Eval::evaluate; using namespace Search; namespace { -// Different node types, used as a template parameter -enum NodeType { - NonPV, - PV, - Root -}; // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { return ((116 - 44 * noTtCutNode) * (d - improving)); } -// Reductions lookup table initialized at startup -int Reductions[MAX_MOVES]; // [depth or moveNumber] - -Depth reduction(bool i, Depth d, int mn, int delta, int rootDelta) { - int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 - + (!i && reductionScale > 880); -} - constexpr int futility_move_count(bool improving, Depth depth) { return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; } @@ -105,9 +83,7 @@ int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); } int stat_malus(Depth d) { return std::min(400 * d - 354, 1201); } // Add a small random component to draw evaluations to avoid 3-fold blindness -Value value_draw(const Thread* thisThread) { - return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); -} +Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } // Skill structure is used to implement strength limit. If we have a UCI_Elo, // we convert it to an appropriate skill level, anchored to the Stash engine. @@ -127,34 +103,30 @@ struct Skill { } bool enabled() const { return level < 20.0; } bool time_to_pick(Depth depth) const { return depth == 1 + int(level); } - Move pick_best(size_t multiPV); + Move pick_best(const RootMoves&, size_t multiPV); double level; Move best = Move::none(); }; -template -Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); - -template -Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); - Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); -void update_all_stats(const Position& pos, - Stack* ss, - Move bestMove, - Value bestValue, - Value beta, - Square prevSq, - Move* quietsSearched, - int quietCount, - Move* capturesSearched, - int captureCount, - Depth depth); +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Value bestValue, + Value beta, + Square prevSq, + Move* quietsSearched, + int quietCount, + Move* capturesSearched, + int captureCount, + Depth depth); // Utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. @@ -187,42 +159,35 @@ uint64_t perft(Position& pos, Depth depth) { } // namespace -// Called at startup to initialize various lookup tables -void Search::init() { - - for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i)); +Search::Worker::Worker(SharedState& sharedState, + std::unique_ptr sm, + size_t thread_id) : + // Unpack the SharedState struct into member variables + thread_idx(thread_id), + manager(std::move(sm)), + options(sharedState.options), + threads(sharedState.threads), + tt(sharedState.tt) { + clear(); } - -// Resets search state to its initial value -void Search::clear() { - - Threads.main()->wait_for_search_finished(); - - Time.availableNodes = 0; - TT.clear(); - Threads.clear(); - Tablebases::init(Options["SyzygyPath"]); // Free mapped files -} - - -// Called when the program receives the UCI 'go' -// command. It searches from the root position and outputs the "bestmove". -void MainThread::search() { - - if (Limits.perft) +void Search::Worker::start_searching() { + // Non-main threads go directly to iterative_deepening() + if (!is_mainthread()) { - nodes = perft(rootPos, Limits.perft); + iterative_deepening(); + return; + } + + if (limits.perft) + { + nodes = perft(rootPos, limits.perft); sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; return; } - Color us = rootPos.side_to_move(); - Time.init(Limits, us, rootPos.game_ply()); - TT.new_search(); - - Eval::NNUE::verify(); + main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options); + tt.new_search(); if (rootMoves.empty()) { @@ -232,73 +197,75 @@ void MainThread::search() { } else { - Threads.start_searching(); // start non-main threads - Thread::search(); // main thread start searching + threads.start_searching(); // start non-main threads + iterative_deepening(); // main thread start searching } // When we reach the maximum depth, we can arrive here without a raise of - // Threads.stop. However, if we are pondering or in an infinite search, + // threads.stop. However, if we are pondering or in an infinite search, // the UCI protocol states that we shouldn't print the best move before the // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here // until the GUI sends one of those commands. - - while (!Threads.stop && (ponder || Limits.infinite)) + while (!threads.stop && (main_manager()->ponder || limits.infinite)) {} // Busy wait for a stop or a ponder reset // Stop the threads if not already stopped (also raise the stop if - // "ponderhit" just reset Threads.ponder). - Threads.stop = true; + // "ponderhit" just reset threads.ponder). + threads.stop = true; // Wait until all threads have finished - Threads.wait_for_search_finished(); + threads.wait_for_search_finished(); // When playing in 'nodes as time' mode, subtract the searched nodes from // the available ones before exiting. - if (Limits.npmsec) - Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + if (limits.npmsec) + main_manager()->tm.advance_nodes_time(limits.inc[rootPos.side_to_move()] + - threads.nodes_searched()); - Thread* bestThread = this; + Worker* bestThread = this; Skill skill = - Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); + Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); - if (int(Options["MultiPV"]) == 1 && !Limits.depth && !skill.enabled() + if (int(options["MultiPV"]) == 1 && !limits.depth && !skill.enabled() && rootMoves[0].pv[0] != Move::none()) - bestThread = Threads.get_best_thread(); + bestThread = threads.get_best_thread()->worker.get(); - bestPreviousScore = bestThread->rootMoves[0].score; - bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; + main_manager()->bestPreviousScore = bestThread->rootMoves[0].score; + main_manager()->bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; // Send again PV info if we have a new best thread if (bestThread != this) - sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; + sync_cout << UCI::pv(*bestThread, main_manager()->tm.elapsed(threads.nodes_searched()), + threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), + TB::RootInTB) + << sync_endl; sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); if (bestThread->rootMoves[0].pv.size() > 1 - || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) + || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); std::cout << sync_endl; } - // Main iterative deepening loop. It calls search() // repeatedly with increasing depth until the allocated thinking time has been // consumed, the user stops the search, or the maximum search depth is reached. -void Thread::search() { +void Search::Worker::iterative_deepening() { // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), // (ss + 2) is needed for initialization of cutOffCnt and killers. - Stack stack[MAX_PLY + 10], *ss = stack + 7; - Move pv[MAX_PLY + 1]; - Value alpha, beta; - Move lastBestMove = Move::none(); - Depth lastBestMoveDepth = 0; - MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int delta, iterIdx = 0; + Stack stack[MAX_PLY + 10], *ss = stack + 7; + Move pv[MAX_PLY + 1]; + Value alpha, beta; + Move lastBestMove = Move::none(); + Depth lastBestMoveDepth = 0; + SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + double timeReduction = 1, totBestMoveChanges = 0; + Color us = rootPos.side_to_move(); + int delta, iterIdx = 0; std::memset(ss - 7, 0, 10 * sizeof(Stack)); for (int i = 7; i > 0; --i) @@ -313,7 +280,7 @@ void Thread::search() { ss->pv = pv; - bestValue = -VALUE_INFINITE; + iterBestValue = -VALUE_INFINITE; if (mainThread) { @@ -325,8 +292,8 @@ void Thread::search() { mainThread->iterValue[i] = mainThread->bestPreviousScore; } - size_t multiPV = size_t(Options["MultiPV"]); - Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); + size_t multiPV = size_t(options["MultiPV"]); + Skill skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); // When playing with strength handicap enable MultiPV search that we will // use behind-the-scenes to retrieve a set of possible moves. @@ -338,8 +305,8 @@ void Thread::search() { int searchAgainCounter = 0; // Iterative deepening loop until requested to stop or the target depth is reached - while (++rootDepth < MAX_PLY && !Threads.stop - && !(Limits.depth && mainThread && rootDepth > Limits.depth)) + while (++rootDepth < MAX_PLY && !threads.stop + && !(limits.depth && mainThread && rootDepth > limits.depth)) { // Age out PV variability metric if (mainThread) @@ -353,11 +320,11 @@ void Thread::search() { size_t pvFirst = 0; pvLast = 0; - if (!Threads.increaseDepth) + if (!threads.increaseDepth) searchAgainCounter++; // MultiPV loop. We perform a full root search for each PV line - for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) + for (pvIdx = 0; pvIdx < multiPV && !threads.stop; ++pvIdx) { if (pvIdx == pvLast) { @@ -390,7 +357,7 @@ void Thread::search() { // for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); + iterBestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -403,29 +370,32 @@ void Thread::search() { // If search has been stopped, we break immediately. Sorting is // safe because RootMoves is still valid, although it refers to // the previous iteration. - if (Threads.stop) + if (threads.stop) break; // When failing high/low give some update (without cluttering // the UI) before a re-search. - if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && Time.elapsed() > 3000) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; + if (mainThread && multiPV == 1 && (iterBestValue <= alpha || iterBestValue >= beta) + && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), + threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), + TB::RootInTB) + << sync_endl; // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. - if (bestValue <= alpha) + if (iterBestValue <= alpha) { beta = (alpha + beta) / 2; - alpha = std::max(bestValue - delta, -VALUE_INFINITE); + alpha = std::max(iterBestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; if (mainThread) mainThread->stopOnPonderhit = false; } - else if (bestValue >= beta) + else if (iterBestValue >= beta) { - beta = std::min(bestValue + delta, int(VALUE_INFINITE)); + beta = std::min(iterBestValue + delta, int(VALUE_INFINITE)); ++failedHighCnt; } else @@ -439,11 +409,16 @@ void Thread::search() { // Sort the PV lines searched so far and update the GUI std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); - if (mainThread && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; + if (mainThread + && (threads.stop || pvIdx + 1 == multiPV + || mainThread->tm.elapsed(threads.nodes_searched()) > 3000)) + sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), + threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), + TB::RootInTB) + << sync_endl; } - if (!Threads.stop) + if (!threads.stop) completedDepth = rootDepth; if (rootMoves[0].pv[0] != lastBestMove) @@ -453,60 +428,62 @@ void Thread::search() { } // Have we found a "mate in x"? - if (Limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * Limits.mate) - Threads.stop = true; + if (limits.mate && iterBestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - iterBestValue <= 2 * limits.mate) + threads.stop = true; if (!mainThread) continue; // If the skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) - skill.pick_best(multiPV); + skill.pick_best(rootMoves, multiPV); // Use part of the gained time from a previous stable move for the current move - for (Thread* th : Threads) + for (Thread* th : threads) { - totBestMoveChanges += th->bestMoveChanges; - th->bestMoveChanges = 0; + totBestMoveChanges += th->worker->bestMoveChanges; + th->worker->bestMoveChanges = 0; } // Do we have time for the next iteration? Can we stop searching now? - if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit) + if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - iterBestValue) + + 6 * (mainThread->iterValue[iterIdx] - iterBestValue)) / 616.6; fallingEval = std::clamp(fallingEval, 0.51, 1.51); // If the bestMove is stable over several iterations, reduce time accordingly timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; double reduction = (1.4 + mainThread->previousTimeReduction) / (2.17 * timeReduction); - double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / Threads.size(); + double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / threads.size(); - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; + double totalTime = + mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); // Stop the search if we have exceeded the totalTime - if (Time.elapsed() > totalTime) + if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". if (mainThread->ponder) mainThread->stopOnPonderhit = true; else - Threads.stop = true; + threads.stop = true; } - else if (!mainThread->ponder && Time.elapsed() > totalTime * 0.50) - Threads.increaseDepth = false; + else if (!mainThread->ponder + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.50) + threads.increaseDepth = false; else - Threads.increaseDepth = true; + threads.increaseDepth = true; } - mainThread->iterValue[iterIdx] = bestValue; + mainThread->iterValue[iterIdx] = iterBestValue; iterIdx = (iterIdx + 1) & 3; } @@ -517,16 +494,34 @@ void Thread::search() { // If the skill level is enabled, swap the best PV line with the sub-optimal one if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), - skill.best ? skill.best : skill.pick_best(multiPV))); + std::swap(rootMoves[0], + *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(rootMoves, multiPV))); +} + +void Search::Worker::clear() { + counterMoves.fill(Move::none()); + mainHistory.fill(0); + captureHistory.fill(0); + pawnHistory.fill(0); + correctionHistory.fill(0); + + for (bool inCheck : {false, true}) + for (StatsType c : {NoCaptures, Captures}) + for (auto& to : continuationHistory[inCheck][c]) + for (auto& h : to) + h->fill(-71); + + + for (int i = 1; i < MAX_MOVES; ++i) + reductions[i] = int((20.37 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } -namespace { - -// Main search function for both PV and non-PV nodes +// Main search function for both PV and non-PV nodes. template -Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { +Value Search::Worker::search( + Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; @@ -539,7 +534,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // if the opponent had an alternative move earlier to this position. if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { - alpha = value_draw(pos.this_thread()); + alpha = value_draw(this->nodes); if (alpha >= beta) return alpha; } @@ -564,7 +559,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo int moveCount, captureCount, quietCount; // Step 1. Initialize node - Thread* thisThread = pos.this_thread(); + Worker* thisThread = this; ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); @@ -573,8 +568,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo maxValue = VALUE_INFINITE; // Check for the available remaining time - if (thisThread == Threads.main()) - static_cast(thisThread)->check_time(); + if (is_mainthread()) + main_manager()->check_time(*this); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) if (PvNode && thisThread->selDepth < ss->ply + 1) @@ -583,10 +578,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (!rootNode) { // Step 2. Check for aborted search and immediate draw - if (Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) + if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) - : value_draw(pos.this_thread()); + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, *thisThread) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -614,7 +609,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); + tte = tt.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() @@ -638,7 +633,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); + update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of // the previous ply (~0 Elo on STC, ~2 Elo on LTC). @@ -676,8 +671,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); // Force check of time on the next occasion - if (thisThread == Threads.main()) - static_cast(thisThread)->callsCnt = 0; + if (is_mainthread()) + main_manager()->callsCnt = 0; if (err != TB::ProbeState::FAIL) { @@ -699,7 +694,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE); + std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE, + tt.generation()); return value; } @@ -715,7 +711,6 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } } - CapturePieceToHistory& captureHistory = thisThread->captureHistory; Value unadjustedStaticEval = VALUE_NONE; @@ -739,7 +734,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Never assume anything about values stored in TT unadjustedStaticEval = ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, *thisThread); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); @@ -757,7 +752,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } else { - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, *thisThread); Value newEval = ss->staticEval @@ -769,7 +764,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), - unadjustedStaticEval); + unadjustedStaticEval, tt.generation()); } // Use static evaluation difference to improve quiet move ordering (~9 Elo) @@ -827,7 +822,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; - pos.do_null_move(st); + pos.do_null_move(st, tt); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, !cutNode); @@ -885,7 +880,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &thisThread->captureHistory); while ((move = mp.next_move()) != Move::none()) if (move != excludedMove && pos.legal(move)) @@ -893,13 +888,14 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo assert(pos.capture_stage(move)); // Prefetch the TT entry for the resulting position - prefetch(TT.first_entry(pos.key_after(move))); + prefetch(tt.first_entry(pos.key_after(move))); ss->currentMove = move; ss->continuationHistory = - &thisThread + &this ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st); // Perform a preliminary qsearch to verify that the move holds @@ -916,7 +912,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // Save ProbCut data into transposition table tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, - move, unadjustedStaticEval); + move, unadjustedStaticEval, tt.generation()); return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) : value; } @@ -944,8 +940,8 @@ moves_loop: // When in check, search starts here Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none(); - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, - &thisThread->pawnHistory, countermove, ss->killers); + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, + contHist, &thisThread->pawnHistory, countermove, ss->killers); value = bestValue; moveCountPruning = singularQuietLMR = false; @@ -978,7 +974,8 @@ moves_loop: // When in check, search starts here ss->moveCount = ++moveCount; - if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) + if (rootNode && is_mainthread() + && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) sync_cout << "info depth " << depth << " currmove " << UCI::move(move, pos.is_chess960()) << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; @@ -995,7 +992,7 @@ moves_loop: // When in check, search starts here int delta = beta - alpha; - Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); + Depth r = reduction(improving, depth, moveCount, delta); // Step 14. Pruning at shallow depth (~120 Elo). // Depth conditions are important for mate finding. @@ -1016,7 +1013,8 @@ moves_loop: // When in check, search starts here Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] - + captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; + + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] + / 7; if (futilityEval < alpha) continue; } @@ -1135,8 +1133,8 @@ moves_loop: // When in check, search starts here // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && move.to_sq() == prevSq - && captureHistory[movedPiece][move.to_sq()] - [type_of(pos.piece_on(move.to_sq()))] + && thisThread->captureHistory[movedPiece][move.to_sq()] + [type_of(pos.piece_on(move.to_sq()))] > 4146) extension = 1; } @@ -1146,7 +1144,7 @@ moves_loop: // When in check, search starts here ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension == 2); // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + prefetch(tt.first_entry(pos.key_after(move))); // Update the current move (this must be done after singular extension search) ss->currentMove = move; @@ -1154,6 +1152,7 @@ moves_loop: // When in check, search starts here &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; // Step 16. Make the move + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); // Decrease reduction if position is or has been on the PV (~4 Elo) @@ -1269,7 +1268,7 @@ moves_loop: // When in check, search starts here // Finished searching the move. If a stop occurred, the return value of // the search cannot be trusted, and we return immediately without // updating best move, PV and TT. - if (Threads.stop.load(std::memory_order_relaxed)) + if (threads.stop.load(std::memory_order_relaxed)) return VALUE_ZERO; if (rootNode) @@ -1371,8 +1370,8 @@ moves_loop: // When in check, search starts here // If there is a move that produces search value greater than alpha we update the stats of searched moves else if (bestMove) - update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, - capturesSearched, captureCount, depth); + update_all_stats(pos, ss, *this, bestMove, bestValue, beta, prevSq, quietsSearched, + quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1400,7 +1399,7 @@ moves_loop: // When in check, search starts here bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, unadjustedStaticEval); + depth, bestMove, unadjustedStaticEval, tt.generation()); // Adjust correction history if (!ss->inCheck && (!bestMove || !pos.capture(bestMove)) @@ -1422,7 +1421,7 @@ moves_loop: // When in check, search starts here // function with zero depth, or recursively with further decreasing depth per call. // (~155 Elo) template -Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { +Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { static_assert(nodeType != Root); constexpr bool PvNode = nodeType == PV; @@ -1435,7 +1434,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // if the opponent had an alternative move earlier to this position. if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { - alpha = value_draw(pos.this_thread()); + alpha = value_draw(this->nodes); if (alpha >= beta) return alpha; } @@ -1460,7 +1459,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->pv[0] = Move::none(); } - Thread* thisThread = pos.this_thread(); + Worker* thisThread = this; bestMove = Move::none(); ss->inCheck = pos.checkers(); moveCount = 0; @@ -1471,7 +1470,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, *thisThread) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1480,7 +1479,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Step 3. Transposition table lookup posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); + tte = tt.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = ss->ttHit ? tte->move() : Move::none(); pvHit = ss->ttHit && tte->is_pv(); @@ -1502,7 +1501,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { // Never assume anything about values stored in TT if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos); + unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos, *thisThread); Value newEval = ss->staticEval @@ -1522,7 +1521,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; + (ss - 1)->currentMove != Move::null() ? evaluate(pos, *thisThread) + : -(ss - 1)->staticEval; Value newEval = ss->staticEval @@ -1539,7 +1539,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - Move::none(), unadjustedStaticEval); + Move::none(), unadjustedStaticEval, tt.generation()); return bestValue; } @@ -1632,7 +1632,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { } // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + prefetch(tt.first_entry(pos.key_after(move))); // Update the current move ss->currentMove = move; @@ -1643,6 +1643,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { quietCheckEvasions += !capture && ss->inCheck; // Step 7. Make and search the move + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); value = -qsearch(pos, ss + 1, -beta, -alpha, depth - 1); pos.undo_move(move); @@ -1686,7 +1687,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, - unadjustedStaticEval); + unadjustedStaticEval, tt.generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1694,6 +1695,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { } +namespace { // Adjusts a mate or TB score from "plies to mate from the root" // to "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. @@ -1759,6 +1761,7 @@ void update_pv(Move* pv, Move move, const Move* childPv) { // Updates stats at the end of search() when a bestMove is found void update_all_stats(const Position& pos, Stack* ss, + Search::Worker& workerThread, Move bestMove, Value bestValue, Value beta, @@ -1770,8 +1773,7 @@ void update_all_stats(const Position& pos, Depth depth) { Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); - CapturePieceToHistory& captureHistory = thisThread->captureHistory; + CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; @@ -1784,19 +1786,19 @@ void update_all_stats(const Position& pos, : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move - update_quiet_stats(pos, ss, bestMove, bestMoveBonus); + update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); int pIndex = pawn_structure_index(pos); - thisThread->pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; + workerThread.pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - thisThread - ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] + workerThread + .pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] << -quietMoveMalus; - thisThread->mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; + workerThread.mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), quietsSearched[i].to_sq(), -quietMoveMalus); } @@ -1842,7 +1844,8 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Updates move sorting heuristics -void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { // Update killers if (ss->killers[0] != move) @@ -1851,25 +1854,23 @@ void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { ss->killers[0] = move; } - Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); - thisThread->mainHistory[us][move.from_to()] << bonus; + Color us = pos.side_to_move(); + workerThread.mainHistory[us][move.from_to()] << bonus; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); // Update countermove history if (((ss - 1)->currentMove).is_ok()) { - Square prevSq = ((ss - 1)->currentMove).to_sq(); - thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; + Square prevSq = ((ss - 1)->currentMove).to_sq(); + workerThread.counterMoves[pos.piece_on(prevSq)][prevSq] = move; } } +} // When playing with strength handicap, choose the best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. -Move Skill::pick_best(size_t multiPV) { - - const RootMoves& rootMoves = Threads.main()->rootMoves; - static PRNG rng(now()); // PRNG sequence should be non-deterministic +Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { + static PRNG rng(now()); // PRNG sequence should be non-deterministic // RootMoves are already sorted by score in descending order Value topScore = rootMoves[0].score; @@ -1897,23 +1898,20 @@ Move Skill::pick_best(size_t multiPV) { return best; } -} // namespace - // Used to print debug info and, more importantly, // to detect when we are out of available time and thus stop the search. -void MainThread::check_time() { - +void SearchManager::check_time(Search::Worker& worker) { if (--callsCnt > 0) return; // When using nodes, ensure checking rate is not lower than 0.1% of nodes - callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512; + callsCnt = worker.limits.nodes ? std::min(512, int(worker.limits.nodes / 1024)) : 512; static TimePoint lastInfoTime = now(); - TimePoint elapsed = Time.elapsed(); - TimePoint tick = Limits.startTime + elapsed; + TimePoint elapsed = tm.elapsed(worker.threads.nodes_searched()); + TimePoint tick = worker.limits.startTime + elapsed; if (tick - lastInfoTime >= 1000) { @@ -1925,72 +1923,18 @@ void MainThread::check_time() { if (ponder) return; - if ((Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) - || (Limits.movetime && elapsed >= Limits.movetime) - || (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))) - Threads.stop = true; + if ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) + || (worker.limits.movetime && elapsed >= worker.limits.movetime) + || (worker.limits.nodes + && worker.threads.nodes_searched() >= uint64_t(worker.limits.nodes))) + worker.threads.stop = true; } - -// Formats PV information according to the UCI protocol. UCI requires -// that all (if any) unsearched PV lines are sent using a previous search score. -string UCI::pv(const Position& pos, Depth depth) { - - std::stringstream ss; - TimePoint elapsed = Time.elapsed() + 1; - const RootMoves& rootMoves = pos.this_thread()->rootMoves; - size_t pvIdx = pos.this_thread()->pvIdx; - size_t multiPV = std::min(size_t(Options["MultiPV"]), rootMoves.size()); - uint64_t nodesSearched = Threads.nodes_searched(); - uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); - - for (size_t i = 0; i < multiPV; ++i) - { - bool updated = rootMoves[i].score != -VALUE_INFINITE; - - if (depth == 1 && !updated && i > 0) - continue; - - Depth d = updated ? depth : std::max(1, depth - 1); - Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; - - if (v == -VALUE_INFINITE) - v = VALUE_ZERO; - - bool tb = TB::RootInTB && std::abs(v) <= VALUE_TB; - v = tb ? rootMoves[i].tbScore : v; - - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; - - ss << "info" - << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCI::value(v); - - if (Options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos.game_ply()); - - if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound - ? " lowerbound" - : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); - - ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / elapsed - << " hashfull " << TT.hashfull() << " tbhits " << tbHits << " time " << elapsed << " pv"; - - for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); - } - - return ss.str(); -} - - // Called in case we have no ponder move before exiting the search, // for instance, in case we stop the search during a fail high at root. // We try hard to have a ponder move to return to the GUI, // otherwise in case of 'ponder on' we have nothing to think about. -bool RootMove::extract_ponder_from_tt(Position& pos) { +bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) { StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); @@ -2003,7 +1947,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { return false; pos.do_move(pv[0], st); - TTEntry* tte = TT.probe(pos.key(), ttHit); + TTEntry* tte = tt.probe(pos.key(), ttHit); if (ttHit) { @@ -2016,12 +1960,14 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { return pv.size() > 1; } -void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { +void Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves) { RootInTB = false; - UseRule50 = bool(Options["Syzygy50MoveRule"]); - ProbeDepth = int(Options["SyzygyProbeDepth"]); - Cardinality = int(Options["SyzygyProbeLimit"]); + UseRule50 = bool(options["Syzygy50MoveRule"]); + ProbeDepth = int(options["SyzygyProbeDepth"]); + Cardinality = int(options["SyzygyProbeLimit"]); bool dtz_available = true; // Tables with fewer pieces than SyzygyProbeLimit are searched with @@ -2035,13 +1981,13 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { // Rank moves using DTZ tables - RootInTB = root_probe(pos, rootMoves); + RootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]); if (!RootInTB) { // DTZ tables are missing; try to rank moves using WDL tables dtz_available = false; - RootInTB = root_probe_wdl(pos, rootMoves); + RootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]); } } diff --git a/src/search.h b/src/search.h index 72e275d3..48a5630c 100644 --- a/src/search.h +++ b/src/search.h @@ -19,19 +19,37 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED +#include +#include +#include #include +#include #include #include "misc.h" #include "movepick.h" +#include "position.h" +#include "timeman.h" #include "types.h" namespace Stockfish { -class Position; +// Different node types, used as a template parameter +enum NodeType { + NonPV, + PV, + Root +}; + +class TranspositionTable; +class ThreadPool; +class OptionsMap; +class UCI; namespace Search { +// Called at startup to initialize various lookup tables, after program startup +void init(int); // Stack struct keeps track of the information we need to remember from nodes // shallower and deeper in the tree during the search. Each search thread has @@ -61,7 +79,7 @@ struct RootMove { explicit RootMove(Move m) : pv(1, m) {} - bool extract_ponder_from_tt(Position& pos); + bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos); bool operator==(const Move& m) const { return pv[0] == m; } // Sort in descending order bool operator<(const RootMove& m) const { @@ -85,7 +103,6 @@ using RootMoves = std::vector; // LimitsType struct stores information sent by GUI about available time to // search the current move, maximum depth/time, or if we are in analysis mode. - struct LimitsType { // Init explicitly due to broken value-initialization of non POD in MSVC @@ -103,10 +120,136 @@ struct LimitsType { int64_t nodes; }; -extern LimitsType Limits; -void init(); -void clear(); +// The UCI stores the uci options, thread pool, and transposition table. +// This struct is used to easily forward data to the Search::Worker class. +struct SharedState { + SharedState(const OptionsMap& o, ThreadPool& tp, TranspositionTable& t) : + options(o), + threads(tp), + tt(t) {} + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; +}; + +class Worker; + +// Null Object Pattern, implement a common interface +// for the SearchManagers. A Null Object will be given to +// non-mainthread workers. +class ISearchManager { + public: + virtual ~ISearchManager() {} + virtual void check_time(Search::Worker&) = 0; +}; + +// SearchManager manages the search from the main thread. It is responsible for +// keeping track of the time, and storing data strictly related to the main thread. +class SearchManager: public ISearchManager { + public: + void check_time(Search::Worker& worker) override; + + Stockfish::TimeManagement tm; + int callsCnt; + std::atomic_bool ponder; + + double previousTimeReduction; + Value bestPreviousScore; + Value bestPreviousAverageScore; + Value iterValue[4]; + bool stopOnPonderhit; + + size_t id; +}; + +class NullSearchManager: public ISearchManager { + public: + void check_time(Search::Worker&) override {} +}; + +// Search::Worker is the class that does the actual search. +// It is instantiated once per thread, and it is responsible for keeping track +// of the search history, and storing data required for the search. +class Worker { + public: + Worker(SharedState&, std::unique_ptr, size_t); + + // Reset histories, usually before a new game + void clear(); + + // Called when the program receives the UCI 'go' + // command. It searches from the root position and outputs the "bestmove". + void start_searching(); + + bool is_mainthread() const { return thread_idx == 0; } + + // Public because evaluate uses this + Value iterBestValue, optimism[COLOR_NB]; + Value rootSimpleEval; + + // Public because they need to be updatable by the stats + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; + CorrectionHistory correctionHistory; + + private: + void iterative_deepening(); + + // Main search function for both PV and non-PV nodes + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + + // Quiescence search function, which is called by the main search + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); + + Depth reduction(bool i, Depth d, int mn, int delta) { + int reductionScale = reductions[d] * reductions[mn]; + return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 + + (!i && reductionScale > 880); + } + + // Get a pointer to the search manager, only allowed to be called by the + // main thread. + SearchManager* main_manager() const { + assert(thread_idx == 0); + return static_cast(manager.get()); + } + + LimitsType limits; + + size_t pvIdx, pvLast; + std::atomic nodes, tbHits, bestMoveChanges; + int selDepth, nmpMinPly; + + Position rootPos; + StateInfo rootState; + RootMoves rootMoves; + Depth rootDepth, completedDepth; + Value rootDelta; + + size_t thread_idx; + + // Reductions lookup table initialized at startup + int reductions[MAX_MOVES]; // [depth or moveNumber] + + // The main thread has a SearchManager, the others have a NullSearchManager + std::unique_ptr manager; + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + + friend class Stockfish::ThreadPool; + friend class Stockfish::UCI; + friend class SearchManager; +}; + } // namespace Search diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 91013dca..6f30bf6b 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -42,7 +42,6 @@ #include "../position.h" #include "../search.h" #include "../types.h" -#include "../uci.h" #ifndef _WIN32 #include @@ -1574,7 +1573,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { +bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50) { ProbeState result = OK; StateInfo st; @@ -1585,7 +1584,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // Check whether a position was repeated since the last zeroing move. bool rep = pos.has_repeated(); - int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1; + int dtz, bound = rule50 ? (MAX_DTZ - 100) : 1; // Probe and rank each move for (auto& m : rootMoves) @@ -1647,7 +1646,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // This is a fallback for the case that some or all DTZ tables are missing. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { +bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) { static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ}; @@ -1655,7 +1654,6 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { StateInfo st; WDLScore wdl; - bool rule50 = Options["Syzygy50MoveRule"]; // Probe and rank each move for (auto& m : rootMoves) diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index cc8eb0d4..d7b412a1 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -25,6 +25,7 @@ namespace Stockfish { class Position; +class OptionsMap; } namespace Stockfish::Tablebases { @@ -47,12 +48,13 @@ enum ProbeState { extern int MaxCardinality; + void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves); -bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); -void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); +bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); +void rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves); } // namespace Stockfish::Tablebases diff --git a/src/thread.cpp b/src/thread.cpp index 01ccd4fc..a512c0a5 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -23,9 +23,8 @@ #include #include #include -#include -#include #include +#include #include #include "evaluate.h" @@ -33,18 +32,21 @@ #include "movegen.h" #include "search.h" #include "syzygy/tbprobe.h" +#include "timeman.h" #include "tt.h" -#include "uci.h" +#include "types.h" +#include "ucioption.h" namespace Stockfish { -ThreadPool Threads; // Global object - - // Constructor launches the thread and waits until it goes to sleep // in idle_loop(). Note that 'searching' and 'exit' should be already set. -Thread::Thread(size_t n) : +Thread::Thread(Search::SharedState& sharedState, + std::unique_ptr sm, + size_t n) : + worker(std::make_unique(sharedState, std::move(sm), n)), idx(n), + nthreads(sharedState.options["Threads"]), stdThread(&Thread::idle_loop, this) { wait_for_search_finished(); @@ -62,24 +64,6 @@ Thread::~Thread() { stdThread.join(); } - -// Reset histories, usually before a new game -void Thread::clear() { - - counterMoves.fill(Move::none()); - mainHistory.fill(0); - captureHistory.fill(0); - pawnHistory.fill(0); - correctionHistory.fill(0); - - for (bool inCheck : {false, true}) - for (StatsType c : {NoCaptures, Captures}) - for (auto& to : continuationHistory[inCheck][c]) - for (auto& h : to) - h->fill(-71); -} - - // Wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); @@ -108,7 +92,7 @@ void Thread::idle_loop() { // some Windows NUMA hardware, for instance in fishtest. To make it simple, // just check if running threads are below a threshold, in this case, all this // NUMA machinery is not needed. - if (Options["Threads"] > 8) + if (nthreads > 8) WinProcGroup::bindThisThread(idx); while (true) @@ -123,36 +107,41 @@ void Thread::idle_loop() { lk.unlock(); - search(); + worker->start_searching(); } } // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. -void ThreadPool::set(size_t requested) { +void ThreadPool::set(Search::SharedState sharedState) { if (threads.size() > 0) // destroy any existing thread(s) { - main()->wait_for_search_finished(); + main_thread()->wait_for_search_finished(); while (threads.size() > 0) delete threads.back(), threads.pop_back(); } + const size_t requested = sharedState.options["Threads"]; + if (requested > 0) // create new thread(s) { - threads.push_back(new MainThread(0)); + threads.push_back(new Thread( + sharedState, std::unique_ptr(new Search::SearchManager()), 0)); + while (threads.size() < requested) - threads.push_back(new Thread(threads.size())); + threads.push_back(new Thread( + sharedState, std::unique_ptr(new Search::NullSearchManager()), + threads.size())); clear(); - // Reallocate the hash with the new threadpool size - TT.resize(size_t(Options["Hash"])); + main_thread()->wait_for_search_finished(); - // Init thread number dependent search params. - Search::init(); + // Reallocate the hash with the new threadpool size + sharedState.tt.resize(sharedState.options["Hash"], requested); } } @@ -161,28 +150,31 @@ void ThreadPool::set(size_t requested) { void ThreadPool::clear() { for (Thread* th : threads) - th->clear(); + th->worker->clear(); - main()->callsCnt = 0; - main()->bestPreviousScore = VALUE_INFINITE; - main()->bestPreviousAverageScore = VALUE_INFINITE; - main()->previousTimeReduction = 1.0; + main_manager()->callsCnt = 0; + main_manager()->bestPreviousScore = VALUE_INFINITE; + main_manager()->bestPreviousAverageScore = VALUE_INFINITE; + main_manager()->previousTimeReduction = 1.0; + main_manager()->tm.clear(); } // Wakes up main thread waiting in idle_loop() and // returns immediately. Main thread will wake up other threads and start the search. -void ThreadPool::start_thinking(Position& pos, - StateListPtr& states, - const Search::LimitsType& limits, - bool ponderMode) { +void ThreadPool::start_thinking(const OptionsMap& options, + Position& pos, + StateListPtr& states, + Search::LimitsType limits, + bool ponderMode) { - main()->wait_for_search_finished(); + main_thread()->wait_for_search_finished(); + + main_manager()->stopOnPonderhit = stop = false; + main_manager()->ponder = ponderMode; + + increaseDepth = true; - main()->stopOnPonderhit = stop = false; - increaseDepth = true; - main()->ponder = ponderMode; - Search::Limits = limits; Search::RootMoves rootMoves; for (const auto& m : MoveList(pos)) @@ -191,7 +183,7 @@ void ThreadPool::start_thinking(Position& pos, rootMoves.emplace_back(m); if (!rootMoves.empty()) - Tablebases::rank_root_moves(pos, rootMoves); + Tablebases::rank_root_moves(options, pos, rootMoves); // After ownership transfer 'states' becomes empty, so if we stop the search // and call 'go' again without setting a new position states.get() == nullptr. @@ -207,15 +199,17 @@ void ThreadPool::start_thinking(Position& pos, // since they are read-only. for (Thread* th : threads) { - th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; - th->rootDepth = th->completedDepth = 0; - th->rootMoves = rootMoves; - th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); - th->rootState = setupStates->back(); - th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); + th->worker->limits = limits; + th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly = + th->worker->bestMoveChanges = 0; + th->worker->rootDepth = th->worker->completedDepth = 0; + th->worker->rootMoves = rootMoves; + th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); + th->worker->rootState = setupStates->back(); + th->worker->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); } - main()->start_searching(); + main_thread()->start_searching(); } Thread* ThreadPool::get_best_thread() const { @@ -226,30 +220,32 @@ Thread* ThreadPool::get_best_thread() const { // Find the minimum score of all threads for (Thread* th : threads) - minScore = std::min(minScore, th->rootMoves[0].score); + minScore = std::min(minScore, th->worker->rootMoves[0].score); // Vote according to score and depth, and select the best thread auto thread_value = [minScore](Thread* th) { - return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); + return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth); }; for (Thread* th : threads) - votes[th->rootMoves[0].pv[0]] += thread_value(th); + votes[th->worker->rootMoves[0].pv[0]] += thread_value(th); for (Thread* th : threads) - if (std::abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + if (std::abs(bestThread->worker->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) { // Make sure we pick the shortest mate / TB conversion or stave off mate the longest - if (th->rootMoves[0].score > bestThread->rootMoves[0].score) + if (th->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score) bestThread = th; } - else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY - || (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] - || (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] - && thread_value(th) * int(th->rootMoves[0].pv.size() > 2) + else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY + && (votes[th->worker->rootMoves[0].pv[0]] + > votes[bestThread->worker->rootMoves[0].pv[0]] + || (votes[th->worker->rootMoves[0].pv[0]] + == votes[bestThread->worker->rootMoves[0].pv[0]] + && thread_value(th) * int(th->worker->rootMoves[0].pv.size() > 2) > thread_value(bestThread) - * int(bestThread->rootMoves[0].pv.size() > 2))))) + * int(bestThread->worker->rootMoves[0].pv.size() > 2))))) bestThread = th; return bestThread; @@ -257,7 +253,7 @@ Thread* ThreadPool::get_best_thread() const { // Start non-main threads - +// Will be invoked by main thread after it has started searching void ThreadPool::start_searching() { for (Thread* th : threads) diff --git a/src/thread.h b/src/thread.h index 7db7c159..6575b14e 100644 --- a/src/thread.h +++ b/src/thread.h @@ -23,91 +23,76 @@ #include #include #include +#include #include #include -#include "movepick.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" -#include "types.h" namespace Stockfish { -// Thread class keeps together all the thread-related stuff. -class Thread { +class OptionsMap; +using Value = int; +// Abstraction of a thread. It contains a pointer to the worker and a native thread. +// After construction, the native thread is started with idle_loop() +// waiting for a signal to start searching. +// When the signal is received, the thread starts searching and when +// the search is finished, it goes back to idle_loop() waiting for a new signal. +class Thread { + public: + Thread(Search::SharedState&, std::unique_ptr, size_t); + virtual ~Thread(); + + void idle_loop(); + void start_searching(); + void wait_for_search_finished(); + size_t id() const { return idx; } + + std::unique_ptr worker; + + private: std::mutex mutex; std::condition_variable cv; - size_t idx; + size_t idx, nthreads; bool exit = false, searching = true; // Set before starting std::thread NativeThread stdThread; - - public: - explicit Thread(size_t); - virtual ~Thread(); - virtual void search(); - void clear(); - void idle_loop(); - void start_searching(); - void wait_for_search_finished(); - size_t id() const { return idx; } - - size_t pvIdx, pvLast; - std::atomic nodes, tbHits, bestMoveChanges; - int selDepth, nmpMinPly; - Value bestValue; - - int optimism[COLOR_NB]; - - Position rootPos; - StateInfo rootState; - Search::RootMoves rootMoves; - Depth rootDepth, completedDepth; - int rootDelta; - Value rootSimpleEval; - CounterMoveHistory counterMoves; - ButterflyHistory mainHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; - PawnHistory pawnHistory; - CorrectionHistory correctionHistory; -}; - - -// MainThread is a derived class specific for main thread -struct MainThread: public Thread { - - using Thread::Thread; - - void search() override; - void check_time(); - - double previousTimeReduction; - Value bestPreviousScore; - Value bestPreviousAverageScore; - Value iterValue[4]; - int callsCnt; - bool stopOnPonderhit; - std::atomic_bool ponder; }; // ThreadPool struct handles all the threads-related stuff like init, starting, // parking and, most importantly, launching a thread. All the access to threads // is done through this class. -struct ThreadPool { +class ThreadPool { - void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + public: + ~ThreadPool() { + // destroy any existing thread(s) + if (threads.size() > 0) + { + main_thread()->wait_for_search_finished(); + + while (threads.size() > 0) + delete threads.back(), threads.pop_back(); + } + } + + void + start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType, bool = false); void clear(); - void set(size_t); + void set(Search::SharedState); - MainThread* main() const { return static_cast(threads.front()); } - uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } - uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; + Search::SearchManager* main_manager() const { + return static_cast(main_thread()->worker.get()->manager.get()); + }; + Thread* main_thread() const { return threads.front(); } + uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); } + uint64_t tb_hits() const { return accumulate(&Search::Worker::tbHits); } + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; std::atomic_bool stop, increaseDepth; @@ -122,17 +107,15 @@ struct ThreadPool { StateListPtr setupStates; std::vector threads; - uint64_t accumulate(std::atomic Thread::*member) const { + uint64_t accumulate(std::atomic Search::Worker::*member) const { uint64_t sum = 0; for (Thread* th : threads) - sum += (th->*member).load(std::memory_order_relaxed); + sum += (th->worker.get()->*member).load(std::memory_order_relaxed); return sum; } }; -extern ThreadPool Threads; - } // namespace Stockfish #endif // #ifndef THREAD_H_INCLUDED diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 4bc62d67..8d424b72 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -30,31 +30,35 @@ #if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) #include + #include namespace Stockfish { -static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; - -template> -void* start_routine(void* ptr) { - P* p = reinterpret_cast(ptr); - (p->first->*(p->second))(); // Call member function pointer - delete p; +// free function to be passed to pthread_create() +inline void* start_routine(void* ptr) { + auto func = reinterpret_cast*>(ptr); + (*func)(); // Call the function + delete func; return nullptr; } class NativeThread { - pthread_t thread; + static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024; + public: - template> - explicit NativeThread(void (T::*fun)(), T* obj) { + template + explicit NativeThread(Function&& fun, Args&&... args) { + auto func = new std::function( + std::bind(std::forward(fun), std::forward(args)...)); + pthread_attr_t attr_storage, *attr = &attr_storage; pthread_attr_init(attr); pthread_attr_setstacksize(attr, TH_STACK_SIZE); - pthread_create(&thread, attr, start_routine, new P(obj, fun)); + pthread_create(&thread, attr, start_routine, func); } + void join() { pthread_join(thread, nullptr); } }; diff --git a/src/timeman.cpp b/src/timeman.cpp index 77db2f62..121f8edb 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -19,30 +19,47 @@ #include "timeman.h" #include +#include #include +#include #include "search.h" -#include "uci.h" +#include "ucioption.h" namespace Stockfish { -TimeManagement Time; // Our global time management object +TimePoint TimeManagement::optimum() const { return optimumTime; } +TimePoint TimeManagement::maximum() const { return maximumTime; } +TimePoint TimeManagement::elapsed(size_t nodes) const { + return useNodesTime ? TimePoint(nodes) : now() - startTime; +} + +void TimeManagement::clear() { + availableNodes = 0; // When in 'nodes as time' mode +} + +void TimeManagement::advance_nodes_time(std::int64_t nodes) { + assert(useNodesTime); + availableNodes += nodes; +} // Called at the beginning of the search and calculates // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) -void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - +void TimeManagement::init(Search::LimitsType& limits, + Color us, + int ply, + const OptionsMap& options) { // If we have no time, no need to initialize TM, except for the start time, // which is used by movetime. startTime = limits.startTime; if (limits.time[us] == 0) return; - TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); - TimePoint npmsec = TimePoint(Options["nodestime"]); + TimePoint moveOverhead = TimePoint(options["Move Overhead"]); + TimePoint npmsec = TimePoint(options["nodestime"]); // optScale is a percentage of available time to use for the current move. // maxScale is a multiplier applied to optimumTime. @@ -54,6 +71,8 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // must be much lower than the real engine speed. if (npmsec) { + useNodesTime = true; + if (!availableNodes) // Only once at game start availableNodes = npmsec * limits.time[us]; // Time is in msec @@ -100,7 +119,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { maximumTime = TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; - if (Options["Ponder"]) + if (options["Ponder"]) optimumTime += optimumTime / 4; } diff --git a/src/timeman.h b/src/timeman.h index 0509158c..b07712a2 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -19,35 +19,41 @@ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED +#include #include #include "misc.h" -#include "search.h" -#include "thread.h" #include "types.h" namespace Stockfish { +class OptionsMap; + +namespace Search { +struct LimitsType; +} + // The TimeManagement class computes the optimal time to think depending on // the maximum available time, the game move number, and other parameters. class TimeManagement { public: - void init(Search::LimitsType& limits, Color us, int ply); - TimePoint optimum() const { return optimumTime; } - TimePoint maximum() const { return maximumTime; } - TimePoint elapsed() const { - return Search::Limits.npmsec ? TimePoint(Threads.nodes_searched()) : now() - startTime; - } + void init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options); - int64_t availableNodes; // When in 'nodes as time' mode + TimePoint optimum() const; + TimePoint maximum() const; + TimePoint elapsed(std::size_t nodes) const; + + void clear(); + void advance_nodes_time(std::int64_t nodes); private: TimePoint startTime; TimePoint optimumTime; TimePoint maximumTime; -}; -extern TimeManagement Time; + std::int64_t availableNodes = 0; // When in 'nodes as time' mode + bool useNodesTime = false; // True if we are in 'nodes as time' mode +}; } // namespace Stockfish diff --git a/src/tt.cpp b/src/tt.cpp index 2e3f7d32..f3f58979 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -26,16 +26,13 @@ #include #include "misc.h" -#include "thread.h" -#include "uci.h" namespace Stockfish { -TranspositionTable TT; // Our global transposition table - // Populates the TTEntry with a new node's data, possibly // overwriting an old position. The update is not atomic and can be racy. -void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { +void TTEntry::save( + Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) { // Preserve any existing move for the same position if (m || uint16_t(k) != key16) @@ -49,7 +46,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) key16 = uint16_t(k); depth8 = uint8_t(d - DEPTH_OFFSET); - genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b); + genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b); value16 = int16_t(v); eval16 = int16_t(ev); } @@ -59,10 +56,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) // Sets the size of the transposition table, // measured in megabytes. Transposition table consists of a power of 2 number // of clusters and each cluster consists of ClusterSize number of TTEntry. -void TranspositionTable::resize(size_t mbSize) { - - Threads.main()->wait_for_search_finished(); - +void TranspositionTable::resize(size_t mbSize, int threadCount) { aligned_large_pages_free(table); clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); @@ -74,28 +68,25 @@ void TranspositionTable::resize(size_t mbSize) { exit(EXIT_FAILURE); } - clear(); + clear(threadCount); } // Initializes the entire transposition table to zero, // in a multi-threaded way. -void TranspositionTable::clear() { - +void TranspositionTable::clear(size_t threadCount) { std::vector threads; - for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) + for (size_t idx = 0; idx < size_t(threadCount); ++idx) { - threads.emplace_back([this, idx]() { + threads.emplace_back([this, idx, threadCount]() { // Thread binding gives faster search on systems with a first-touch policy - if (Options["Threads"] > 8) + if (threadCount > 8) WinProcGroup::bindThisThread(idx); // Each thread will zero its part of the hash table - const size_t stride = size_t(clusterCount / Options["Threads"]), - start = size_t(stride * idx), - len = - idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start; + const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx), + len = idx != size_t(threadCount) - 1 ? stride : clusterCount - start; std::memset(&table[start], 0, len * sizeof(Cluster)); }); diff --git a/src/tt.h b/src/tt.h index 61e854c1..4115ee7a 100644 --- a/src/tt.h +++ b/src/tt.h @@ -45,7 +45,7 @@ struct TTEntry { Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } bool is_pv() const { return bool(genBound8 & 0x4); } Bound bound() const { return Bound(genBound8 & 0x3); } - void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); private: friend class TranspositionTable; @@ -88,23 +88,23 @@ class TranspositionTable { void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things TTEntry* probe(const Key key, bool& found) const; int hashfull() const; - void resize(size_t mbSize); - void clear(); + void resize(size_t mbSize, int threadCount); + void clear(size_t threadCount); TTEntry* first_entry(const Key key) const { return &table[mul_hi64(key, clusterCount)].entry[0]; } + uint8_t generation() const { return generation8; } + private: friend struct TTEntry; size_t clusterCount; - Cluster* table; - uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 + Cluster* table = nullptr; + uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 }; -extern TranspositionTable TT; - } // namespace Stockfish #endif // #ifndef TT_H_INCLUDED diff --git a/src/tune.cpp b/src/tune.cpp index 1dddca0c..88b3b791 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -24,14 +24,15 @@ #include #include -#include "uci.h" +#include "ucioption.h" using std::string; namespace Stockfish { bool Tune::update_on_last; -const UCI::Option* LastOption = nullptr; +const Option* LastOption = nullptr; +OptionsMap* Tune::options; static std::map TuneResults; string Tune::next(string& names, bool pop) { @@ -53,13 +54,13 @@ string Tune::next(string& names, bool pop) { return name; } -static void on_tune(const UCI::Option& o) { +static void on_tune(const Option& o) { if (!Tune::update_on_last || LastOption == &o) Tune::read_options(); } -static void make_option(const string& n, int v, const SetRange& r) { +static void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) { // Do not generate option when there is nothing to tune (ie. min = max) if (r(v).first == r(v).second) @@ -68,8 +69,8 @@ static void make_option(const string& n, int v, const SetRange& r) { if (TuneResults.count(n)) v = TuneResults[n]; - Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); - LastOption = &Options[n]; + (*options)[n] << Option(v, r(v).first, r(v).second, on_tune); + LastOption = &((*options)[n]); // Print formatted parameters, ready to be copy-pasted in Fishtest std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," @@ -79,13 +80,13 @@ static void make_option(const string& n, int v, const SetRange& r) { template<> void Tune::Entry::init_option() { - make_option(name, value, range); + make_option(options, name, value, range); } template<> void Tune::Entry::read_option() { - if (Options.count(name)) - value = int(Options[name]); + if (options->count(name)) + value = int((*options)[name]); } // Instead of a variable here we have a PostUpdate function: just call it diff --git a/src/tune.h b/src/tune.h index 17057001..b88c085f 100644 --- a/src/tune.h +++ b/src/tune.h @@ -28,6 +28,8 @@ namespace Stockfish { +class OptionsMap; + using Range = std::pair; // Option's min-max values using RangeFun = Range(int); @@ -151,7 +153,8 @@ class Tune { return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis } - static void init() { + static void init(OptionsMap& o) { + options = &o; for (auto& e : instance().list) e->init_option(); read_options(); @@ -160,7 +163,9 @@ class Tune { for (auto& e : instance().list) e->read_option(); } - static bool update_on_last; + + static bool update_on_last; + static OptionsMap* options; }; // Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() diff --git a/src/uci.cpp b/src/uci.cpp index be902277..82fb25c1 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,111 +22,156 @@ #include #include #include -#include #include #include -#include #include #include #include -#include #include #include "benchmark.h" #include "evaluate.h" -#include "misc.h" #include "movegen.h" #include "nnue/evaluate_nnue.h" #include "nnue/nnue_architecture.h" #include "position.h" #include "search.h" -#include "thread.h" +#include "syzygy/tbprobe.h" +#include "types.h" +#include "ucioption.h" namespace Stockfish { -namespace { +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int NormalizeToPawnValue = 328; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; -// FEN string for the initial position in standard chess -const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +UCI::UCI(int argc, char** argv) : + cli(argc, argv) { + + evalFiles = {{Eval::NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None", ""}}, + {Eval::NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None", ""}}}; -// Called when the engine receives the "position" UCI command. -// It sets up the position that is described in the given FEN string ("fen") or -// the initial position ("startpos") and then makes the moves given in the following -// move list ("moves"). -void position(Position& pos, std::istringstream& is, StateListPtr& states) { + options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); - Move m; - std::string token, fen; + options["Threads"] << Option(1, 1, 1024, [this](const Option&) { + threads.set({options, threads, tt}); + }); - is >> token; + options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { + threads.main_thread()->wait_for_search_finished(); + tt.resize(o, options["Threads"]); + }); - if (token == "startpos") - { - fen = StartFEN; - is >> token; // Consume the "moves" token, if any - } - else if (token == "fen") - while (is >> token && token != "moves") - fen += token + " "; - else - return; + options["Clear Hash"] << Option(true, [this](const Option&) { search_clear(); }); + options["Ponder"] << Option(false); + options["MultiPV"] << Option(1, 1, 500); + 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(1320, 1320, 3190); + options["UCI_ShowWDL"] << Option(false); + options["SyzygyPath"] << Option("", [](const Option& o) { Tablebases::init(o); }); + options["SyzygyProbeDepth"] << Option(1, 1, 100); + options["Syzygy50MoveRule"] << Option(true); + options["SyzygyProbeLimit"] << Option(7, 0, 7); + options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) { + evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + }); - states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one - pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + threads.set({options, threads, tt}); - // Parse the move list, if any - while (is >> token && (m = UCI::to_move(pos, token)) != Move::none()) - { - states->emplace_back(); - pos.do_move(m, states->back()); - } + search_clear(); // After threads are up } -// Prints the evaluation of the current position, -// consistent with the UCI options set so far. -void trace_eval(Position& pos) { +void UCI::loop() { + Position pos; + std::string token, cmd; StateListPtr states(new std::deque(1)); - Position p; - p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); - Eval::NNUE::verify(); + pos.set(StartFEN, false, &states->back()); - sync_cout << "\n" << Eval::trace(p) << sync_endl; + for (int i = 1; i < cli.argc; ++i) + cmd += std::string(cli.argv[i]) + " "; + + do + { + if (cli.argc == 1 + && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + cmd = "quit"; + + std::istringstream is(cmd); + + token.clear(); // Avoid a stale if getline() returns nothing or a blank line + is >> std::skipws >> token; + + if (token == "quit" || token == "stop") + threads.stop = true; + + // The GUI sends 'ponderhit' to tell that the user has played the expected move. + // So, 'ponderhit' is sent if pondering was done on the same move that the user + // has played. The search should continue, but should also switch from pondering + // to the normal search. + else if (token == "ponderhit") + threads.main_manager()->ponder = false; // Switch to the normal search + + else if (token == "uci") + sync_cout << "id name " << engine_info(true) << "\n" + << options << "\nuciok" << sync_endl; + + else if (token == "setoption") + setoption(is); + else if (token == "go") + go(pos, is, states); + else if (token == "position") + position(pos, is, states); + else if (token == "ucinewgame") + search_clear(); + else if (token == "isready") + sync_cout << "readyok" << sync_endl; + + // Add custom non-UCI commands, mainly for debugging purposes. + // These commands must not be used during a search! + else if (token == "flip") + pos.flip(); + else if (token == "bench") + bench(pos, is, states); + else if (token == "d") + sync_cout << pos << sync_endl; + else if (token == "eval") + trace_eval(pos); + else if (token == "compiler") + sync_cout << compiler_info() << sync_endl; + else if (token == "export_net") + { + std::optional filename; + std::string f; + if (is >> std::skipws >> f) + filename = f; + Eval::NNUE::save_eval(filename, Eval::NNUE::Big, evalFiles); + } + else if (token == "--help" || token == "help" || token == "--license" || token == "license") + sync_cout + << "\nStockfish is a powerful chess engine for playing and analyzing." + "\nIt is released as free software licensed under the GNU GPLv3 License." + "\nStockfish is normally used with a graphical user interface (GUI) and implements" + "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." + "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" + "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" + << sync_endl; + else if (!token.empty() && token[0] != '#') + sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." + << sync_endl; + + } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } +void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { -// Called when the engine receives the "setoption" UCI command. -// The function updates the UCI option ("name") to the given value ("value"). - -void setoption(std::istringstream& is) { - - Threads.main()->wait_for_search_finished(); - - std::string token, name, value; - - is >> token; // Consume the "name" token - - // Read the option name (can contain spaces) - while (is >> token && token != "value") - name += (name.empty() ? "" : " ") + token; - - // Read the option value (can contain spaces) - while (is >> token) - value += (value.empty() ? "" : " ") + token; - - if (Options.count(name)) - Options[name] = value; - else - sync_cout << "No such option: " << name << sync_endl; -} - - -// Called when the engine receives the "go" UCI command. The function sets the -// thinking time and other parameters from the input string then stars with a search - -void go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits; std::string token; @@ -137,7 +182,7 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) { while (is >> token) if (token == "searchmoves") // Needs to be the last command on the line while (is >> token) - limits.searchmoves.push_back(UCI::to_move(pos, token)); + limits.searchmoves.push_back(to_move(pos, token)); else if (token == "wtime") is >> limits.time[WHITE]; @@ -164,16 +209,12 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) { else if (token == "ponder") ponderMode = true; - Threads.start_thinking(pos, states, limits, ponderMode); + Eval::NNUE::verify(options, evalFiles); + + threads.start_thinking(options, pos, states, limits, ponderMode); } - -// Called when the engine receives the "bench" command. -// First, a list of UCI commands is set up according to the bench -// parameters, then it is run one by one, printing a summary at the end. - -void bench(Position& pos, std::istream& args, StateListPtr& states) { - +void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { std::string token; uint64_t num, nodes = 0, cnt = 1; @@ -196,8 +237,8 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) { if (token == "go") { go(pos, is, states); - Threads.main()->wait_for_search_finished(); - nodes += Threads.nodes_searched(); + threads.main_thread()->wait_for_search_finished(); + nodes += threads.nodes_searched(); } else trace_eval(pos); @@ -208,9 +249,9 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) { position(pos, is, states); else if (token == "ucinewgame") { - Search::clear(); + search_clear(); // Search::clear() may take a while elapsed = now(); - } // Search::clear() may take a while + } } elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' @@ -222,6 +263,160 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) { << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; } +void UCI::trace_eval(Position& pos) { + StateListPtr states(new std::deque(1)); + Position p; + p.set(pos.fen(), options["UCI_Chess960"], &states->back()); + + Eval::NNUE::verify(options, evalFiles); + + sync_cout << "\n" << Eval::trace(p, *threads.main_thread()->worker.get()) << sync_endl; +} + +void UCI::search_clear() { + threads.main_thread()->wait_for_search_finished(); + + tt.clear(options["Threads"]); + threads.clear(); + Tablebases::init(options["SyzygyPath"]); // Free mapped files +} + +void UCI::setoption(std::istringstream& is) { + threads.main_thread()->wait_for_search_finished(); + options.setoption(is); +} + +void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) { + Move m; + std::string token, fen; + + is >> token; + + if (token == "startpos") + { + fen = StartFEN; + is >> token; // Consume the "moves" token, if any + } + else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + + states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one + pos.set(fen, options["UCI_Chess960"], &states->back()); + + // Parse the move list, if any + while (is >> token && (m = to_move(pos, token)) != Move::none()) + { + states->emplace_back(); + pos.do_move(m, states->back()); + } +} + +int UCI::to_cp(Value v) { return 100 * v / NormalizeToPawnValue; } + +std::string UCI::value(Value v) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + + std::stringstream ss; + + if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + ss << "cp " << to_cp(v); + else if (std::abs(v) <= VALUE_TB) + { + const int ply = VALUE_TB - std::abs(v); // recompute ss->ply + ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); + } + else + ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; + + return ss.str(); +} + +std::string UCI::square(Square s) { + return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; +} + +std::string UCI::move(Move m, bool chess960) { + if (m == Move::none()) + return "(none)"; + + if (m == Move::null()) + return "0000"; + + Square from = m.from_sq(); + Square to = m.to_sq(); + + if (m.type_of() == CASTLING && !chess960) + to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); + + std::string move = square(from) + square(to); + + if (m.type_of() == PROMOTION) + move += " pnbrqk"[m.promotion_type()]; + + return move; +} + +std::string UCI::pv(const Search::Worker& workerThread, + TimePoint elapsed, + uint64_t nodesSearched, + uint64_t tb_hits, + int hashfull, + bool rootInTB) { + std::stringstream ss; + TimePoint time = elapsed + 1; + const auto& rootMoves = workerThread.rootMoves; + const auto& depth = workerThread.completedDepth; + const auto& pos = workerThread.rootPos; + size_t pvIdx = workerThread.pvIdx; + size_t multiPV = std::min(size_t(workerThread.options["MultiPV"]), rootMoves.size()); + uint64_t tbHits = tb_hits + (rootInTB ? rootMoves.size() : 0); + + + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; + + if (depth == 1 && !updated && i > 0) + continue; + + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; + + bool tb = rootInTB && std::abs(v) <= VALUE_TB; + v = tb ? rootMoves[i].tbScore : v; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + ss << "info" + << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 + << " score " << value(v); + + if (workerThread.options["UCI_ShowWDL"]) + ss << wdl(v, pos.game_ply()); + + if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + ss << (rootMoves[i].scoreLowerbound + ? " lowerbound" + : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + + ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / time << " hashfull " + << hashfull << " tbhits " << tbHits << " time " << time << " pv"; + + for (Move m : rootMoves[i].pv) + ss << " " << move(m, pos.is_chess960()); + } + + return ss.str(); +} + +namespace { // The win rate model returns the probability of winning (in per mille units) given an // eval and a game ply. It fits the LTC fishtest statistics rather accurately. int win_rate_model(Value v, int ply) { @@ -236,7 +431,7 @@ int win_rate_model(Value v, int ply) { constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330}; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 - static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); + static_assert(NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; @@ -247,132 +442,9 @@ int win_rate_model(Value v, int ply) { // Return the win rate in per mille units, rounded to the nearest integer return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); } - -} // namespace - - -// Waits for a command from the stdin, parses it, and then calls the appropriate -// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a -// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, -// like running 'bench', the function returns immediately after the command is executed. -// In addition to the UCI ones, some additional debug commands are also supported. -void UCI::loop(int argc, char* argv[]) { - - Position pos; - std::string token, cmd; - StateListPtr states(new std::deque(1)); - - pos.set(StartFEN, false, &states->back(), Threads.main()); - - for (int i = 1; i < argc; ++i) - cmd += std::string(argv[i]) + " "; - - do - { - if (argc == 1 - && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication - cmd = "quit"; - - std::istringstream is(cmd); - - token.clear(); // Avoid a stale if getline() returns nothing or a blank line - is >> std::skipws >> token; - - if (token == "quit" || token == "stop") - Threads.stop = true; - - // The GUI sends 'ponderhit' to tell that the user has played the expected move. - // So, 'ponderhit' is sent if pondering was done on the same move that the user - // has played. The search should continue, but should also switch from pondering - // to the normal search. - else if (token == "ponderhit") - Threads.main()->ponder = false; // Switch to the normal search - - else if (token == "uci") - sync_cout << "id name " << engine_info(true) << "\n" - << Options << "\nuciok" << sync_endl; - - else if (token == "setoption") - setoption(is); - else if (token == "go") - go(pos, is, states); - else if (token == "position") - position(pos, is, states); - else if (token == "ucinewgame") - Search::clear(); - else if (token == "isready") - sync_cout << "readyok" << sync_endl; - - // Add custom non-UCI commands, mainly for debugging purposes. - // These commands must not be used during a search! - else if (token == "flip") - pos.flip(); - else if (token == "bench") - bench(pos, is, states); - else if (token == "d") - sync_cout << pos << sync_endl; - else if (token == "eval") - trace_eval(pos); - else if (token == "compiler") - sync_cout << compiler_info() << sync_endl; - else if (token == "export_net") - { - std::optional filename; - std::string f; - if (is >> std::skipws >> f) - filename = f; - Eval::NNUE::save_eval(filename, Eval::NNUE::Big); - } - else if (token == "--help" || token == "help" || token == "--license" || token == "license") - sync_cout - << "\nStockfish is a powerful chess engine for playing and analyzing." - "\nIt is released as free software licensed under the GNU GPLv3 License." - "\nStockfish is normally used with a graphical user interface (GUI) and implements" - "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." - "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" - "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" - << sync_endl; - else if (!token.empty() && token[0] != '#') - sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." - << sync_endl; - - } while (token != "quit" && argc == 1); // The command-line arguments are one-shot } - -// Turns a Value to an integer centipawn number, -// without treatment of mate and similar special scores. -int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; } - -// Converts a Value to a string by adhering to the UCI protocol specification: -// -// cp The score from the engine's point of view in centipawns. -// mate Mate in 'y' moves (not plies). If the engine is getting mated, -// uses negative values for 'y'. -std::string UCI::value(Value v) { - - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - - std::stringstream ss; - - if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << UCI::to_cp(v); - else if (std::abs(v) <= VALUE_TB) - { - const int ply = VALUE_TB - std::abs(v); // recompute ss->ply - ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); - } - else - ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; - - return ss.str(); -} - - -// Reports the win-draw-loss (WDL) statistics given an evaluation -// and a game ply based on the data gathered for fishtest LTC games. std::string UCI::wdl(Value v, int ply) { - std::stringstream ss; int wdl_w = win_rate_model(v, ply); @@ -383,49 +455,12 @@ std::string UCI::wdl(Value v, int ply) { return ss.str(); } - -// Converts a Square to a string in algebraic notation (g1, a7, etc.) -std::string UCI::square(Square s) { - return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; -} - - -// Converts a Move to a string in coordinate notation (g1f3, a7a8q). -// The only special case is castling where the e1g1 notation is printed in -// standard chess mode and in e1h1 notation it is printed in Chess960 mode. -// Internally, all castling moves are always encoded as 'king captures rook'. -std::string UCI::move(Move m, bool chess960) { - - if (m == Move::none()) - return "(none)"; - - if (m == Move::null()) - return "0000"; - - Square from = m.from_sq(); - Square to = m.to_sq(); - - if (m.type_of() == CASTLING && !chess960) - to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - - std::string move = UCI::square(from) + UCI::square(to); - - if (m.type_of() == PROMOTION) - move += " pnbrqk"[m.promotion_type()]; - - return move; -} - - -// Converts a string representing a move in coordinate notation -// (g1f3, a7a8q) to the corresponding legal Move, if any. Move UCI::to_move(const Position& pos, std::string& str) { - if (str.length() == 5) str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased for (const auto& m : MoveList(pos)) - if (str == UCI::move(m, pos.is_chess960())) + if (str == move(m, pos.is_chess960())) return m; return Move::none(); diff --git a/src/uci.h b/src/uci.h index d249da74..cd113b1a 100644 --- a/src/uci.h +++ b/src/uci.h @@ -19,77 +19,70 @@ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED -#include -#include -#include +#include +#include #include +#include -#include "types.h" +#include "evaluate.h" +#include "misc.h" +#include "position.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" namespace Stockfish { -class Position; +namespace Eval::NNUE { +enum NetSize : int; +} -namespace UCI { +namespace Search { +class Worker; +} -// Normalizes the internal value as reported by evaluate or search -// to the UCI centipawn result used in output. This value is derived from -// the win_rate_model() such that Stockfish outputs an advantage of -// "100 centipawns" for a position if the engine has a 50% probability to win -// from this position in self-play at fishtest LTC time control. -const int NormalizeToPawnValue = 328; - -class Option; - -// Define a custom comparator, because the UCI options should be case-insensitive -struct CaseInsensitiveLess { - bool operator()(const std::string&, const std::string&) const; -}; - -// The options container is defined as a std::map -using OptionsMap = std::map; - -// The Option class implements each option as specified by the UCI protocol -class Option { - - using OnChange = void (*)(const Option&); +class Move; +enum Square : int; +using Value = int; +class UCI { public: - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char* v, OnChange = nullptr); - Option(double v, int minv, int maxv, OnChange = nullptr); - Option(const char* v, const char* cur, OnChange = nullptr); + UCI(int argc, char** argv); - Option& operator=(const std::string&); - void operator<<(const Option&); - operator int() const; - operator std::string() const; - bool operator==(const char*) const; + void loop(); + + static int to_cp(Value v); + static std::string value(Value v); + static std::string square(Square s); + static std::string move(Move m, bool chess960); + static std::string pv(const Search::Worker& workerThread, + TimePoint elapsed, + uint64_t nodesSearched, + uint64_t tb_hits, + int hashfull, + bool rootInTB); + static std::string wdl(Value v, int ply); + static Move to_move(const Position& pos, std::string& str); + + const std::string& workingDirectory() const { return cli.workingDirectory; } + + OptionsMap options; + + std::unordered_map evalFiles; private: - friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + TranspositionTable tt; + ThreadPool threads; + CommandLine cli; - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; + void go(Position& pos, std::istringstream& is, StateListPtr& states); + void bench(Position& pos, std::istream& args, StateListPtr& states); + void position(Position& pos, std::istringstream& is, StateListPtr& states); + void trace_eval(Position& pos); + void search_clear(); + void setoption(std::istringstream& is); }; -void init(OptionsMap&); -void loop(int argc, char* argv[]); -int to_cp(Value v); -std::string value(Value v); -std::string square(Square s); -std::string move(Move m, bool chess960); -std::string pv(const Position& pos, Depth depth); -std::string wdl(Value v, int ply); -Move to_move(const Position& pos, std::string& str); - -} // namespace UCI - -extern UCI::OptionsMap Options; - } // namespace Stockfish #endif // #ifndef UCI_H_INCLUDED diff --git a/src/ucioption.cpp b/src/ucioption.cpp index f8cbcc53..c7de7e3f 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -16,104 +16,53 @@ along with this program. If not, see . */ +#include "ucioption.h" + #include #include #include -#include -#include -#include -#include -#include +#include #include -#include +#include -#include "evaluate.h" #include "misc.h" -#include "search.h" -#include "syzygy/tbprobe.h" -#include "thread.h" -#include "tt.h" -#include "types.h" -#include "uci.h" - -using std::string; namespace Stockfish { -UCI::OptionsMap Options; // Global object +bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const { -namespace UCI { - -// 'On change' actions, triggered by an option's value change -static void on_clear_hash(const Option&) { Search::clear(); } -static void on_hash_size(const Option& o) { TT.resize(size_t(o)); } -static void on_logger(const Option& o) { start_logger(o); } -static void on_threads(const Option& o) { Threads.set(size_t(o)); } -static void on_tb_path(const Option& o) { Tablebases::init(o); } -static void on_eval_file(const Option&) { Eval::NNUE::init(); } - -// Our case insensitive less() function as required by UCI protocol -bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const { - - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return tolower(c1) < tolower(c2); }); + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); } +void OptionsMap::setoption(std::istringstream& is) { + std::string token, name, value; -// Initializes the UCI options to their hard-coded default values -void init(OptionsMap& o) { + is >> token; // Consume the "name" token - constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; - o["Debug Log File"] << Option("", on_logger); - o["Threads"] << Option(1, 1, 1024, on_threads); - o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); - o["Clear Hash"] << Option(on_clear_hash); - o["Ponder"] << Option(false); - o["MultiPV"] << Option(1, 1, MAX_MOVES); - o["Skill Level"] << Option(20, 0, 20); - o["Move Overhead"] << Option(10, 0, 5000); - o["nodestime"] << Option(0, 0, 10000); - o["UCI_Chess960"] << Option(false); - o["UCI_LimitStrength"] << Option(false); - o["UCI_Elo"] << Option(1320, 1320, 3190); - o["UCI_ShowWDL"] << Option(false); - o["SyzygyPath"] << Option("", on_tb_path); - o["SyzygyProbeDepth"] << Option(1, 1, 100); - o["Syzygy50MoveRule"] << Option(true); - o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["EvalFile"] << Option(EvalFileDefaultNameBig, on_eval_file); - // Enable this after fishtest workers support EvalFileSmall - // o["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, on_eval_file); + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; + + if (options_map.count(name)) + options_map[name] = value; + else + sync_cout << "No such option: " << name << sync_endl; } - -// Used to print all the options default values in chronological -// insertion order (the idx field) and in the format defined by the UCI protocol. -std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { - - for (size_t idx = 0; idx < om.size(); ++idx) - for (const auto& it : om) - if (it.second.idx == idx) - { - const Option& o = it.second; - os << "\noption name " << it.first << " type " << o.type; - - if (o.type == "string" || o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; - - if (o.type == "spin") - os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " - << o.max; - - break; - } - - return os; +Option OptionsMap::operator[](const std::string& name) const { + auto it = options_map.find(name); + return it != options_map.end() ? it->second : Option(); } +Option& OptionsMap::operator[](const std::string& name) { return options_map[name]; } -// Option class constructors and conversion operators +std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } Option::Option(const char* v, OnChange f) : type("string"), @@ -184,19 +133,19 @@ void Option::operator<<(const Option& o) { // 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. -Option& Option::operator=(const string& v) { +Option& Option::operator=(const std::string& v) { assert(!type.empty()); if ((type != "button" && type != "string" && v.empty()) || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (stof(v) < min || stof(v) > max))) + || (type == "spin" && (std::stof(v) < min || std::stof(v) > max))) return *this; if (type == "combo") { OptionsMap comboMap; // To have case insensitive compare - string token; + std::string token; std::istringstream ss(defaultValue); while (ss >> token) comboMap[token] << Option(); @@ -213,6 +162,24 @@ Option& Option::operator=(const string& v) { return *this; } -} // namespace UCI +std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto& it : om.options_map) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; -} // namespace Stockfish + if (o.type == "string" || o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; + + if (o.type == "spin") + os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " + << o.max; + + break; + } + + return os; +} +} diff --git a/src/ucioption.h b/src/ucioption.h new file mode 100644 index 00000000..b575d164 --- /dev/null +++ b/src/ucioption.h @@ -0,0 +1,81 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UCIOPTION_H_INCLUDED +#define UCIOPTION_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace Stockfish { +// Define a custom comparator, because the UCI options should be case-insensitive +struct CaseInsensitiveLess { + bool operator()(const std::string&, const std::string&) const; +}; + +class Option; + +class OptionsMap { + public: + void setoption(std::istringstream&); + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + Option operator[](const std::string&) const; + Option& operator[](const std::string&); + + std::size_t count(const std::string&) const; + + private: + // The options container is defined as a std::map + using OptionsStore = std::map; + + OptionsStore options_map; +}; + +// The Option class implements each option as specified by the UCI protocol +class Option { + public: + using OnChange = std::function; + + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(double v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char* cur, OnChange = nullptr); + + Option& operator=(const std::string&); + void operator<<(const Option&); + operator int() const; + operator std::string() const; + bool operator==(const char*) const; + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + private: + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; +}; + +} +#endif // #ifndef UCIOPTION_H_INCLUDED From 3372ee9c2671dedcbf70ad6a744ef0825eaaba94 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 11 Jan 2024 13:25:01 +0300 Subject: [PATCH 532/678] Remove threatenedByPawn term for queen threats Passed STC: https://tests.stockfishchess.org/tests/view/659d614c79aa8af82b9677d0 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 151776 W: 38690 L: 38597 D: 74489 Ptnml(0-2): 522, 17841, 39015, 18042, 468 Passed LTC: https://tests.stockfishchess.org/tests/view/659d94d379aa8af82b967cb2 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 91908 W: 23075 L: 22924 D: 45909 Ptnml(0-2): 70, 10311, 25037, 10470, 66 closes https://github.com/official-stockfish/Stockfish/pull/4977 Bench: 1266493 --- src/movepick.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 6a562996..c6532520 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -198,15 +198,15 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= !(threatenedPieces & from) - ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 - + bool(to & threatenedByMinor) * 10000 - + bool(to & threatenedByPawn) * 20000 - : pt == ROOK ? bool(to & threatenedByMinor) * 25000 - + bool(to & threatenedByPawn) * 10000 - : pt != PAWN ? bool(to & threatenedByPawn) * 15000 - : 0) - : 0; + m.value -= + !(threatenedPieces & from) + ? (pt == QUEEN + ? bool(to & threatenedByRook) * 50000 + bool(to & threatenedByMinor) * 10000 + : pt == ROOK + ? bool(to & threatenedByMinor) * 25000 + bool(to & threatenedByPawn) * 10000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0) + : 0; } else // Type == EVASIONS From eec361f64c7d28ff3a5add64bddc5a96800f7fba Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 8 Jan 2024 01:03:38 -0800 Subject: [PATCH 533/678] Simplify bad quiets The main difference is that instead of returning the first bad quiet as a good one we fall through. This is actually more correct and simpler to implement. Non regression STC: https://tests.stockfishchess.org/tests/view/659bbb3479aa8af82b964ec7 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 150944 W: 38399 L: 38305 D: 74240 Ptnml(0-2): 485, 18042, 38298, 18188, 459 Non regression LTC: https://tests.stockfishchess.org/tests/view/659c6e6279aa8af82b9660eb LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 192060 W: 47871 L: 47823 D: 96366 Ptnml(0-2): 144, 21912, 51845, 22010, 119 The cutoff is now -8K instead of -7.5K. -7.5K failed. https://tests.stockfishchess.org/tests/view/659a1f4b79aa8af82b962a0e This was likely a false negative. closes https://github.com/official-stockfish/Stockfish/pull/4975 Bench: 1308279 --- src/movepick.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index c6532520..e521689e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -312,19 +312,11 @@ top: return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) { - Move tmp = *(cur - 1); - if ((cur - 1)->value < -7500 && (cur - 1)->value > quiet_threshold(depth)) - { - // Remaining quiets are bad - beginBadQuiets = cur; + if ((cur - 1)->value > -8000 || (cur - 1)->value <= quiet_threshold(depth)) + return *(cur - 1); - // Prepare the pointers to loop over the bad captures - cur = moves; - endMoves = endBadCaptures; - - ++stage; - } - return tmp; + // Remaining quiets are bad + beginBadQuiets = cur - 1; } // Prepare the pointers to loop over the bad captures From cf5b070913755e2aac2a6e827e7abaa91c0b1d35 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 00:30:03 +0100 Subject: [PATCH 534/678] Remove unused method init() is no longer used, and was previously replaced by the clear function. fixes https://github.com/official-stockfish/Stockfish/issues/4981 No functional change --- src/search.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.h b/src/search.h index 48a5630c..90ed82b9 100644 --- a/src/search.h +++ b/src/search.h @@ -48,8 +48,6 @@ class UCI; namespace Search { -// Called at startup to initialize various lookup tables, after program startup -void init(int); // Stack struct keeps track of the information we need to remember from nodes // shallower and deeper in the tree during the search. Each search thread has @@ -176,6 +174,7 @@ class Worker { public: Worker(SharedState&, std::unique_ptr, size_t); + // Called at instantiation to initialize Reductions tables // Reset histories, usually before a new game void clear(); From 12e97701b299a2abfaa86d67b670411a39e5ccce Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 10:39:57 +0100 Subject: [PATCH 535/678] Fix UCI options Fixes the type for 'Clear Hash' and uses MAX_MOVES for 'MultiPV' as we had before. No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 82fb25c1..aa493c63 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -64,9 +64,9 @@ UCI::UCI(int argc, char** argv) : tt.resize(o, options["Threads"]); }); - options["Clear Hash"] << Option(true, [this](const Option&) { search_clear(); }); + options["Clear Hash"] << Option([this](const Option&) { search_clear(); }); options["Ponder"] << Option(false); - options["MultiPV"] << Option(1, 1, 500); + 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); From 88331add0d1220068f1bc1c0e1db88598425dafc Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 13 Jan 2024 20:19:33 +0100 Subject: [PATCH 536/678] Remove the dependency on a Worker from evaluate Also remove dead code, `rootSimpleEval` is no longer used since the introduction of dual net. `iterBestValue` is also no longer used in evaluate and can be reduced to a local variable. closes https://github.com/official-stockfish/Stockfish/pull/4979 No functional change --- src/evaluate.cpp | 15 +++------------ src/evaluate.h | 8 ++------ src/search.cpp | 38 ++++++++++++++++++++------------------ src/search.h | 6 ++---- src/thread.cpp | 4 +--- src/uci.cpp | 2 +- 6 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3e067e4c..45658798 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -35,7 +35,6 @@ #include "nnue/evaluate_nnue.h" #include "nnue/nnue_architecture.h" #include "position.h" -#include "search.h" #include "types.h" #include "uci.h" #include "ucioption.h" @@ -196,7 +195,7 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) { +Value Eval::evaluate(const Position& pos, int optimism) { assert(!pos.checkers()); @@ -217,8 +216,6 @@ Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) { Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) : NNUE::evaluate(pos, true, &nnueComplexity); - int optimism = workerThread.optimism[stm]; - // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; @@ -240,17 +237,11 @@ Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) { // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos, Search::Worker& workerThread) { +std::string Eval::trace(Position& pos) { if (pos.checkers()) return "Final evaluation: none (in check)"; - // Reset any global variable used in eval - workerThread.iterBestValue = VALUE_ZERO; - workerThread.rootSimpleEval = VALUE_ZERO; - workerThread.optimism[WHITE] = VALUE_ZERO; - workerThread.optimism[BLACK] = VALUE_ZERO; - std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); ss << '\n' << NNUE::trace(pos) << '\n'; @@ -262,7 +253,7 @@ std::string Eval::trace(Position& pos, Search::Worker& workerThread) { v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos, workerThread); + v = evaluate(pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index 8a9d6fc7..729baa6b 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,16 +29,12 @@ namespace Stockfish { class Position; class OptionsMap; -namespace Search { -class Worker; -} - namespace Eval { -std::string trace(Position& pos, Search::Worker& workerThread); +std::string trace(Position& pos); int simple_eval(const Position& pos, Color c); -Value evaluate(const Position& pos, const Search::Worker& workerThread); +Value evaluate(const Position& pos, int optimism); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/search.cpp b/src/search.cpp index 5530d125..b23740d8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -280,7 +280,7 @@ void Search::Worker::iterative_deepening() { ss->pv = pv; - iterBestValue = -VALUE_INFINITE; + Value bestValue = -VALUE_INFINITE; if (mainThread) { @@ -357,7 +357,7 @@ void Search::Worker::iterative_deepening() { // for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - iterBestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); + bestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -375,7 +375,7 @@ void Search::Worker::iterative_deepening() { // When failing high/low give some update (without cluttering // the UI) before a re-search. - if (mainThread && multiPV == 1 && (iterBestValue <= alpha || iterBestValue >= beta) + if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), @@ -384,18 +384,18 @@ void Search::Worker::iterative_deepening() { // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. - if (iterBestValue <= alpha) + if (bestValue <= alpha) { beta = (alpha + beta) / 2; - alpha = std::max(iterBestValue - delta, -VALUE_INFINITE); + alpha = std::max(bestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; if (mainThread) mainThread->stopOnPonderhit = false; } - else if (iterBestValue >= beta) + else if (bestValue >= beta) { - beta = std::min(iterBestValue + delta, int(VALUE_INFINITE)); + beta = std::min(bestValue + delta, int(VALUE_INFINITE)); ++failedHighCnt; } else @@ -428,8 +428,8 @@ void Search::Worker::iterative_deepening() { } // Have we found a "mate in x"? - if (limits.mate && iterBestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - iterBestValue <= 2 * limits.mate) + if (limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * limits.mate) threads.stop = true; if (!mainThread) @@ -449,8 +449,8 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - iterBestValue) - + 6 * (mainThread->iterValue[iterIdx] - iterBestValue)) + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 616.6; fallingEval = std::clamp(fallingEval, 0.51, 1.51); @@ -483,7 +483,7 @@ void Search::Worker::iterative_deepening() { threads.increaseDepth = true; } - mainThread->iterValue[iterIdx] = iterBestValue; + mainThread->iterValue[iterIdx] = bestValue; iterIdx = (iterIdx + 1) & 3; } @@ -580,7 +580,7 @@ Value Search::Worker::search( // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, *thisThread) + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score @@ -734,7 +734,7 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, *thisThread); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); @@ -752,7 +752,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, *thisThread); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); Value newEval = ss->staticEval @@ -1470,7 +1470,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, *thisThread) : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) + : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1501,7 +1502,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // Never assume anything about values stored in TT if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos, *thisThread); + unadjustedStaticEval = ss->staticEval = bestValue = + evaluate(pos, thisThread->optimism[us]); Value newEval = ss->staticEval @@ -1521,7 +1523,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != Move::null() ? evaluate(pos, *thisThread) + (ss - 1)->currentMove != Move::null() ? evaluate(pos, thisThread->optimism[us]) : -(ss - 1)->staticEval; Value newEval = diff --git a/src/search.h b/src/search.h index 90ed82b9..68c9f2aa 100644 --- a/src/search.h +++ b/src/search.h @@ -184,10 +184,6 @@ class Worker { bool is_mainthread() const { return thread_idx == 0; } - // Public because evaluate uses this - Value iterBestValue, optimism[COLOR_NB]; - Value rootSimpleEval; - // Public because they need to be updatable by the stats CounterMoveHistory counterMoves; ButterflyHistory mainHistory; @@ -226,6 +222,8 @@ class Worker { std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; + Value optimism[COLOR_NB]; + Position rootPos; StateInfo rootState; RootMoves rootMoves; diff --git a/src/thread.cpp b/src/thread.cpp index a512c0a5..9dc4446f 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -27,7 +27,6 @@ #include #include -#include "evaluate.h" #include "misc.h" #include "movegen.h" #include "search.h" @@ -205,8 +204,7 @@ void ThreadPool::start_thinking(const OptionsMap& options, th->worker->rootDepth = th->worker->completedDepth = 0; th->worker->rootMoves = rootMoves; th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); - th->worker->rootState = setupStates->back(); - th->worker->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); + th->worker->rootState = setupStates->back(); } main_thread()->start_searching(); diff --git a/src/uci.cpp b/src/uci.cpp index aa493c63..789f3454 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -270,7 +270,7 @@ void UCI::trace_eval(Position& pos) { Eval::NNUE::verify(options, evalFiles); - sync_cout << "\n" << Eval::trace(p, *threads.main_thread()->worker.get()) << sync_endl; + sync_cout << "\n" << Eval::trace(p) << sync_endl; } void UCI::search_clear() { From b5e8169a85f6937d7d9d90612863fe5eec72d6ca Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 13 Jan 2024 21:35:02 +0100 Subject: [PATCH 537/678] Add ignoreRevsFile to CONTRIBUTING.md closes https://github.com/official-stockfish/Stockfish/pull/4980 No functional change --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e72e1db..e589b4fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,6 +61,16 @@ Changes to Stockfish C++ code should respect our coding style defined by [.clang-format](.clang-format). You can format your changes by running `make format`. This requires clang-format version 17 to be installed on your system. +## Navigate + +For experienced Git users who frequently use git blame, it is recommended to +configure the blame.ignoreRevsFile setting. +This setting is useful for excluding noisy formatting commits. + +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` + ## Community and Communication - Join the [Stockfish discord][discord-link] to discuss ideas, issues, and From 32e46fc47f521d2b93f96c63267fc0bda26332dc Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 8 Jan 2024 23:20:23 -0800 Subject: [PATCH 538/678] Remove some outdated SIMD functions Since https://github.com/official-stockfish/Stockfish/pull/4391 the x2 SIMD functions no longer serve any useful purpose. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/659cf42579aa8af82b966d55 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 67392 W: 17222 L: 17037 D: 33133 Ptnml(0-2): 207, 7668, 17762, 7851, 208 closes https://github.com/official-stockfish/Stockfish/pull/4974 No functional change --- src/nnue/layers/affine_transform.h | 19 +++-------- src/nnue/layers/simd.h | 54 ------------------------------ 2 files changed, 5 insertions(+), 68 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index e6852236..ad9167c0 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -200,21 +200,18 @@ class AffineTransform { #define vec_setzero _mm512_setzero_si512 #define vec_set_32 _mm512_set1_epi32 #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 #define vec_hadd Simd::m512_hadd #elif defined(USE_AVX2) using vec_t = __m256i; #define vec_setzero _mm256_setzero_si256 #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_setzero _mm_setzero_si128 #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 #define vec_hadd Simd::m128_hadd #endif @@ -231,16 +228,14 @@ class AffineTransform { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = biasvec[k]; - for (IndexType i = 0; i < NumChunks; i += 2) + for (IndexType i = 0; i < NumChunks; ++i) { - const vec_t in0 = vec_set_32(input32[i + 0]); - const vec_t in1 = vec_set_32(input32[i + 1]); + const vec_t in0 = vec_set_32(input32[i]); const auto col0 = - reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); - const auto col1 = - reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); + reinterpret_cast(&weights[i * OutputDimensions * 4]); + for (IndexType k = 0; k < NumRegs; ++k) - vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]); + vec_add_dpbusd_32(acc[k], in0, col0[k]); } vec_t* outptr = reinterpret_cast(output); @@ -250,7 +245,6 @@ class AffineTransform { #undef vec_setzero #undef vec_set_32 #undef vec_add_dpbusd_32 - #undef vec_add_dpbusd_32x2 #undef vec_hadd } else if constexpr (OutputDimensions == 1) @@ -263,14 +257,12 @@ class AffineTransform { #define vec_setzero _mm256_setzero_si256 #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_setzero _mm_setzero_si128 #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 #define vec_hadd Simd::m128_hadd #endif @@ -294,7 +286,6 @@ class AffineTransform { #undef vec_setzero #undef vec_set_32 #undef vec_add_dpbusd_32 - #undef vec_add_dpbusd_32x2 #undef vec_hadd } #else diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 6f4c9d20..cec41474 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -87,21 +87,6 @@ m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum #endif } -[[maybe_unused]] static void -m512_add_dpbusd_epi32x2(__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1) { - - #if defined(USE_VNNI) - acc = _mm512_dpbusd_epi32(acc, a0, b0); - acc = _mm512_dpbusd_epi32(acc, a1, b1); - #else - __m512i product0 = _mm512_maddubs_epi16(a0, b0); - __m512i product1 = _mm512_maddubs_epi16(a1, b1); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); - #endif -} - #endif #if defined(USE_AVX2) @@ -124,21 +109,6 @@ m512_add_dpbusd_epi32x2(__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512 #endif } -[[maybe_unused]] static void -m256_add_dpbusd_epi32x2(__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1) { - - #if defined(USE_VNNI) - acc = _mm256_dpbusd_epi32(acc, a0, b0); - acc = _mm256_dpbusd_epi32(acc, a1, b1); - #else - __m256i product0 = _mm256_maddubs_epi16(a0, b0); - __m256i product1 = _mm256_maddubs_epi16(a1, b1); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); - #endif -} - #endif #if defined(USE_SSSE3) @@ -156,27 +126,10 @@ m256_add_dpbusd_epi32x2(__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256 acc = _mm_add_epi32(acc, product0); } -[[maybe_unused]] static void -m128_add_dpbusd_epi32x2(__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1) { - - __m128i product0 = _mm_maddubs_epi16(a0, b0); - __m128i product1 = _mm_maddubs_epi16(a1, b1); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); -} - #endif #if defined(USE_NEON_DOTPROD) -[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( - int32x4_t& acc, int8x16_t a0, int8x16_t b0, int8x16_t a1, int8x16_t b1) { - - acc = vdotq_s32(acc, a0, b0); - acc = vdotq_s32(acc, a1, b1); -} - [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { @@ -198,13 +151,6 @@ dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { return neon_m128_reduce_add_epi32(sum) + bias; } -[[maybe_unused]] static void -neon_m128_add_dpbusd_epi32x2(int32x4_t& acc, int8x8_t a0, int8x8_t b0, int8x8_t a1, int8x8_t b1) { - - int16x8_t product = vmull_s8(a0, b0); - product = vmlal_s8(product, a1, b1); - acc = vpadalq_s16(acc, product); -} #endif #if USE_NEON >= 8 From a5675f19d87a15a5e599a108dfaa2d08ac50d6a5 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 14:29:12 +0100 Subject: [PATCH 539/678] Remove global TB variables from search.cpp Follow up cleanup of #4968, removes the global variables from search and instead uses a dedicated tb config struct. closes https://github.com/official-stockfish/Stockfish/pull/4982 No functional change --- src/bitboard.cpp | 6 ++-- src/search.cpp | 73 +++++------------------------------------- src/search.h | 4 ++- src/syzygy/tbprobe.cpp | 59 +++++++++++++++++++++++++++++++++- src/syzygy/tbprobe.h | 18 +++++++++-- src/thread.cpp | 4 +-- 6 files changed, 89 insertions(+), 75 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 72afabb6..32c626d4 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -44,15 +44,13 @@ Bitboard BishopTable[0x1480]; // To store bishop attacks void init_magics(PieceType pt, Bitboard table[], Magic magics[]); -} - // Returns the bitboard of target square for the given step // from the given square. If the step is off the board, returns empty bitboard. -inline Bitboard safe_destination(Square s, int step) { +Bitboard safe_destination(Square s, int step) { Square to = Square(s + step); return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); } - +} // Returns an ASCII representation of a bitboard suitable // to be printed to standard output. Useful for debugging. diff --git a/src/search.cpp b/src/search.cpp index b23740d8..8901dfd1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -29,7 +29,6 @@ #include #include -#include "bitboard.h" #include "evaluate.h" #include "misc.h" #include "movegen.h" @@ -46,14 +45,6 @@ namespace Stockfish { -namespace Tablebases { - -int Cardinality; -bool RootInTB; -bool UseRule50; -Depth ProbeDepth; -} - namespace TB = Tablebases; using Eval::evaluate; @@ -237,7 +228,7 @@ void Search::Worker::start_searching() { if (bestThread != this) sync_cout << UCI::pv(*bestThread, main_manager()->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - TB::RootInTB) + tbConfig.rootInTB) << sync_endl; sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); @@ -379,7 +370,7 @@ void Search::Worker::iterative_deepening() { && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - TB::RootInTB) + tbConfig.rootInTB) << sync_endl; // In case of failing low/high increase aspiration window and @@ -414,7 +405,7 @@ void Search::Worker::iterative_deepening() { || mainThread->tm.elapsed(threads.nodes_searched()) > 3000)) sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - TB::RootInTB) + tbConfig.rootInTB) << sync_endl; } @@ -659,13 +650,13 @@ Value Search::Worker::search( } // Step 5. Tablebases probe - if (!rootNode && !excludedMove && TB::Cardinality) + if (!rootNode && !excludedMove && tbConfig.cardinality) { int piecesCount = pos.count(); - if (piecesCount <= TB::Cardinality - && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && pos.rule50_count() == 0 - && !pos.can_castle(ANY_CASTLING)) + if (piecesCount <= tbConfig.cardinality + && (piecesCount < tbConfig.cardinality || depth >= tbConfig.probeDepth) + && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING)) { TB::ProbeState err; TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); @@ -678,7 +669,7 @@ Value Search::Worker::search( { thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); - int drawScore = TB::UseRule50 ? 1 : 0; + int drawScore = tbConfig.useRule50 ? 1 : 0; Value tbValue = VALUE_TB - ss->ply; @@ -1962,53 +1953,5 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po return pv.size() > 1; } -void Tablebases::rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves) { - - RootInTB = false; - UseRule50 = bool(options["Syzygy50MoveRule"]); - ProbeDepth = int(options["SyzygyProbeDepth"]); - Cardinality = int(options["SyzygyProbeLimit"]); - bool dtz_available = true; - - // Tables with fewer pieces than SyzygyProbeLimit are searched with - // ProbeDepth == DEPTH_ZERO - if (Cardinality > MaxCardinality) - { - Cardinality = MaxCardinality; - ProbeDepth = 0; - } - - if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) - { - // Rank moves using DTZ tables - RootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]); - - if (!RootInTB) - { - // DTZ tables are missing; try to rank moves using WDL tables - dtz_available = false; - RootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]); - } - } - - if (RootInTB) - { - // Sort moves according to TB rank - std::stable_sort(rootMoves.begin(), rootMoves.end(), - [](const RootMove& a, const RootMove& b) { return a.tbRank > b.tbRank; }); - - // Probe during search only if DTZ is not available and we are winning - if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) - Cardinality = 0; - } - else - { - // Clean up if root_probe() and root_probe_wdl() have failed - for (auto& m : rootMoves) - m.tbRank = 0; - } -} } // namespace Stockfish diff --git a/src/search.h b/src/search.h index 68c9f2aa..daf0ff85 100644 --- a/src/search.h +++ b/src/search.h @@ -29,6 +29,7 @@ #include "misc.h" #include "movepick.h" #include "position.h" +#include "syzygy/tbprobe.h" #include "timeman.h" #include "types.h" @@ -48,7 +49,6 @@ class UCI; namespace Search { - // Stack struct keeps track of the information we need to remember from nodes // shallower and deeper in the tree during the search. Each search thread has // its own array of Stack objects, indexed by the current ply. @@ -238,6 +238,8 @@ class Worker { // The main thread has a SearchManager, the others have a NullSearchManager std::unique_ptr manager; + Tablebases::Config tbConfig; + const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 6f30bf6b..722dc9d3 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -18,7 +18,6 @@ #include "tbprobe.h" -#include #include #include #include @@ -32,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +42,7 @@ #include "../position.h" #include "../search.h" #include "../types.h" +#include "../ucioption.h" #ifndef _WIN32 #include @@ -1680,4 +1681,60 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo return true; } +Config Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves) { + Config config; + + if (rootMoves.empty()) + return config; + + config.rootInTB = false; + config.useRule50 = bool(options["Syzygy50MoveRule"]); + config.probeDepth = int(options["SyzygyProbeDepth"]); + config.cardinality = int(options["SyzygyProbeLimit"]); + + bool dtz_available = true; + + // Tables with fewer pieces than SyzygyProbeLimit are searched with + // probeDepth == DEPTH_ZERO + if (config.cardinality > MaxCardinality) + { + config.cardinality = MaxCardinality; + config.probeDepth = 0; + } + + if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + // Rank moves using DTZ tables + config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]); + + if (!config.rootInTB) + { + // DTZ tables are missing; try to rank moves using WDL tables + dtz_available = false; + config.rootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]); + } + } + + if (config.rootInTB) + { + // Sort moves according to TB rank + std::stable_sort( + rootMoves.begin(), rootMoves.end(), + [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); + + // Probe during search only if DTZ is not available and we are winning + if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) + config.cardinality = 0; + } + else + { + // Clean up if root_probe() and root_probe_wdl() have failed + for (auto& m : rootMoves) + m.tbRank = 0; + } + + return config; +} } // namespace Stockfish diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index d7b412a1..e10950f4 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -20,16 +20,30 @@ #define TBPROBE_H #include +#include -#include "../search.h" namespace Stockfish { class Position; class OptionsMap; + +using Depth = int; + +namespace Search { +struct RootMove; +using RootMoves = std::vector; +} } namespace Stockfish::Tablebases { +struct Config { + int cardinality = 0; + bool rootInTB = false; + bool useRule50 = false; + Depth probeDepth = 0; +}; + enum WDLScore { WDLLoss = -2, // Loss WDLBlessedLoss = -1, // Loss, but draw under 50-move rule @@ -54,7 +68,7 @@ WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); -void rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves); +Config rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves); } // namespace Stockfish::Tablebases diff --git a/src/thread.cpp b/src/thread.cpp index 9dc4446f..a4bc3d67 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -181,8 +181,7 @@ void ThreadPool::start_thinking(const OptionsMap& options, || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) rootMoves.emplace_back(m); - if (!rootMoves.empty()) - Tablebases::rank_root_moves(options, pos, rootMoves); + Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves); // After ownership transfer 'states' becomes empty, so if we stop the search // and call 'go' again without setting a new position states.get() == nullptr. @@ -205,6 +204,7 @@ void ThreadPool::start_thinking(const OptionsMap& options, th->worker->rootMoves = rootMoves; th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); th->worker->rootState = setupStates->back(); + th->worker->tbConfig = tbConfig; } main_thread()->start_searching(); From f15e4f50aae3bea3991da4f72a9ff124e4db644b Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 20:44:38 +0100 Subject: [PATCH 540/678] Update installation guide links in CONTRIBUTING.md Link to more user friendly installation guides, these are shorter and easier to follow. closes https://github.com/official-stockfish/Stockfish/pull/4985 No functional change --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e589b4fc..cf9cecda 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ making contributions to Stockfish. In case you do not have a C++ compiler installed, you can follow the instructions from our wiki. -- [Linux][linux-compiling-link] +- [Ubuntu][ubuntu-compiling-link] - [Windows][windows-compiling-link] - [macOS][macos-compiling-link] @@ -92,6 +92,6 @@ Thank you for contributing to Stockfish and helping us make it even better! [discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new [creating-my-first-test]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test#create-your-test [issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues -[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux -[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#windows -[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos +[ubuntu-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-1 +[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler +[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-2 From 0c7f56dea6ee9a46a4be8481b2316711cb356f02 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:31:23 +0300 Subject: [PATCH 541/678] Fix mated-in behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This addresses the issue where Stockfish may output non-proven checkmate scores if the search is prematurely halted, either due to a time control or node limit, before it explores other possibilities where the checkmate score could have been delayed or refuted. The fix also replaces staving off from proven mated scores in a multithread environment making use of the threads instead of a negative effect with multithreads (1t was better in proving mated in scores than more threads). Issue reported on mate tracker repo by and this PR is co-authored with @robertnurnberg Special thanks to @AndyGrant for outlining that a fix is eventually possible. Passed Adj off SMP STC: https://tests.stockfishchess.org/tests/view/65a125d779aa8af82b96c3eb LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 303256 W: 75823 L: 75892 D: 151541 Ptnml(0-2): 406, 35269, 80395, 35104, 454 Passed Adj off SMP LTC: https://tests.stockfishchess.org/tests/view/65a37add79aa8af82b96f0f7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 56056 W: 13951 L: 13770 D: 28335 Ptnml(0-2): 11, 5910, 16002, 6097, 8 Passed all tests in matetrack without any better mate for opponent found in 1t and multithreads. Fixed bugs in https://github.com/official-stockfish/Stockfish/pull/4976 closes https://github.com/official-stockfish/Stockfish/pull/4990 Bench: 1308279 Co-Authored-By: Robert Nürnberg <28635489+robertnurnberg@users.noreply.github.com> --- src/misc.h | 15 ++++++++++++++ src/search.cpp | 55 ++++++++++++++++++++++++++++++++++---------------- src/search.h | 2 +- src/thread.cpp | 20 ++++++++++++------ src/thread.h | 2 +- 5 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/misc.h b/src/misc.h index 994f551d..f73e7889 100644 --- a/src/misc.h +++ b/src/misc.h @@ -19,12 +19,14 @@ #ifndef MISC_H_INCLUDED #define MISC_H_INCLUDED +#include #include #include #include #include #include #include +#include #define stringify2(x) #x #define stringify(x) stringify2(x) @@ -188,6 +190,19 @@ struct CommandLine { std::string workingDirectory; // path of the working directory }; +namespace Utility { + +template +void move_to_front(std::vector& vec, Predicate pred) { + auto it = std::find_if(vec.begin(), vec.end(), pred); + + if (it != vec.end()) + { + std::rotate(vec.begin(), it, it + 1); + } +} +} + } // namespace Stockfish #endif // #ifndef MISC_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 8901dfd1..8363f221 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -248,15 +248,16 @@ void Search::Worker::iterative_deepening() { // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), // (ss + 2) is needed for initialization of cutOffCnt and killers. - Stack stack[MAX_PLY + 10], *ss = stack + 7; - Move pv[MAX_PLY + 1]; - Value alpha, beta; - Move lastBestMove = Move::none(); - Depth lastBestMoveDepth = 0; - SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int delta, iterIdx = 0; + Stack stack[MAX_PLY + 10], *ss = stack + 7; + Move pv[MAX_PLY + 1]; + Value alpha, beta; + Value lastBestScore = -VALUE_INFINITE; + std::vector lastBestPV = {Move::none()}; + Depth lastBestMoveDepth = 0; + SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + double timeReduction = 1, totBestMoveChanges = 0; + Color us = rootPos.side_to_move(); + int delta, iterIdx = 0; std::memset(ss - 7, 0, 10 * sizeof(Stack)); for (int i = 7; i > 0; --i) @@ -402,7 +403,12 @@ void Search::Worker::iterative_deepening() { if (mainThread && (threads.stop || pvIdx + 1 == multiPV - || mainThread->tm.elapsed(threads.nodes_searched()) > 3000)) + || mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + // A thread that aborted search can have mated-in/TB-loss PV and score + // that cannot be trusted, i.e. it can be delayed or refuted if we would have + // had time to fully search other root-moves. Thus we suppress this output and + // below pick a proven score/PV for this thread (from the previous iteration). + && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), tbConfig.rootInTB) @@ -412,9 +418,21 @@ void Search::Worker::iterative_deepening() { if (!threads.stop) completedDepth = rootDepth; - if (rootMoves[0].pv[0] != lastBestMove) + // We make sure not to pick an unproven mated-in score, + // in case this thread prematurely stopped search (aborted-search). + if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE + && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) { - lastBestMove = rootMoves[0].pv[0]; + // Bring the last best move to the front for best thread selection. + Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)]( + const auto& rm) { return rm == lastBestPV[0]; }); + rootMoves[0].pv = lastBestPV; + rootMoves[0].score = rootMoves[0].uciScore = lastBestScore; + } + else if (rootMoves[0].pv[0] != lastBestPV[0]) + { + lastBestPV = rootMoves[0].pv; + lastBestScore = rootMoves[0].score; lastBestMoveDepth = rootDepth; } @@ -1916,11 +1934,14 @@ void SearchManager::check_time(Search::Worker& worker) { if (ponder) return; - if ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) - || (worker.limits.movetime && elapsed >= worker.limits.movetime) - || (worker.limits.nodes - && worker.threads.nodes_searched() >= uint64_t(worker.limits.nodes))) - worker.threads.stop = true; + if ( + // Later we rely on the fact that we can at least use the mainthread previous + // root-search score and PV in a multithreaded environment to prove mated-in scores. + worker.completedDepth >= 1 + && ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) + || (worker.limits.movetime && elapsed >= worker.limits.movetime) + || (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes))) + worker.threads.stop = worker.threads.abortedSearch = true; } // Called in case we have no ponder move before exiting the search, diff --git a/src/search.h b/src/search.h index daf0ff85..4bd013ad 100644 --- a/src/search.h +++ b/src/search.h @@ -115,7 +115,7 @@ struct LimitsType { std::vector searchmoves; TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; int movestogo, depth, mate, perft, infinite; - int64_t nodes; + uint64_t nodes; }; diff --git a/src/thread.cpp b/src/thread.cpp index a4bc3d67..4c1d01f4 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -20,8 +20,6 @@ #include #include -#include -#include #include #include #include @@ -169,8 +167,8 @@ void ThreadPool::start_thinking(const OptionsMap& options, main_thread()->wait_for_search_finished(); - main_manager()->stopOnPonderhit = stop = false; - main_manager()->ponder = ponderMode; + main_manager()->stopOnPonderhit = stop = abortedSearch = false; + main_manager()->ponder = ponderMode; increaseDepth = true; @@ -229,13 +227,23 @@ Thread* ThreadPool::get_best_thread() const { votes[th->worker->rootMoves[0].pv[0]] += thread_value(th); for (Thread* th : threads) - if (std::abs(bestThread->worker->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + if (bestThread->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY) { - // Make sure we pick the shortest mate / TB conversion or stave off mate the longest + // Make sure we pick the shortest mate / TB conversion if (th->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score) bestThread = th; } + else if (bestThread->worker->rootMoves[0].score != -VALUE_INFINITE + && bestThread->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) + { + // Make sure we pick the shortest mated / TB conversion + if (th->worker->rootMoves[0].score != -VALUE_INFINITE + && th->worker->rootMoves[0].score < bestThread->worker->rootMoves[0].score) + bestThread = th; + } else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || (th->worker->rootMoves[0].score != -VALUE_INFINITE + && th->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) || (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY && (votes[th->worker->rootMoves[0].pv[0]] > votes[bestThread->worker->rootMoves[0].pv[0]] diff --git a/src/thread.h b/src/thread.h index 6575b14e..a2a1d18c 100644 --- a/src/thread.h +++ b/src/thread.h @@ -94,7 +94,7 @@ class ThreadPool { void start_searching(); void wait_for_search_finished() const; - std::atomic_bool stop, increaseDepth; + std::atomic_bool stop, abortedSearch, increaseDepth; auto cbegin() const noexcept { return threads.cbegin(); } auto begin() noexcept { return threads.begin(); } From 6c02329860d49198ff6a20b8469fd50dd1aa2eab Mon Sep 17 00:00:00 2001 From: Torsten Hellwig Date: Mon, 15 Jan 2024 13:13:53 +0100 Subject: [PATCH 542/678] Fix dotprod detection This fixes the detection of dotprod capable CPUs. Previously it looked for the `dotprod` flag, but this does not exist (https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm64/kernel/cpuinfo.c#n50). The correct flag that specifies the dotprod capability is the `asimddp` flag. fixes #4931 closes https://github.com/official-stockfish/Stockfish/pull/4991 No functional change --- scripts/get_native_properties.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index ae23c3bb..fb124021 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -79,7 +79,7 @@ case $uname_s in 'aarch64') file_os='android' true_arch='armv8' - if check_flags 'dotprod'; then + if check_flags 'asimddp'; then true_arch="$true_arch-dotprod" fi ;; From 0fbad56c50edab533e5a2f0319016d14e6a9f99c Mon Sep 17 00:00:00 2001 From: pb00067 Date: Mon, 15 Jan 2024 15:32:06 +0100 Subject: [PATCH 543/678] Refactor code for correcting unadjustedStaticEval Passed non-regression STC: https://tests.stockfishchess.org/tests/live_elo/65a4df6a79aa8af82b970ca0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 43328 W: 11103 L: 10892 D: 21333 Ptnml(0-2): 120, 4920, 11407, 5063, 154 https://github.com/official-stockfish/Stockfish/pull/4992 No functional change --- src/search.cpp | 45 ++++++++++----------------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8363f221..ffa37ab6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -62,8 +62,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; } -// Guarantee evaluation does not hit the tablebase range -constexpr Value to_static_eval(const Value v) { +// Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range +Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { + auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; + v += cv * std::abs(cv) / 16384; return std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -747,13 +749,7 @@ Value Search::Worker::search( else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); - Value newEval = - ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] - * std::abs(thisThread->correctionHistory[us][pawn_structure_index(pos)]) - / 16384; - - ss->staticEval = eval = to_static_eval(newEval); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // ttValue can be used as a better position evaluation (~7 Elo) if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) @@ -762,14 +758,7 @@ Value Search::Worker::search( else { unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); - - Value newEval = - ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] - * std::abs(thisThread->correctionHistory[us][pawn_structure_index(pos)]) - / 16384; - - ss->staticEval = eval = to_static_eval(newEval); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), @@ -1513,15 +1502,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos, thisThread->optimism[us]); - - Value newEval = - ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] - * std::abs( - thisThread->correctionHistory[us][pawn_structure_index(pos)]) - / 16384; - - ss->staticEval = bestValue = to_static_eval(newEval); + ss->staticEval = bestValue = + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // ttValue can be used as a better position evaluation (~13 Elo) if (ttValue != VALUE_NONE @@ -1534,15 +1516,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, unadjustedStaticEval = ss->staticEval = bestValue = (ss - 1)->currentMove != Move::null() ? evaluate(pos, thisThread->optimism[us]) : -(ss - 1)->staticEval; - - Value newEval = - ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] - * std::abs( - thisThread->correctionHistory[us][pawn_structure_index(pos)]) - / 16384; - - ss->staticEval = bestValue = to_static_eval(newEval); + ss->staticEval = bestValue = + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); } // Stand pat. Return immediately if static value is at least beta From 9a9702d668af807b9044fef5a83f6ed0854ce44f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 16 Jan 2024 13:23:49 +0300 Subject: [PATCH 544/678] Remove threatenedByPawn from rook threat Can be simplified away. Passed STC: https://tests.stockfishchess.org/tests/view/65a3fa4179aa8af82b96face LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 30592 W: 7903 L: 7674 D: 15015 Ptnml(0-2): 96, 3590, 7711, 3787, 112 Passed LTC: https://tests.stockfishchess.org/tests/view/65a42b9a79aa8af82b96fe88 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 73656 W: 18382 L: 18212 D: 37062 Ptnml(0-2): 47, 8287, 19981, 8475, 38 closes https://github.com/official-stockfish/Stockfish/pull/4993 Bench: 1430061 --- src/movepick.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index e521689e..b2638350 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -198,15 +198,13 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= - !(threatenedPieces & from) - ? (pt == QUEEN - ? bool(to & threatenedByRook) * 50000 + bool(to & threatenedByMinor) * 10000 - : pt == ROOK - ? bool(to & threatenedByMinor) * 25000 + bool(to & threatenedByPawn) * 10000 - : pt != PAWN ? bool(to & threatenedByPawn) * 15000 - : 0) - : 0; + m.value -= !(threatenedPieces & from) + ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 + + bool(to & threatenedByMinor) * 10000 + : pt == ROOK ? bool(to & threatenedByMinor) * 25000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0) + : 0; } else // Type == EVASIONS From c8bc2ce4fae2bc9a5dd9b5121274c47d5c9262c0 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:45:59 +0000 Subject: [PATCH 545/678] Improve ttPv reduction This patch allows a partial reduction decrease when a node is likely to fail low, and increases the reduction decrease when a node has failed high. Passed STC: https://tests.stockfishchess.org/tests/view/65a626e779aa8af82b9722bc LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 157824 W: 40332 L: 39835 D: 77657 Ptnml(0-2): 543, 18617, 40098, 19108, 546 Passed LTC: https://tests.stockfishchess.org/tests/view/65a7290279aa8af82b97328a LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 57228 W: 14475 L: 14111 D: 28642 Ptnml(0-2): 34, 6278, 15633, 6628, 41 closes https://github.com/official-stockfish/Stockfish/pull/4994 Bench: 1364759 --- src/search.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ffa37ab6..3c630db0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -944,11 +944,6 @@ moves_loop: // When in check, search starts here value = bestValue; moveCountPruning = singularQuietLMR = false; - // Indicate PvNodes that will probably fail low if the node was searched - // at a depth equal to or greater than the current depth, and the result - // of this search was a fail low. - bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) && tte->depth() >= depth; - // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. while ((move = mp.next_move(moveCountPruning)) != Move::none()) @@ -1153,9 +1148,10 @@ moves_loop: // When in check, search starts here thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV (~4 Elo) - if (ss->ttPv && !likelyFailLow) - r -= 1 + (cutNode && tte->depth() >= depth) + (ttValue > alpha); + // Decrease reduction if position is or has been on the PV (~7 Elo) + if (ss->ttPv) + r -= !(tte->bound() == BOUND_UPPER && PvNode) + (cutNode && tte->depth() >= depth) + + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss - 1)->moveCount > 7) From 856e60d12f11cd250cf00cdd336ed87c25bd0677 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 17 Jan 2024 22:58:46 +0100 Subject: [PATCH 546/678] Refactor NativeThread start_routine Removes the free function and fixes the formatting for the function call. closes https://github.com/official-stockfish/Stockfish/pull/4995 No functional change --- src/thread_win32_osx.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 8d424b72..1d9a834f 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -34,14 +34,6 @@ namespace Stockfish { -// free function to be passed to pthread_create() -inline void* start_routine(void* ptr) { - auto func = reinterpret_cast*>(ptr); - (*func)(); // Call the function - delete func; - return nullptr; -} - class NativeThread { pthread_t thread; @@ -56,6 +48,15 @@ class NativeThread { pthread_attr_t attr_storage, *attr = &attr_storage; pthread_attr_init(attr); pthread_attr_setstacksize(attr, TH_STACK_SIZE); + + auto start_routine = [](void* ptr) -> void* { + auto f = reinterpret_cast*>(ptr); + // Call the function + (*f)(); + delete f; + return nullptr; + }; + pthread_create(&thread, attr, start_routine, func); } From aa15a9179b8cc5598a6bd04b7c2cfeb81282a0c7 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Fri, 19 Jan 2024 04:51:02 +0000 Subject: [PATCH 547/678] Refactor ttPv reduction conditions closes https://github.com/official-stockfish/Stockfish/pull/4999 No functional change --- src/search.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3c630db0..57e87fb2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1148,25 +1148,24 @@ moves_loop: // When in check, search starts here thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV (~7 Elo) + // Decrease reduction if position is or has been on the PV (~5 Elo) if (ss->ttPv) - r -= !(tte->bound() == BOUND_UPPER && PvNode) + (cutNode && tte->depth() >= depth) - + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); + r -= 1 + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss - 1)->moveCount > 7) r--; - // Increase reduction for cut nodes (~3 Elo) + // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2; + r += 2 - (tte->depth() >= depth && ss->ttPv); // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) r++; - // Decrease reduction for PvNodes (~2 Elo) - if (PvNode) + // Decrease reduction for PvNodes (~3 Elo) + if (PvNode && tte->bound() != BOUND_UPPER) r--; // Decrease reduction if a quiet ttMove has been singularly extended (~1 Elo) From e860f620aa3eb42f8a6be78c030c75855d0c9ff0 Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Fri, 19 Jan 2024 07:32:56 +0100 Subject: [PATCH 548/678] Reduce futility_margin further when improving The idea of this is to unroll the futility_margin calculation to allow for the improving flag to have a greater effect on the futility margin. The current factor is 1.5 instead of the previous 1 resulting in a deduction of an extra margin/2 from futilit_margin if improving. The chosen value was not tuned, meaning that there is room for tweaking it. This patch is partially inspired by @Vizvezdenec, who, although quite different in execution, tested another idea where the futility_margin is lowered further when improving [1]. [1]: (first take) https://tests.stockfishchess.org/tests/view/65a56b1879aa8af82b97164b Passed STC: https://tests.stockfishchess.org/tests/live_elo/65a8bfc179aa8af82b974e3c LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 161152 W: 41321 L: 40816 D: 79015 Ptnml(0-2): 559, 19030, 40921, 19479, 587 Passed rebased LTC: https://tests.stockfishchess.org/tests/live_elo/65a8b9ef79aa8af82b974dc0 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 96024 W: 24172 L: 23728 D: 48124 Ptnml(0-2): 56, 10598, 26275, 11012, 71 closes https://github.com/official-stockfish/Stockfish/pull/5000 Bench: 1281703 --- AUTHORS | 2 +- src/search.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6f518ec2..a179d273 100644 --- a/AUTHORS +++ b/AUTHORS @@ -46,6 +46,7 @@ candirufish Chess13234 Chris Cain (ceebo) clefrks +Clemens L. (rn5f107s2) Cody Ho (aesrentai) Dale Weiler (graphitemaster) Daniel Axtens (daxtens) @@ -183,7 +184,6 @@ Raminder Singh renouve Reuven Peleg (R-Peleg) Richard Lloyd (Richard-Lloyd) -rn5f107s2 Robert Nürnberg (robertnurnberg) Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) diff --git a/src/search.cpp b/src/search.cpp index 57e87fb2..c244207d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,8 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return ((116 - 44 * noTtCutNode) * (d - improving)); + Value futilityMult = 116 - 44 * noTtCutNode; + return (futilityMult * d - 3 * futilityMult / 2 * improving); } constexpr int futility_move_count(bool improving, Depth depth) { From ad9fcbc4961229286e5043d984dc172d1b0de052 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Thu, 18 Jan 2024 07:31:59 +0300 Subject: [PATCH 549/678] Refactor get_best_thread Make get_best_thread function easier to understand. Passed non-reg SMP STC: https://tests.stockfishchess.org/tests/view/65a91c6679aa8af82b975500 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 186000 W: 46379 L: 46325 D: 93296 Ptnml(0-2): 269, 21374, 49634, 21480, 243 closes https://github.com/official-stockfish/Stockfish/pull/5001 No functional change --- src/thread.cpp | 59 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/src/thread.cpp b/src/thread.cpp index 4c1d01f4..3cce7c56 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -210,49 +210,66 @@ void ThreadPool::start_thinking(const OptionsMap& options, Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = threads.front(); std::unordered_map votes; - Value minScore = VALUE_NONE; + + Thread* bestThread = threads.front(); + Value minScore = VALUE_NONE; // Find the minimum score of all threads for (Thread* th : threads) minScore = std::min(minScore, th->worker->rootMoves[0].score); // Vote according to score and depth, and select the best thread - auto thread_value = [minScore](Thread* th) { + auto thread_voting_value = [minScore](Thread* th) { return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth); }; for (Thread* th : threads) - votes[th->worker->rootMoves[0].pv[0]] += thread_value(th); + votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th); for (Thread* th : threads) - if (bestThread->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY) + { + const auto bestThreadScore = bestThread->worker->rootMoves[0].score; + const auto newThreadScore = th->worker->rootMoves[0].score; + + const auto bestThreadPV = bestThread->worker->rootMoves[0].pv; + const auto newThreadPV = th->worker->rootMoves[0].pv; + + const auto bestThreadMoveVote = votes[bestThreadPV[0]]; + const auto newThreadMoveVote = votes[newThreadPV[0]]; + + + const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; + const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; + + const bool bestThreadInProvenLoss = + bestThreadScore != -VALUE_INFINITE && bestThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + const bool newThreadInProvenLoss = + newThreadScore != -VALUE_INFINITE && newThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + + // Note that we make sure not to pick a thread with truncated-PV for better viewer experience. + const bool betterVotingValue = + thread_voting_value(th) * int(newThreadPV.size() > 2) + > thread_voting_value(bestThread) * int(bestThreadPV.size() > 2); + + if (bestThreadInProvenWin) { // Make sure we pick the shortest mate / TB conversion - if (th->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score) + if (newThreadScore > bestThreadScore) bestThread = th; } - else if (bestThread->worker->rootMoves[0].score != -VALUE_INFINITE - && bestThread->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) + else if (bestThreadInProvenLoss) { // Make sure we pick the shortest mated / TB conversion - if (th->worker->rootMoves[0].score != -VALUE_INFINITE - && th->worker->rootMoves[0].score < bestThread->worker->rootMoves[0].score) + if (newThreadInProvenLoss && newThreadScore < bestThreadScore) bestThread = th; } - else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY - || (th->worker->rootMoves[0].score != -VALUE_INFINITE - && th->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) - || (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && (votes[th->worker->rootMoves[0].pv[0]] - > votes[bestThread->worker->rootMoves[0].pv[0]] - || (votes[th->worker->rootMoves[0].pv[0]] - == votes[bestThread->worker->rootMoves[0].pv[0]] - && thread_value(th) * int(th->worker->rootMoves[0].pv.size() > 2) - > thread_value(bestThread) - * int(bestThread->worker->rootMoves[0].pv.size() > 2))))) + else if (newThreadInProvenWin || newThreadInProvenLoss + || (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY + && (newThreadMoveVote > bestThreadMoveVote + || (newThreadMoveVote == bestThreadMoveVote && betterVotingValue)))) bestThread = th; + } return bestThread; } From a901474bf9579ba259179eb09618d8401a156f64 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 20 Jan 2024 19:15:20 +0100 Subject: [PATCH 550/678] Update the WDL model Update the internal WDL model. After the dual net merge, the internal evaluations have drifted upwards a bit. With this PR `NormalizeToPawnValue` changes from `328` to `345`. The new model was fitted based on about 200M positions extracted from 3.4M fishtest LTC games from the last two weeks, involving SF versions from 6deb88728fb141e853243c2873ad0cda4dd19320 to current master. Apart from the WDL model parameter update, this PR implements the following changes: WDL Model: - an incorrect 8-move shift in master's WDL model has been fixed - the polynomials `p_a` and `p_b` are fitted over the move range [8, 120] - the coefficients for `p_a` and `p_b` are optimized by maximizing the probability of predicting the observed outcome (credits to @vondele) SF code: - for wdl values, move will be clamped to `max(8, min(120, move))` - no longer clamp the internal eval to [-4000,4000] - compute `NormalizeToPawnValue` with `round`, not `trunc` The PR only affects displayed `cp` and `wdl` values. closes https://github.com/official-stockfish/Stockfish/pull/5002 No functional change --- src/uci.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 789f3454..2a55fbfa 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -43,7 +43,7 @@ namespace Stockfish { constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int NormalizeToPawnValue = 328; +constexpr int NormalizeToPawnValue = 345; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; UCI::UCI(int argc, char** argv) : @@ -421,26 +421,23 @@ namespace { // eval and a game ply. It fits the LTC fishtest statistics rather accurately. int win_rate_model(Value v, int ply) { - // The model only captures up to 240 plies, so limit the input and then rescale - double m = std::min(240, ply) / 64.0; + // The fitted model only uses data for moves in [8, 120], and is anchored at move 32. + double m = std::clamp(ply / 2 + 1, 8, 120) / 32.0; // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = {0.38036525, -2.82015070, 23.17882135, 307.36768407}; - constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330}; + constexpr double as[] = {-2.00568292, 10.45906746, 1.67438883, 334.45864705}; + constexpr double bs[] = {-4.97134419, 36.15096345, -82.25513499, 117.35186805}; - // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 - static_assert(NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); + // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at move 32. + static_assert(NormalizeToPawnValue == int(0.5 + as[0] + as[1] + as[2] + as[3])); double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - // Transform the eval to centipawns with limited range - double x = std::clamp(double(v), -4000.0, 4000.0); - - // Return the win rate in per mille units, rounded to the nearest integer - return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); + // Return the win rate in per mille units, rounded to the nearest integer. + return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); } } From a6fd17f27d7675332166e9e6ea8210237281fc77 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:10:38 +0800 Subject: [PATCH 551/678] VLTC search tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search parameters were tuned using 152k games at 180+1.8. Passed VLTC: https://tests.stockfishchess.org/tests/view/65a7a81979aa8af82b973a20 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 117338 W: 29244 L: 28848 D: 59246 Ptnml(0-2): 24, 12474, 33267, 12890, 14 Passed VVLTC: https://tests.stockfishchess.org/tests/view/65ab246679aa8af82b977982 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 28164 W: 7239 L: 6957 D: 13968 Ptnml(0-2): 3, 2651, 8490, 2937, 1 STC Elo estimate: https://tests.stockfishchess.org/tests/view/65ac7c0979aa8af82b9792a6 Elo: -0.53 ± 2.0 (95%) LOS: 30.4% Total: 30000 W: 7688 L: 7734 D: 14578 Ptnml(0-2): 102, 3617, 7614, 3559, 108 nElo: -1.03 ± 3.9 (95%) PairsRatio: 0.99 closes https://github.com/official-stockfish/Stockfish/pull/5003 Bench: 1235377 --- src/search.cpp | 74 +++++++++++++++++++++++++------------------------- src/search.h | 4 +-- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c244207d..a22df12c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - Value futilityMult = 116 - 44 * noTtCutNode; + Value futilityMult = 114 - 47 * noTtCutNode; return (futilityMult * d - 3 * futilityMult / 2 * improving); } @@ -66,15 +66,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 16384; + v += cv * std::abs(cv) / 14095; return std::clamp(int(v), 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(268 * d - 352, 1153); } +int stat_bonus(Depth d) { return std::min(265 * d - 349, 1112); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(400 * d - 354, 1201); } +int stat_malus(Depth d) { return std::min(482 * d - 326, 1172); } // 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); } @@ -334,12 +334,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(9) + int(avg) * avg / 14847; + delta = Value(9) + int(avg) * avg / 13181; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, int(VALUE_INFINITE)); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 121 * avg / (std::abs(avg) + 109); + optimism[us] = 132 * avg / (std::abs(avg) + 98); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -769,7 +769,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1680, 1406); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -790,7 +790,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 472 - (284 - 165 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 435 - (327 - 167 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -799,17 +799,17 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 9 + if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - - (ss - 1)->statScore / 337 + - (ss - 1)->statScore / 327 >= beta - && eval >= beta && eval < 29008 // smaller than TB wins + && eval >= beta && eval < 27734 // smaller than TB wins && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17496 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17787 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 22 * depth + 313 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { @@ -863,7 +863,7 @@ Value Search::Worker::search( if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 163 - 67 * improving; + probCutBeta = beta + 173 - 73 * improving; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -923,7 +923,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 425; + probCutBeta = beta + 427; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -1006,7 +1006,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 277 + 298 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -1014,7 +1014,7 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -187 * depth)) + if (!pos.see_ge(move, -203 * depth)) continue; } else @@ -1026,18 +1026,18 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3752 * depth) + if (lmrDepth < 6 && history < -4195 * depth) continue; - history += 2 * thisThread->mainHistory[us][move.from_to()]; + history += 69 * thisThread->mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 7838; + lmrDepth += history / 6992; lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 14 - && ss->staticEval + (bestValue < ss->staticEval - 57 ? 124 : 71) - + 118 * lmrDepth + if (!ss->inCheck && lmrDepth < 15 + && ss->staticEval + (bestValue < ss->staticEval - 63 ? 137 : 64) + + 111 * lmrDepth <= alpha) continue; @@ -1064,11 +1064,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 27) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 31) + 2 * (PvNode && tte->is_pv()) && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (66 + 58 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (58 + 52 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1082,7 +1082,7 @@ moves_loop: // When in check, search starts here singularQuietLMR = !ttCapture; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 17 && ss->doubleExtensions <= 11) + if (!PvNode && value < singularBeta - 16 && ss->doubleExtensions <= 12) { extension = 2; depth += depth < 15; @@ -1109,7 +1109,7 @@ moves_loop: // When in check, search starts here // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) - extension = depth < 19 ? -2 : -1; + extension = depth < 20 ? -2 : -1; // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo) else if (ttValue <= value) @@ -1122,14 +1122,14 @@ moves_loop: // When in check, search starts here // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][move.to_sq()] >= 4325) + && (*contHist[0])[movedPiece][move.to_sq()] >= 4111) extension = 1; // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4146) + > 4484) extension = 1; } @@ -1189,10 +1189,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 3817; + + (*contHist[3])[movedPiece][move.to_sq()] - 4119; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / 14767; + r -= ss->statScore / 15373; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1215,7 +1215,7 @@ 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 + 53 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 51 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1331,7 +1331,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13782 && value > -11541) + if (depth > 2 && depth < 12 && beta < 13195 && value > -12346) depth -= 2; assert(depth > 0); @@ -1370,7 +1370,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + ((ss - 1)->statScore < -18782) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -16797) + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1529,7 +1529,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 182; + futilityBase = ss->staticEval + 186; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1609,7 +1609,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -77)) + if (!pos.see_ge(move, -76)) continue; } @@ -1764,7 +1764,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 177 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move diff --git a/src/search.h b/src/search.h index 4bd013ad..3a099c5d 100644 --- a/src/search.h +++ b/src/search.h @@ -205,8 +205,8 @@ class Worker { Depth reduction(bool i, Depth d, int mn, int delta) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 - + (!i && reductionScale > 880); + return (reductionScale + 1177 - int(delta) * 776 / int(rootDelta)) / 1024 + + (!i && reductionScale > 842); } // Get a pointer to the search manager, only allowed to be called by the From 2b62c4452db5a02c5ed7d4a2b4c4cb1529fb82b7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 23 Jan 2024 13:39:06 +0300 Subject: [PATCH 552/678] Remove redundant max operation on lmrDepth Removed a restriction that prohibited history heuristics sum in futility pruning to exceed some negative value. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 279040 W: 71095 L: 71143 D: 136802 Ptnml(0-2): 949, 33574, 70474, 33622, 901 https://tests.stockfishchess.org/tests/view/65aaef4c79aa8af82b977631 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 75156 W: 18884 L: 18715 D: 37557 Ptnml(0-2): 52, 8445, 20408, 8628, 45 https://tests.stockfishchess.org/tests/view/65ae7ef3c865510db026abf5 closes https://github.com/official-stockfish/Stockfish/pull/5004 Bench: 1566543 --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a22df12c..f5395f98 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1032,7 +1032,6 @@ moves_loop: // When in check, search starts here history += 69 * thisThread->mainHistory[us][move.from_to()] / 32; lmrDepth += history / 6992; - lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 From 3d49a99aaf75f6f44ef6ec5a22b0acd191b8d01e Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 24 Jan 2024 14:38:59 +0300 Subject: [PATCH 553/678] Refactor history score calculation Passed STC: https://tests.stockfishchess.org/tests/view/65ad08b179aa8af82b979dd1 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 161376 W: 41582 L: 41498 D: 78296 Ptnml(0-2): 633, 19354, 40611, 19476, 614 Passed LTC: https://tests.stockfishchess.org/tests/view/65af966fc865510db026c0f0 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 116526 W: 29269 L: 29143 D: 58114 Ptnml(0-2): 71, 13252, 31509, 13342, 89 closes https://github.com/official-stockfish/Stockfish/pull/5006 Bench: 1317504 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f5395f98..f4b37253 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1029,7 +1029,7 @@ moves_loop: // When in check, search starts here if (lmrDepth < 6 && history < -4195 * depth) continue; - history += 69 * thisThread->mainHistory[us][move.from_to()] / 32; + history += 2 * thisThread->mainHistory[us][move.from_to()]; lmrDepth += history / 6992; From 1dfbde2d1056b49442aab9bf5145ad30d0e87dd1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 00:21:46 +0100 Subject: [PATCH 554/678] Move perft out of search This splits the logic of search and perft. Before, threads were started, which then constructed a search object, which then started perft and returned immediately. All of this is unnecessary, instead uci should start perft right away. closes https://github.com/official-stockfish/Stockfish/pull/5008 No functional change --- src/Makefile | 2 +- src/perft.h | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/search.cpp | 36 -------------------------- src/uci.cpp | 8 +++++- 4 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 src/perft.h diff --git a/src/Makefile b/src/Makefile index 9680ca7f..907b6155 100644 --- a/src/Makefile +++ b/src/Makefile @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h + tt.h tune.h types.h uci.h ucioption.h perft.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/perft.h b/src/perft.h new file mode 100644 index 00000000..2edc3ad0 --- /dev/null +++ b/src/perft.h @@ -0,0 +1,69 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PERFT_H_INCLUDED +#define PERFT_H_INCLUDED + +#include + +#include "movegen.h" +#include "position.h" +#include "types.h" +#include "uci.h" + +namespace Stockfish { + +// Utility to verify move generation. All the leaf nodes up +// to the given depth are generated and counted, and the sum is returned. +template +uint64_t perft(Position& pos, Depth depth) { + + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2); + + for (const auto& m : MoveList(pos)) + { + if (Root && depth <= 1) + cnt = 1, nodes++; + else + { + pos.do_move(m, st); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); + nodes += cnt; + pos.undo_move(m); + } + if (Root) + sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + } + return nodes; +} + +inline void perft(const std::string& fen, Depth depth, bool isChess960) { + StateListPtr states(new std::deque(1)); + Position p; + p.set(fen, isChess960, &states->back()); + + uint64_t nodes = perft(p, depth); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; +} +} + +#endif // PERFT_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index f4b37253..086bff34 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -122,37 +122,8 @@ void update_all_stats(const Position& pos, int captureCount, Depth depth); -// Utility to verify move generation. All the leaf nodes up -// to the given depth are generated and counted, and the sum is returned. -template -uint64_t perft(Position& pos, Depth depth) { - - StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - - uint64_t cnt, nodes = 0; - const bool leaf = (depth == 2); - - for (const auto& m : MoveList(pos)) - { - if (Root && depth <= 1) - cnt = 1, nodes++; - else - { - pos.do_move(m, st); - cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); - nodes += cnt; - pos.undo_move(m); - } - if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; - } - return nodes; -} - } // namespace - Search::Worker::Worker(SharedState& sharedState, std::unique_ptr sm, size_t thread_id) : @@ -173,13 +144,6 @@ void Search::Worker::start_searching() { return; } - if (limits.perft) - { - nodes = perft(rootPos, limits.perft); - sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; - return; - } - main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options); tt.new_search(); diff --git a/src/uci.cpp b/src/uci.cpp index 2a55fbfa..e6107d47 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -39,6 +39,7 @@ #include "syzygy/tbprobe.h" #include "types.h" #include "ucioption.h" +#include "perft.h" namespace Stockfish { @@ -172,7 +173,6 @@ void UCI::loop() { void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { - Search::LimitsType limits; std::string token; bool ponderMode = false; @@ -211,6 +211,12 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { Eval::NNUE::verify(options, evalFiles); + if (limits.perft) + { + perft(pos.fen(), limits.perft, options["UCI_Chess960"]); + return; + } + threads.start_thinking(options, pos, states, limits, ponderMode); } From 37bd1e774ee4eb03e558062284da1e72cbce5a95 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 25 Jan 2024 23:22:07 +0300 Subject: [PATCH 555/678] Do more double extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parameter tweak from Black Marlin chess engine. Choose a significantly lower value that triggers in 95% of cases, compared to the usual 84% in standard benchmark runs. Since the introduction by https://github.com/official-stockfish/Stockfish/commit/33a858eaa1f792b3413384a3d0993dba36aca92e this constant has only decreased in value over time. 2-16-17-18-21-22-25-26-52-71-75-93-140 Failed STC really fast: https://tests.stockfishchess.org/tests/view/65b11d05c865510db026df7b LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 13216 W: 3242 L: 3485 D: 6489 Ptnml(0-2): 50, 1682, 3371, 1471, 34 Was reasonable at LTC: https://tests.stockfishchess.org/tests/view/65b13e20c865510db026e210 Elo: 1.18 ± 1.5 (95%) LOS: 94.3% Total: 50000 W: 12517 L: 12347 D: 25136 Ptnml(0-2): 31, 5598, 13579, 5754, 38 nElo: 2.45 ± 3.0 (95%) PairsRatio: 1.03 Passed VLTC with STC bounds: https://tests.stockfishchess.org/tests/view/65b18870c865510db026e769 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 30456 W: 7726 L: 7448 D: 15282 Ptnml(0-2): 6, 3111, 8717, 3387, 7 Passed VVLTC with LTC bounds: https://tests.stockfishchess.org/tests/view/65b20b95c865510db026eef0 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 36134 W: 9158 L: 8859 D: 18117 Ptnml(0-2): 3, 3455, 10850, 3758, 1 closes https://github.com/official-stockfish/Stockfish/pull/5013 Bench: 1503692 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 086bff34..9b74141b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1045,7 +1045,7 @@ moves_loop: // When in check, search starts here singularQuietLMR = !ttCapture; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 16 && ss->doubleExtensions <= 12) + if (!PvNode && value < singularBeta - 2 && ss->doubleExtensions <= 12) { extension = 2; depth += depth < 15; From c17ec9524d57c2fdaba1fd7f16ce30744780b6aa Mon Sep 17 00:00:00 2001 From: Ahmed Kerimov Date: Fri, 26 Jan 2024 13:52:56 +0300 Subject: [PATCH 556/678] Move OnChange callback in Option ctors Parameter 'f' is passed by value and only copied once. Moving it to avoid unnecessary copies. closes https://github.com/official-stockfish/Stockfish/pull/5014 No functional change --- AUTHORS | 1 + src/ucioption.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index a179d273..cc8edafa 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,6 +12,7 @@ Hisayori Noda (nodchip) # All other authors of Stockfish code (in alphabetical order) Aditya (absimaldata) Adrian Petrescu (apetresc) +Ahmed Kerimov (wcdbmv) Ajith Chandy Jose (ajithcj) Alain Savard (Rocky640) Alayan Feh (Alayan-stk-2) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index c7de7e3f..e1ffe546 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -68,7 +68,7 @@ Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), - on_change(f) { + on_change(std::move(f)) { defaultValue = currentValue = v; } @@ -76,7 +76,7 @@ Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), - on_change(f) { + on_change(std::move(f)) { defaultValue = currentValue = (v ? "true" : "false"); } @@ -84,13 +84,13 @@ Option::Option(OnChange f) : type("button"), min(0), max(0), - on_change(f) {} + on_change(std::move(f)) {} Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), - on_change(f) { + on_change(std::move(f)) { defaultValue = currentValue = std::to_string(v); } @@ -98,7 +98,7 @@ Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), - on_change(f) { + on_change(std::move(f)) { defaultValue = v; currentValue = cur; } From fcbb02ffdeebb65c970ecf5aebaa3078cdf8f374 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:27:49 +0000 Subject: [PATCH 557/678] Use ttPv in depth condition of singular extensions This replaces the PvNode condition and tte Pv call previously with using the precomputed ttPv, and also removes the multiplier of 2. This new depth condition occurs with approximately equal frequency (47%) to the old depth condition (measured when the other conditions in the if are true), so non-linear scaling behaviour isn't expected. Passed Non-Reg STC: https://tests.stockfishchess.org/tests/view/65b0e132c865510db026da27 LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 243232 W: 62432 L: 62437 D: 118363 Ptnml(0-2): 910, 28937, 61900, 28986, 883 Passed Non-Reg LTC: https://tests.stockfishchess.org/tests/view/65b2053bc865510db026eea1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 190596 W: 47666 L: 47618 D: 95312 Ptnml(0-2): 115, 21710, 51596, 21766, 111 closes https://github.com/official-stockfish/Stockfish/pull/5015 Bench: 1492957 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9b74141b..6a464961 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1027,7 +1027,7 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 31) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 31) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { From 13eb023fc09343c80c45f51df83a1b9f6401bd35 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 27 Jan 2024 10:54:44 +0100 Subject: [PATCH 558/678] Simplify array initializations also retire a few std::memset calls. Passed non-regresion STC: https://tests.stockfishchess.org/tests/view/65b8e162c865510db0276901 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 97504 W: 25294 L: 25140 D: 47070 Ptnml(1-2): 378, 11102, 25667, 11198, 407 closes https://github.com/official-stockfish/Stockfish/pull/5018 No functional change --- src/position.cpp | 10 +++++----- src/search.cpp | 38 +++++++++++++++++++------------------- src/search.h | 13 +++++++------ 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 6202381d..c89b1eb0 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -19,6 +19,7 @@ #include "position.h" #include +#include #include #include #include @@ -107,9 +108,8 @@ inline int H1(Key h) { return h & 0x1fff; } inline int H2(Key h) { return (h >> 16) & 0x1fff; } // Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves -Key cuckoo[8192]; -Move cuckooMove[8192]; - +std::array cuckoo; +std::array cuckooMove; // Initializes at startup the various arrays used to compute hash keys void Position::init() { @@ -130,8 +130,8 @@ void Position::init() { Zobrist::noPawns = rng.rand(); // Prepare the cuckoo tables - std::memset(cuckoo, 0, sizeof(cuckoo)); - std::memset(cuckooMove, 0, sizeof(cuckooMove)); + cuckoo.fill(0); + cuckooMove.fill(Move::none()); [[maybe_unused]] int count = 0; for (Piece pc : Pieces) for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) diff --git a/src/search.cpp b/src/search.cpp index 6a464961..29b5c524 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -212,21 +211,26 @@ void Search::Worker::start_searching() { // consumed, the user stops the search, or the maximum search depth is reached. void Search::Worker::iterative_deepening() { + SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + + Move pv[MAX_PLY + 1]; + + Depth lastBestMoveDepth = 0; + Value lastBestScore = -VALUE_INFINITE; + auto lastBestPV = std::vector{Move::none()}; + + Value alpha, beta; + Value bestValue = -VALUE_INFINITE; + Color us = rootPos.side_to_move(); + double timeReduction = 1, totBestMoveChanges = 0; + int delta, iterIdx = 0; + // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), // (ss + 2) is needed for initialization of cutOffCnt and killers. - Stack stack[MAX_PLY + 10], *ss = stack + 7; - Move pv[MAX_PLY + 1]; - Value alpha, beta; - Value lastBestScore = -VALUE_INFINITE; - std::vector lastBestPV = {Move::none()}; - Depth lastBestMoveDepth = 0; - SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int delta, iterIdx = 0; + Stack stack[MAX_PLY + 10] = {}; + Stack* ss = stack + 7; - std::memset(ss - 7, 0, 10 * sizeof(Stack)); for (int i = 7; i > 0; --i) { (ss - i)->continuationHistory = @@ -239,16 +243,12 @@ void Search::Worker::iterative_deepening() { ss->pv = pv; - Value bestValue = -VALUE_INFINITE; - if (mainThread) { if (mainThread->bestPreviousScore == VALUE_INFINITE) - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = VALUE_ZERO; + mainThread->iterValue.fill(VALUE_ZERO); else - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = mainThread->bestPreviousScore; + mainThread->iterValue.fill(mainThread->bestPreviousScore); } size_t multiPV = size_t(options["MultiPV"]); @@ -489,7 +489,7 @@ void Search::Worker::clear() { h->fill(-71); - for (int i = 1; i < MAX_MOVES; ++i) + for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((20.37 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } diff --git a/src/search.h b/src/search.h index 3a099c5d..b4a65d8e 100644 --- a/src/search.h +++ b/src/search.h @@ -19,6 +19,7 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED +#include #include #include #include @@ -153,11 +154,11 @@ class SearchManager: public ISearchManager { int callsCnt; std::atomic_bool ponder; - double previousTimeReduction; - Value bestPreviousScore; - Value bestPreviousAverageScore; - Value iterValue[4]; - bool stopOnPonderhit; + std::array iterValue; + double previousTimeReduction; + Value bestPreviousScore; + Value bestPreviousAverageScore; + bool stopOnPonderhit; size_t id; }; @@ -233,7 +234,7 @@ class Worker { size_t thread_idx; // Reductions lookup table initialized at startup - int reductions[MAX_MOVES]; // [depth or moveNumber] + std::array reductions; // [depth or moveNumber] // The main thread has a SearchManager, the others have a NullSearchManager std::unique_ptr manager; From 16afec058229067e2f3965672aff450d6a9babc7 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 27 Jan 2024 22:27:22 +0100 Subject: [PATCH 559/678] Refactor pv printing Also fix the case which is currently printing depth 0. fixes #5019 closes https://github.com/official-stockfish/Stockfish/pull/5020 No functional change --- src/search.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++-------- src/search.h | 6 +++++ src/uci.cpp | 58 +---------------------------------------- src/uci.h | 11 -------- 4 files changed, 66 insertions(+), 79 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 29b5c524..0a07c8bb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "evaluate.h" #include "misc.h" @@ -192,9 +193,7 @@ void Search::Worker::start_searching() { // Send again PV info if we have a new best thread if (bestThread != this) - sync_cout << UCI::pv(*bestThread, main_manager()->tm.elapsed(threads.nodes_searched()), - threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - tbConfig.rootInTB) + sync_cout << main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth) << sync_endl; sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); @@ -336,10 +335,7 @@ void Search::Worker::iterative_deepening() { // the UI) before a re-search. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) - sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), - threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - tbConfig.rootInTB) - << sync_endl; + sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. @@ -376,10 +372,7 @@ void Search::Worker::iterative_deepening() { // had time to fully search other root-moves. Thus we suppress this output and // below pick a proven score/PV for this thread (from the previous iteration). && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) - sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), - threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - tbConfig.rootInTB) - << sync_endl; + sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; } if (!threads.stop) @@ -1878,6 +1871,61 @@ void SearchManager::check_time(Search::Worker& worker) { worker.threads.stop = worker.threads.abortedSearch = true; } +std::string SearchManager::pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const { + std::stringstream ss; + + const auto nodes = threads.nodes_searched(); + const auto& rootMoves = worker.rootMoves; + const auto& pos = worker.rootPos; + size_t pvIdx = worker.pvIdx; + TimePoint time = tm.elapsed(nodes) + 1; + size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); + uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); + + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; + + if (depth == 1 && !updated && i > 0) + continue; + + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; + + bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB; + v = tb ? rootMoves[i].tbScore : v; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + ss << "info" + << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 + << " score " << UCI::value(v); + + if (worker.options["UCI_ShowWDL"]) + ss << UCI::wdl(v, pos.game_ply()); + + if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + ss << (rootMoves[i].scoreLowerbound + ? " lowerbound" + : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + + ss << " nodes " << nodes << " nps " << nodes * 1000 / time << " hashfull " << tt.hashfull() + << " tbhits " << tbHits << " time " << time << " pv"; + + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(m, pos.is_chess960()); + } + + return ss.str(); +} + // Called in case we have no ponder move before exiting the search, // for instance, in case we stop the search during a fail high at root. // We try hard to have a ponder move to return to the GUI, diff --git a/src/search.h b/src/search.h index b4a65d8e..c8534b40 100644 --- a/src/search.h +++ b/src/search.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "misc.h" #include "movepick.h" @@ -150,6 +151,11 @@ class SearchManager: public ISearchManager { public: void check_time(Search::Worker& worker) override; + std::string pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const; + Stockfish::TimeManagement tm; int callsCnt; std::atomic_bool ponder; diff --git a/src/uci.cpp b/src/uci.cpp index e6107d47..d1d69d69 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "benchmark.h" #include "evaluate.h" @@ -365,63 +366,6 @@ std::string UCI::move(Move m, bool chess960) { return move; } -std::string UCI::pv(const Search::Worker& workerThread, - TimePoint elapsed, - uint64_t nodesSearched, - uint64_t tb_hits, - int hashfull, - bool rootInTB) { - std::stringstream ss; - TimePoint time = elapsed + 1; - const auto& rootMoves = workerThread.rootMoves; - const auto& depth = workerThread.completedDepth; - const auto& pos = workerThread.rootPos; - size_t pvIdx = workerThread.pvIdx; - size_t multiPV = std::min(size_t(workerThread.options["MultiPV"]), rootMoves.size()); - uint64_t tbHits = tb_hits + (rootInTB ? rootMoves.size() : 0); - - - for (size_t i = 0; i < multiPV; ++i) - { - bool updated = rootMoves[i].score != -VALUE_INFINITE; - - if (depth == 1 && !updated && i > 0) - continue; - - Depth d = updated ? depth : std::max(1, depth - 1); - Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; - - if (v == -VALUE_INFINITE) - v = VALUE_ZERO; - - bool tb = rootInTB && std::abs(v) <= VALUE_TB; - v = tb ? rootMoves[i].tbScore : v; - - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; - - ss << "info" - << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << value(v); - - if (workerThread.options["UCI_ShowWDL"]) - ss << wdl(v, pos.game_ply()); - - if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound - ? " lowerbound" - : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); - - ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / time << " hashfull " - << hashfull << " tbhits " << tbHits << " time " << time << " pv"; - - for (Move m : rootMoves[i].pv) - ss << " " << move(m, pos.is_chess960()); - } - - return ss.str(); -} - namespace { // The win rate model returns the probability of winning (in per mille units) given an // eval and a game ply. It fits the LTC fishtest statistics rather accurately. diff --git a/src/uci.h b/src/uci.h index cd113b1a..9d5f524a 100644 --- a/src/uci.h +++ b/src/uci.h @@ -19,7 +19,6 @@ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED -#include #include #include #include @@ -37,10 +36,6 @@ namespace Eval::NNUE { enum NetSize : int; } -namespace Search { -class Worker; -} - class Move; enum Square : int; using Value = int; @@ -55,12 +50,6 @@ class UCI { static std::string value(Value v); static std::string square(Square s); static std::string move(Move m, bool chess960); - static std::string pv(const Search::Worker& workerThread, - TimePoint elapsed, - uint64_t nodesSearched, - uint64_t tb_hits, - int hashfull, - bool rootInTB); static std::string wdl(Value v, int ply); static Move to_move(const Position& pos, std::string& str); From 3cce4c4cf4e39f05cea01e1020080284bf5a0fae Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 31 Jan 2024 22:47:02 +0100 Subject: [PATCH 560/678] Add Apple Silicon Runners to CI GitHub CI runners are available for macOS 14, these runners are using apple silicon chips (M1). https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/ closes https://github.com/official-stockfish/Stockfish/pull/5025 No functional change --- .github/workflows/stockfish_binaries.yml | 46 ++++++++++++++++++-- .github/workflows/stockfish_compile_test.yml | 17 ++++++++ .github/workflows/stockfish_test.yml | 22 ++++++++-- 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index eff2c2c9..2911bada 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -31,6 +31,13 @@ jobs: comp: clang shell: bash archive_ext: tar + - name: MacOS 14 Apple Clang M1 + os: macos-14 + simple_name: macos-m1 + compiler: clang++ + comp: clang + shell: bash + archive_ext: tar - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 simple_name: windows @@ -51,9 +58,32 @@ jobs: - x86-64-avx512 - x86-64-vnni256 - x86-64-vnni512 + - apple-silicon exclude: + # Apple M1 + - binaries: x86-64 + config: { os: macos-14 } + - binaries: x86-64-sse41-popcnt + config: { os: macos-14 } + - binaries: x86-64-avx2 + config: { os: macos-14 } + - binaries: x86-64-bmi2 + config: { os: macos-14 } + - binaries: x86-64-avxvnni + config: { os: macos-14 } + - binaries: x86-64-avxvnni + config: { os: macos-14 } + - binaries: x86-64-avx512 + config: { os: macos-14 } + - binaries: x86-64-vnni256 + config: { os: macos-14 } + - binaries: x86-64-vnni512 + config: { os: macos-14 } + - binaries: x86-64-avxvnni config: { ubuntu-20.04 } + + # Apple x86_64 (no sde) - binaries: x86-64-avxvnni config: { os: macos-13 } - binaries: x86-64-avx512 @@ -62,6 +92,14 @@ jobs: config: { os: macos-13 } - binaries: x86-64-vnni512 config: { os: macos-13 } + + # Apple silicon from windows, macos-13 and ubuntu + - binaries: apple-silicon + config: { os: windows-2022 } + - binaries: apple-silicon + config: { os: macos-13 } + - binaries: apple-silicon + config: { os: ubuntu-20.04 } defaults: run: working-directory: src @@ -77,7 +115,7 @@ jobs: - name: Install fixed GCC on Linux if: runner.os == 'Linux' - uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 + uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 with: version: 11 @@ -90,7 +128,7 @@ jobs: - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 + uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 with: environmentVariableName: SDE_DIR sdeVersion: 9.27.0 @@ -183,7 +221,7 @@ jobs: - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} @@ -206,7 +244,7 @@ jobs: - name: Prerelease if: github.ref_name == 'master' && env.CHANGES == '0' continue-on-error: true - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 1adc3e34..a47fcb0f 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -26,6 +26,12 @@ jobs: compiler: clang++ comp: clang shell: bash + - name: MacOS 14 Apple Clang M1 + os: macos-14 + compiler: clang++ + comp: clang + shell: bash + m1: true - name: MacOS 13 GCC 11 os: macos-13 compiler: g++-11 @@ -75,26 +81,37 @@ jobs: # x86-64 with newer extensions tests - name: Compile x86-64-avx2 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-avx2 build - name: Compile x86-64-bmi2 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-bmi2 build - name: Compile x86-64-avx512 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-avx512 build - name: Compile x86-64-vnni512 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-vnni512 build - name: Compile x86-64-vnni256 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-vnni256 build + + - name: Compile apple-silicon build + if: matrix.config.m1 + run: | + make clean + make -j2 ARCH=apple-silicon build diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index cff3ef1b..867099ee 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -43,16 +43,16 @@ jobs: compiler: g++ comp: gcc run_riscv64_tests: true - base_image: 'riscv64/alpine:edge' - platform: linux/riscv64 + base_image: "riscv64/alpine:edge" + platform: linux/riscv64 shell: bash - name: Linux GCC ppc64 os: ubuntu-22.04 compiler: g++ comp: gcc run_ppc64_tests: true - base_image: 'ppc64le/alpine:latest' - platform: linux/ppc64le + base_image: "ppc64le/alpine:latest" + platform: linux/ppc64le shell: bash - name: MacOS 13 Apple Clang os: macos-13 @@ -60,6 +60,13 @@ jobs: comp: clang run_64bit_tests: true shell: bash + - name: MacOS 14 Apple Clang M1 + os: macos-14 + compiler: clang++ + comp: clang + run_64bit_tests: false + run_m1_tests: true + shell: bash - name: MacOS 13 GCC 11 os: macos-13 compiler: g++-11 @@ -281,6 +288,13 @@ jobs: make -j2 ARCH=general-64 build ../tests/signature.sh $benchref + - name: Test apple-silicon build + if: matrix.config.run_m1_tests + run: | + make clean + make -j2 ARCH=apple-silicon build + ../tests/signature.sh $benchref + # armv8 tests - name: Test armv8 build From 56b342f9b27827e77cb9e898aa2ed69b628d672f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 27 Jan 2024 21:55:00 +0300 Subject: [PATCH 561/678] Simplify the extension formula Simplify the extension formula in the case of cutNode by removing the depth condition and always setting extension to -2. Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 277280 W: 70760 L: 70802 D: 135718 Ptnml(0-2): 971, 31775, 73153, 31807, 934 https://tests.stockfishchess.org/tests/view/65ad08f779aa8af82b979dd6 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 452976 W: 112992 L: 113215 D: 226769 Ptnml(0-2): 266, 51041, 124112, 50788, 281 https://tests.stockfishchess.org/tests/view/65ae466fc865510db026a760 closes https://github.com/official-stockfish/Stockfish/pull/5021 Bench: 1492957 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 0a07c8bb..6eb05081 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1065,7 +1065,7 @@ moves_loop: // When in check, search starts here // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) - extension = depth < 20 ? -2 : -1; + extension = -2; // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo) else if (ttValue <= value) From f2b6b5cfc9bd27c0595520c96f6e1f8416296f75 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:48:45 +0000 Subject: [PATCH 562/678] Introduce Triple Extensions This replaces singularquietLMR with triple instead of double extending non-capture ttmoves that have value far below singularBeta. This threshold value is initially set to 200, there is scope for more scaling by reducing it as occured with double extensions. Passed STC: https://tests.stockfishchess.org/tests/view/65b683b8c865510db0274074 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 222912 W: 58141 L: 57535 D: 107236 Ptnml(0-2): 1063, 26244, 56154, 27014, 981 Passed LTC: https://tests.stockfishchess.org/tests/view/65bae6d4c865510db0278eb5 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 66306 W: 16825 L: 16440 D: 33041 Ptnml(0-2): 40, 7374, 17952, 7735, 52 closes https://github.com/official-stockfish/Stockfish/pull/5027 bench 1394701 --- src/search.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6eb05081..d47582a5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -522,7 +522,7 @@ Value Search::Worker::search( Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture, singularQuietLMR; + bool givesCheck, improving, priorCapture; bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; @@ -900,7 +900,7 @@ moves_loop: // When in check, search starts here contHist, &thisThread->pawnHistory, countermove, ss->killers); value = bestValue; - moveCountPruning = singularQuietLMR = false; + moveCountPruning = false; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1034,13 +1034,12 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - extension = 1; - singularQuietLMR = !ttCapture; + extension = 1; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 2 && ss->doubleExtensions <= 12) + if (!PvNode && value < singularBeta - 2 && ss->doubleExtensions <= 15) { - extension = 2; + extension = 2 + (value < singularBeta - 200 && !ttCapture); depth += depth < 15; } } @@ -1091,7 +1090,7 @@ moves_loop: // When in check, search starts here // Add extension to new depth newDepth += extension; - ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension == 2); + ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension >= 2); // Speculative prefetch as early as possible prefetch(tt.first_entry(pos.key_after(move))); @@ -1125,10 +1124,6 @@ moves_loop: // When in check, search starts here if (PvNode && tte->bound() != BOUND_UPPER) r--; - // Decrease reduction if a quiet ttMove has been singularly extended (~1 Elo) - if (singularQuietLMR) - r--; - // Increase reduction on repetition (~1 Elo) if (move == (ss - 4)->currentMove && pos.has_repeated()) r += 2; From e815227c3081269f7b37538cf5f32c838991db29 Mon Sep 17 00:00:00 2001 From: gab8192 Date: Thu, 1 Feb 2024 20:38:48 +0100 Subject: [PATCH 563/678] Simplify LMR condition Apply LMR on captures the same way it is applied on quiets Passed Non-Reg STC: https://tests.stockfishchess.org/tests/view/65bbf39bc865510db027a14a LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 77152 W: 19970 L: 19791 D: 37391 Ptnml(0-2): 304, 9159, 19496, 9288, 329 Passed Non-Reg LTC: https://tests.stockfishchess.org/tests/view/65bc8889c865510db027ac9e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 103230 W: 25997 L: 25858 D: 51375 Ptnml(0-2): 71, 11687, 27958, 11830, 69 Hit rate of removed condition (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1)) Total 1253801 Hits 1228904 Hit Rate (%) 98.0143 Hit rate of previous LMR (depth >= 2 && moveCount > 1 + rootNode && ...) Total 1253801 Hits 727234 Hit Rate (%) 58.0023 Hit rate of simplified LMR (depth >= 2 && moveCount > 1 + rootNode) Total 1201839 Hits 713540 Hit Rate (%) 59.3707 closes https://github.com/official-stockfish/Stockfish/pull/5028 Bench: 1438224 --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d47582a5..538f0f30 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1146,11 +1146,7 @@ moves_loop: // When in check, search starts here r -= ss->statScore / 15373; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) - // We use various heuristics for the sons of a node after the first son has - // been searched. In general, we would like to reduce them, but there are many - // cases where we extend a son if it has good chances to be "interesting". - if (depth >= 2 && moveCount > 1 + rootNode - && (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1))) + if (depth >= 2 && moveCount > 1 + rootNode) { // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension From ededadcd6f7fbb9eb122f5fe336025cc4b11753b Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Thu, 1 Feb 2024 08:13:06 +0800 Subject: [PATCH 564/678] VVLTC search tune Search parameters were tuned at 60+0.6 8-thread. Link to the tuning attempt: https://tests.stockfishchess.org/tests/view/65b84e8dc865510db0276030 The most significant change is the triple extension parameter, from 200 to 78. This presumably improves scaling. Additionally, the value < singularBeta - 2 condition for double extensions was removed. This can simply be considered a parameter tweak from 2 to 0. Passed VVLTC: https://tests.stockfishchess.org/tests/view/65baec69c865510db0278f19 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 26136 W: 6564 L: 6305 D: 13267 Ptnml(0-2): 2, 2413, 7977, 2676, 0 Passed VVLTC vs passed PR #5027: https://tests.stockfishchess.org/tests/view/65bc2adfc865510db027a561 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 52968 W: 13372 L: 13046 D: 26550 Ptnml(0-2): 4, 4944, 16265, 5264, 7 STC Elo estimate: https://tests.stockfishchess.org/tests/view/65be5514c865510db027cbc5 closes https://github.com/official-stockfish/Stockfish/pull/5029 Bench: 1478189 --- src/search.cpp | 74 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 538f0f30..e57f2557 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - Value futilityMult = 114 - 47 * noTtCutNode; + Value futilityMult = 116 - 47 * noTtCutNode; return (futilityMult * d - 3 * futilityMult / 2 * improving); } @@ -66,15 +66,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 14095; + v += cv * std::abs(cv) / 12890; return std::clamp(int(v), 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(265 * d - 349, 1112); } +int stat_bonus(Depth d) { return std::min(253 * d - 356, 1117); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(482 * d - 326, 1172); } +int stat_malus(Depth d) { return std::min(517 * d - 308, 1206); } // 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); } @@ -297,12 +297,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(9) + int(avg) * avg / 13181; + delta = Value(9) + int(avg) * avg / 12480; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, int(VALUE_INFINITE)); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 132 * avg / (std::abs(avg) + 98); + optimism[us] = 131 * avg / (std::abs(avg) + 95); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -726,7 +726,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1680, 1406); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1661, 1495); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -747,7 +747,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 435 - (327 - 167 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 450 - (332 - 160 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -760,20 +760,20 @@ Value Search::Worker::search( && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss - 1)->statScore / 327 >= beta - && eval >= beta && eval < 27734 // smaller than TB wins + && eval >= beta && eval < 28702 // smaller than TB wins && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17787 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 22 * depth + 313 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17379 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 329 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 148, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -787,7 +787,7 @@ Value Search::Worker::search( // Do not return unproven mate or TB scores if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { - if (thisThread->nmpMinPly || depth < 15) + if (thisThread->nmpMinPly || depth < 16) return nullValue; assert(!thisThread->nmpMinPly); // Recursive verification is not allowed @@ -820,7 +820,7 @@ Value Search::Worker::search( if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 173 - 73 * improving; + probCutBeta = beta + 182 - 68 * improving; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -880,7 +880,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 427; + probCutBeta = beta + 446; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -963,7 +963,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 277 + 298 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 279 + 295 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -971,7 +971,7 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -203 * depth)) + if (!pos.see_ge(move, -204 * depth)) continue; } else @@ -983,17 +983,17 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4195 * depth) + if (lmrDepth < 6 && history < -4215 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 6992; + lmrDepth += history / 6658; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 63 ? 137 : 64) - + 111 * lmrDepth + && ss->staticEval + (bestValue < ss->staticEval - 58 ? 139 : 55) + + 121 * lmrDepth <= alpha) continue; @@ -1020,11 +1020,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 31) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 29) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (58 + 52 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (62 + 52 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1037,10 +1037,10 @@ moves_loop: // When in check, search starts here extension = 1; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 2 && ss->doubleExtensions <= 15) + if (!PvNode && ss->doubleExtensions <= 16) { - extension = 2 + (value < singularBeta - 200 && !ttCapture); - depth += depth < 15; + extension = 2 + (value < singularBeta - 78 && !ttCapture); + depth += depth < 16; } } @@ -1077,14 +1077,14 @@ moves_loop: // When in check, search starts here // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][move.to_sq()] >= 4111) + && (*contHist[0])[movedPiece][move.to_sq()] >= 4339) extension = 1; // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4484) + > 4356) extension = 1; } @@ -1140,10 +1140,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4119; + + (*contHist[3])[movedPiece][move.to_sq()] - 4409; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / 15373; + r -= ss->statScore / 14894; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1162,7 +1162,7 @@ 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 + 51 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 49 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1278,7 +1278,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13195 && value > -12346) + if (depth > 2 && depth < 13 && beta < 13710 && value > -12589) depth -= 2; assert(depth > 0); @@ -1317,8 +1317,8 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -16797) - + ((ss - 1)->moveCount > 10); + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15401) + + ((ss - 1)->moveCount > 11); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1476,7 +1476,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 186; + futilityBase = ss->staticEval + 204; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1556,7 +1556,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -76)) + if (!pos.see_ge(move, -75)) continue; } @@ -1711,7 +1711,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 177 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 167 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 59691d46a13880534802fe7e610f56813f0e47fc Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 4 Feb 2024 12:59:26 +0300 Subject: [PATCH 565/678] Assorted trivial cleanups Renaming doubleExtensions variable to multiExtensions, since now we have also triple extensions. Some extra cleanups. Recent tests used to measure the elo worth: https://tests.stockfishchess.org/tests/view/659fd0c379aa8af82b96abc3 https://tests.stockfishchess.org/tests/view/65a8f3da79aa8af82b9751e3 https://tests.stockfishchess.org/tests/view/65b51824c865510db0272740 https://tests.stockfishchess.org/tests/view/65b58fbfc865510db0272f5b closes https://github.com/official-stockfish/Stockfish/pull/5032 No functional change --- src/nnue/nnue_feature_transformer.h | 4 +--- src/search.cpp | 20 ++++++++++---------- src/search.h | 11 +++++------ 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 9a162ac9..3399b82d 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -281,7 +281,7 @@ class FeatureTransformer { reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); vec_t* out = reinterpret_cast(output + offset); - for (IndexType j = 0; j < NumOutputChunks; j += 1) + for (IndexType j = 0; j < NumOutputChunks; ++j) { const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); @@ -676,9 +676,7 @@ class FeatureTransformer { update_accumulator_incremental(pos, oldest_st, states_to_update); } else - { update_accumulator_refresh(pos); - } } template diff --git a/src/search.cpp b/src/search.cpp index e57f2557..336678c0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -571,7 +571,7 @@ Value Search::Worker::search( (ss + 1)->excludedMove = bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; - ss->doubleExtensions = (ss - 1)->doubleExtensions; + ss->multipleExtensions = (ss - 1)->multipleExtensions; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; @@ -1036,8 +1036,8 @@ moves_loop: // When in check, search starts here { extension = 1; - // Avoid search explosion by limiting the number of double extensions - if (!PvNode && ss->doubleExtensions <= 16) + // We make sure to limit the extensions in some way to avoid a search explosion + if (!PvNode && ss->multipleExtensions <= 16) { extension = 2 + (value < singularBeta - 78 && !ttCapture); depth += depth < 16; @@ -1090,7 +1090,7 @@ moves_loop: // When in check, search starts here // Add extension to new depth newDepth += extension; - ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension >= 2); + ss->multipleExtensions = (ss - 1)->multipleExtensions + (extension >= 2); // Speculative prefetch as early as possible prefetch(tt.first_entry(pos.key_after(move))); @@ -1142,7 +1142,7 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()] + (*contHist[3])[movedPiece][move.to_sq()] - 4409; - // Decrease/increase reduction for moves with a good/bad history (~25 Elo) + // Decrease/increase reduction for moves with a good/bad history (~8 Elo) r -= ss->statScore / 14894; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) @@ -1150,7 +1150,7 @@ moves_loop: // When in check, search starts here { // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension - // beyond the first move depth. This may lead to hidden double extensions. + // beyond the first move depth. This may lead to hidden multiple extensions. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r, newDepth + 1)); @@ -1371,8 +1371,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, assert(PvNode || (alpha == beta - 1)); assert(depth <= 0); - // Check if we have an upcoming move that draws by repetition, or - // if the opponent had an alternative move earlier to this position. + // Check if we have an upcoming move that draws by repetition, or if + // the opponent had an alternative move earlier to this position. (~1 Elo) if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(this->nodes); @@ -1520,7 +1520,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, 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. + // than alpha we can prune this move. (~2 Elo) if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); @@ -1528,7 +1528,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, } // If static eval is much lower than alpha and move is not winning material - // we can prune this move. + // we can prune this move. (~2 Elo) if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); diff --git a/src/search.h b/src/search.h index c8534b40..97cb2ca4 100644 --- a/src/search.h +++ b/src/search.h @@ -67,7 +67,7 @@ struct Stack { bool inCheck; bool ttPv; bool ttHit; - int doubleExtensions; + int multipleExtensions; int cutoffCnt; }; @@ -136,9 +136,8 @@ struct SharedState { class Worker; -// Null Object Pattern, implement a common interface -// for the SearchManagers. A Null Object will be given to -// non-mainthread workers. +// Null Object Pattern, implement a common interface for the SearchManagers. +// A Null Object will be given to non-mainthread workers. class ISearchManager { public: virtual ~ISearchManager() {} @@ -185,8 +184,8 @@ class Worker { // Reset histories, usually before a new game void clear(); - // Called when the program receives the UCI 'go' - // command. It searches from the root position and outputs the "bestmove". + // Called when the program receives the UCI 'go' command. + // It searches from the root position and outputs the "bestmove". void start_searching(); bool is_mainthread() const { return thread_idx == 0; } From a20726eb0b6c049e191ce0f04dac4f4f923efcee Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 9 Feb 2024 17:56:58 +0100 Subject: [PATCH 566/678] Refactor the CI workflows This refactors the CI workflows to group some logic and makes sure that all (pre)release binaries are actually tested. The screenshot below shows the execution logic of the reworked ci, https://github.com/Disservin/Stockfish/actions/runs/7773581379. You can also hover over the cards to see the execution flow. The `matrix.json` and `arm_matrix.json` define the binaries which will be uploaded to GitHub. Afterwards a matrix is created and each job compiles a profile guided build for that arch and uploads that as an artifact to GitHub. The Binaries/ARM_Binaries workflow's are called when the previous step has been completed, and uploads all artifacts to the (pre)release. This also fixes some indentations and renames the workflows, see https://github.com/official-stockfish/Stockfish/actions, where every workflow is called `Stockfish` vs https://github.com/Disservin/Stockfish/actions. It also increases the parallel compilation used for make from `-j2 to -j4`. It now also prevents the prerelease action from running on forks. A test release can be viewed here https://github.com/Disservin/Stockfish/releases. closes https://github.com/official-stockfish/Stockfish/pull/5035 No functional change --- .github/ci/arm_matrix.json | 51 ++++ .github/{workflows => ci}/libcxx17.imp | 0 .github/ci/matrix.json | 160 +++++++++++ .github/workflows/arm_compilation.yml | 94 +++++++ ...fish_format_check.yml => clang-format.yml} | 22 +- .github/workflows/codeql.yml | 46 ++-- .github/workflows/compilation.yml | 89 +++++++ .../{stockfish_analyzers.yml => iwyu.yml} | 4 +- ...tockfish_sanitizers.yml => sanitizers.yml} | 7 +- .github/workflows/stockfish.yml | 60 +++-- .github/workflows/stockfish_arm_binaries.yml | 170 ------------ .github/workflows/stockfish_binaries.yml | 252 ------------------ .github/workflows/stockfish_compile_test.yml | 117 -------- .../{stockfish_test.yml => tests.yml} | 49 ++-- .github/workflows/upload_binaries.yml | 105 ++++++++ 15 files changed, 608 insertions(+), 618 deletions(-) create mode 100644 .github/ci/arm_matrix.json rename .github/{workflows => ci}/libcxx17.imp (100%) create mode 100644 .github/ci/matrix.json create mode 100644 .github/workflows/arm_compilation.yml rename .github/workflows/{stockfish_format_check.yml => clang-format.yml} (84%) create mode 100644 .github/workflows/compilation.yml rename .github/workflows/{stockfish_analyzers.yml => iwyu.yml} (92%) rename .github/workflows/{stockfish_sanitizers.yml => sanitizers.yml} (93%) delete mode 100644 .github/workflows/stockfish_arm_binaries.yml delete mode 100644 .github/workflows/stockfish_binaries.yml delete mode 100644 .github/workflows/stockfish_compile_test.yml rename .github/workflows/{stockfish_test.yml => tests.yml} (91%) create mode 100644 .github/workflows/upload_binaries.yml diff --git a/.github/ci/arm_matrix.json b/.github/ci/arm_matrix.json new file mode 100644 index 00000000..70f2efaa --- /dev/null +++ b/.github/ci/arm_matrix.json @@ -0,0 +1,51 @@ +{ + "config": [ + { + "name": "Android NDK aarch64", + "os": "ubuntu-22.04", + "simple_name": "android", + "compiler": "aarch64-linux-android21-clang++", + "emu": "qemu-aarch64", + "comp": "ndk", + "shell": "bash", + "archive_ext": "tar" + }, + { + "name": "Android NDK arm", + "os": "ubuntu-22.04", + "simple_name": "android", + "compiler": "armv7a-linux-androideabi21-clang++", + "emu": "qemu-arm", + "comp": "ndk", + "shell": "bash", + "archive_ext": "tar" + } + ], + "binaries": ["armv8-dotprod", "armv8", "armv7", "armv7-neon"], + "exclude": [ + { + "binaries": "armv8-dotprod", + "config": { + "compiler": "armv7a-linux-androideabi21-clang++" + } + }, + { + "binaries": "armv8", + "config": { + "compiler": "armv7a-linux-androideabi21-clang++" + } + }, + { + "binaries": "armv7", + "config": { + "compiler": "aarch64-linux-android21-clang++" + } + }, + { + "binaries": "armv7-neon", + "config": { + "compiler": "aarch64-linux-android21-clang++" + } + } + ] +} diff --git a/.github/workflows/libcxx17.imp b/.github/ci/libcxx17.imp similarity index 100% rename from .github/workflows/libcxx17.imp rename to .github/ci/libcxx17.imp diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json new file mode 100644 index 00000000..c6563ead --- /dev/null +++ b/.github/ci/matrix.json @@ -0,0 +1,160 @@ +{ + "config": [ + { + "name": "Ubuntu 20.04 GCC", + "os": "ubuntu-20.04", + "simple_name": "ubuntu", + "compiler": "g++", + "comp": "gcc", + "shell": "bash", + "archive_ext": "tar", + "sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future --" + }, + { + "name": "MacOS 13 Apple Clang", + "os": "macos-13", + "simple_name": "macos", + "compiler": "clang++", + "comp": "clang", + "shell": "bash", + "archive_ext": "tar" + }, + { + "name": "MacOS 14 Apple Clang M1", + "os": "macos-14", + "simple_name": "macos-m1", + "compiler": "clang++", + "comp": "clang", + "shell": "bash", + "archive_ext": "tar" + }, + { + "name": "Windows 2022 Mingw-w64 GCC x86_64", + "os": "windows-2022", + "simple_name": "windows", + "compiler": "g++", + "comp": "mingw", + "msys_sys": "mingw64", + "msys_env": "x86_64-gcc", + "shell": "msys2 {0}", + "ext": ".exe", + "sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future --", + "archive_ext": "zip" + } + ], + "binaries": [ + "x86-64", + "x86-64-sse41-popcnt", + "x86-64-avx2", + "x86-64-bmi2", + "x86-64-avxvnni", + "x86-64-avx512", + "x86-64-vnni256", + "x86-64-vnni512", + "apple-silicon" + ], + "exclude": [ + { + "binaries": "x86-64", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-sse41-popcnt", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avx2", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-bmi2", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avx512", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-vnni256", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-vnni512", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "ubuntu-20.04": null + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "x86-64-avx512", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "x86-64-vnni256", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "x86-64-vnni512", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "windows-2022" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "ubuntu-20.04" + } + } + ] +} diff --git a/.github/workflows/arm_compilation.yml b/.github/workflows/arm_compilation.yml new file mode 100644 index 00000000..ef141971 --- /dev/null +++ b/.github/workflows/arm_compilation.yml @@ -0,0 +1,94 @@ +name: Compilation +on: + workflow_call: + inputs: + matrix: + type: string + required: true +jobs: + Compilation: + name: ${{ matrix.config.name }} ${{ matrix.binaries }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + EMU: ${{ matrix.config.emu }} + EXT: ${{ matrix.config.ext }} + BINARY: ${{ matrix.binaries }} + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.matrix) }} + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download required linux packages + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install qemu-user + + - name: Install NDK + if: runner.os == 'Linux' + run: | + if [ $COMP == ndk ]; then + NDKV="21.4.7075529" + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;$NDKV" + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV + ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin + echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV + fi + + - name: Extract the bench number from the commit history + run: | + for hash in $(git rev-list -100 HEAD); do + benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + done + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" + + - name: Download the used network from the fishtest framework + run: make net + + - name: Check compiler + run: | + if [ $COMP == ndk ]; then + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + fi + $COMPILER -v + + - name: Test help target + run: make help + + - name: Check git + run: git --version + + # Compile profile guided builds + + - name: Compile ${{ matrix.binaries }} build + run: | + if [ $COMP == ndk ]; then + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + fi + make clean + make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU + make strip ARCH=$BINARY COMP=$COMP + WINE_PATH=$EMU ../tests/signature.sh $benchref + mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT + + - name: Remove non src files + run: git clean -fx + + - name: Upload artifact for (pre)-release + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} + path: . diff --git a/.github/workflows/stockfish_format_check.yml b/.github/workflows/clang-format.yml similarity index 84% rename from .github/workflows/stockfish_format_check.yml rename to .github/workflows/clang-format.yml index 7a47ab6f..0eb3fc70 100644 --- a/.github/workflows/stockfish_format_check.yml +++ b/.github/workflows/clang-format.yml @@ -3,17 +3,17 @@ # executes no shell script nor runs make. # Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ -name: Stockfish +name: Clang-Format on: pull_request_target: branches: - - 'master' + - "master" paths: - - '**.cpp' - - '**.h' + - "**.cpp" + - "**.h" jobs: - Stockfish: - name: clang-format check + Clang-Format: + name: Clang-Format runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 @@ -21,16 +21,16 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Run clang-format style check - uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 + uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 id: clang-format continue-on-error: true with: - clang-format-version: '17' - exclude-regex: 'incbin' + clang-format-version: "17" + exclude-regex: "incbin" - name: Comment on PR if: steps.clang-format.outcome == 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 with: message: | clang-format 17 needs to be run on this PR. @@ -42,7 +42,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome != 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 with: message: | _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d6da8a1c..1c3a3a6b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,12 +2,12 @@ name: "CodeQL" on: push: - branches: [ 'master' ] + branches: ["master"] pull_request: # The branches below must be a subset of the branches above - branches: [ 'master' ] + branches: ["master"] schedule: - - cron: '17 18 * * 1' + - cron: "17 18 * * 1" jobs: analyze: @@ -21,33 +21,33 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'cpp' ] + language: ["cpp"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Use only 'java' to analyze code written in Java, Kotlin, or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality - - name: Build - working-directory: src - run: make -j build ARCH=x86-64-modern + - name: Build + working-directory: src + run: make -j build ARCH=x86-64-modern - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml new file mode 100644 index 00000000..964b5f05 --- /dev/null +++ b/.github/workflows/compilation.yml @@ -0,0 +1,89 @@ +name: Compilation +on: + workflow_call: + inputs: + matrix: + type: string + required: true +jobs: + Compilation: + name: ${{ matrix.config.name }} ${{ matrix.binaries }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + EXT: ${{ matrix.config.ext }} + NAME: ${{ matrix.config.simple_name }} + BINARY: ${{ matrix.binaries }} + SDE: ${{ matrix.config.sde }} + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.matrix) }} + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v4 + + - name: Install fixed GCC on Linux + if: runner.os == 'Linux' + uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 + with: + version: 11 + + - name: Setup msys and install required packages + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.config.msys_sys }} + install: mingw-w64-${{ matrix.config.msys_env }} make git zip + + - name: Download SDE package + if: runner.os == 'Linux' || runner.os == 'Windows' + uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 + with: + environmentVariableName: SDE_DIR + sdeVersion: 9.27.0 + + - name: Download the used network from the fishtest framework + run: make net + + - name: Check compiler + run: $COMPILER -v + + - name: Test help target + run: make help + + - name: Check git + run: git --version + + - name: Check compiler + run: $COMPILER -v + + - name: Show g++ cpu info + if: runner.os != 'macOS' + run: g++ -Q -march=native --help=target + + - name: Show clang++ cpu info + if: runner.os == 'macOS' + run: clang++ -E - -march=native -### + + # x86-64 with newer extensions tests + + - name: Compile ${{ matrix.config.binaries }} build + run: | + make clean + make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE" + make strip ARCH=$BINARY COMP=$COMP + WINE_PATH="$SDE" ../tests/signature.sh $benchref + mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT + + - name: Remove non src files + run: git clean -fx + + - name: Upload artifact for (pre)-release + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} + path: . diff --git a/.github/workflows/stockfish_analyzers.yml b/.github/workflows/iwyu.yml similarity index 92% rename from .github/workflows/stockfish_analyzers.yml rename to .github/workflows/iwyu.yml index f54cdd7c..0552a598 100644 --- a/.github/workflows/stockfish_analyzers.yml +++ b/.github/workflows/iwyu.yml @@ -1,4 +1,4 @@ -name: Stockfish +name: IWYU on: workflow_call: jobs: @@ -44,4 +44,4 @@ jobs: make analyze COMP=clang CXX=include-what-you-use - CXXFLAGS="-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/workflows/libcxx17.imp' -Xiwyu --error" + CXXFLAGS="-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/ci/libcxx17.imp' -Xiwyu --error" diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/sanitizers.yml similarity index 93% rename from .github/workflows/stockfish_sanitizers.yml rename to .github/workflows/sanitizers.yml index e3f04617..7ab1f997 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -1,8 +1,8 @@ -name: Stockfish +name: Sanitizers on: workflow_call: jobs: - Stockfish: + Test-under-sanitizers: name: ${{ matrix.sanitizers.name }} runs-on: ${{ matrix.config.os }} env: @@ -10,6 +10,7 @@ jobs: COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: + fail-fast: false matrix: config: - name: Ubuntu 22.04 GCC @@ -60,5 +61,5 @@ jobs: run: | export CXXFLAGS="-O1 -fno-inline" make clean - make -j2 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null + make -j4 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }} diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index e8db5235..1c0dd0e1 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -1,8 +1,8 @@ name: Stockfish on: push: - tags: - - '*' + tags: + - "*" branches: - master - tools @@ -13,7 +13,7 @@ on: - tools jobs: Prerelease: - if: github.ref == 'refs/heads/master' + if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')) runs-on: ubuntu-latest steps: # returns null if no pre-release exists @@ -25,24 +25,52 @@ jobs: echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV - # delete old previous pre-release and tag - - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1 + # delete old previous pre-release and tag + - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1 if: env.COMMIT_SHA != 'null' with: tag_name: ${{ env.COMMIT_SHA }} github_token: ${{ secrets.GITHUB_TOKEN }} - - Analyzers: - uses: ./.github/workflows/stockfish_analyzers.yml + Matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + arm_matrix: ${{ steps.set-arm-matrix.outputs.arm_matrix }} + steps: + - uses: actions/checkout@v4 + - id: set-matrix + run: | + TASKS=$(echo $(cat .github/ci/matrix.json) ) + echo "MATRIX=$TASKS" >> $GITHUB_OUTPUT + - id: set-arm-matrix + run: | + TASKS_ARM=$(echo $(cat .github/ci/arm_matrix.json) ) + echo "ARM_MATRIX=$TASKS_ARM" >> $GITHUB_OUTPUT + Compilation: + needs: [Matrix] + uses: ./.github/workflows/compilation.yml + with: + matrix: ${{ needs.Matrix.outputs.matrix }} + ARMCompilation: + needs: [Matrix] + uses: ./.github/workflows/arm_compilation.yml + with: + matrix: ${{ needs.Matrix.outputs.arm_matrix }} + IWYU: + uses: ./.github/workflows/iwyu.yml Sanitizers: - uses: ./.github/workflows/stockfish_sanitizers.yml + uses: ./.github/workflows/sanitizers.yml Tests: - uses: ./.github/workflows/stockfish_test.yml - Compiles: - uses: ./.github/workflows/stockfish_compile_test.yml + uses: ./.github/workflows/tests.yml Binaries: - if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag') - uses: ./.github/workflows/stockfish_binaries.yml + if: github.repository == 'official-stockfish/Stockfish' + needs: [Matrix, Prerelease, Compilation] + uses: ./.github/workflows/upload_binaries.yml + with: + matrix: ${{ needs.Matrix.outputs.matrix }} ARM_Binaries: - if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag') - uses: ./.github/workflows/stockfish_arm_binaries.yml + if: github.repository == 'official-stockfish/Stockfish' + needs: [Matrix, Prerelease, ARMCompilation] + uses: ./.github/workflows/upload_binaries.yml + with: + matrix: ${{ needs.Matrix.outputs.arm_matrix }} diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml deleted file mode 100644 index 203c00f2..00000000 --- a/.github/workflows/stockfish_arm_binaries.yml +++ /dev/null @@ -1,170 +0,0 @@ -name: Stockfish -on: - workflow_call: -jobs: - Stockfish: - name: ${{ matrix.config.name }} ${{ matrix.binaries }} - runs-on: ${{ matrix.config.os }} - env: - COMPILER: ${{ matrix.config.compiler }} - COMP: ${{ matrix.config.comp }} - EMU: ${{ matrix.config.emu }} - EXT: ${{ matrix.config.ext }} - OS: ${{ matrix.config.os }} - BINARY: ${{ matrix.binaries }} - strategy: - matrix: - config: - - name: Android NDK aarch64 - os: ubuntu-22.04 - compiler: aarch64-linux-android21-clang++ - emu: qemu-aarch64 - comp: ndk - shell: bash - - name: Android NDK arm - os: ubuntu-22.04 - compiler: armv7a-linux-androideabi21-clang++ - emu: qemu-arm - comp: ndk - shell: bash - binaries: - - armv8-dotprod - - armv8 - - armv7 - - armv7-neon - exclude: - - binaries: armv8-dotprod - config: {compiler: armv7a-linux-androideabi21-clang++} - - binaries: armv8 - config: {compiler: armv7a-linux-androideabi21-clang++} - - binaries: armv7 - config: {compiler: aarch64-linux-android21-clang++} - - binaries: armv7-neon - config: {compiler: aarch64-linux-android21-clang++} - defaults: - run: - working-directory: src - shell: ${{ matrix.config.shell }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Download required linux packages - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install qemu-user - - - name: Install NDK - if: runner.os == 'Linux' - run: | - if [ $COMP == ndk ]; then - NDKV="21.4.7075529" - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk - SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;$NDKV" - ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV - ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin - echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV - fi - - - name: Extract the bench number from the commit history - run: | - for hash in $(git rev-list -100 HEAD); do - benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true - done - [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" - - - name: Download the used network from the fishtest framework - run: make net - - - name: Check compiler - run: | - if [ $COMP == ndk ]; then - export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH - fi - $COMPILER -v - - - name: Test help target - run: make help - - - name: Check git - run: git --version - - # Compile profile guided builds - - - name: Compile ${{ matrix.binaries }} build - run: | - if [ $COMP == ndk ]; then - export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH - export LDFLAGS="-static -Wno-unused-command-line-argument" - fi - make clean - make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU - make strip ARCH=$BINARY COMP=$COMP - WINE_PATH=$EMU ../tests/signature.sh $benchref - mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT - - - name: Remove non src files - run: rm -f *.o .depend *.nnue - - - name: Download wiki - run: | - git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki - cd ../wiki - rm -rf .git - - - name: Create tar archive. - run: | - cd .. - mkdir stockfish - cp -r wiki stockfish/ - cp -r src stockfish/ - cp stockfish-android-$BINARY$EXT stockfish/ - cp "Top CPU Contributors.txt" stockfish/ - cp Copying.txt stockfish/ - cp AUTHORS stockfish/ - cp CITATION.cff stockfish/ - cp README.md stockfish/ - cp CONTRIBUTING.md stockfish/ - tar -cvf stockfish-android-$BINARY.tar stockfish - - - name: Upload binaries - uses: actions/upload-artifact@v3 - with: - name: stockfish-android-${{ matrix.binaries }} - path: stockfish-android-${{ matrix.binaries }}.tar - - - name: Release - if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 - with: - files: stockfish-android-${{ matrix.binaries }}.tar - - - name: Get last commit sha - id: last_commit - run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV - - - name: Get commit date - id: commit_date - run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV - - # Make sure that an old ci which still runs on master doesn't recreate a prerelease - - name: Check Pullable Commits - id: check_commits - run: | - git fetch - CHANGES=$(git rev-list HEAD..origin/master --count) - echo "CHANGES=$CHANGES" >> $GITHUB_ENV - - - name: Prerelease - if: github.ref_name == 'master' && env.CHANGES == '0' - continue-on-error: true - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 - with: - name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} - tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} - prerelease: true - files: stockfish-android-${{ matrix.binaries }}.tar diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml deleted file mode 100644 index 2911bada..00000000 --- a/.github/workflows/stockfish_binaries.yml +++ /dev/null @@ -1,252 +0,0 @@ -name: Stockfish -on: - workflow_call: -jobs: - Stockfish: - name: ${{ matrix.config.name }} ${{ matrix.binaries }} - runs-on: ${{ matrix.config.os }} - env: - COMPILER: ${{ matrix.config.compiler }} - COMP: ${{ matrix.config.comp }} - EXT: ${{ matrix.config.ext }} - SDE: ${{ matrix.config.sde }} - NAME: ${{ matrix.config.simple_name }} - BINARY: ${{ matrix.binaries }} - strategy: - fail-fast: false - matrix: - config: - - name: Ubuntu 20.04 GCC - os: ubuntu-20.04 - simple_name: ubuntu - compiler: g++ - comp: gcc - shell: bash - archive_ext: tar - sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future -- - - name: MacOS 13 Apple Clang - os: macos-13 - simple_name: macos - compiler: clang++ - comp: clang - shell: bash - archive_ext: tar - - name: MacOS 14 Apple Clang M1 - os: macos-14 - simple_name: macos-m1 - compiler: clang++ - comp: clang - shell: bash - archive_ext: tar - - name: Windows 2022 Mingw-w64 GCC x86_64 - os: windows-2022 - simple_name: windows - compiler: g++ - comp: mingw - msys_sys: mingw64 - msys_env: x86_64-gcc - shell: msys2 {0} - ext: .exe - sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future -- - archive_ext: zip - binaries: - - x86-64 - - x86-64-sse41-popcnt - - x86-64-avx2 - - x86-64-bmi2 - - x86-64-avxvnni - - x86-64-avx512 - - x86-64-vnni256 - - x86-64-vnni512 - - apple-silicon - exclude: - # Apple M1 - - binaries: x86-64 - config: { os: macos-14 } - - binaries: x86-64-sse41-popcnt - config: { os: macos-14 } - - binaries: x86-64-avx2 - config: { os: macos-14 } - - binaries: x86-64-bmi2 - config: { os: macos-14 } - - binaries: x86-64-avxvnni - config: { os: macos-14 } - - binaries: x86-64-avxvnni - config: { os: macos-14 } - - binaries: x86-64-avx512 - config: { os: macos-14 } - - binaries: x86-64-vnni256 - config: { os: macos-14 } - - binaries: x86-64-vnni512 - config: { os: macos-14 } - - - binaries: x86-64-avxvnni - config: { ubuntu-20.04 } - - # Apple x86_64 (no sde) - - binaries: x86-64-avxvnni - config: { os: macos-13 } - - binaries: x86-64-avx512 - config: { os: macos-13 } - - binaries: x86-64-vnni256 - config: { os: macos-13 } - - binaries: x86-64-vnni512 - config: { os: macos-13 } - - # Apple silicon from windows, macos-13 and ubuntu - - binaries: apple-silicon - config: { os: windows-2022 } - - binaries: apple-silicon - config: { os: macos-13 } - - binaries: apple-silicon - config: { os: ubuntu-20.04 } - defaults: - run: - working-directory: src - shell: ${{ matrix.config.shell }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Download required macOS packages - if: runner.os == 'macOS' - run: brew install coreutils - - - name: Install fixed GCC on Linux - if: runner.os == 'Linux' - uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 - with: - version: 11 - - - name: Setup msys and install required packages - if: runner.os == 'Windows' - uses: msys2/setup-msys2@v2 - with: - msystem: ${{ matrix.config.msys_sys }} - install: mingw-w64-${{ matrix.config.msys_env }} make git zip - - - name: Download SDE package - if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 - with: - environmentVariableName: SDE_DIR - sdeVersion: 9.27.0 - - - name: Download the used network from the fishtest framework - run: make net - - - name: Extract the bench number from the commit history - run: | - for hash in $(git rev-list -100 HEAD); do - benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true - done - [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" - - - name: Check compiler - run: $COMPILER -v - - - name: Show g++ cpu info - if: runner.os != 'macOS' - run: g++ -Q -march=native --help=target - - - name: Show clang++ cpu info - if: runner.os == 'macOS' - run: clang++ -E - -march=native -### - - - name: Test help target - run: make help - - - name: Check git - run: git --version - - # Compile profile guided builds - - - name: Compile ${{ matrix.binaries }} build - run: | - make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE" - make strip ARCH=$BINARY COMP=$COMP - WINE_PATH="$SDE" ../tests/signature.sh $benchref - mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT - - - name: Remove non src files - run: git clean -fx - - - name: Download wiki - run: | - git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki - rm -rf ../wiki/.git - - - name: Create directory. - run: | - cd .. - mkdir stockfish - cp -r wiki stockfish/ - cp -r src stockfish/ - cp stockfish-$NAME-$BINARY$EXT stockfish/ - cp "Top CPU Contributors.txt" stockfish/ - cp Copying.txt stockfish/ - cp AUTHORS stockfish/ - cp CITATION.cff stockfish/ - cp README.md stockfish/ - cp CONTRIBUTING.md stockfish/ - - - name: Create tar - if: runner.os != 'Windows' - run: | - cd .. - tar -cvf stockfish-$NAME-$BINARY.tar stockfish - - - name: Create zip - if: runner.os == 'Windows' - run: | - cd .. - zip -r stockfish-$NAME-$BINARY.zip stockfish - - - name: Upload binaries - if: runner.os != 'Windows' - uses: actions/upload-artifact@v3 - with: - name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} - path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar - - # Artifacts automatically get zipped. - # To avoid double-zipping, we use the unzipped directory - - name: Upload binaries - if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 - with: - name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} - path: stockfish - - - name: Release - if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 - with: - files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} - - - name: Get last commit sha - id: last_commit - run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV - - - name: Get commit date - id: commit_date - run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV - - # Make sure that an old ci that still runs on master doesn't recreate a prerelease - - name: Check Pullable Commits - id: check_commits - run: | - git fetch - CHANGES=$(git rev-list HEAD..origin/master --count) - echo "CHANGES=$CHANGES" >> $GITHUB_ENV - - - name: Prerelease - if: github.ref_name == 'master' && env.CHANGES == '0' - continue-on-error: true - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 - with: - name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} - tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} - prerelease: true - files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml deleted file mode 100644 index a47fcb0f..00000000 --- a/.github/workflows/stockfish_compile_test.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Stockfish -on: - workflow_call: -jobs: - Stockfish: - name: ${{ matrix.config.name }} - runs-on: ${{ matrix.config.os }} - env: - COMPILER: ${{ matrix.config.compiler }} - COMP: ${{ matrix.config.comp }} - strategy: - matrix: - config: - - name: Ubuntu 20.04 GCC - os: ubuntu-20.04 - compiler: g++ - comp: gcc - shell: bash - - name: Ubuntu 20.04 Clang - os: ubuntu-20.04 - compiler: clang++ - comp: clang - shell: bash - - name: MacOS 13 Apple Clang - os: macos-13 - compiler: clang++ - comp: clang - shell: bash - - name: MacOS 14 Apple Clang M1 - os: macos-14 - compiler: clang++ - comp: clang - shell: bash - m1: true - - name: MacOS 13 GCC 11 - os: macos-13 - compiler: g++-11 - comp: gcc - shell: bash - - name: Windows 2022 Mingw-w64 GCC x86_64 - os: windows-2022 - compiler: g++ - comp: mingw - msys_sys: mingw64 - msys_env: x86_64-gcc - shell: msys2 {0} - - name: Windows 2022 Mingw-w64 Clang x86_64 - os: windows-2022 - compiler: clang++ - comp: clang - msys_sys: clang64 - msys_env: clang-x86_64-clang - shell: msys2 {0} - - defaults: - run: - working-directory: src - shell: ${{ matrix.config.shell }} - steps: - - uses: actions/checkout@v4 - - - name: Setup msys and install required packages - if: runner.os == 'Windows' - uses: msys2/setup-msys2@v2 - with: - msystem: ${{matrix.config.msys_sys}} - install: mingw-w64-${{matrix.config.msys_env}} make git - - - name: Download the used network from the fishtest framework - run: make net - - - name: Check compiler - run: $COMPILER -v - - - name: Test help target - run: make help - - - name: Check git - run: git --version - - # x86-64 with newer extensions tests - - - name: Compile x86-64-avx2 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-avx2 build - - - name: Compile x86-64-bmi2 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-bmi2 build - - - name: Compile x86-64-avx512 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-avx512 build - - - name: Compile x86-64-vnni512 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-vnni512 build - - - name: Compile x86-64-vnni256 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-vnni256 build - - - name: Compile apple-silicon build - if: matrix.config.m1 - run: | - make clean - make -j2 ARCH=apple-silicon build diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/tests.yml similarity index 91% rename from .github/workflows/stockfish_test.yml rename to .github/workflows/tests.yml index 867099ee..702e86e5 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/tests.yml @@ -1,8 +1,8 @@ -name: Stockfish +name: Tests on: workflow_call: jobs: - Stockfish: + Test-Targets: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} env: @@ -10,6 +10,7 @@ jobs: COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: + fail-fast: false matrix: config: - name: Ubuntu 20.04 GCC @@ -190,35 +191,35 @@ jobs: run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean - make -j2 ARCH=x86-32 optimize=no debug=yes build + make -j4 ARCH=x86-32 optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-32 build if: matrix.config.run_32bit_tests run: | make clean - make -j2 ARCH=x86-32 build + make -j4 ARCH=x86-32 build ../tests/signature.sh $benchref - name: Test x86-32-sse41-popcnt build if: matrix.config.run_32bit_tests run: | make clean - make -j2 ARCH=x86-32-sse41-popcnt build + make -j4 ARCH=x86-32-sse41-popcnt build ../tests/signature.sh $benchref - name: Test x86-32-sse2 build if: matrix.config.run_32bit_tests run: | make clean - make -j2 ARCH=x86-32-sse2 build + make -j4 ARCH=x86-32-sse2 build ../tests/signature.sh $benchref - name: Test general-32 build if: matrix.config.run_32bit_tests run: | make clean - make -j2 ARCH=general-32 build + make -j4 ARCH=general-32 build ../tests/signature.sh $benchref # x86-64 tests @@ -228,21 +229,21 @@ jobs: run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean - make -j2 ARCH=x86-64-avx2 optimize=no debug=yes build + make -j4 ARCH=x86-64-avx2 optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-64-bmi2 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-bmi2 build + make -j4 ARCH=x86-64-bmi2 build ../tests/signature.sh $benchref - name: Test x86-64-avx2 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-avx2 build + make -j4 ARCH=x86-64-avx2 build ../tests/signature.sh $benchref # Test a deprecated arch @@ -250,49 +251,49 @@ jobs: if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-modern build + make -j4 ARCH=x86-64-modern build ../tests/signature.sh $benchref - name: Test x86-64-sse41-popcnt build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-sse41-popcnt build + make -j4 ARCH=x86-64-sse41-popcnt build ../tests/signature.sh $benchref - name: Test x86-64-ssse3 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-ssse3 build + make -j4 ARCH=x86-64-ssse3 build ../tests/signature.sh $benchref - name: Test x86-64-sse3-popcnt build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-sse3-popcnt build + make -j4 ARCH=x86-64-sse3-popcnt build ../tests/signature.sh $benchref - name: Test x86-64 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64 build + make -j4 ARCH=x86-64 build ../tests/signature.sh $benchref - name: Test general-64 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=general-64 build + make -j4 ARCH=general-64 build ../tests/signature.sh $benchref - name: Test apple-silicon build if: matrix.config.run_m1_tests run: | make clean - make -j2 ARCH=apple-silicon build + make -j4 ARCH=apple-silicon build ../tests/signature.sh $benchref # armv8 tests @@ -303,7 +304,7 @@ jobs: export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean - make -j2 ARCH=armv8 build + make -j4 ARCH=armv8 build ../tests/signature.sh $benchref - name: Test armv8-dotprod build @@ -312,7 +313,7 @@ jobs: export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean - make -j2 ARCH=armv8-dotprod build + make -j4 ARCH=armv8-dotprod build ../tests/signature.sh $benchref # armv7 tests @@ -323,7 +324,7 @@ jobs: export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean - make -j2 ARCH=armv7 build + make -j4 ARCH=armv7 build ../tests/signature.sh $benchref - name: Test armv7-neon build @@ -332,7 +333,7 @@ jobs: export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean - make -j2 ARCH=armv7-neon build + make -j4 ARCH=armv7-neon build ../tests/signature.sh $benchref # riscv64 tests @@ -340,7 +341,7 @@ jobs: - name: Test riscv64 build if: matrix.config.run_riscv64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > script.sh + echo "export LDFLAGS='-static' && make clean && make -j4 ARCH=riscv64 build" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder ../tests/signature.sh $benchref @@ -349,7 +350,7 @@ jobs: - name: Test ppc64 build if: matrix.config.run_ppc64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > script.sh + echo "export LDFLAGS='-static' && make clean && make -j4 ARCH=ppc-64 build" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder ../tests/signature.sh $benchref @@ -359,6 +360,6 @@ jobs: if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-avx2 build + make -j4 ARCH=x86-64-avx2 build ../tests/perft.sh ../tests/reprosearch.sh diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml new file mode 100644 index 00000000..7c8f470b --- /dev/null +++ b/.github/workflows/upload_binaries.yml @@ -0,0 +1,105 @@ +name: Upload Binaries +on: + workflow_call: + inputs: + matrix: + type: string + required: true + +jobs: + Artifacts: + name: ${{ matrix.config.name }} ${{ matrix.binaries }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + EXT: ${{ matrix.config.ext }} + NAME: ${{ matrix.config.simple_name }} + BINARY: ${{ matrix.binaries }} + SDE: ${{ matrix.config.sde }} + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.matrix) }} + defaults: + run: + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v4 + + - name: Download artifact from compilation + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} + path: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} + + - name: Setup msys and install required packages + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.config.msys_sys }} + install: mingw-w64-${{ matrix.config.msys_env }} make git zip + + - name: Create Package + run: | + mkdir stockfish + + - name: Download wiki + run: | + git clone https://github.com/official-stockfish/Stockfish.wiki.git wiki + rm -rf wiki/.git + mv wiki stockfish/ + + - name: Copy files + run: | + mv "${{ matrix.config.simple_name }} ${{ matrix.binaries }}" stockfish-workflow + cd stockfish-workflow + cp -r src ../stockfish/ + cp stockfish-$NAME-$BINARY$EXT ../stockfish/ + cp "Top CPU Contributors.txt" ../stockfish/ + cp Copying.txt ../stockfish/ + cp AUTHORS ../stockfish/ + cp CITATION.cff ../stockfish/ + cp README.md ../stockfish/ + cp CONTRIBUTING.md ../stockfish/ + + - name: Create tar + if: runner.os != 'Windows' + run: | + tar -cvf stockfish-$NAME-$BINARY.tar stockfish + + - name: Create zip + if: runner.os == 'Windows' + run: | + zip -r stockfish-$NAME-$BINARY.zip stockfish + + - name: Release + if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + with: + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} + + - name: Get last commit sha + id: last_commit + run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV + + - name: Get commit date + id: commit_date + run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + + # Make sure that an old ci that still runs on master doesn't recreate a prerelease + - name: Check Pullable Commits + id: check_commits + run: | + git fetch + CHANGES=$(git rev-list HEAD..origin/master --count) + echo "CHANGES=$CHANGES" >> $GITHUB_ENV + + - name: Prerelease + if: github.ref_name == 'master' && env.CHANGES == '0' + continue-on-error: true + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + with: + name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + prerelease: true + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} From f2984471c90d82284720f681314ff87bf5fd833c Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 1 Feb 2024 21:09:48 +0100 Subject: [PATCH 567/678] Tweak capture scoring for move ordering Move divisor from capture scoring to good capture check and sligthly increase it. This has several effects: - its a speedup because for quience and probcut search the division now never happens. For main search its delayed and can be avoided if a good capture triggers a cutoff - through the higher resolution of scores we have a more granular sorting STC: https://tests.stockfishchess.org/tests/view/65bf2a93c865510db027dc27 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 470016 W: 122150 L: 121173 D: 226693 Ptnml(0-2): 2133, 55705, 118374, 56644, 2152 LTC: https://tests.stockfishchess.org/tests/view/65c1d16dc865510db0281339 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 98988 W: 25121 L: 24667 D: 49200 Ptnml(0-2): 77, 10998, 26884, 11464, 71 closes https://github.com/official-stockfish/Stockfish/pull/5036 Bench: 1233867 --- src/movepick.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b2638350..e86438c3 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -167,9 +167,8 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = - (7 * int(PieceValue[pos.piece_on(m.to_sq())]) - + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]) - / 16; + 7 * int(PieceValue[pos.piece_on(m.to_sq())]) + + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]; else if constexpr (Type == QUIETS) { @@ -269,7 +268,8 @@ top: case GOOD_CAPTURE : if (select([&]() { // Move losing capture to endBadCaptures to be tried later - return pos.see_ge(*cur, -cur->value) ? true : (*endBadCaptures++ = *cur, false); + return pos.see_ge(*cur, -cur->value / 18) ? true + : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); From c0107b3c27e9542a3319e9af0396794b7b2df890 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:56:13 +0800 Subject: [PATCH 568/678] Remove simple eval With the recent introduction of the dual NNUE, the need for simple eval is no longer there. Passed STC: https://tests.stockfishchess.org/tests/view/65c1f735c865510db0281652 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 85312 W: 22009 L: 21837 D: 41466 Ptnml(0-2): 334, 10155, 21567, 10205, 395 Passed LTC: https://tests.stockfishchess.org/tests/view/65c2d64bc865510db0282810 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 49956 W: 12596 L: 12402 D: 24958 Ptnml(0-2): 28, 5553, 13624, 5743, 30 closes https://github.com/official-stockfish/Stockfish/pull/5037 Bench 1213676 --- src/evaluate.cpp | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 45658798..c8656fc2 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -199,33 +199,24 @@ Value Eval::evaluate(const Position& pos, int optimism) { assert(!pos.checkers()); - int v; - Color stm = pos.side_to_move(); - int shuffling = pos.rule50_count(); - int simpleEval = simple_eval(pos, stm); + int simpleEval = simple_eval(pos, pos.side_to_move()); + bool smallNet = std::abs(simpleEval) > 1050; - bool lazy = std::abs(simpleEval) > 2550; - if (lazy) - v = simpleEval; - else - { - bool smallNet = std::abs(simpleEval) > 1050; + int nnueComplexity; - int nnueComplexity; + Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) + : NNUE::evaluate(pos, true, &nnueComplexity); - Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) - : NNUE::evaluate(pos, true, &nnueComplexity); + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; - nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; - - int npm = pos.non_pawn_material() / 64; - v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; - } + int npm = pos.non_pawn_material() / 64; + int v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; // Damp down the evaluation linearly when shuffling - v = v * (200 - shuffling) / 214; + int shuffling = pos.rule50_count(); + v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range v = std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 15093d43c413a9af4b8adfe4d7031f822c58fbf2 Mon Sep 17 00:00:00 2001 From: gahtan-syarif <155860115+gahtan-syarif@users.noreply.github.com> Date: Thu, 8 Feb 2024 04:20:45 +0700 Subject: [PATCH 569/678] Simplify opponent movecount reduction This removes the reduction decrease that occured when the previous ply had a movecount greater than 7. Passed STC: https://tests.stockfishchess.org/tests/view/65c3f6dac865510db0283ef6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 11968 W: 3205 L: 2953 D: 5810 Ptnml(0-2): 38, 1310, 3064, 1506, 66 Passed LTC: https://tests.stockfishchess.org/tests/view/65c42377c865510db0284217 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 35676 W: 9113 L: 8905 D: 17658 Ptnml(0-2): 22, 3893, 9802, 4097, 24 closes https://github.com/official-stockfish/Stockfish/pull/5040 Bench: 1148379 --- AUTHORS | 1 + src/search.cpp | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index cc8edafa..9b1faee7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -78,6 +78,7 @@ Fauzi Akram Dabat (fauzi2) Felix Wittmann gamander Gabriele Lombardo (gabe) +Gahtan Nahdi Gary Heckman (gheckman) George Sobala (gsobala) gguliash diff --git a/src/search.cpp b/src/search.cpp index 336678c0..63e7699e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1108,10 +1108,6 @@ moves_loop: // When in check, search starts here if (ss->ttPv) r -= 1 + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); - // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss - 1)->moveCount > 7) - r--; - // Increase reduction for cut nodes (~4 Elo) if (cutNode) r += 2 - (tte->depth() >= depth && ss->ttPv); From 96837bc4396d205536cdaabfc17e4885a48b0588 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 7 Feb 2024 22:00:51 +0800 Subject: [PATCH 570/678] Remove check extension Passed simplification STC: https://tests.stockfishchess.org/tests/view/65c38d2ac865510db02836cf LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 52288 W: 13578 L: 13371 D: 25339 Ptnml(0-2): 197, 6171, 13265, 6250, 261 Passed simplification LTC: https://tests.stockfishchess.org/tests/view/65c4470ec865510db0284473 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 44958 W: 11255 L: 11055 D: 22648 Ptnml(0-2): 37, 4962, 12274, 5176, 30 closes https://github.com/official-stockfish/Stockfish/pull/5041 Bench: 1116591 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 63e7699e..05afc9ce 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1071,10 +1071,6 @@ moves_loop: // When in check, search starts here extension = -1; } - // Check extensions (~1 Elo) - else if (givesCheck && depth > 10) - extension = 1; - // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] && (*contHist[0])[movedPiece][move.to_sq()] >= 4339) From 9699f4f79ae9bb193c1f74adf53886a1a56f8a91 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 8 Feb 2024 13:54:40 -0800 Subject: [PATCH 571/678] Fix the alignment of the transformer buffer Fixes the issue mentioned in https://github.com/official-stockfish/Stockfish/commit/584d9efedcde330eeb96a99215552ddfb06f52ba#r138417600. Thanks to @cj5716 and @peregrineshahin for spotting this! closes https://github.com/official-stockfish/Stockfish/pull/5042 No functional change --- src/nnue/evaluate_nnue.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index d4a4dbe4..5bd7e83d 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -197,11 +197,10 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer < Small ? TransformedFeatureDimensionsSmall - : TransformedFeatureDimensionsBig, - nullptr - > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall + : TransformedFeatureDimensionsBig, + nullptr > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else From 21dff6c2765d075a4e109e5dda98b7bf5af2cec9 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 10 Feb 2024 11:32:10 +0100 Subject: [PATCH 572/678] Update CI actions - Update codeql to v3 - Switch from dev-drprasad to native github cli - Update softprops/action-gh-release to node 20 commit `thollander/actions-comment-pull-request` needs to be bumped to node20 too, but the author hasnt done so atm closes https://github.com/official-stockfish/Stockfish/pull/5044 No functional change --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/stockfish.yml | 8 ++++---- .github/workflows/upload_binaries.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1c3a3a6b..d949a5a7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,6 +48,6 @@ jobs: run: make -j build ARCH=x86-64-modern - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 1c0dd0e1..22cd9af3 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -26,11 +26,11 @@ jobs: echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV # delete old previous pre-release and tag - - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1 + - uses: actions/checkout@v4 + - run: gh release delete ${{ env.COMMIT_SHA }} --cleanup-tag if: env.COMMIT_SHA != 'null' - with: - tag_name: ${{ env.COMMIT_SHA }} - github_token: ${{ secrets.GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Matrix: runs-on: ubuntu-latest outputs: diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 7c8f470b..97bcf96f 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -74,7 +74,7 @@ jobs: - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 with: files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} From 3d5b16df7c2125ba52a25b3d4b69fc1261c4eb80 Mon Sep 17 00:00:00 2001 From: GoldenRare Date: Tue, 6 Feb 2024 05:45:52 -0500 Subject: [PATCH 573/678] Remove unnecessary assignments related to adjusted static evaluation In both search and qsearch, there are instances where we do unadjustedStaticEval = ss->staticEval = eval/bestValue = tte->eval(), but immediately after re-assign ss-static and eval/bestValue to some new value, which makes the initial assignment redundant. closes https://github.com/official-stockfish/Stockfish/pull/5045 No functional change --- src/search.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 05afc9ce..b186d8a9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -701,9 +701,9 @@ Value Search::Worker::search( else if (ss->ttHit) { // Never assume anything about values stored in TT - unadjustedStaticEval = ss->staticEval = eval = tte->eval(); - if (eval == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = tte->eval(); + if (unadjustedStaticEval == VALUE_NONE) + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); @@ -715,7 +715,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -1434,9 +1434,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (ss->ttHit) { // Never assume anything about values stored in TT - if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = bestValue = - evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = tte->eval(); + if (unadjustedStaticEval == VALUE_NONE) + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1448,10 +1448,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, else { // In case of null move search, use previous static eval with a different sign - unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != Move::null() ? evaluate(pos, thisThread->optimism[us]) - : -(ss - 1)->staticEval; - ss->staticEval = bestValue = + unadjustedStaticEval = (ss - 1)->currentMove != Move::null() + ? evaluate(pos, thisThread->optimism[us]) + : -(ss - 1)->staticEval; + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); } From 9068fdc57bcaaa142938e18a529761de1063f786 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 10 Feb 2024 13:58:38 +0100 Subject: [PATCH 574/678] Assorted cleanups Assorted cleanups closes https://github.com/official-stockfish/Stockfish/pull/5046 No functional change Co-Authored-By: Shahin M. Shahin <41402573+peregrineshahin@users.noreply.github.com> Co-Authored-By: cj5716 <125858804+cj5716@users.noreply.github.com> --- src/bitboard.h | 3 --- src/evaluate.cpp | 2 +- src/movepick.cpp | 2 +- src/position.h | 2 -- src/search.cpp | 37 ++++++++++++++++--------------------- src/search.h | 18 +++++++----------- 6 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index d028be02..cdff4c75 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -163,7 +163,6 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { inline Bitboard line_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); - return LineBB[s1][s2]; } @@ -178,7 +177,6 @@ inline Bitboard line_bb(Square s1, Square s2) { inline Bitboard between_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); - return BetweenBB[s1][s2]; } @@ -216,7 +214,6 @@ template inline Bitboard attacks_bb(Square s) { assert((Pt != PAWN) && (is_ok(s))); - return PseudoAttacks[Pt][s]; } diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c8656fc2..da086766 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -219,7 +219,7 @@ Value Eval::evaluate(const Position& pos, int optimism) { v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range - v = std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); return v; } diff --git a/src/movepick.cpp b/src/movepick.cpp index e86438c3..33791922 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -173,7 +173,7 @@ void MovePicker::score() { else if constexpr (Type == QUIETS) { Piece pc = pos.moved_piece(m); - PieceType pt = type_of(pos.moved_piece(m)); + PieceType pt = type_of(pc); Square from = m.from_sq(); Square to = m.to_sq(); diff --git a/src/position.h b/src/position.h index 7ce3556f..154ed652 100644 --- a/src/position.h +++ b/src/position.h @@ -252,13 +252,11 @@ inline CastlingRights Position::castling_rights(Color c) const { inline bool Position::castling_impeded(CastlingRights cr) const { assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - return pieces() & castlingPath[cr]; } inline Square Position::castling_rook_square(CastlingRights cr) const { assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - return castlingRookSquare[cr]; } diff --git a/src/search.cpp b/src/search.cpp index b186d8a9..4e0d808e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -67,7 +67,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; v += cv * std::abs(cv) / 12890; - return std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth @@ -297,9 +297,9 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(9) + int(avg) * avg / 12480; + delta = 9 + avg * avg / 12480; alpha = std::max(avg - delta, -VALUE_INFINITE); - beta = std::min(avg + delta, int(VALUE_INFINITE)); + beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) optimism[us] = 131 * avg / (std::abs(avg) + 95); @@ -350,7 +350,7 @@ void Search::Worker::iterative_deepening() { } else if (bestValue >= beta) { - beta = std::min(bestValue + delta, int(VALUE_INFINITE)); + beta = std::min(bestValue + delta, VALUE_INFINITE); ++failedHighCnt; } else @@ -481,7 +481,6 @@ void Search::Worker::clear() { for (auto& h : to) h->fill(-71); - for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((20.37 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -538,7 +537,7 @@ Value Search::Worker::search( // Check for the available remaining time if (is_mainthread()) - main_manager()->check_time(*this); + main_manager()->check_time(*thisThread); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) if (PvNode && thisThread->selDepth < ss->ply + 1) @@ -680,10 +679,8 @@ Value Search::Worker::search( } } - - Value unadjustedStaticEval = VALUE_NONE; - // Step 6. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) { // Skip early pruning when in check @@ -820,11 +817,10 @@ Value Search::Worker::search( if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 182 - 68 * improving; - // Step 11. ProbCut (~10 Elo) // 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 + 182 - 68 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -1285,7 +1281,6 @@ moves_loop: // When in check, search starts here { if (capture) capturesSearched[captureCount++] = move; - else quietsSearched[quietCount++] = move; } @@ -1424,9 +1419,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; - Value unadjustedStaticEval = VALUE_NONE; - // Step 4. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; else @@ -1521,7 +1515,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // If static eval is much lower than alpha and move is not winning material // we can prune this move. (~2 Elo) - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1597,7 +1591,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (ss->inCheck && bestValue == -VALUE_INFINITE) { assert(!MoveList(pos).size()); - return mated_in(ss->ply); // Plies to mate from the root } @@ -1615,6 +1608,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, return bestValue; } +Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { + int reductionScale = reductions[d] * reductions[mn]; + return (reductionScale + 1177 - delta * 776 / rootDelta) / 1024 + (!i && reductionScale > 842); +} namespace { // Adjusts a mate or TB score from "plies to mate from the root" @@ -1623,7 +1620,6 @@ namespace { Value value_to_tt(Value v, int ply) { assert(v != VALUE_NONE); - return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; } @@ -1805,9 +1801,9 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { for (size_t i = 0; i < multiPV; ++i) { // This is our magic formula - int push = int((weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) - / 128); + int push = (weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) + / 128; if (rootMoves[i].score + push >= maxScore) { @@ -1921,7 +1917,6 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po bool ttHit; assert(pv.size() == 1); - if (pv[0] == Move::none()) return false; diff --git a/src/search.h b/src/search.h index 97cb2ca4..2d1077f8 100644 --- a/src/search.h +++ b/src/search.h @@ -47,7 +47,6 @@ enum NodeType { class TranspositionTable; class ThreadPool; class OptionsMap; -class UCI; namespace Search { @@ -124,10 +123,12 @@ struct LimitsType { // The UCI stores the uci options, thread pool, and transposition table. // This struct is used to easily forward data to the Search::Worker class. struct SharedState { - SharedState(const OptionsMap& o, ThreadPool& tp, TranspositionTable& t) : - options(o), - threads(tp), - tt(t) {} + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable) : + options(optionsMap), + threads(threadPool), + tt(transpositionTable) {} const OptionsMap& options; ThreadPool& threads; @@ -209,11 +210,7 @@ class Worker { template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); - Depth reduction(bool i, Depth d, int mn, int delta) { - int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1177 - int(delta) * 776 / int(rootDelta)) / 1024 - + (!i && reductionScale > 842); - } + Depth reduction(bool i, Depth d, int mn, int delta); // Get a pointer to the search manager, only allowed to be called by the // main thread. @@ -251,7 +248,6 @@ class Worker { TranspositionTable& tt; friend class Stockfish::ThreadPool; - friend class Stockfish::UCI; friend class SearchManager; }; From 91a4cea437fc0ae177808dd0a9ef791f38229c7b Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 10 Feb 2024 20:17:21 +0300 Subject: [PATCH 575/678] Adjust best value in main search depending on depth This patch does similar thing to how it's done for qsearch - in case of fail high adjust result to lower value. Difference is that it is done only for non-pv nodes and it's depth dependent - so lower depth entries will have bigger adjustment and higher depth entries will have smaller adjustment. Passed STC: https://tests.stockfishchess.org/tests/view/65c3c0cbc865510db0283b21 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 112032 W: 29142 L: 28705 D: 54185 Ptnml(0-2): 479, 13152, 28326, 13571, 488 Passed LTC: https://tests.stockfishchess.org/tests/view/65c52e62c865510db02855d5 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 132480 W: 33457 L: 32936 D: 66087 Ptnml(0-2): 67, 14697, 36222, 15156, 98 closes https://github.com/official-stockfish/Stockfish/pull/5047 Bench: 1168241 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 4e0d808e..7a60e973 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1293,6 +1293,11 @@ moves_loop: // When in check, search starts here assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); + // Adjust best value for fail high cases at non-pv nodes + if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && + std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + bestValue = (bestValue * (depth + 2) + beta) / (depth + 3); + if (!moveCount) bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; From 531747ee7889d9b61b9841a57bb6d582459999d6 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 10 Feb 2024 15:06:38 -0800 Subject: [PATCH 576/678] Improve thread voting inefficiencies Initialize the unordered map to a reasonable number of buckets and make the move hashes well distributed. For more see https://github.com/official-stockfish/Stockfish/pull/4958#issuecomment-1937351190 Also make bestThreadPV and newThreadPV references so we don't copy entire vectors. closes https://github.com/official-stockfish/Stockfish/pull/5048 No functional change --- src/thread.cpp | 8 +++----- src/types.h | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/thread.cpp b/src/thread.cpp index 3cce7c56..2e42abd4 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -210,10 +210,9 @@ void ThreadPool::start_thinking(const OptionsMap& options, Thread* ThreadPool::get_best_thread() const { - std::unordered_map votes; - Thread* bestThread = threads.front(); Value minScore = VALUE_NONE; + std::unordered_map votes(2 * std::min(size(), bestThread->worker->rootMoves.size())); // Find the minimum score of all threads for (Thread* th : threads) @@ -232,13 +231,12 @@ Thread* ThreadPool::get_best_thread() const { const auto bestThreadScore = bestThread->worker->rootMoves[0].score; const auto newThreadScore = th->worker->rootMoves[0].score; - const auto bestThreadPV = bestThread->worker->rootMoves[0].pv; - const auto newThreadPV = th->worker->rootMoves[0].pv; + const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv; + const auto& newThreadPV = th->worker->rootMoves[0].pv; const auto bestThreadMoveVote = votes[bestThreadPV[0]]; const auto newThreadMoveVote = votes[newThreadPV[0]]; - const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; diff --git a/src/types.h b/src/types.h index e83b306d..8b0ffb0c 100644 --- a/src/types.h +++ b/src/types.h @@ -397,7 +397,7 @@ class Move { constexpr std::uint16_t raw() const { return data; } struct MoveHash { - std::size_t operator()(const Move& m) const { return m.data; } + std::size_t operator()(const Move& m) const { return make_key(m.data); } }; protected: From c115e5171eed2650b59f9a8da7cd6153e4cff873 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sat, 10 Feb 2024 04:00:36 +0700 Subject: [PATCH 577/678] Remove quiet tt move extensions Passed STC: https://tests.stockfishchess.org/tests/view/65c6934cc865510db0286e90 LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 54016 W: 14065 L: 13854 D: 26097 Ptnml(0-2): 231, 6381, 13581, 6576, 239 Passed LTC: https://tests.stockfishchess.org/tests/view/65c72b91c865510db0287a1a LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 55098 W: 13850 L: 13658 D: 27590 Ptnml(0-2): 37, 6257, 14777, 6433, 45 closes https://github.com/official-stockfish/Stockfish/pull/5049 Bench: 1027182 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7a60e973..b36eb05d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1067,11 +1067,6 @@ moves_loop: // When in check, search starts here extension = -1; } - // Quiet ttMove extensions (~1 Elo) - else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][move.to_sq()] >= 4339) - extension = 1; - // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] From 7ccde25baf03e77926644b282fed68ba0b5ddf95 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 11 Feb 2024 20:13:19 +0100 Subject: [PATCH 578/678] Format code using clang-format No functional change --- src/search.cpp | 4 ++-- src/thread.cpp | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b36eb05d..7bae7a4a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1289,8 +1289,8 @@ moves_loop: // When in check, search starts here assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); // Adjust best value for fail high cases at non-pv nodes - if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && - std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) bestValue = (bestValue * (depth + 2) + beta) / (depth + 3); if (!moveCount) diff --git a/src/thread.cpp b/src/thread.cpp index 2e42abd4..61beb399 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -212,7 +212,9 @@ Thread* ThreadPool::get_best_thread() const { Thread* bestThread = threads.front(); Value minScore = VALUE_NONE; - std::unordered_map votes(2 * std::min(size(), bestThread->worker->rootMoves.size())); + + std::unordered_map votes( + 2 * std::min(size(), bestThread->worker->rootMoves.size())); // Find the minimum score of all threads for (Thread* th : threads) From 5c0388310731ba4bf7989210cb69be67b53bb43d Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:59:05 +0800 Subject: [PATCH 579/678] VVLTC search tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search parameters were tuned using 16k games at VVLTC. They were tuned starting with the new parameters (in search only) of PR #5039. Passed VVLTC: https://tests.stockfishchess.org/tests/view/65c8a8fc1d8e83c78bfcd163 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 20826 W: 5355 L: 5100 D: 10371 Ptnml(0-2): 1, 1941, 6275, 2194, 2 Passed 2nd VVLTC: https://tests.stockfishchess.org/tests/view/65cadc2d1d8e83c78bfcfdaf LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 17710 W: 4611 L: 4352 D: 8747 Ptnml(0-2): 1, 1586, 5422, 1845, 1 STC Elo estimate: https://tests.stockfishchess.org/tests/view/65cb6aed1d8e83c78bfd0802 Elo: -1.46 ± 1.8 (95%) LOS: 5.5% Total: 40000 W: 10267 L: 10435 D: 19298 Ptnml(0-2): 200, 4860, 10023, 4742, 175 nElo: -2.77 ± 3.4 (95%) PairsRatio: 0.97 Bench: 1198939 --- src/search.cpp | 64 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7bae7a4a..ae1c3414 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - Value futilityMult = 116 - 47 * noTtCutNode; + Value futilityMult = 117 - 44 * noTtCutNode; return (futilityMult * d - 3 * futilityMult / 2 * improving); } @@ -66,15 +66,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 12890; + v += cv * std::abs(cv) / 12475; return std::clamp(v, 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(253 * d - 356, 1117); } +int stat_bonus(Depth d) { return std::min(246 * d - 351, 1136); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(517 * d - 308, 1206); } +int stat_malus(Depth d) { return std::min(519 * d - 306, 1258); } // 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); } @@ -297,12 +297,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 12480; + delta = 9 + avg * avg / 12487; 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] = 131 * avg / (std::abs(avg) + 95); + optimism[us] = 134 * avg / (std::abs(avg) + 97); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -482,7 +482,7 @@ void Search::Worker::clear() { h->fill(-71); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((20.37 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((18.79 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -723,7 +723,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1661, 1495); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1723, 1455); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -744,7 +744,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 450 - (332 - 160 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 438 - (332 - 154 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -755,22 +755,22 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - - (ss - 1)->statScore / 327 + - (ss - 1)->statScore / 314 >= beta - && eval >= beta && eval < 28702 // smaller than TB wins + && eval >= beta && eval < 30016 // smaller than TB wins && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17379 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 329 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16620 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 330 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 148, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 154, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -820,7 +820,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 182 - 68 * improving; + probCutBeta = beta + 181 - 68 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -876,7 +876,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 446; + probCutBeta = beta + 452; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -959,7 +959,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 279 + 295 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 277 + 292 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -967,7 +967,7 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -204 * depth)) + if (!pos.see_ge(move, -197 * depth)) continue; } else @@ -979,16 +979,16 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4215 * depth) + if (lmrDepth < 6 && history < -4211 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 6658; + lmrDepth += history / 6437; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 58 ? 139 : 55) + && ss->staticEval + (bestValue < ss->staticEval - 57 ? 144 : 57) + 121 * lmrDepth <= alpha) continue; @@ -1016,11 +1016,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 29) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (62 + 52 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (60 + 54 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1071,7 +1071,7 @@ moves_loop: // When in check, search starts here else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4356) + > 4394) extension = 1; } @@ -1123,10 +1123,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4409; + + (*contHist[3])[movedPiece][move.to_sq()] - 4392; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 14894; + r -= ss->statScore / 14189; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1261,7 +1261,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 13 && beta < 13710 && value > -12589) + if (depth > 2 && depth < 13 && beta < 13652 && value > -12761) depth -= 2; assert(depth > 0); @@ -1304,7 +1304,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15401) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15736) + ((ss - 1)->moveCount > 11); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1462,7 +1462,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 204; + futilityBase = ss->staticEval + 206; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1542,7 +1542,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -75)) + if (!pos.see_ge(move, -74)) continue; } @@ -1610,7 +1610,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1177 - delta * 776 / rootDelta) / 1024 + (!i && reductionScale > 842); + return (reductionScale + 1118 - delta * 793 / rootDelta) / 1024 + (!i && reductionScale > 863); } namespace { @@ -1699,7 +1699,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 167 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 166 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From f4f0b32d55defe93a79ec7afcce47d1d795879a8 Mon Sep 17 00:00:00 2001 From: Tierynn Byrnes Date: Tue, 6 Feb 2024 14:55:28 +1000 Subject: [PATCH 580/678] Refactor timeman.cpp Move optExtra, optConstant and maxConstant into lower scope. closes https://github.com/official-stockfish/Stockfish/pull/5052 No functional change --- AUTHORS | 1 + src/timeman.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9b1faee7..40a38bd5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -216,6 +216,7 @@ Taras Vuk (TarasVuk) Thanar2 thaspel theo77186 +TierynnB Ting-Hsuan Huang (fffelix-huang) Tobias Steinmann Tomasz Sobczyk (Sopel97) diff --git a/src/timeman.cpp b/src/timeman.cpp index 121f8edb..72a447af 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -28,7 +28,6 @@ namespace Stockfish { - TimePoint TimeManagement::optimum() const { return optimumTime; } TimePoint TimeManagement::maximum() const { return maximumTime; } TimePoint TimeManagement::elapsed(size_t nodes) const { @@ -89,18 +88,19 @@ void TimeManagement::init(Search::LimitsType& limits, TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); - // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); - - // Calculate time constants based on current time left. - double optConstant = std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); - double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); - // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed actual available // game time for the current move, so also cap to 20% of available game time. if (limits.movestogo == 0) { + // Use extra time with larger increments + double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); + + // Calculate time constants based on current time left. + double optConstant = + std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); + double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); + optScale = std::min(0.0120 + std::pow(ply + 3.1, 0.44) * optConstant, 0.21 * limits.time[us] / double(timeLeft)) * optExtra; From bf2c7306ac7f83200ba4d894867e3c0c78c0802c Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 11 Feb 2024 14:19:44 +0100 Subject: [PATCH 581/678] Use node counting to early stop search This introduces a form of node counting which can be used to further tweak the usage of our search time. The current approach stops the search when almost all nodes are searched on a single move. The idea originally came from Koivisto, but the implemention is a bit different, Koivisto scales the optimal time by the nodes effort and then determines if the search should be stopped. We just scale down the `totalTime` and stop the search if we exceed it and the effort is large enough. Passed STC: https://tests.stockfishchess.org/tests/view/65c8e0661d8e83c78bfcd5ec LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 88672 W: 22907 L: 22512 D: 43253 Ptnml(0-2): 310, 10163, 23041, 10466, 356 Passed LTC: https://tests.stockfishchess.org/tests/view/65ca632b1d8e83c78bfcf554 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 170856 W: 42910 L: 42320 D: 85626 Ptnml(0-2): 104, 18337, 47960, 18919, 108 closes https://github.com/official-stockfish/Stockfish/pull/5053 Bench: 1198939 --- src/search.cpp | 17 +++++++++++++++++ src/search.h | 2 ++ src/thread.cpp | 2 ++ 3 files changed, 21 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index ae1c3414..95744653 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -418,6 +419,10 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { + auto bestmove = rootMoves[0].pv[0]; + int nodesEffort = effort[bestmove.from_sq()][bestmove.to_sq()] * 100 + / std::max(size_t(1), size_t(nodes)); + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 616.6; @@ -435,6 +440,13 @@ void Search::Worker::iterative_deepening() { if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); + if (completedDepth >= 10 && nodesEffort >= 95 + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 3 / 4 + && !mainThread->ponder) + { + threads.stop = true; + } + // Stop the search if we have exceeded the totalTime if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) { @@ -1087,6 +1099,8 @@ moves_loop: // When in check, search starts here ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; + uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; + // Step 16. Make the move thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); @@ -1186,6 +1200,9 @@ moves_loop: // When in check, search starts here // Step 19. Undo move pos.undo_move(move); + if (rootNode) + effort[move.from_sq()][move.to_sq()] += nodes - nodeCount; + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); // Step 20. Check for a new best move diff --git a/src/search.h b/src/search.h index 2d1077f8..4a1c68bb 100644 --- a/src/search.h +++ b/src/search.h @@ -219,6 +219,8 @@ class Worker { return static_cast(manager.get()); } + std::array, SQUARE_NB> effort; + LimitsType limits; size_t pvIdx, pvLast; diff --git a/src/thread.cpp b/src/thread.cpp index 61beb399..95646601 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "misc.h" #include "movegen.h" @@ -203,6 +204,7 @@ void ThreadPool::start_thinking(const OptionsMap& options, th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); th->worker->rootState = setupStates->back(); th->worker->tbConfig = tbConfig; + th->worker->effort = {}; } main_thread()->start_searching(); From 9d61822b5dda7f99d46ed9ad346afa8c34c302a8 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sat, 10 Feb 2024 03:51:05 +0700 Subject: [PATCH 582/678] Remove penalty for quiet ttMove that fails low Passed STC non-reg: https://tests.stockfishchess.org/tests/view/65c691a7c865510db0286e6e LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 234336 W: 60258 L: 60255 D: 113823 Ptnml(0-2): 966, 28141, 58918, 28210, 933 Passed LTC non-reg: https://tests.stockfishchess.org/tests/view/65c8d0d31d8e83c78bfcd4a6 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 235206 W: 59134 L: 59132 D: 116940 Ptnml(0-2): 135, 26908, 63517, 26906, 137 https://github.com/official-stockfish/Stockfish/pull/5054 Bench: 1287996 --- src/search.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 95744653..cb71acba 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -621,13 +621,6 @@ Value Search::Worker::search( update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -stat_malus(depth + 1)); } - // Penalty for a quiet ttMove that fails low (~1 Elo) - else if (!ttCapture) - { - int penalty = -stat_malus(depth); - thisThread->mainHistory[us][ttMove.from_to()] << penalty; - update_continuation_histories(ss, pos.moved_piece(ttMove), ttMove.to_sq(), penalty); - } } // Partial workaround for the graph history interaction problem From f3df0cfb84250f03662a6fd50ea20c9677a0a1d0 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:50:16 +0800 Subject: [PATCH 583/678] Simplify TT PV reduction This also removes some incorrect fail-high logic. Passed STC: https://tests.stockfishchess.org/tests/view/65cb3b641d8e83c78bfd04a9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 87968 W: 22634 L: 22468 D: 42866 Ptnml(0-2): 315, 10436, 22323, 10588, 322 Passed LTC: https://tests.stockfishchess.org/tests/view/65cccee21d8e83c78bfd222c LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 70794 W: 17846 L: 17672 D: 35276 Ptnml(0-2): 44, 7980, 19189, 8126, 58 closes https://github.com/official-stockfish/Stockfish/pull/5055 Bench: 1474424 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cb71acba..c407ae6b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1098,9 +1098,9 @@ moves_loop: // When in check, search starts here thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV (~5 Elo) + // Decrease reduction if position is or has been on the PV (~7 Elo) if (ss->ttPv) - r -= 1 + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); + r -= 1 + (ttValue > alpha) + (tte->depth() >= depth); // Increase reduction for cut nodes (~4 Elo) if (cutNode) From 8e75548f2a10969c1c9211056999efbcebe63f9a Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 6 Feb 2024 11:21:15 -0500 Subject: [PATCH 584/678] Update default main net to nn-b1a57edbea57.nnue Created by retraining the previous main net `nn-baff1edbea57.nnue` with: - some of the same options as before: ranger21, more WDL skipping - the addition of T80 nov+dec 2023 data - increasing loss by 15% when prediction is too high, up from 10% - use of torch.compile to speed up training by over 25% ```yaml experiment-name: 2560--S9-514G-T80-augtodec2023-more-wdl-skip-15p-more-loss-high-q-sk28 training-dataset: # https://github.com/official-stockfish/Stockfish/pull/4782 - /data/S6-514G-1ee1aba5ed.binpack - /data/test80-aug2023-2tb7p.v6.min.binpack - /data/test80-sep2023-2tb7p.binpack - /data/test80-oct2023-2tb7p.binpack - /data/test80-nov2023-2tb7p.binpack - /data/test80-dec2023-2tb7p.binpack early-fen-skipping: 28 start-from-engine-test-net: True nnue-pytorch-branch: linrock/nnue-pytorch/r21-more-wdl-skip-15p-more-loss-high-q-torch-compile num-epochs: 1000 lr: 4.375e-4 gamma: 0.995 start-lambda: 1.0 end-lambda: 0.7 ``` Epoch 819 trained with the above config led to this PR. Use of torch.compile decorators in nnue-pytorch model.py was found to speed up training by at least 25% on Ampere gpus when using recent pytorch compiled with cuda 12: https://catalog.ngc.nvidia.com/orgs/nvidia/containers/pytorch See recent main net PRs for more info on - ranger21 and more WDL skipping: https://github.com/official-stockfish/Stockfish/pull/4942 - increasing loss when Q is too high: https://github.com/official-stockfish/Stockfish/pull/4972 Training data can be found at: https://robotmoon.com/nnue-training-data/ Passed STC: https://tests.stockfishchess.org/tests/view/65cd76151d8e83c78bfd2f52 LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 78336 W: 20504 L: 20115 D: 37717 Ptnml(0-2): 317, 9225, 19721, 9562, 343 Passed LTC: https://tests.stockfishchess.org/tests/view/65ce5be61d8e83c78bfd43e9 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 41016 W: 10492 L: 10159 D: 20365 Ptnml(0-2): 22, 4533, 11071, 4854, 28 closes https://github.com/official-stockfish/Stockfish/pull/5056 Bench: 1351997 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 729baa6b..53928bf6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ Value evaluate(const Position& pos, int optimism); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultNameBig "nn-baff1edbea57.nnue" +#define EvalFileDefaultNameBig "nn-b1a57edbea57.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" struct EvalFile { From fc41f64dfd8a61d0e275ddbecec292833458b86a Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:46:37 +0800 Subject: [PATCH 585/678] Simplify PV node reduction Reduce less on PV nodes even with an upperbound TT entry. Passed STC: https://tests.stockfishchess.org/tests/view/65cb3a861d8e83c78bfd0497 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 118752 W: 30441 L: 30307 D: 58004 Ptnml(0-2): 476, 14179, 29921, 14335, 465 Passed LTC: https://tests.stockfishchess.org/tests/view/65cd3b951d8e83c78bfd2b0d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 155058 W: 38549 L: 38464 D: 78045 Ptnml(0-2): 85, 17521, 42219, 17632, 72 closes https://github.com/official-stockfish/Stockfish/pull/5057 Bench: 1303971 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c407ae6b..55a92947 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1111,7 +1111,7 @@ moves_loop: // When in check, search starts here r++; // Decrease reduction for PvNodes (~3 Elo) - if (PvNode && tte->bound() != BOUND_UPPER) + if (PvNode) r--; // Increase reduction on repetition (~1 Elo) From d07033d5da9c4e1a383f8be45bd9be43ce7b2f95 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 24 Feb 2024 11:56:33 +0100 Subject: [PATCH 586/678] Expose EvalFileSmall option for small net Since https://github.com/official-stockfish/fishtest/pull/1870 has been merged it's time for this update. 5k Fixed Games showed no problems. https://tests.stockfishchess.org/tests/view/65d9cc274c0e22b904f574d7 closes https://github.com/official-stockfish/Stockfish/pull/5068 No functional change --- src/evaluate.cpp | 13 +++---------- src/uci.cpp | 3 +++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index da086766..f22c0d06 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -77,11 +77,7 @@ NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, for (auto& [netSize, evalFile] : evalFiles) { - // Replace with - // options[evalFile.optionName] - // once fishtest supports the uci option EvalFileSmall - std::string user_eval_file = - netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; + std::string user_eval_file = options[evalFile.optionName]; if (user_eval_file.empty()) user_eval_file = evalFile.defaultName; @@ -149,11 +145,8 @@ void NNUE::verify(const OptionsMap& optio for (const auto& [netSize, evalFile] : evalFiles) { - // Replace with - // options[evalFile.optionName] - // once fishtest supports the uci option EvalFileSmall - std::string user_eval_file = - netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; + std::string user_eval_file = options[evalFile.optionName]; + if (user_eval_file.empty()) user_eval_file = evalFile.defaultName; diff --git a/src/uci.cpp b/src/uci.cpp index d1d69d69..35f725b5 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -83,6 +83,9 @@ UCI::UCI(int argc, char** argv) : options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) { evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); }); + options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option&) { + evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + }); threads.set({options, threads, tt}); From bec83a1869ae6d1bbcfc7d82d569e12823b03bfa Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 24 Feb 2024 17:54:06 +0100 Subject: [PATCH 587/678] Update Top CPU Contributors closes https://github.com/official-stockfish/Stockfish/pull/5069 No functional change --- Top CPU Contributors.txt | 189 +++++++++++++++++++++------------------ 1 file changed, 104 insertions(+), 85 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 74c471b7..11636e84 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,139 +1,146 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20. +Contributors to Fishtest with >10,000 CPU hours, as of 2024-02-24. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 37457426 2850540907 -technologov 14135647 742892808 -linrock 4423514 303254809 +noobpwnftw 39302472 3055513453 +technologov 20845762 994893444 +linrock 8616428 560281417 mlang 3026000 200065824 +okrout 2332151 222639518 +pemo 1800019 60274069 dew 1689162 100033738 -okrout 1578136 148855886 -pemo 1508508 48814305 -grandphish2 1461406 91540343 -TueRens 1194790 70400852 -JojoM 947612 61773190 +TueRens 1474943 75121774 +grandphish2 1463002 91616949 +JojoM 1109702 72927902 +olafm 978631 71037944 +sebastronomy 939955 44920556 tvijlbrief 796125 51897690 -sebastronomy 742434 38218524 +gvreuls 711320 49142318 mibere 703840 46867607 -gvreuls 651026 42988582 -oz 543438 39314736 -cw 517858 34869755 +oz 646268 46293638 +rpngn 572571 38928563 +leszek 531858 39316505 +cw 518116 34894291 fastgm 503862 30260818 -leszek 467278 33514883 -CSU_Dynasty 464940 31177118 -ctoks 434416 28506889 -crunchy 427035 27344275 -maximmasiutin 424795 26577722 -bcross 415722 29060963 -olafm 395922 32268020 -rpngn 348378 24560289 -velislav 342567 22138992 +CSU_Dynasty 468784 31385034 +ctoks 434591 28520597 +maximmasiutin 429983 27066286 +crunchy 427414 27371625 +bcross 415724 29061187 +velislav 342588 22140902 +mgrabiak 338763 23999170 Fisherman 327231 21829379 -mgrabiak 300612 20608380 +robal 299836 20213182 Dantist 296386 18031762 -nordlandia 246201 16189678 -robal 241300 15656382 +ncfish1 267604 17881149 +nordlandia 249322 16420192 marrco 234581 17714473 -ncfish1 227517 15233777 +tolkki963 233490 19773930 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 +Calis007 188631 12795784 Thanar 179852 12365359 +Fifis 176209 10638245 vdv 175544 9904472 spams 157128 10319326 +DesolatedDodo 156659 10210328 +armo9494 155355 10566898 sqrt2 147963 9724586 -DesolatedDodo 146350 9536172 -Calis007 143165 9478764 -vdbergh 138650 9064413 +jcAEie 140086 10603658 +vdbergh 139746 9172061 CoffeeOne 137100 5024116 -armo9494 136191 9460264 malala 136182 8002293 xoto 133759 9159372 davar 129023 8376525 DMBK 122960 8980062 dsmith 122059 7570238 +javran 121564 10144656 amicic 119661 7938029 +sschnee 118107 7389266 +Wolfgang 114616 8070494 Data 113305 8220352 BrunoBanani 112960 7436849 +Wencey 111502 5991676 +cuistot 108503 7006992 CypressChess 108331 7759788 skiminki 107583 7218170 -jcAEie 105675 8238962 MaZePallas 102823 6633619 sterni1971 100532 5880772 sunu 100167 7040199 zeryl 99331 6221261 -thirdlife 99124 2242380 +thirdlife 99156 2245320 ElbertoOne 99028 7023771 -cuistot 98853 6069816 +Dubslow 98600 6903242 +markkulix 97010 7643900 bigpen0r 94809 6529203 brabos 92118 6186135 -Wolfgang 91939 6105872 +Maxim 90818 3283364 psk 89957 5984901 -sschnee 88235 5268000 +megaman7de 88822 6052132 racerschmacer 85805 6122790 -Fifis 85722 5709729 -Dubslow 84986 6042456 +maposora 85710 7778146 Vizvezdenec 83761 5344740 0x3C33 82614 5271253 BRAVONE 81239 5054681 nssy 76497 5259388 jromang 76106 5236025 teddybaer 75125 5407666 -tolkki963 74762 5149662 -megaman7de 74351 4940352 -Wencey 74181 4711488 Pking_cda 73776 5293873 -yurikvelo 73150 5004382 -markkulix 72607 5304642 +yurikvelo 73516 5036928 +MarcusTullius 71053 4803477 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 +Spprtr 69646 4806763 +Mineta 66325 4537742 manap 66273 4121774 +szupaw 65468 5669742 tinker 64333 4268790 qurashee 61208 3429862 -Mineta 59357 4418202 -Spprtr 58723 3911011 -AGI 58147 4325994 +woutboat 59496 4906352 +AGI 58195 4329580 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 -MarcusTullius 56746 3762951 ttruscott 56010 3680085 rkl 55132 4164467 +jmdana 54697 4012593 renouve 53811 3501516 -javran 53785 4627608 +notchris 52433 4044590 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 +Goatminola 51004 4432492 rap 49985 3219146 pb00067 49733 3298934 +GPUex 48686 3684998 OuaisBla 48626 3445134 ronaldjerum 47654 3240695 biffhero 46564 3111352 +oryx 45533 3539290 VoyagerOne 45476 3452465 -jmdana 44893 3065205 -maposora 44597 4039578 -oryx 44570 3454238 speedycpu 43842 3003273 jbwiebe 43305 2805433 -GPUex 42378 3133332 Antihistamine 41788 2761312 mhunt 41735 2691355 homyur 39893 2850481 gri 39871 2515779 Garf 37741 2999686 SC 37299 2731694 +Sylvain27 36520 1467082 csnodgrass 36207 2688994 +Gaster319 35655 3149442 strelock 34716 2074055 -szupaw 34102 2880346 EthanOConnor 33370 2090311 slakovv 32915 2021889 +gopeto 31884 2076712 Gelma 31771 1551204 -gopeto 31671 2060990 kdave 31157 2198362 manapbk 30987 1810399 +ZacHFX 30551 2238078 Prcuvu 30377 2170122 anst 30301 2190091 jkiiski 30136 1904470 @@ -142,27 +149,31 @@ hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 belzedar94 28846 1811530 +votoanthuan 27978 2285818 +shawnxu 27438 2465810 chriswk 26902 1868317 xwziegtm 26897 2124586 achambord 26582 1767323 Patrick_G 26276 1801617 yorkman 26193 1992080 -Ulysses 25288 1689730 +Ulysses 25397 1701264 +Jopo12321 25227 1652482 SFTUser 25182 1675689 -nabildanial 24942 1519409 +nabildanial 25068 1531665 Sharaf_DG 24765 1786697 -Maxim 24705 1502062 rodneyc 24376 1416402 +jsys14 24297 1721230 agg177 23890 1395014 -Goatminola 23763 1956036 -Ente 23639 1671638 -Jopo12321 23467 1483172 +srowen 23842 1342508 +Ente 23752 1678188 +jojo2357 23479 2061238 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 cisco2015 22920 1763301 -jsys14 22824 1591906 Zirie 22542 1472937 +Nullvalue 22490 1970374 +AndreasKrug 22485 1769491 team-oh 22272 1636708 Roady 22220 1465606 MazeOfGalious 21978 1629593 @@ -173,79 +184,83 @@ dex 21612 1467203 nesoneg 21494 1463031 user213718 21454 1404128 sphinx 21211 1384728 -AndreasKrug 21097 1634811 +qoo_charly_cai 21135 1514907 jjoshua2 21001 1423089 Zake9298 20938 1565848 horst.prack 20878 1465656 0xB00B1ES 20590 1208666 +Serpensin 20487 1729674 +Dinde 20440 1292390 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 wei 19973 1745989 -notchris 19958 1800128 -Serpensin 19840 1697528 -Gaster319 19712 1677310 fishtester 19617 1257388 rstoesser 19569 1293588 eudhan 19274 1283717 -votoanthuan 19108 1609992 vulcan 18871 1729392 Karpovbot 18766 1053178 -qoo_charly_cai 18543 1284937 +WoodMan777 18556 1628264 jundery 18445 1115855 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17414 981289 +ols 17291 1042003 iisiraider 17275 1049015 +Skiff84 17111 950248 DragonLord 17014 1162790 redstone59 16842 1461780 -Alb11747 16787 1213926 +Karby 16839 1010124 +Alb11747 16787 1213990 +pirt 16493 1237199 +Naven94 16414 951718 +wizardassassin 16392 1148672 IgorLeMasson 16064 1147232 -Karby 15982 979610 scuzzi 15757 968735 ako027ako 15671 1173203 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 -Naven94 15054 834762 OssumOpossum 14857 1007129 -ZacHFX 14783 1021842 +LunaticBFF57 14525 1190310 enedene 14476 905279 +IslandLambda 14393 958196 bpfliegel 14233 882523 +YELNAMRON 14230 1128094 mpx86 14019 759568 jpulman 13982 870599 -Skiff84 13826 721996 +getraideBFF 13871 1172846 +Nesa92 13806 1116101 crocogoat 13803 1117422 -Nesa92 13786 1114691 joster 13710 946160 mbeier 13650 1044928 Hjax 13535 915487 -Nullvalue 13468 1140498 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 -pirt 13100 1009897 Machariel 13010 863104 infinigon 12991 943216 mabichito 12903 749391 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 +mschmidt 12644 863193 korposzczur 12606 838168 +tsim67 12570 890180 +Jackfish 12553 836958 fatmurphy 12547 853210 +Oakwen 12503 853105 SapphireBrand 12416 969604 -Oakwen 12399 844109 deflectooor 12386 579392 modolief 12386 896470 +TataneSan 12358 609332 Farseer 12249 694108 -Jackfish 12213 805008 pgontarz 12151 848794 dbernier 12103 860824 -getraideBFF 12072 1024966 +FormazChar 11989 907809 stocky 11954 699440 -mschmidt 11941 803401 -MooTheCow 11870 773598 -FormazChar 11766 885707 +somethingintheshadows 11940 989472 +MooTheCow 11892 776126 +3cho 11842 1036786 whelanh 11557 245188 -3cho 11494 1031076 infinity 11470 727027 aga 11412 695127 torbjo 11395 729145 @@ -253,15 +268,19 @@ Thomas A. Anderson 11372 732094 savage84 11358 670860 d64 11263 789184 ali-al-zhrani 11245 779246 +ckaz 11170 680866 snicolet 11106 869170 dapper 11032 771402 -ols 10947 624903 -Karmatron 10828 677458 +Ethnikoi 10993 945906 +Snuuka 10938 435504 +Karmatron 10859 678058 basepi 10637 744851 +jibarbosa 10628 857100 Cubox 10621 826448 +mecevdimitar 10609 787318 michaelrpg 10509 739239 +Def9Infinity 10427 686978 OIVAS7572 10420 995586 -jojo2357 10419 929708 -WoodMan777 10380 873720 +wxt9861 10412 1013864 Garruk 10365 706465 dzjp 10343 732529 From 5c2b3859579e20cb1abc8d53ae7ee229fbc1e335 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 24 Feb 2024 17:55:08 +0100 Subject: [PATCH 588/678] Update the WDL model Based on 130M positions from 2.1M games. ``` Look recursively in directory pgns for games from SPRT tests using books matching "UHO_4060_v..epd|UHO_Lichess_4852_v1.epd" for SF revisions between 8e75548f2a10969c1c9211056999efbcebe63f9a (from 2024-02-17 17:11:46 +0100) and HEAD (from 2024-02-17 17:13:07 +0100). Based on 127920843 positions from 2109240 games, NormalizeToPawnValue should change from 345 to 356. ``` The patch only affects the UCI-reported cp and wdl values. closes https://github.com/official-stockfish/Stockfish/pull/5070 No functional change --- src/uci.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 35f725b5..4d4ea689 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -45,7 +45,7 @@ namespace Stockfish { constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int NormalizeToPawnValue = 345; +constexpr int NormalizeToPawnValue = 356; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; UCI::UCI(int argc, char** argv) : @@ -380,8 +380,8 @@ int win_rate_model(Value v, int ply) { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = {-2.00568292, 10.45906746, 1.67438883, 334.45864705}; - constexpr double bs[] = {-4.97134419, 36.15096345, -82.25513499, 117.35186805}; + constexpr double as[] = {-1.06249702, 7.42016937, 0.89425629, 348.60356174}; + constexpr double bs[] = {-5.33122190, 39.57831533, -90.84473771, 123.40620748}; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at move 32. static_assert(NormalizeToPawnValue == int(0.5 + as[0] + as[1] + as[2] + as[3])); From e67cc979fd2c0e66dfc2b2f2daa0117458cfc462 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 21 Feb 2024 22:17:23 +0100 Subject: [PATCH 589/678] Stockfish 16.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 16.1 Bench: 1303971 --- Stockfish 16.1 Today, we have the pleasure to announce Stockfish 16.1. As always, you can freely download it at https://stockfishchess.org/download and use it in the GUI of your choice[1]. Don't forget to join our Discord server[2] to get in touch with the community of developers and users of the project! *Quality of chess play* In our testing against its predecessor, Stockfish 16.1 shows a notable improvement in performance, with an Elo gain of up to 27 points and winning over 2 times more game pairs[3] than it loses. *Update highlights* *Improved evaluation* - Updated neural network architecture: The neural network architecture has undergone two updates and is currently in its 8th version[4]. - Removal of handcrafted evaluation (HCE): This release marks the removal of the traditional handcrafted evaluation and the transition to a fully neural network-based approach[5]. - Dual NNUE: For the first time, Stockfish includes a secondary neural network[6], used to quickly evaluate positions that are easily decided. *UCI Options removed* `Use NNUE` and `UCI_AnalyseMode`[7] have been removed as they no longer had any effect. `SlowMover`[8] has also been removed in favor of `Move Overhead`. *More binaries* We now offer 13 new binaries. These new binaries include `avx512`, `vnni256`, `vnni512`, `m1-apple-silicon`, and `armv8-dotprod`, which take advantage of specific CPU instructions for improved performance. For most users, using `sse41-popcnt` (formerly `modern`), `avx2`, or `bmi2` should be enough, but if your CPU supports these new instructions, feel free to try them! *Development changes* - Updated testing book: This new book[9], now derived exclusively from the open Lichess database[10], is 10 times larger than its predecessor, and has been used to test potential improvements to Stockfish over the past few months. - Consolidation of repositories: Aiming to simplify access to our resources, we have moved most Stockfish-related repositories into the official Stockfish organization[11] on GitHub. - Growing maintainer team: We welcome Disservin[12] to the team of maintainers of the project! This extra pair of hands will ensure the lasting success of Stockfish. *Thank you* The Stockfish project builds on a thriving community of enthusiasts (thanks everybody!) who contribute their expertise, time, and resources to build a free and open-source chess engine that is robust, widely available, and very strong. We would like to express our gratitude for the 10k stars[13] that light up our GitHub project! Thank you for your support and encouragement – your recognition means a lot to us. We invite our chess fans to join the Fishtest testing framework[14], and programmers to contribute to the project either directly to Stockfish[15] (C++), to Fishtest[16] (HTML, CSS, JavaScript, and Python), to our trainer nnue-pytorch[17] (C++ and Python), or to our website[18] (HTML, CSS/SCSS, and JavaScript). The Stockfish team [1] https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage#download-a-chess-gui [2] https://discord.gg/GWDRS3kU6R [3] https://tests.stockfishchess.org/tests/view/65d666051d8e83c78bfddbd8 [4] https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md#sfnnv8-architecture [5] https://github.com/official-stockfish/Stockfish/commit/af110e0 [6] https://github.com/official-stockfish/Stockfish/commit/584d9ef [7] https://github.com/official-stockfish/Stockfish/commit/c53d2ec [8] https://github.com/official-stockfish/Stockfish/commit/536d692 [9] https://github.com/official-stockfish/books/commit/426eca4 [10] https://database.lichess.org/ [11] https://github.com/official-stockfish/ [12] https://github.com/Disservin [13] https://github.com/official-stockfish/Stockfish/stargazers [14] https://github.com/official-stockfish/fishtest/wiki/Running-the-worker [15] https://github.com/official-stockfish/Stockfish [16] https://github.com/official-stockfish/fishtest [17] https://github.com/official-stockfish/nnue-pytorch [18] https://github.com/official-stockfish/stockfish-web --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 4885a5cd..1d089971 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -75,7 +75,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "dev"; +constexpr std::string_view version = "16.1"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From abcc090a625d7891146645ee493aec5ee8046dc5 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 24 Feb 2024 20:40:48 +0100 Subject: [PATCH 590/678] Restore development closes https://github.com/official-stockfish/Stockfish/pull/5073 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 1d089971..4885a5cd 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -75,7 +75,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "16.1"; +constexpr std::string_view version = "dev"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From c83c7f4e713c31a9d0811cdb8fb6469dc3a330be Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 24 Feb 2024 20:30:40 +0100 Subject: [PATCH 591/678] Make binaries executable again in CI closes https://github.com/official-stockfish/Stockfish/pull/5072 No functional change --- .github/workflows/upload_binaries.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 97bcf96f..0dfd7244 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -65,6 +65,7 @@ jobs: - name: Create tar if: runner.os != 'Windows' run: | + chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT tar -cvf stockfish-$NAME-$BINARY.tar stockfish - name: Create zip From 0c22d5bb1ab735a5191f713c18428d66a17535df Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 25 Feb 2024 11:16:55 +0100 Subject: [PATCH 592/678] Update Actions to Node20 ensure our CI continues to run after Node16 is obsolote on github. closes https://github.com/official-stockfish/Stockfish/pull/5074 No functional change --- .github/workflows/clang-format.yml | 4 ++-- .github/workflows/upload_binaries.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 0eb3fc70..e20e0d5d 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -30,7 +30,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome == 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0 with: message: | clang-format 17 needs to be run on this PR. @@ -42,7 +42,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome != 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0 with: message: | _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 0dfd7244..015b514c 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -98,7 +98,7 @@ jobs: - name: Prerelease if: github.ref_name == 'master' && env.CHANGES == '0' continue-on-error: true - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} From f77eddfa2ff6d5f496b398a9d743a5f02d358dc8 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Tue, 27 Feb 2024 19:27:40 +0700 Subject: [PATCH 593/678] Join conditions for move sorting heuristics closes https://github.com/official-stockfish/Stockfish/pull/5078 No functional change. --- src/search.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 55a92947..3de1f69b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -607,20 +607,17 @@ Value Search::Worker::search( && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) - if (ttMove) + if (ttMove && ttValue >= beta) { - if (ttValue >= beta) - { - // Bonus for a quiet ttMove that fails high (~2 Elo) - if (!ttCapture) - update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); + // Bonus for a quiet ttMove that fails high (~2 Elo) + if (!ttCapture) + update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); - // Extra penalty for early quiet moves of - // the previous ply (~0 Elo on STC, ~2 Elo on LTC). - if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_malus(depth + 1)); - } + // Extra penalty for early quiet moves of + // the previous ply (~0 Elo on STC, ~2 Elo on LTC). + if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + -stat_malus(depth + 1)); } // Partial workaround for the graph history interaction problem From 0a3eb1d8fa1815f1f800e38b990247b9e58e27f5 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 20 Feb 2024 22:47:26 +0100 Subject: [PATCH 594/678] Document TT code more Slight refactor of the TT code with the goal to make it easier to understand / tweak. Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/65d51e401d8e83c78bfdc427 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 56416 W: 14750 L: 14550 D: 27116 Ptnml(0-2): 227, 6386, 14796, 6558, 241 closes https://github.com/official-stockfish/Stockfish/pull/5061 No functional change --- src/tt.cpp | 33 ++++++++++++++++++++------------- src/tt.h | 25 ++++++++++++++++++------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index f3f58979..8ef06e63 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -19,6 +19,7 @@ #include "tt.h" #include +#include #include #include #include @@ -53,6 +54,18 @@ void TTEntry::save( } +uint8_t TTEntry::relative_age(const uint8_t generation8) const { + // Due to our packed storage format for generation and its cyclic + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + + return (TranspositionTable::GENERATION_CYCLE + generation8 - genBound8) + & TranspositionTable::GENERATION_MASK; +} + + // Sets the size of the transposition table, // measured in megabytes. Transposition table consists of a power of 2 number // of clusters and each cluster consists of ClusterSize number of TTEntry. @@ -111,24 +124,18 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16 || !tte[i].depth8) { - tte[i].genBound8 = - uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh + constexpr uint8_t lowerBits = GENERATION_DELTA - 1; - return found = bool(tte[i].depth8), &tte[i]; + // Refresh with new generation, keeping the lower bits the same. + tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & lowerBits)); + return found = bool(tte[i].depth8), &tte[i]; } // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; for (int i = 1; i < ClusterSize; ++i) - // Due to our packed storage format for generation and its cyclic - // nature we add GENERATION_CYCLE (256 is the modulus, plus what - // is needed to keep the unrelated lowest n bits from affecting - // the result) to calculate the entry age correctly even after - // generation8 overflows into the next cycle. - if (replace->depth8 - - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) - > tte[i].depth8 - - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) + if (replace->depth8 - replace->relative_age(generation8) + > tte[i].depth8 - tte[i].relative_age(generation8)) replace = &tte[i]; return found = false, replace; @@ -137,7 +144,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { // Returns an approximation of the hashtable // occupation during a search. The hash is x permill full, as per UCI protocol. - +// Only counts entries which match the current generation. int TranspositionTable::hashfull() const { int cnt = 0; diff --git a/src/tt.h b/src/tt.h index 4115ee7a..554a81a5 100644 --- a/src/tt.h +++ b/src/tt.h @@ -46,6 +46,8 @@ struct TTEntry { bool is_pv() const { return bool(genBound8 & 0x4); } Bound bound() const { return Bound(genBound8 & 0x3); } void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); + // The returned age is a multiple of TranspositionTable::GENERATION_DELTA + uint8_t relative_age(const uint8_t generation8) const; private: friend class TranspositionTable; @@ -76,16 +78,25 @@ class TranspositionTable { static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); // Constants used to refresh the hash table periodically - static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things - static constexpr int GENERATION_DELTA = - (1 << GENERATION_BITS); // increment for generation field - static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length - static constexpr int GENERATION_MASK = - (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number + + // We have 8 bits available where the lowest 3 bits are + // reserved for other things. + static constexpr unsigned GENERATION_BITS = 3; + // increment for generation field + static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); + // cycle length + static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA; + // mask to pull out generation number + static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; public: ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things + + void new_search() { + // increment by delta to keep lower bits as is + generation8 += GENERATION_DELTA; + } + TTEntry* probe(const Key key, bool& found) const; int hashfull() const; void resize(size_t mbSize, int threadCount); From 7831131591fca89714a376099d6581ec0242244f Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 29 Feb 2024 14:27:00 -0800 Subject: [PATCH 595/678] Only evaluate the PSQT part of the small net for large evals. Thanks to Viren6 for suggesting to set complexity to 0. STC https://tests.stockfishchess.org/tests/view/65d7d6709b2da0226a5a203f LLR: 2.92 (-2.94,2.94) <0.00,2.00> Total: 328384 W: 85316 L: 84554 D: 158514 Ptnml(0-2): 1414, 39076, 82486, 39766, 1450 LTC https://tests.stockfishchess.org/tests/view/65dce6d290f639b028a54d2e LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 165162 W: 41918 L: 41330 D: 81914 Ptnml(0-2): 102, 18332, 45124, 18922, 101 closes https://github.com/official-stockfish/Stockfish/pull/5083 bench: 1504003 --- src/evaluate.cpp | 5 +- src/nnue/evaluate_nnue.cpp | 47 ++--- src/nnue/evaluate_nnue.h | 5 +- src/nnue/nnue_accumulator.h | 1 + src/nnue/nnue_feature_transformer.h | 260 +++++++++++++++------------- src/position.cpp | 23 ++- 6 files changed, 193 insertions(+), 148 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index f22c0d06..cd026036 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -194,11 +194,12 @@ Value Eval::evaluate(const Position& pos, int optimism) { int simpleEval = simple_eval(pos, pos.side_to_move()); bool smallNet = std::abs(simpleEval) > 1050; + bool psqtOnly = std::abs(simpleEval) > 2500; int nnueComplexity; - Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) - : NNUE::evaluate(pos, true, &nnueComplexity); + Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity, psqtOnly) + : NNUE::evaluate(pos, true, &nnueComplexity, false); // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 5bd7e83d..efcf5b01 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -179,16 +179,16 @@ write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDe void hint_common_parent_position(const Position& pos) { - int simpleEval = simple_eval(pos, pos.side_to_move()); - if (std::abs(simpleEval) > 1050) - featureTransformerSmall->hint_common_access(pos); + int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); + if (simpleEvalAbs > 1050) + featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > 2500); else - featureTransformerBig->hint_common_access(pos); + featureTransformerBig->hint_common_access(pos, false); } // Evaluation function. Perform differential calculation. template -Value evaluate(const Position& pos, bool adjusted, int* complexity) { +Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -213,15 +213,19 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = Net_Size == Small - ? featureTransformerSmall->transform(pos, transformedFeatures, bucket) - : featureTransformerBig->transform(pos, transformedFeatures, bucket); - const auto positional = Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) - : networkBig[bucket]->propagate(transformedFeatures); + const int bucket = (pos.count() - 1) / 4; + const auto psqt = + Net_Size == Small + ? featureTransformerSmall->transform(pos, transformedFeatures, bucket, psqtOnly) + : featureTransformerBig->transform(pos, transformedFeatures, bucket, psqtOnly); + + const auto positional = + !psqtOnly ? (Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) + : networkBig[bucket]->propagate(transformedFeatures)) + : 0; if (complexity) - *complexity = std::abs(psqt - positional) / OutputScale; + *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; // Give more value to positional evaluation when adjusted flag is set if (adjusted) @@ -231,8 +235,8 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { return static_cast((psqt + positional) / OutputScale); } -template Value evaluate(const Position& pos, bool adjusted, int* complexity); -template Value evaluate(const Position& pos, bool adjusted, int* complexity); +template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); +template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); @@ -265,8 +269,9 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { t.correctBucket = (pos.count() - 1) / 4; for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { - const auto materialist = featureTransformerBig->transform(pos, transformedFeatures, bucket); - const auto positional = networkBig[bucket]->propagate(transformedFeatures); + const auto materialist = + featureTransformerBig->transform(pos, transformedFeatures, bucket, false); + const auto positional = networkBig[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); t.positional[bucket] = static_cast(positional / OutputScale); @@ -370,16 +375,18 @@ std::string trace(Position& pos) { auto st = pos.state(); pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = false; - st->accumulatorBig.computed[BLACK] = false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; Value eval = evaluate(pos); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = false; - st->accumulatorBig.computed[BLACK] = false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; } writeSquare(f, r, pc, v); diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index ea88f890..c7b37860 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -76,7 +76,10 @@ using LargePagePtr = std::unique_ptr>; std::string trace(Position& pos); template -Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); +Value evaluate(const Position& pos, + bool adjusted = false, + int* complexity = nullptr, + bool psqtOnly = false); void hint_common_parent_position(const Position& pos); std::optional load_eval(std::istream& stream, NetSize netSize); diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 0b05d00d..c0746b4e 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -34,6 +34,7 @@ struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[2][Size]; std::int32_t psqtAccumulation[2][PSQTBuckets]; bool computed[2]; + bool computedPSQT[2]; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 3399b82d..b42f1604 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -250,18 +250,21 @@ class FeatureTransformer { } // Convert input features - std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { - update_accumulator(pos); - update_accumulator(pos); + std::int32_t + transform(const Position& pos, OutputType* output, int bucket, bool psqtOnly) const { + update_accumulator(pos, psqtOnly); + update_accumulator(pos, psqtOnly); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& accumulation = (pos.state()->*accPtr).accumulation; const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; - - const auto psqt = + const auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; + if (psqtOnly) + return psqt; + + const auto& accumulation = (pos.state()->*accPtr).accumulation; for (IndexType p = 0; p < 2; ++p) { @@ -312,20 +315,22 @@ class FeatureTransformer { return psqt; } // end of function transform() - void hint_common_access(const Position& pos) const { - hint_common_access_for_perspective(pos); - hint_common_access_for_perspective(pos); + void hint_common_access(const Position& pos, bool psqtOnly) const { + hint_common_access_for_perspective(pos, psqtOnly); + hint_common_access_for_perspective(pos, psqtOnly); } private: template [[nodiscard]] std::pair - try_find_computed_accumulator(const Position& pos) const { + try_find_computed_accumulator(const Position& pos, bool psqtOnly) 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(), *next = nullptr; int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !(st->*accPtr).computed[Perspective]) + while (st->previous + && (!(st->*accPtr).computedPSQT[Perspective] + || (!psqtOnly && !(st->*accPtr).computed[Perspective]))) { // This governs when a full feature refresh is needed and how many // updates are better than just one full refresh. @@ -347,7 +352,8 @@ class FeatureTransformer { template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, - StateInfo* states_to_update[N]) const { + StateInfo* states_to_update[N], + bool psqtOnly) const { static_assert(N > 0); assert(states_to_update[N - 1] == nullptr); @@ -383,7 +389,8 @@ class FeatureTransformer { for (; i >= 0; --i) { - (states_to_update[i]->*accPtr).computed[Perspective] = true; + (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; + (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; @@ -403,31 +410,34 @@ class FeatureTransformer { { assert(states_to_update[0]); - auto accIn = - reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); - auto accOut = reinterpret_cast( - &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); - - const IndexType offsetR0 = HalfDimensions * removed[0][0]; - auto columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0][0]; - auto columnA = reinterpret_cast(&weights[offsetA]); - - if (removed[0].size() == 1) + if (!psqtOnly) { - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); - } - else - { - const IndexType offsetR1 = HalfDimensions * removed[0][1]; - auto columnR1 = reinterpret_cast(&weights[offsetR1]); + auto accIn = + reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); + auto accOut = reinterpret_cast( + &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); + + if (removed[0].size() == 1) + { + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); + + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); + } } auto accPsqtIn = @@ -461,41 +471,43 @@ class FeatureTransformer { } else { - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - // Load accumulator - auto accTileIn = reinterpret_cast( - &(st->*accPtr).accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTileIn[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) + if (!psqtOnly) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - auto accTileOut = reinterpret_cast( - &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); + // Load accumulator + auto accTileIn = reinterpret_cast( + &(st->*accPtr).accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTileOut[k], acc[k]); + acc[k] = vec_load(&accTileIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = + reinterpret_cast(&(states_to_update[i]->*accPtr) + .accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); + } } - } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -537,8 +549,10 @@ class FeatureTransformer { #else for (IndexType i = 0; states_to_update[i]; ++i) { - std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], - (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); + if (!psqtOnly) + std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], + (st->*accPtr).accumulation[Perspective], + HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = @@ -549,10 +563,12 @@ class FeatureTransformer { // Difference calculation for the deactivated features for (const auto index : removed[i]) { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] -= @@ -562,10 +578,12 @@ class FeatureTransformer { // Difference calculation for the activated features for (const auto index : added[i]) { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] += @@ -576,7 +594,7 @@ class FeatureTransformer { } template - void update_accumulator_refresh(const Position& pos) const { + void update_accumulator_refresh(const Position& pos, bool psqtOnly) const { #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array // is defined in the VECTOR code below, once in each branch @@ -587,33 +605,35 @@ class FeatureTransformer { // Refresh the accumulator // Could be extracted to a separate function because it's done in 2 places, // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = true; + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = !psqtOnly; + accumulator.computedPSQT[Perspective] = true; FeatureSet::IndexList active; FeatureSet::append_active_indices(pos, active); #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - for (const auto index : active) + if (!psqtOnly) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + auto accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); } - auto accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { for (std::size_t k = 0; k < NumPsqtRegs; ++k) @@ -635,18 +655,21 @@ class FeatureTransformer { } #else - std::memcpy(accumulator.accumulation[Perspective], biases, - HalfDimensions * sizeof(BiasType)); + if (!psqtOnly) + std::memcpy(accumulator.accumulation[Perspective], biases, + HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) accumulator.psqtAccumulation[Perspective][k] = 0; for (const auto index : active) { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[Perspective][j] += weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) accumulator.psqtAccumulation[Perspective][k] += @@ -656,7 +679,7 @@ class FeatureTransformer { } template - void hint_common_access_for_perspective(const Position& pos) const { + void hint_common_access_for_perspective(const Position& pos, bool psqtOnly) const { // Works like update_accumulator, but performs less work. // Updates ONLY the accumulator for pos. @@ -664,27 +687,31 @@ class FeatureTransformer { // Look for a usable accumulator of an earlier position. We keep track // of the estimated gain in terms of features to be added/subtracted. // Fast early exit. - if ((pos.state()->*accPtr).computed[Perspective]) + if ((pos.state()->*accPtr).computed[Perspective] + || (psqtOnly && (pos.state()->*accPtr).computedPSQT[Perspective])) return; - auto [oldest_st, _] = try_find_computed_accumulator(pos); + auto [oldest_st, _] = try_find_computed_accumulator(pos, psqtOnly); - if ((oldest_st->*accPtr).computed[Perspective]) + if ((oldest_st->*accPtr).computed[Perspective] + || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) { // Only update current position accumulator to minimize work. StateInfo* states_to_update[2] = {pos.state(), nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update); + update_accumulator_incremental(pos, oldest_st, states_to_update, + psqtOnly); } else - update_accumulator_refresh(pos); + update_accumulator_refresh(pos, psqtOnly); } template - void update_accumulator(const Position& pos) const { + void update_accumulator(const Position& pos, bool psqtOnly) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos); + auto [oldest_st, next] = try_find_computed_accumulator(pos, psqtOnly); - if ((oldest_st->*accPtr).computed[Perspective]) + if ((oldest_st->*accPtr).computed[Perspective] + || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) { if (next == nullptr) return; @@ -697,12 +724,11 @@ class FeatureTransformer { StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(), nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update); + update_accumulator_incremental(pos, oldest_st, states_to_update, + psqtOnly); } else - { - update_accumulator_refresh(pos); - } + update_accumulator_refresh(pos, psqtOnly); } alignas(CacheLineSize) BiasType biases[HalfDimensions]; diff --git a/src/position.cpp b/src/position.cpp index c89b1eb0..2263afe7 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -680,10 +680,14 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - auto& dp = st->dirtyPiece; - dp.dirty_num = 1; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = + st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = + false; + + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; Color us = sideToMove; Color them = ~us; @@ -965,10 +969,13 @@ void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { newSt.previous = st; 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; + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = + st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = + false; if (st->epSquare != SQ_NONE) { From 6d0d430860fb72137339c001b991f2f4440e45eb Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:47:52 +0700 Subject: [PATCH 596/678] Simplify IIR Simplified depth reduction for PV nodes without a ttMove to 3. Passed STC non-reg: https://tests.stockfishchess.org/tests/view/65d1a90a1d8e83c78bfd855a LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 363168 W: 93648 L: 93791 D: 175729 Ptnml(0-2): 1557, 43692, 91221, 43565, 1549 Passed LTC non-reg: https://tests.stockfishchess.org/tests/view/65d5612d1d8e83c78bfdc8e2 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 58818 W: 14946 L: 14761 D: 29111 Ptnml(0-2): 36, 6595, 15962, 6780, 36 closes https://github.com/official-stockfish/Stockfish/pull/5062 Bench: 1505827 --- src/search.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3de1f69b..951e206c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -805,13 +805,11 @@ Value Search::Worker::search( } // Step 10. Internal iterative reductions (~9 Elo) - // For PV nodes without a ttMove, we decrease depth by 2, - // or by 4 if the current position is present in the TT and - // the stored depth is greater than or equal to the current depth. - // Use qsearch if depth <= 0. + // For PV nodes without a ttMove, we decrease depth by 3. if (PvNode && !ttMove) - depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); + depth -= 3; + // Use qsearch if depth <= 0. if (depth <= 0) return qsearch(pos, ss, alpha, beta); From b0ac8a4e3bad2ed1b066850b6f151ddbe48d1dc6 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Thu, 22 Feb 2024 04:37:26 +0700 Subject: [PATCH 597/678] Simplify extension when ttMove is assumed to fail high over current beta Simplify extension value to -3 when ttMove is assumed to fail high over current beta. Passed non-reg STC: https://tests.stockfishchess.org/tests/view/65d66ed81d8e83c78bfddcba LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 235136 W: 60711 L: 60708 D: 113717 Ptnml(0-2): 969, 27904, 59874, 27797, 1024 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/65da2994944f2a78d4733107 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 662850 W: 166161 L: 166602 D: 330087 Ptnml(0-2): 394, 74895, 181274, 74482, 380 closes https://github.com/official-stockfish/Stockfish/pull/5088 Bench: 1553115 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 951e206c..70baffe3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1056,7 +1056,7 @@ moves_loop: // When in check, search starts here // If the ttMove is assumed to fail high over current beta (~7 Elo) else if (ttValue >= beta) - extension = -2 - !PvNode; + extension = -3; // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) From a615efb19f5dfb4b205ed3a9dd8525e54e8777cc Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 26 Feb 2024 18:08:22 +0300 Subject: [PATCH 598/678] Simplify Time Management Instead of having a formula for using extra time with larger increments. Simply set it to 1 when the increment is lower than 0.5s and to 1.1 when the increment is higher. The values can later on be further improved. Passed STC: https://tests.stockfishchess.org/tests/view/65d25d3c1d8e83c78bfd9293 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 27488 W: 7077 L: 6848 D: 13563 Ptnml(0-2): 96, 3041, 7267, 3218, 122 Passed LTC: https://tests.stockfishchess.org/tests/view/65d2a72c1d8e83c78bfd97fa LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 137568 W: 34612 L: 34512 D: 68444 Ptnml(0-2): 60, 14672, 39221, 14770, 61 Passed VLTC: https://tests.stockfishchess.org/tests/view/65d7d7d39b2da0226a5a205b LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 139650 W: 35229 L: 35134 D: 69287 Ptnml(0-2): 33, 14227, 41218, 14306, 41 Passed also the TCEC TC style suggested by vondele: https://tests.stockfishchess.org/tests/view/65e4ca73416ecd92c162a57d LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 134150 W: 34278 L: 34163 D: 65709 Ptnml(0-2): 561, 15727, 34444, 15722, 621 closes https://github.com/official-stockfish/Stockfish/pull/5076 Bench: 1553115 --- src/timeman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 72a447af..e620dede 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -94,7 +94,7 @@ void TimeManagement::init(Search::LimitsType& limits, if (limits.movestogo == 0) { // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); + double optExtra = limits.inc[us] < 500 ? 1.0 : 1.1; // Calculate time constants based on current time left. double optConstant = From a96b0d46093c67707e4e75e7aa5aa057b7c131a2 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 4 Mar 2024 16:13:36 +0300 Subject: [PATCH 599/678] Update elo estimates Tests used to change the elo worth of some functions: https://tests.stockfishchess.org/tests/view/65c3f69dc865510db0283eef https://tests.stockfishchess.org/tests/view/65c3f935c865510db0283f2a https://tests.stockfishchess.org/tests/view/65d1489f1d8e83c78bfd7dbf https://tests.stockfishchess.org/tests/view/65ce9d361d8e83c78bfd4951 https://tests.stockfishchess.org/tests/view/65cfcd901d8e83c78bfd6184 closes https://github.com/official-stockfish/Stockfish/pull/5089 No functional change --- src/search.cpp | 8 ++++---- src/timeman.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 70baffe3..8ed7841e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -614,7 +614,7 @@ Value Search::Worker::search( update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of - // the previous ply (~0 Elo on STC, ~2 Elo on LTC). + // the previous ply (~1 Elo on STC, ~2 Elo on LTC) if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -stat_malus(depth + 1)); @@ -1067,7 +1067,7 @@ moves_loop: // When in check, search starts here extension = -1; } - // Recapture extensions (~1 Elo) + // Recapture extensions (~0 Elo on STC, ~1 Elo on LTC) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] @@ -1105,7 +1105,7 @@ moves_loop: // When in check, search starts here if (ttCapture) r++; - // Decrease reduction for PvNodes (~3 Elo) + // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) r--; @@ -1167,7 +1167,7 @@ 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 (~1 Elo) + // Increase reduction if ttMove is not present (~6 Elo) if (!ttMove) r += 2; diff --git a/src/timeman.cpp b/src/timeman.cpp index e620dede..b64ec773 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -89,8 +89,8 @@ void TimeManagement::init(Search::LimitsType& limits, - moveOverhead * (2 + mtg)); // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed actual available - // game time for the current move, so also cap to 20% of available game time. + // If there is a healthy increment, timeLeft can exceed the actual available + // game time for the current move, so also cap to a percentage of available game time. if (limits.movestogo == 0) { // Use extra time with larger increments From bd579ab5d1a931a09a62f2ed33b5149ada7bc65f Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 1 Mar 2024 10:34:03 -0800 Subject: [PATCH 600/678] Update default main net to nn-1ceb1ade0001.nnue Created by retraining the previous main net `nn-b1a57edbea57.nnue` with: - some of the same options as before: - ranger21, more WDL skipping, 15% more loss when Q is too high - removal of the huge 514G pre-interleaved binpack - removal of SF-generated dfrc data (dfrc99-16tb7p-filt-v2.min.binpack) - interleaving many binpacks at training time - training with some bestmove capture positions where SEE < 0 - increased usage of torch.compile to speed up training by up to 40% ```yaml experiment-name: 2560--S10-dfrc0-to-dec2023-skip-more-wdl-15p-more-loss-high-q-see-ge0-sk28 nnue-pytorch-branch: linrock/nnue-pytorch/r21-more-wdl-skip-15p-more-loss-high-q-skip-see-ge0-torch-compile-more start-from-engine-test-net: True early-fen-skipping: 28 training-dataset: # similar, not the exact same as: # https://github.com/official-stockfish/Stockfish/pull/4635 - /data/S5-5af/leela96.v2.min.binpack - /data/S5-5af/test60-2021-11-12-novdec-12tb7p.v6-dd.min.binpack - /data/S5-5af/test77-2021-12-dec-16tb7p.v6-dd.min.binpack - /data/S5-5af/test78-2022-01-to-05-jantomay-16tb7p.v6-dd.min.binpack - /data/S5-5af/test78-2022-06-to-09-juntosep-16tb7p.v6-dd.min.binpack - /data/S5-5af/test79-2022-04-apr-16tb7p.v6-dd.min.binpack - /data/S5-5af/test79-2022-05-may-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2022-06-jun-16tb7p.v6-dd.min.unmin.binpack - /data/S5-5af/test80-2022-07-jul-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2022-08-aug-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2022-09-sep-16tb7p.v6-dd.min.unmin.binpack - /data/S5-5af/test80-2022-10-oct-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2022-11-nov-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2023-01-jan-16tb7p.v6-sk20.min.binpack - /data/S5-5af/test80-2023-02-feb-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2023-03-mar-2tb7p.min.unmin.binpack - /data/S5-5af/test80-2023-04-apr-2tb7p.binpack - /data/S5-5af/test80-2023-05-may-2tb7p.min.dd.binpack # https://github.com/official-stockfish/Stockfish/pull/4782 - /data/S6-1ee1aba5ed/test80-2023-06-jun-2tb7p.binpack - /data/S6-1ee1aba5ed/test80-2023-07-jul-2tb7p.min.binpack # https://github.com/official-stockfish/Stockfish/pull/4972 - /data/S8-baff1edbea57/test80-2023-08-aug-2tb7p.v6.min.binpack - /data/S8-baff1edbea57/test80-2023-09-sep-2tb7p.binpack - /data/S8-baff1edbea57/test80-2023-10-oct-2tb7p.binpack # https://github.com/official-stockfish/Stockfish/pull/5056 - /data/S9-b1a57edbea57/test80-2023-11-nov-2tb7p.binpack - /data/S9-b1a57edbea57/test80-2023-12-dec-2tb7p.binpack num-epochs: 800 lr: 4.375e-4 gamma: 0.995 start-lambda: 1.0 end-lambda: 0.7 ``` This particular net was reached at epoch 759. Use of more torch.compile decorators in nnue-pytorch model.py than in the previous main net training run sped up training by up to 40% on Tesla gpus when using recent pytorch compiled with cuda 12: https://github.com/linrock/nnue-tools/blob/7fb9831/Dockerfile Skipping positions with bestmove captures where static exchange evaluation is >= 0 is based on the implementation from Sopel's NNUE training & experimentation log: https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY Experiment 293 - only skip captures with see>=0 Positions with bestmove captures where score == 0 are always skipped for compatibility with minimized binpacks, since the original minimizer sets scores to 0 for slight improvements in compression. The trainer branch used was: https://github.com/linrock/nnue-pytorch/tree/r21-more-wdl-skip-15p-more-loss-high-q-skip-see-ge0-torch-compile-more Binpacks were renamed to be sorted chronologically by default when sorted by name. The binpack data are otherwise the same as binpacks with similar names in the prior naming convention. Training data can be found at: https://robotmoon.com/nnue-training-data/ Passed STC: https://tests.stockfishchess.org/tests/view/65e3ddd1f2ef6c733362ae5c LLR: 2.92 (-2.94,2.94) <0.00,2.00> Total: 149792 W: 39153 L: 38661 D: 71978 Ptnml(0-2): 675, 17586, 37905, 18032, 698 Passed LTC: https://tests.stockfishchess.org/tests/view/65e4d91c416ecd92c162a69b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 64416 W: 16517 L: 16135 D: 31764 Ptnml(0-2): 38, 7218, 17313, 7602, 37 closes https://github.com/official-stockfish/Stockfish/pull/5090 Bench: 1373183 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 53928bf6..33fed3d5 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ Value evaluate(const Position& pos, int optimism); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultNameBig "nn-b1a57edbea57.nnue" +#define EvalFileDefaultNameBig "nn-1ceb1ade0001.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" struct EvalFile { From 1db969e6200afe4f023469a56aa5edf755d92bbb Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Thu, 15 Feb 2024 23:01:02 +0100 Subject: [PATCH 601/678] Reduce futility_margin if opponents last move was bad This reduces the futiltiy_margin if our opponents last move was bad by around ~1/3 when not improving and ~1/2.7 when improving, the idea being to retroactively futility prune moves that were played, but turned out to be bad. A bad move is being defined as their staticEval before their move being lower as our staticEval now is. If the depth is 2 and we are improving the opponent worsening flag is not set, in order to not risk having a too low futility_margin, due to the fact that when these conditions are met the futility_margin already drops quite low. Passed STC: https://tests.stockfishchess.org/tests/live_elo/65e3977bf2ef6c733362aae3 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 122432 W: 31884 L: 31436 D: 59112 Ptnml(0-2): 467, 14404, 31035, 14834, 476 Passed LTC: https://tests.stockfishchess.org/tests/live_elo/65e47f40f2ef6c733362b6d2 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 421692 W: 106572 L: 105452 D: 209668 Ptnml(0-2): 216, 47217, 114865, 48327, 221 closes https://github.com/official-stockfish/Stockfish/pull/5092 Bench: 1565939 --- src/search.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8ed7841e..f135a090 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -53,11 +53,13 @@ using namespace Search; namespace { - // Futility margin -Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - Value futilityMult = 117 - 44 * noTtCutNode; - return (futilityMult * d - 3 * futilityMult / 2 * improving); +Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { + Value futilityMult = 117 - 44 * noTtCutNode; + Value improvingDeduction = 3 * improving * futilityMult / 2; + Value worseningDeduction = (331 + 45 * improving) * oppWorsening * futilityMult / 1024; + + return futilityMult * d - improvingDeduction - worseningDeduction; } constexpr int futility_move_count(bool improving, Depth depth) { @@ -533,7 +535,7 @@ Value Search::Worker::search( Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture; + bool givesCheck, improving, priorCapture, opponenWorsening; bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; @@ -742,6 +744,8 @@ Value Search::Worker::search( ? ss->staticEval > (ss - 2)->staticEval : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; + opponenWorsening = ss->staticEval + (ss - 1)->staticEval > 2 && (depth != 2 || !improving); + // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. @@ -756,7 +760,7 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. if (!ss->ttPv && depth < 11 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponenWorsening) - (ss - 1)->statScore / 314 >= beta && eval >= beta && eval < 30016 // smaller than TB wins From 6136d094c5f46456964889754ae2d6098834b14f Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 7 Mar 2024 11:57:18 +0300 Subject: [PATCH 602/678] Introduce double extensions for PV nodes Our double/triple extensions were allowed only for non-pv nodes. This patch allows them to be done for PV nodes, with some stricter conditions. Passed STC: https://tests.stockfishchess.org/tests/view/65d657ec1d8e83c78bfddab8 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 339424 W: 88097 L: 87318 D: 164009 Ptnml(0-2): 1573, 39935, 85729, 41090, 1385 Passed LTC: https://tests.stockfishchess.org/tests/view/65dd63824b19edc854ebc433 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 459564 W: 115812 L: 114614 D: 229138 Ptnml(0-2): 248, 51441, 125173, 52705, 215 closes https://github.com/official-stockfish/Stockfish/pull/5093 Bench: 1714391 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index f135a090..ff32ecc1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1042,6 +1042,8 @@ moves_loop: // When in check, search starts here extension = 2 + (value < singularBeta - 78 && !ttCapture); depth += depth < 16; } + if (PvNode && !ttCapture && ss->multipleExtensions <= 5 && value < singularBeta - 50) + extension = 2; } // Multi-cut pruning From 748791f80dbc29793e473e3e9eda83ffa0afcfaa Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Wed, 6 Mar 2024 20:56:55 +0300 Subject: [PATCH 603/678] Fix `go mate x` in multithreading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes two issues with master for go mate x: - when running go mate x in losing positions, master always goes to the maximal depth, arguably against what the UCI protocol demands - when running go mate x in winning positions with multiple threads, master may return non-mate scores from the search (this issue is present in stockfish since at least sf16) The issues are fixed by (a) also checking if score is mate -x and by (b) only letting mainthread stop the search for go mate x commands, and by not looking for a best thread but using mainthread as per the default. Related: niklasf/python-chess#1070 More diagnostics can be found here peregrineshahin#6 (comment) closes https://github.com/official-stockfish/Stockfish/pull/5094 No functional change Co-Authored-By: Robert Nürnberg <28635489+robertnurnberg@users.noreply.github.com> --- src/search.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ff32ecc1..7bf3e7f9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -187,7 +187,7 @@ void Search::Worker::start_searching() { Skill skill = Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); - if (int(options["MultiPV"]) == 1 && !limits.depth && !skill.enabled() + if (int(options["MultiPV"]) == 1 && !limits.depth && !limits.mate && !skill.enabled() && rootMoves[0].pv[0] != Move::none()) bestThread = threads.get_best_thread()->worker.get(); @@ -399,14 +399,18 @@ void Search::Worker::iterative_deepening() { lastBestMoveDepth = rootDepth; } - // Have we found a "mate in x"? - if (limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * limits.mate) - threads.stop = true; - if (!mainThread) continue; + // Have we found a "mate in x"? + if (limits.mate && rootMoves[0].score == rootMoves[0].uciScore + && ((rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - rootMoves[0].score <= 2 * limits.mate) + || (rootMoves[0].score != -VALUE_INFINITE + && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY + && VALUE_MATE + rootMoves[0].score <= 2 * limits.mate))) + threads.stop = true; + // If the skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(rootMoves, multiPV); From 0f01a516d2ddd475bbe3bccab176dbbccb879053 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:48:02 +0800 Subject: [PATCH 604/678] VLTC time management tune Result of 35k games of SPSA tuning at 180+1.8. Tuning attempt can be found here: https://tests.stockfishchess.org/tests/view/65e40599f2ef6c733362b03b Passed VLTC 180+1.8: https://tests.stockfishchess.org/tests/view/65e5a6f5416ecd92c162b5d4 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 31950 W: 8225 L: 7949 D: 15776 Ptnml(0-2): 3, 3195, 9309, 3459, 9 Passed VLTC 240+2.4: https://tests.stockfishchess.org/tests/view/65e714de0ec64f0526c3d1f1 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 65108 W: 16558 L: 16202 D: 32348 Ptnml(0-2): 7, 6366, 19449, 6728, 4 closes https://github.com/official-stockfish/Stockfish/pull/5095 Bench: 1714391 --- src/search.cpp | 20 ++++++++++---------- src/timeman.cpp | 14 +++++++------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7bf3e7f9..335149d5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -429,15 +429,15 @@ void Search::Worker::iterative_deepening() { int nodesEffort = effort[bestmove.from_sq()][bestmove.to_sq()] * 100 / std::max(size_t(1), size_t(nodes)); - double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) - / 616.6; - fallingEval = std::clamp(fallingEval, 0.51, 1.51); + double fallingEval = (1067 + 223 * (mainThread->bestPreviousAverageScore - bestValue) + + 97 * (mainThread->iterValue[iterIdx] - bestValue)) + / 10000.0; + fallingEval = std::clamp(fallingEval, 0.580, 1.667); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.17 * timeReduction); - double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / threads.size(); + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.495 : 0.687; + double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); + double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; @@ -446,8 +446,8 @@ void Search::Worker::iterative_deepening() { if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); - if (completedDepth >= 10 && nodesEffort >= 95 - && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 3 / 4 + if (completedDepth >= 10 && nodesEffort >= 97 + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.739 && !mainThread->ponder) { threads.stop = true; @@ -464,7 +464,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else if (!mainThread->ponder - && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.50) + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.506) threads.increaseDepth = false; else threads.increaseDepth = true; diff --git a/src/timeman.cpp b/src/timeman.cpp index b64ec773..4607344e 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -94,17 +94,17 @@ void TimeManagement::init(Search::LimitsType& limits, if (limits.movestogo == 0) { // Use extra time with larger increments - double optExtra = limits.inc[us] < 500 ? 1.0 : 1.1; + double optExtra = limits.inc[us] < 500 ? 1.0 : 1.13; // Calculate time constants based on current time left. double optConstant = - std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); - double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); + std::min(0.00308 + 0.000319 * std::log10(limits.time[us] / 1000.0), 0.00506); + double maxConstant = std::max(3.39 + 3.01 * std::log10(limits.time[us] / 1000.0), 2.93); - optScale = std::min(0.0120 + std::pow(ply + 3.1, 0.44) * optConstant, - 0.21 * limits.time[us] / double(timeLeft)) + optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant, + 0.213 * limits.time[us] / double(timeLeft)) * optExtra; - maxScale = std::min(6.9, maxConstant + ply / 12.2); + maxScale = std::min(6.64, maxConstant + ply / 12.0); } // x moves in y seconds (+ z increment) @@ -117,7 +117,7 @@ void TimeManagement::init(Search::LimitsType& limits, // Limit the maximum possible time for this move optimumTime = TimePoint(optScale * timeLeft); maximumTime = - TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + TimePoint(std::min(0.825 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; if (options["Ponder"]) optimumTime += optimumTime / 4; From 632f1c21cd271e7c4c242fdafa328a55ec63b9cb Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Thu, 7 Mar 2024 22:01:40 +0100 Subject: [PATCH 605/678] Fix wrong constant usage in go mate Fixes an oversight in https://github.com/official-stockfish/Stockfish/pull/5094 In theory, master could stop search when run with `go mate 247` and return a TB loss (not a mate score). Also fixes the spelling of opponenWorsening. closes https://github.com/official-stockfish/Stockfish/pull/5096 No functional change --- src/search.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 335149d5..533cf61b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -407,7 +407,7 @@ void Search::Worker::iterative_deepening() { && ((rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - rootMoves[0].score <= 2 * limits.mate) || (rootMoves[0].score != -VALUE_INFINITE - && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY + && rootMoves[0].score <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + rootMoves[0].score <= 2 * limits.mate))) threads.stop = true; @@ -539,7 +539,7 @@ Value Search::Worker::search( Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture, opponenWorsening; + bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; @@ -748,7 +748,7 @@ Value Search::Worker::search( ? ss->staticEval > (ss - 2)->staticEval : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; - opponenWorsening = ss->staticEval + (ss - 1)->staticEval > 2 && (depth != 2 || !improving); + opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2 && (depth != 2 || !improving); // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, @@ -764,7 +764,7 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. if (!ss->ttPv && depth < 11 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponenWorsening) + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - (ss - 1)->statScore / 314 >= beta && eval >= beta && eval < 30016 // smaller than TB wins @@ -1046,7 +1046,8 @@ moves_loop: // When in check, search starts here extension = 2 + (value < singularBeta - 78 && !ttCapture); depth += depth < 16; } - if (PvNode && !ttCapture && ss->multipleExtensions <= 5 && value < singularBeta - 50) + if (PvNode && !ttCapture && ss->multipleExtensions <= 5 + && value < singularBeta - 50) extension = 2; } From b6dfd6bd54979b5ba96716c2b246d84e5fa5b9fb Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 9 Mar 2024 12:14:57 +0100 Subject: [PATCH 606/678] Assorted cleanups - fix naming convention for `workingDirectory` - use type alias for `EvalFiles` everywhere - move `ponderMode` into `LimitsType` - move limits parsing into standalone static function closes https://github.com/official-stockfish/Stockfish/pull/5098 No functional change --- src/main.cpp | 3 +-- src/nnue/evaluate_nnue.cpp | 7 +++---- src/nnue/evaluate_nnue.h | 11 ++--------- src/search.h | 2 ++ src/thread.cpp | 5 ++--- src/thread.h | 3 +-- src/uci.cpp | 15 ++++++++++----- src/uci.h | 13 +++++-------- 8 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index de07d6a8..6ce656d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,7 +17,6 @@ */ #include -#include #include "bitboard.h" #include "evaluate.h" @@ -40,7 +39,7 @@ int main(int argc, char* argv[]) { Tune::init(uci.options); - uci.evalFiles = Eval::NNUE::load_networks(uci.workingDirectory(), uci.options, uci.evalFiles); + uci.evalFiles = Eval::NNUE::load_networks(uci.working_directory(), uci.options, uci.evalFiles); uci.loop(); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index efcf5b01..1efd83bf 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include "../evaluate.h" #include "../misc.h" @@ -452,9 +451,9 @@ bool save_eval(std::ostream& stream, } // Save eval, to a file given by its name -bool save_eval(const std::optional& filename, - NetSize netSize, - const std::unordered_map& evalFiles) { +bool save_eval(const std::optional& filename, + NetSize netSize, + const EvalFiles& evalFiles) { std::string actualFilename; std::string msg; diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index c7b37860..febe8f9d 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -26,8 +26,8 @@ #include #include #include -#include +#include "../evaluate.h" #include "../misc.h" #include "../types.h" #include "nnue_architecture.h" @@ -35,11 +35,6 @@ namespace Stockfish { class Position; - -namespace Eval { -struct EvalFile; -} - } namespace Stockfish::Eval::NNUE { @@ -87,9 +82,7 @@ bool save_eval(std::ostream& stream, NetSize netSize, const std::string& name, const std::string& netDescription); -bool save_eval(const std::optional& filename, - NetSize netSize, - const std::unordered_map&); +bool save_eval(const std::optional& filename, NetSize netSize, const EvalFiles&); } // namespace Stockfish::Eval::NNUE diff --git a/src/search.h b/src/search.h index 4a1c68bb..bb9f63ff 100644 --- a/src/search.h +++ b/src/search.h @@ -109,6 +109,7 @@ struct LimitsType { time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); movestogo = depth = mate = perft = infinite = 0; nodes = 0; + ponderMode = false; } bool use_time_management() const { return time[WHITE] || time[BLACK]; } @@ -117,6 +118,7 @@ struct LimitsType { TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; int movestogo, depth, mate, perft, infinite; uint64_t nodes; + bool ponderMode; }; diff --git a/src/thread.cpp b/src/thread.cpp index 95646601..b62f5d8a 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -163,13 +163,12 @@ void ThreadPool::clear() { void ThreadPool::start_thinking(const OptionsMap& options, Position& pos, StateListPtr& states, - Search::LimitsType limits, - bool ponderMode) { + Search::LimitsType limits) { main_thread()->wait_for_search_finished(); main_manager()->stopOnPonderhit = stop = abortedSearch = false; - main_manager()->ponder = ponderMode; + main_manager()->ponder = limits.ponderMode; increaseDepth = true; diff --git a/src/thread.h b/src/thread.h index a2a1d18c..0d4c252c 100644 --- a/src/thread.h +++ b/src/thread.h @@ -79,8 +79,7 @@ class ThreadPool { } } - void - start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType, bool = false); + void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType); void clear(); void set(Search::SharedState); diff --git a/src/uci.cpp b/src/uci.cpp index 4d4ea689..357369bf 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -175,11 +175,9 @@ void UCI::loop() { } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } -void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { - +Search::LimitsType UCI::parse_limits(const Position& pos, std::istream& is) { Search::LimitsType limits; std::string token; - bool ponderMode = false; limits.startTime = now(); // The search starts as early as possible @@ -211,7 +209,14 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { else if (token == "infinite") limits.infinite = 1; else if (token == "ponder") - ponderMode = true; + limits.ponderMode = true; + + return limits; +} + +void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { + + Search::LimitsType limits = parse_limits(pos, is); Eval::NNUE::verify(options, evalFiles); @@ -221,7 +226,7 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { return; } - threads.start_thinking(options, pos, states, limits, ponderMode); + threads.start_thinking(options, pos, states, limits); } void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { diff --git a/src/uci.h b/src/uci.h index 9d5f524a..f25bb8d5 100644 --- a/src/uci.h +++ b/src/uci.h @@ -21,7 +21,6 @@ #include #include -#include #include "evaluate.h" #include "misc.h" @@ -29,13 +28,10 @@ #include "thread.h" #include "tt.h" #include "ucioption.h" +#include "search.h" namespace Stockfish { -namespace Eval::NNUE { -enum NetSize : int; -} - class Move; enum Square : int; using Value = int; @@ -53,11 +49,12 @@ class UCI { static std::string wdl(Value v, int ply); static Move to_move(const Position& pos, std::string& str); - const std::string& workingDirectory() const { return cli.workingDirectory; } + static Search::LimitsType parse_limits(const Position& pos, std::istream& is); - OptionsMap options; + const std::string& working_directory() const { return cli.workingDirectory; } - std::unordered_map evalFiles; + OptionsMap options; + Eval::NNUE::EvalFiles evalFiles; private: TranspositionTable tt; From 10e27329783c9f92bad8b7bd7fe496382a7fc0cd Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:58:21 +0100 Subject: [PATCH 607/678] VVLTC search tune Result of 32k games of tuning at 60+0.6 8-thread. Link to the tuning attempt: https://tests.stockfishchess.org/tests/view/65def7b04b19edc854ebdec8 Passed VVLTC first SPRT: https://tests.stockfishchess.org/tests/view/65e51b53416ecd92c162ab7f LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 37570 W: 9613 L: 9342 D: 18615 Ptnml(0-2): 2, 3454, 11601, 3727, 1 Passed VVLTC second SPRT: https://tests.stockfishchess.org/tests/view/65e87d1c0ec64f0526c3eb39 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 123158 W: 31463 L: 31006 D: 60689 Ptnml(0-2): 5, 11589, 37935, 12044, 6 Note: The small net and psqt-only thresholds have been moved to evaluate.h. The reasoning is that these values are used in both `evaluate.cpp` and `evaluate_nnue.cpp`, and thus unifying their usage avoids inconsistencies during testing, where one occurrence is changed without the other (this happened during the search tune SPRT). closes https://github.com/official-stockfish/Stockfish/pull/5101 Bench: 1741218 --- src/evaluate.cpp | 4 +- src/evaluate.h | 2 + src/nnue/evaluate_nnue.cpp | 4 +- src/search.cpp | 75 +++++++++++++++++++------------------- 4 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index cd026036..1eb58fd2 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -193,8 +193,8 @@ Value Eval::evaluate(const Position& pos, int optimism) { assert(!pos.checkers()); int simpleEval = simple_eval(pos, pos.side_to_move()); - bool smallNet = std::abs(simpleEval) > 1050; - bool psqtOnly = std::abs(simpleEval) > 2500; + bool smallNet = std::abs(simpleEval) > SmallNetThreshold; + bool psqtOnly = std::abs(simpleEval) > PsqtOnlyThreshold; int nnueComplexity; diff --git a/src/evaluate.h b/src/evaluate.h index 33fed3d5..a690a3bb 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -31,6 +31,8 @@ class OptionsMap; namespace Eval { +constexpr inline int SmallNetThreshold = 1139, PsqtOnlyThreshold = 2500; + std::string trace(Position& pos); int simple_eval(const Position& pos, Color c); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 1efd83bf..854fed06 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -179,8 +179,8 @@ write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDe void hint_common_parent_position(const Position& pos) { int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); - if (simpleEvalAbs > 1050) - featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > 2500); + if (simpleEvalAbs > Eval::SmallNetThreshold) + featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); else featureTransformerBig->hint_common_access(pos, false); } diff --git a/src/search.cpp b/src/search.cpp index 533cf61b..75a747d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 117 - 44 * noTtCutNode; + Value futilityMult = 121 - 43 * noTtCutNode; Value improvingDeduction = 3 * improving * futilityMult / 2; Value worseningDeduction = (331 + 45 * improving) * oppWorsening * futilityMult / 1024; @@ -69,15 +69,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 12475; + v += cv * std::abs(cv) / 10759; return std::clamp(v, 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(246 * d - 351, 1136); } +int stat_bonus(Depth d) { return std::min(249 * d - 327, 1192); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(519 * d - 306, 1258); } +int stat_malus(Depth d) { return std::min(516 * d - 299, 1432); } // 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); } @@ -300,12 +300,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 12487; + delta = 9 + avg * avg / 12804; 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] = 134 * avg / (std::abs(avg) + 97); + optimism[us] = 131 * avg / (std::abs(avg) + 90); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -500,7 +500,7 @@ void Search::Worker::clear() { h->fill(-71); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((18.79 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((19.02 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -731,7 +731,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1723, 1455); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1621, 1237); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -754,7 +754,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 438 - (332 - 154 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 462 - (296 - 145 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -763,24 +763,23 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 11 + if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 314 + - (ss - 1)->statScore / 287 >= beta - && eval >= beta && eval < 30016 // smaller than TB wins - && (!ttMove || ttCapture)) + && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16620 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 330 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16211 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 20 * depth + 314 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 154, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 151, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -828,7 +827,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 181 - 68 * improving; + probCutBeta = beta + 164 - 62 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -884,7 +883,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 452; + probCutBeta = beta + 410; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -967,7 +966,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 277 + 292 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 298 + 288 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -975,7 +974,7 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -197 * depth)) + if (!pos.see_ge(move, -202 * depth)) continue; } else @@ -987,17 +986,17 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4211 * depth) + if (lmrDepth < 6 && history < -4125 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 6437; + lmrDepth += history / 5686; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 57 ? 144 : 57) - + 121 * lmrDepth + && ss->staticEval + (bestValue < ss->staticEval - 55 ? 153 : 58) + + 118 * lmrDepth <= alpha) continue; @@ -1024,11 +1023,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 29) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (60 + 54 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (58 + 55 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1044,7 +1043,7 @@ moves_loop: // When in check, search starts here if (!PvNode && ss->multipleExtensions <= 16) { extension = 2 + (value < singularBeta - 78 && !ttCapture); - depth += depth < 16; + depth += depth < 14; } if (PvNode && !ttCapture && ss->multipleExtensions <= 5 && value < singularBeta - 50) @@ -1082,7 +1081,7 @@ moves_loop: // When in check, search starts here else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4394) + > 4315) extension = 1; } @@ -1136,10 +1135,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4392; + + (*contHist[3])[movedPiece][move.to_sq()] - 4587; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 14189; + r -= ss->statScore / 12372; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1158,7 +1157,7 @@ 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 + 49 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 48 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1277,7 +1276,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 13 && beta < 13652 && value > -12761) + if (depth > 2 && depth < 12 && beta < 13665 && value > -12276) depth -= 2; assert(depth > 0); @@ -1320,8 +1319,8 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15736) - + ((ss - 1)->moveCount > 11); + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14446) + + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1478,7 +1477,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 206; + futilityBase = ss->staticEval + 221; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1558,7 +1557,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -74)) + if (!pos.see_ge(move, -79)) continue; } @@ -1626,7 +1625,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1118 - delta * 793 / rootDelta) / 1024 + (!i && reductionScale > 863); + return (reductionScale + 1091 - delta * 759 / rootDelta) / 1024 + (!i && reductionScale > 952); } namespace { @@ -1715,7 +1714,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 166 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 167 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From f072634e245774f957b715118ecb586264cf04f1 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Mon, 11 Mar 2024 06:25:07 +0700 Subject: [PATCH 608/678] Simplify opponentWorsening condition Passed non-reg STC: https://tests.stockfishchess.org/tests/view/65ea18650ec64f0526c4033a LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 226624 W: 58601 L: 58589 D: 109434 Ptnml(0-2): 1030, 27193, 56819, 27275, 995 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/65eb7a220ec64f0526c4161a LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 243882 W: 61462 L: 61469 D: 120951 Ptnml(0-2): 197, 27559, 66419, 27586, 180 closes https://github.com/official-stockfish/Stockfish/pull/5102 Bench: 1601012 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 75a747d9..16f45df1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -748,7 +748,7 @@ Value Search::Worker::search( ? ss->staticEval > (ss - 2)->staticEval : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; - opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2 && (depth != 2 || !improving); + opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, From 1a26d698de33ed50f182b325b359da61bab67abe Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 9 Mar 2024 14:42:37 +0100 Subject: [PATCH 609/678] Refactor Network Usage Continuing from PR #4968, this update improves how Stockfish handles network usage, making it easier to manage and modify networks in the future. With the introduction of a dedicated Network class, creating networks has become straightforward. See uci.cpp: ```cpp NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::embeddedNNUEBig) ``` The new `Network` encapsulates all network-related logic, significantly reducing the complexity previously required to support multiple network types, such as the distinction between small and big networks #4915. Non-Regression STC: https://tests.stockfishchess.org/tests/view/65edd26c0ec64f0526c43584 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 33760 W: 8887 L: 8661 D: 16212 Ptnml(0-2): 143, 3795, 8808, 3961, 173 Non-Regression SMP STC: https://tests.stockfishchess.org/tests/view/65ed71970ec64f0526c42fdd LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 59088 W: 15121 L: 14931 D: 29036 Ptnml(0-2): 110, 6640, 15829, 6880, 85 Compiled with `make -j profile-build` ``` bash ./bench_parallel.sh ./stockfish ./stockfish-nnue 13 50 sf_base = 1568540 +/- 7637 (95%) sf_test = 1573129 +/- 7301 (95%) diff = 4589 +/- 8720 (95%) speedup = 0.29260% +/- 0.556% (95%) ``` Compiled with `make -j build` ``` bash ./bench_parallel.sh ./stockfish ./stockfish-nnue 13 50 sf_base = 1472653 +/- 7293 (95%) sf_test = 1491928 +/- 7661 (95%) diff = 19275 +/- 7154 (95%) speedup = 1.30886% +/- 0.486% (95%) ``` closes https://github.com/official-stockfish/Stockfish/pull/5100 No functional change --- src/Makefile | 8 +- src/evaluate.cpp | 164 +----------- src/evaluate.h | 32 +-- src/main.cpp | 3 - src/misc.h | 25 ++ src/nnue/evaluate_nnue.cpp | 488 ----------------------------------- src/nnue/evaluate_nnue.h | 89 ------- src/nnue/network.cpp | 422 ++++++++++++++++++++++++++++++ src/nnue/network.h | 128 +++++++++ src/nnue/nnue_architecture.h | 7 +- src/nnue/nnue_misc.cpp | 202 +++++++++++++++ src/nnue/nnue_misc.h | 63 +++++ src/search.cpp | 31 ++- src/search.h | 32 ++- src/thread.cpp | 10 +- src/thread.h | 17 +- src/uci.cpp | 45 ++-- src/uci.h | 8 +- 18 files changed, 948 insertions(+), 826 deletions(-) delete mode 100644 src/nnue/evaluate_nnue.cpp delete mode 100644 src/nnue/evaluate_nnue.h create mode 100644 src/nnue/network.cpp create mode 100644 src/nnue/network.h create mode 100644 src/nnue/nnue_misc.cpp create mode 100644 src/nnue/nnue_misc.h diff --git a/src/Makefile b/src/Makefile index 907b6155..bd04d2c4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,15 +55,15 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ - nnue/evaluate_nnue.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ + nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.cpp OBJS = $(notdir $(SRCS:.cpp=.o)) @@ -502,7 +502,7 @@ endif # In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. # Currently we don't know how to make PGO builds with the NDK yet. ifeq ($(COMP),ndk) - CXXFLAGS += -stdlib=libc++ -fPIE + CXXFLAGS += -stdlib=libc++ -fPIE -mcmodel=large comp=clang ifeq ($(arch),armv7) CXX=armv7a-linux-androideabi16-clang++ diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 1eb58fd2..56abe6cb 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -22,161 +22,18 @@ #include #include #include -#include #include #include -#include #include -#include -#include -#include "incbin/incbin.h" -#include "misc.h" -#include "nnue/evaluate_nnue.h" -#include "nnue/nnue_architecture.h" +#include "nnue/network.h" +#include "nnue/nnue_misc.h" #include "position.h" #include "types.h" #include "uci.h" -#include "ucioption.h" - -// Macro to embed the default efficiently updatable neural network (NNUE) file -// data in the engine binary (using incbin.h, by Dale Weiler). -// This macro invocation will declare the following three variables -// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data -// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end -// const unsigned int gEmbeddedNNUESize; // the size of the embedded file -// Note that this does not work in Microsoft Visual Studio. -#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) -INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); -INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); -#else -const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; -const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; -const unsigned int gEmbeddedNNUEBigSize = 1; -const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; -const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; -const unsigned int gEmbeddedNNUESmallSize = 1; -#endif - namespace Stockfish { -namespace Eval { - - -// Tries to load a NNUE network at startup time, or when the engine -// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" -// The name of the NNUE network is always retrieved from the EvalFile option. -// We search the given network in three locations: internally (the default -// network may be embedded in the binary), in the active working directory and -// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY -// variable to have the engine search in a special directory in their distro. -NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, - const OptionsMap& options, - NNUE::EvalFiles evalFiles) { - - for (auto& [netSize, evalFile] : evalFiles) - { - std::string user_eval_file = options[evalFile.optionName]; - - if (user_eval_file.empty()) - user_eval_file = evalFile.defaultName; - -#if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = {"", "", rootDirectory, - stringify(DEFAULT_NNUE_DIRECTORY)}; -#else - std::vector dirs = {"", "", rootDirectory}; -#endif - - for (const std::string& directory : dirs) - { - if (evalFile.current != user_eval_file) - { - if (directory != "") - { - std::ifstream stream(directory + user_eval_file, std::ios::binary); - auto description = NNUE::load_eval(stream, netSize); - - if (description.has_value()) - { - evalFile.current = user_eval_file; - evalFile.netDescription = description.value(); - } - } - - if (directory == "" && user_eval_file == evalFile.defaultName) - { - // C++ way to prepare a buffer for a memory stream - class MemoryBuffer: public std::basic_streambuf { - public: - MemoryBuffer(char* p, size_t n) { - setg(p, p, p + n); - setp(p, p + n); - } - }; - - MemoryBuffer buffer( - const_cast(reinterpret_cast( - netSize == Small ? gEmbeddedNNUESmallData : gEmbeddedNNUEBigData)), - size_t(netSize == Small ? gEmbeddedNNUESmallSize : gEmbeddedNNUEBigSize)); - (void) gEmbeddedNNUEBigEnd; // Silence warning on unused variable - (void) gEmbeddedNNUESmallEnd; - - std::istream stream(&buffer); - auto description = NNUE::load_eval(stream, netSize); - - if (description.has_value()) - { - evalFile.current = user_eval_file; - evalFile.netDescription = description.value(); - } - } - } - } - } - - return evalFiles; -} - -// Verifies that the last net used was loaded successfully -void NNUE::verify(const OptionsMap& options, - const std::unordered_map& evalFiles) { - - for (const auto& [netSize, evalFile] : evalFiles) - { - std::string user_eval_file = options[evalFile.optionName]; - - if (user_eval_file.empty()) - user_eval_file = evalFile.defaultName; - - if (evalFile.current != user_eval_file) - { - std::string msg1 = - "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = - "The network file " + user_eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, " - "including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: " - "https://tests.stockfishchess.org/api/nn/" - + evalFile.defaultName; - std::string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; - - exit(EXIT_FAILURE); - } - - sync_cout << "info string NNUE evaluation using " << user_eval_file << sync_endl; - } -} -} - // Returns a static, purely materialistic evaluation of the position from // the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. @@ -188,7 +45,7 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos, int optimism) { +Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int optimism) { assert(!pos.checkers()); @@ -198,8 +55,8 @@ Value Eval::evaluate(const Position& pos, int optimism) { int nnueComplexity; - Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity, psqtOnly) - : NNUE::evaluate(pos, true, &nnueComplexity, false); + Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) + : networks.big.evaluate(pos, true, &nnueComplexity, false); // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; @@ -222,23 +79,22 @@ Value Eval::evaluate(const Position& pos, int optimism) { // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos) { +std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pos.checkers()) return "Final evaluation: none (in check)"; std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos) << '\n'; + ss << '\n' << NNUE::trace(pos, networks) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v; - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; + Value v = networks.big.evaluate(pos, false); + v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos, VALUE_ZERO); + v = evaluate(networks, pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index a690a3bb..754a92eb 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -20,51 +20,33 @@ #define EVALUATE_H_INCLUDED #include -#include #include "types.h" namespace Stockfish { class Position; -class OptionsMap; namespace Eval { constexpr inline int SmallNetThreshold = 1139, PsqtOnlyThreshold = 2500; -std::string trace(Position& pos); - -int simple_eval(const Position& pos, Color c); -Value evaluate(const Position& pos, int optimism); - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the -// name of the macro, as it is used in the Makefile. +// name of the macro or the location where this macro is defined, as it is used +// in the Makefile/Fishtest. #define EvalFileDefaultNameBig "nn-1ceb1ade0001.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" -struct EvalFile { - // UCI option name - std::string optionName; - // Default net name, will use one of the macros above - std::string defaultName; - // Selected net name, either via uci option or default - std::string current; - // Net description extracted from the net file - std::string netDescription; -}; - namespace NNUE { +struct Networks; +} -enum NetSize : int; +std::string trace(Position& pos, const Eval::NNUE::Networks& networks); -using EvalFiles = std::unordered_map; +int simple_eval(const Position& pos, Color c); +Value evaluate(const NNUE::Networks& networks, const Position& pos, int optimism); -EvalFiles load_networks(const std::string&, const OptionsMap&, EvalFiles); -void verify(const OptionsMap&, const EvalFiles&); - -} // namespace NNUE } // namespace Eval diff --git a/src/main.cpp b/src/main.cpp index 6ce656d7..33d5d375 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,6 @@ #include #include "bitboard.h" -#include "evaluate.h" #include "misc.h" #include "position.h" #include "tune.h" @@ -39,8 +38,6 @@ int main(int argc, char* argv[]) { Tune::init(uci.options); - uci.evalFiles = Eval::NNUE::load_networks(uci.working_directory(), uci.options, uci.evalFiles); - uci.loop(); return 0; diff --git a/src/misc.h b/src/misc.h index f73e7889..9ad5c3ca 100644 --- a/src/misc.h +++ b/src/misc.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,30 @@ void* aligned_large_pages_alloc(size_t size); // nop if mem == nullptr void aligned_large_pages_free(void* mem); +// Deleter for automating release of memory area +template +struct AlignedDeleter { + void operator()(T* ptr) const { + ptr->~T(); + std_aligned_free(ptr); + } +}; + +template +struct LargePageDeleter { + void operator()(T* ptr) const { + ptr->~T(); + aligned_large_pages_free(ptr); + } +}; + +template +using AlignedPtr = std::unique_ptr>; + +template +using LargePagePtr = std::unique_ptr>; + + void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); void dbg_stdev_of(int64_t value, int slot = 0); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp deleted file mode 100644 index 854fed06..00000000 --- a/src/nnue/evaluate_nnue.cpp +++ /dev/null @@ -1,488 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// Code for calculating NNUE evaluation function - -#include "evaluate_nnue.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../evaluate.h" -#include "../misc.h" -#include "../position.h" -#include "../types.h" -#include "../uci.h" -#include "nnue_accumulator.h" -#include "nnue_common.h" - -namespace Stockfish::Eval::NNUE { - -// Input feature converter -LargePagePtr> - featureTransformerBig; -LargePagePtr> - featureTransformerSmall; - -// Evaluation function -AlignedPtr> networkBig[LayerStacks]; -AlignedPtr> networkSmall[LayerStacks]; - -// Evaluation function file names - -namespace Detail { - -// Initialize the evaluation function parameters -template -void initialize(AlignedPtr& pointer) { - - pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); - std::memset(pointer.get(), 0, sizeof(T)); -} - -template -void initialize(LargePagePtr& pointer) { - - static_assert(alignof(T) <= 4096, - "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); - pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); - std::memset(pointer.get(), 0, sizeof(T)); -} - -// Read evaluation function parameters -template -bool read_parameters(std::istream& stream, T& reference) { - - std::uint32_t header; - header = read_little_endian(stream); - if (!stream || header != T::get_hash_value()) - return false; - return reference.read_parameters(stream); -} - -// Write evaluation function parameters -template -bool write_parameters(std::ostream& stream, const T& reference) { - - write_little_endian(stream, T::get_hash_value()); - return reference.write_parameters(stream); -} - -} // namespace Detail - - -// Initialize the evaluation function parameters -static void initialize(NetSize netSize) { - - if (netSize == Small) - { - Detail::initialize(featureTransformerSmall); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(networkSmall[i]); - } - else - { - Detail::initialize(featureTransformerBig); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(networkBig[i]); - } -} - -// Read network header -static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { - std::uint32_t version, size; - - version = read_little_endian(stream); - *hashValue = read_little_endian(stream); - size = read_little_endian(stream); - if (!stream || version != Version) - return false; - desc->resize(size); - stream.read(&(*desc)[0], size); - return !stream.fail(); -} - -// Write network header -static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { - write_little_endian(stream, Version); - write_little_endian(stream, hashValue); - write_little_endian(stream, std::uint32_t(desc.size())); - stream.write(&desc[0], desc.size()); - return !stream.fail(); -} - -// Read network parameters -static bool read_parameters(std::istream& stream, NetSize netSize, std::string& netDescription) { - - std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) - return false; - if (hashValue != HashValue[netSize]) - return false; - if (netSize == Big && !Detail::read_parameters(stream, *featureTransformerBig)) - return false; - if (netSize == Small && !Detail::read_parameters(stream, *featureTransformerSmall)) - return false; - for (std::size_t i = 0; i < LayerStacks; ++i) - { - if (netSize == Big && !Detail::read_parameters(stream, *(networkBig[i]))) - return false; - if (netSize == Small && !Detail::read_parameters(stream, *(networkSmall[i]))) - return false; - } - return stream && stream.peek() == std::ios::traits_type::eof(); -} - -// Write network parameters -static bool -write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDescription) { - - if (!write_header(stream, HashValue[netSize], netDescription)) - return false; - if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig)) - return false; - if (netSize == Small && !Detail::write_parameters(stream, *featureTransformerSmall)) - return false; - for (std::size_t i = 0; i < LayerStacks; ++i) - { - if (netSize == Big && !Detail::write_parameters(stream, *(networkBig[i]))) - return false; - if (netSize == Small && !Detail::write_parameters(stream, *(networkSmall[i]))) - return false; - } - return bool(stream); -} - -void hint_common_parent_position(const Position& pos) { - - int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); - if (simpleEvalAbs > Eval::SmallNetThreshold) - featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); - else - featureTransformerBig->hint_common_access(pos, false); -} - -// Evaluation function. Perform differential calculation. -template -Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly) { - - // We manually align the arrays on the stack because with gcc < 9.3 - // overaligning stack variables with alignas() doesn't work correctly. - - constexpr uint64_t alignment = CacheLineSize; - constexpr int delta = 24; - -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall - : TransformedFeatureDimensionsBig, - nullptr > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; - - auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); -#else - - alignas(alignment) TransformedFeatureType - transformedFeatures[FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall - : TransformedFeatureDimensionsBig, - nullptr > ::BufferSize]; -#endif - - ASSERT_ALIGNED(transformedFeatures, alignment); - - const int bucket = (pos.count() - 1) / 4; - const auto psqt = - Net_Size == Small - ? featureTransformerSmall->transform(pos, transformedFeatures, bucket, psqtOnly) - : featureTransformerBig->transform(pos, transformedFeatures, bucket, psqtOnly); - - const auto positional = - !psqtOnly ? (Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) - : networkBig[bucket]->propagate(transformedFeatures)) - : 0; - - if (complexity) - *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; - - // Give more value to positional evaluation when adjusted flag is set - if (adjusted) - return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) - / (1024 * OutputScale)); - else - return static_cast((psqt + positional) / OutputScale); -} - -template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); -template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); - -struct NnueEvalTrace { - static_assert(LayerStacks == PSQTBuckets); - - Value psqt[LayerStacks]; - Value positional[LayerStacks]; - std::size_t correctBucket; -}; - -static NnueEvalTrace trace_evaluate(const Position& pos) { - - // We manually align the arrays on the stack because with gcc < 9.3 - // overaligning stack variables with alignas() doesn't work correctly. - constexpr uint64_t alignment = CacheLineSize; - -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; - - auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); -#else - alignas(alignment) TransformedFeatureType - transformedFeatures[FeatureTransformer::BufferSize]; -#endif - - ASSERT_ALIGNED(transformedFeatures, alignment); - - NnueEvalTrace t{}; - t.correctBucket = (pos.count() - 1) / 4; - for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) - { - const auto materialist = - featureTransformerBig->transform(pos, transformedFeatures, bucket, false); - const auto positional = networkBig[bucket]->propagate(transformedFeatures); - - t.psqt[bucket] = static_cast(materialist / OutputScale); - t.positional[bucket] = static_cast(positional / OutputScale); - } - - return t; -} - -constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); - - -// Converts a Value into (centi)pawns and writes it in a buffer. -// The buffer must have capacity for at least 5 chars. -static void format_cp_compact(Value v, char* buffer) { - - buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - - int cp = std::abs(UCI::to_cp(v)); - if (cp >= 10000) - { - buffer[1] = '0' + cp / 10000; - cp %= 10000; - buffer[2] = '0' + cp / 1000; - cp %= 1000; - buffer[3] = '0' + cp / 100; - buffer[4] = ' '; - } - else if (cp >= 1000) - { - buffer[1] = '0' + cp / 1000; - cp %= 1000; - buffer[2] = '0' + cp / 100; - cp %= 100; - buffer[3] = '.'; - buffer[4] = '0' + cp / 10; - } - else - { - buffer[1] = '0' + cp / 100; - cp %= 100; - buffer[2] = '.'; - buffer[3] = '0' + cp / 10; - cp %= 10; - buffer[4] = '0' + cp / 1; - } -} - - -// Converts a Value into pawns, always keeping two decimals -static void format_cp_aligned_dot(Value v, std::stringstream& stream) { - - const double pawns = std::abs(0.01 * UCI::to_cp(v)); - - stream << (v < 0 ? '-' - : v > 0 ? '+' - : ' ') - << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; -} - - -// Returns a string with the value of each piece on a board, -// and a table for (PSQT, Layers) values bucket by bucket. -std::string trace(Position& pos) { - - std::stringstream ss; - - char board[3 * 8 + 1][8 * 8 + 2]; - std::memset(board, ' ', sizeof(board)); - for (int row = 0; row < 3 * 8 + 1; ++row) - board[row][8 * 8 + 1] = '\0'; - - // A lambda to output one box of the board - auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - const int x = int(file) * 8; - const int y = (7 - int(rank)) * 3; - for (int i = 1; i < 8; ++i) - board[y][x + i] = board[y + 3][x + i] = '-'; - for (int i = 1; i < 3; ++i) - board[y + i][x] = board[y + i][x + 8] = '|'; - board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; - if (pc != NO_PIECE) - board[y + 1][x + 4] = PieceToChar[pc]; - if (value != VALUE_NONE) - format_cp_compact(value, &board[y + 2][x + 2]); - }; - - // We estimate the value of each piece by doing a differential evaluation from - // the current base eval, simulating the removal of the piece from its square. - Value base = evaluate(pos); - base = pos.side_to_move() == WHITE ? base : -base; - - for (File f = FILE_A; f <= FILE_H; ++f) - for (Rank r = RANK_1; r <= RANK_8; ++r) - { - Square sq = make_square(f, r); - Piece pc = pos.piece_on(sq); - Value v = VALUE_NONE; - - if (pc != NO_PIECE && type_of(pc) != KING) - { - auto st = pos.state(); - - pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; - - Value eval = evaluate(pos); - eval = pos.side_to_move() == WHITE ? eval : -eval; - v = base - eval; - - pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; - } - - writeSquare(f, r, pc, v); - } - - ss << " NNUE derived piece values:\n"; - for (int row = 0; row < 3 * 8 + 1; ++row) - ss << board[row] << '\n'; - ss << '\n'; - - auto t = trace_evaluate(pos); - - ss << " NNUE network contributions " - << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl - << "+------------+------------+------------+------------+\n" - << "| Bucket | Material | Positional | Total |\n" - << "| | (PSQT) | (Layers) | |\n" - << "+------------+------------+------------+------------+\n"; - - for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) - { - ss << "| " << bucket << " "; - ss << " | "; - format_cp_aligned_dot(t.psqt[bucket], ss); - ss << " " - << " | "; - format_cp_aligned_dot(t.positional[bucket], ss); - ss << " " - << " | "; - format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); - ss << " " - << " |"; - if (bucket == t.correctBucket) - ss << " <-- this bucket is used"; - ss << '\n'; - } - - ss << "+------------+------------+------------+------------+\n"; - - return ss.str(); -} - - -// Load eval, from a file stream or a memory stream -std::optional load_eval(std::istream& stream, NetSize netSize) { - - initialize(netSize); - std::string netDescription; - return read_parameters(stream, netSize, netDescription) ? std::make_optional(netDescription) - : std::nullopt; -} - -// Save eval, to a file stream or a memory stream -bool save_eval(std::ostream& stream, - NetSize netSize, - const std::string& name, - const std::string& netDescription) { - - if (name.empty() || name == "None") - return false; - - return write_parameters(stream, netSize, netDescription); -} - -// Save eval, to a file given by its name -bool save_eval(const std::optional& filename, - NetSize netSize, - const EvalFiles& evalFiles) { - - std::string actualFilename; - std::string msg; - - if (filename.has_value()) - actualFilename = filename.value(); - else - { - if (evalFiles.at(netSize).current - != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) - { - msg = "Failed to export a net. " - "A non-embedded net can only be saved if the filename is specified"; - - sync_cout << msg << sync_endl; - return false; - } - actualFilename = (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig); - } - - std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream, netSize, evalFiles.at(netSize).current, - evalFiles.at(netSize).netDescription); - - msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; - - sync_cout << msg << sync_endl; - return saved; -} - - -} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h deleted file mode 100644 index febe8f9d..00000000 --- a/src/nnue/evaluate_nnue.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// header used in NNUE evaluation function - -#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED -#define NNUE_EVALUATE_NNUE_H_INCLUDED - -#include -#include -#include -#include -#include - -#include "../evaluate.h" -#include "../misc.h" -#include "../types.h" -#include "nnue_architecture.h" -#include "nnue_feature_transformer.h" - -namespace Stockfish { -class Position; -} - -namespace Stockfish::Eval::NNUE { - -// Hash value of evaluation function structure -constexpr std::uint32_t HashValue[2] = { - FeatureTransformer::get_hash_value() - ^ Network::get_hash_value(), - FeatureTransformer::get_hash_value() - ^ Network::get_hash_value()}; - -// Deleter for automating release of memory area -template -struct AlignedDeleter { - void operator()(T* ptr) const { - ptr->~T(); - std_aligned_free(ptr); - } -}; - -template -struct LargePageDeleter { - void operator()(T* ptr) const { - ptr->~T(); - aligned_large_pages_free(ptr); - } -}; - -template -using AlignedPtr = std::unique_ptr>; - -template -using LargePagePtr = std::unique_ptr>; - -std::string trace(Position& pos); -template -Value evaluate(const Position& pos, - bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false); -void hint_common_parent_position(const Position& pos); - -std::optional load_eval(std::istream& stream, NetSize netSize); -bool save_eval(std::ostream& stream, - NetSize netSize, - const std::string& name, - const std::string& netDescription); -bool save_eval(const std::optional& filename, NetSize netSize, const EvalFiles&); - -} // namespace Stockfish::Eval::NNUE - -#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp new file mode 100644 index 00000000..5d4e0954 --- /dev/null +++ b/src/nnue/network.cpp @@ -0,0 +1,422 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "network.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../evaluate.h" +#include "../incbin/incbin.h" +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_common.h" +#include "nnue_misc.h" + +namespace { +// Macro to embed the default efficiently updatable neural network (NNUE) file +// data in the engine binary (using incbin.h, by Dale Weiler). +// This macro invocation will declare the following three variables +// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data +// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end +// const unsigned int gEmbeddedNNUESize; // the size of the embedded file +// Note that this does not work in Microsoft Visual Studio. +#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) +INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); +INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); +#else +const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; +const unsigned int gEmbeddedNNUEBigSize = 1; +const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; +const unsigned int gEmbeddedNNUESmallSize = 1; +#endif +} + + +namespace Stockfish::Eval::NNUE { + +const EmbeddedNNUE embeddedNNUEBig(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); +const EmbeddedNNUE + embeddedNNUESmall(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); + + +namespace Detail { + +// Initialize the evaluation function parameters +template +void initialize(AlignedPtr& pointer) { + + pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); +} + +template +void initialize(LargePagePtr& pointer) { + + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); +} + +// Read evaluation function parameters +template +bool read_parameters(std::istream& stream, T& reference) { + + std::uint32_t header; + header = read_little_endian(stream); + if (!stream || header != T::get_hash_value()) + return false; + return reference.read_parameters(stream); +} + +// Write evaluation function parameters +template +bool write_parameters(std::ostream& stream, const T& reference) { + + write_little_endian(stream, T::get_hash_value()); + return reference.write_parameters(stream); +} + +} // namespace Detail + + +template +void Network::load(const std::string& rootDirectory, std::string evalfilePath) { +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", rootDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", rootDirectory}; +#endif + + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + for (const auto& directory : dirs) + { + if (evalFile.current != evalfilePath) + { + if (directory != "") + { + load_user_net(directory, evalfilePath); + } + + if (directory == "" && evalfilePath == evalFile.defaultName) + { + load_internal(); + } + } + } +} + + +template +bool Network::save(const std::optional& filename) const { + std::string actualFilename; + std::string msg; + + if (filename.has_value()) + actualFilename = filename.value(); + else + { + if (evalFile.current != evalFile.defaultName) + { + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; + + sync_cout << msg << sync_endl; + return false; + } + + actualFilename = evalFile.defaultName; + } + + std::ofstream stream(actualFilename, std::ios_base::binary); + bool saved = save(stream, evalFile.current, evalFile.netDescription); + + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; + + sync_cout << msg << sync_endl; + return saved; +} + + +template +Value Network::evaluate(const Position& pos, + bool adjusted, + int* complexity, + bool psqtOnly) const { + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + + constexpr uint64_t alignment = CacheLineSize; + constexpr int delta = 24; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); +#else + alignas(alignment) TransformedFeatureType transformedFeatures + [FeatureTransformer::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + + const int bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket, psqtOnly); + const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0; + + if (complexity) + *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; + + // Give more value to positional evaluation when adjusted flag is set + if (adjusted) + return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) + / (1024 * OutputScale)); + else + return static_cast((psqt + positional) / OutputScale); +} + + +template +void Network::verify(std::string evalfilePath) const { + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + if (evalFile.current != evalfilePath) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + evalFile.defaultName; + std::string msg5 = "The engine will be terminated now."; + + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; + exit(EXIT_FAILURE); + } + + sync_cout << "info string NNUE evaluation using " << evalfilePath << sync_endl; +} + + +template +void Network::hint_common_access(const Position& pos, bool psqtOnl) const { + featureTransformer->hint_common_access(pos, psqtOnl); +} + + +template +NnueEvalTrace Network::trace_evaluate(const Position& pos) const { + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); +#else + alignas(alignment) TransformedFeatureType transformedFeatures + [FeatureTransformer::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + + NnueEvalTrace t{}; + t.correctBucket = (pos.count() - 1) / 4; + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) + { + const auto materialist = + featureTransformer->transform(pos, transformedFeatures, bucket, false); + const auto positional = network[bucket]->propagate(transformedFeatures); + + t.psqt[bucket] = static_cast(materialist / OutputScale); + t.positional[bucket] = static_cast(positional / OutputScale); + } + + return t; +} + + +template +void Network::load_user_net(const std::string& dir, + const std::string& evalfilePath) { + std::ifstream stream(dir + evalfilePath, std::ios::binary); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalfilePath; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::load_internal() { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } + }; + + MemoryBuffer buffer(const_cast(reinterpret_cast(embedded.data)), + size_t(embedded.size)); + + std::istream stream(&buffer); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalFile.defaultName; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::initialize() { + Detail::initialize(featureTransformer); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(network[i]); +} + + +template +bool Network::save(std::ostream& stream, + const std::string& name, + const std::string& netDescription) const { + if (name.empty() || name == "None") + return false; + + return write_parameters(stream, netDescription); +} + + +template +std::optional Network::load(std::istream& stream) { + initialize(); + std::string description; + + return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt; +} + + +// Read network header +template +bool Network::read_header(std::istream& stream, + std::uint32_t* hashValue, + std::string* desc) const { + std::uint32_t version, size; + + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; + desc->resize(size); + stream.read(&(*desc)[0], size); + return !stream.fail(); +} + + +// Write network header +template +bool Network::write_header(std::ostream& stream, + std::uint32_t hashValue, + const std::string& desc) const { + write_little_endian(stream, Version); + write_little_endian(stream, hashValue); + write_little_endian(stream, std::uint32_t(desc.size())); + stream.write(&desc[0], desc.size()); + return !stream.fail(); +} + + +template +bool Network::read_parameters(std::istream& stream, + std::string& netDescription) const { + std::uint32_t hashValue; + if (!read_header(stream, &hashValue, &netDescription)) + return false; + if (hashValue != Network::hash) + return false; + if (!Detail::read_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::read_parameters(stream, *(network[i]))) + return false; + } + return stream && stream.peek() == std::ios::traits_type::eof(); +} + + +template +bool Network::write_parameters(std::ostream& stream, + const std::string& netDescription) const { + if (!write_header(stream, Network::hash, netDescription)) + return false; + if (!Detail::write_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::write_parameters(stream, *(network[i]))) + return false; + } + return bool(stream); +} + +// Explicit template instantiation + +template class Network< + NetworkArchitecture, + FeatureTransformer>; + +template class Network< + NetworkArchitecture, + FeatureTransformer>; + +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h new file mode 100644 index 00000000..c1ed7717 --- /dev/null +++ b/src/nnue/network.h @@ -0,0 +1,128 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NETWORK_H_INCLUDED +#define NETWORK_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_feature_transformer.h" +#include "nnue_misc.h" + +namespace Stockfish::Eval::NNUE { + +struct EmbeddedNNUE { + EmbeddedNNUE(const unsigned char* embeddedData, + const unsigned char* embeddedEnd, + const unsigned int embeddedSize) : + data(embeddedData), + end(embeddedEnd), + size(embeddedSize) {} + const unsigned char* data; + const unsigned char* end; + const unsigned int size; +}; + +extern const EmbeddedNNUE embeddedNNUEBig; +extern const EmbeddedNNUE embeddedNNUESmall; + +template +class Network { + public: + Network(EvalFile file, EmbeddedNNUE embeddedEval) : + evalFile(file), + embedded(embeddedEval) {} + + void load(const std::string& rootDirectory, std::string evalfilePath); + bool save(const std::optional& filename) const; + + + Value evaluate(const Position& pos, + bool adjusted = false, + int* complexity = nullptr, + bool psqtOnly = false) const; + + + void hint_common_access(const Position& pos, bool psqtOnl) const; + + void verify(std::string evalfilePath) const; + NnueEvalTrace trace_evaluate(const Position& pos) const; + + private: + void load_user_net(const std::string&, const std::string&); + void load_internal(); + + void initialize(); + + bool save(std::ostream&, const std::string&, const std::string&) const; + std::optional load(std::istream&); + + bool read_header(std::istream&, std::uint32_t*, std::string*) const; + bool write_header(std::ostream&, std::uint32_t, const std::string&) const; + + bool read_parameters(std::istream&, std::string&) const; + bool write_parameters(std::ostream&, const std::string&) const; + + // Input feature converter + LargePagePtr featureTransformer; + + // Evaluation function + AlignedPtr network[LayerStacks]; + + EvalFile evalFile; + EmbeddedNNUE embedded; + + // Hash value of evaluation function structure + static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); +}; + +// Definitions of the network types +using SmallFeatureTransformer = + FeatureTransformer; +using SmallNetworkArchitecture = + NetworkArchitecture; + +using BigFeatureTransformer = + FeatureTransformer; +using BigNetworkArchitecture = NetworkArchitecture; + +using NetworkBig = Network; +using NetworkSmall = Network; + + +struct Networks { + Networks(NetworkBig&& nB, NetworkSmall&& nS) : + big(std::move(nB)), + small(std::move(nS)) {} + + NetworkBig big; + NetworkSmall small; +}; + + +} // namespace Stockfish + +#endif diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index b222ab99..05efb813 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -37,11 +37,6 @@ namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2_hm; -enum NetSize : int { - Big, - Small -}; - // Number of input feature dimensions after conversion constexpr IndexType TransformedFeatureDimensionsBig = 2560; constexpr int L2Big = 15; @@ -55,7 +50,7 @@ constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; template -struct Network { +struct NetworkArchitecture { static constexpr IndexType TransformedFeatureDimensions = L1; static constexpr int FC_0_OUTPUTS = L2; static constexpr int FC_1_OUTPUTS = L3; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp new file mode 100644 index 00000000..c443aaf1 --- /dev/null +++ b/src/nnue/nnue_misc.cpp @@ -0,0 +1,202 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Code for calculating NNUE evaluation function + +#include "nnue_misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../evaluate.h" +#include "../position.h" +#include "../types.h" +#include "../uci.h" +#include "network.h" +#include "nnue_accumulator.h" + +namespace Stockfish::Eval::NNUE { + + +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); + + +void hint_common_parent_position(const Position& pos, const Networks& networks) { + + int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); + if (simpleEvalAbs > Eval::SmallNetThreshold) + networks.small.hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); + else + networks.big.hint_common_access(pos, false); +} + + +// Converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +static void format_cp_compact(Value v, char* buffer) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(UCI::to_cp(v)); + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; + buffer[3] = '0' + cp / 100; + buffer[4] = ' '; + } + else if (cp >= 1000) + { + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; + buffer[3] = '.'; + buffer[4] = '0' + cp / 10; + } + else + { + buffer[1] = '0' + cp / 100; + cp %= 100; + buffer[2] = '.'; + buffer[3] = '0' + cp / 10; + cp %= 10; + buffer[4] = '0' + cp / 1; + } +} + + +// Converts a Value into pawns, always keeping two decimals +static void format_cp_aligned_dot(Value v, std::stringstream& stream) { + + const double pawns = std::abs(0.01 * UCI::to_cp(v)); + + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} + + +// Returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { + + std::stringstream ss; + + char board[3 * 8 + 1][8 * 8 + 2]; + std::memset(board, ' ', sizeof(board)); + for (int row = 0; row < 3 * 8 + 1; ++row) + board[row][8 * 8 + 1] = '\0'; + + // A lambda to output one box of the board + auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y + 2][x + 2]); + }; + + // We estimate the value of each piece by doing a differential evaluation from + // the current base eval, simulating the removal of the piece from its square. + Value base = networks.big.evaluate(pos); + base = pos.side_to_move() == WHITE ? base : -base; + + for (File f = FILE_A; f <= FILE_H; ++f) + for (Rank r = RANK_1; r <= RANK_8; ++r) + { + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; + + if (pc != NO_PIECE && type_of(pc) != KING) + { + auto st = pos.state(); + + pos.remove_piece(sq); + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; + + Value eval = networks.big.evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; + } + + writeSquare(f, r, pc, v); + } + + ss << " NNUE derived piece values:\n"; + for (int row = 0; row < 3 * 8 + 1; ++row) + ss << board[row] << '\n'; + ss << '\n'; + + auto t = networks.big.trace_evaluate(pos); + + ss << " NNUE network contributions " + << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl + << "+------------+------------+------------+------------+\n" + << "| Bucket | Material | Positional | Total |\n" + << "| | (PSQT) | (Layers) | |\n" + << "+------------+------------+------------+------------+\n"; + + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) + { + ss << "| " << bucket << " "; + ss << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + ss << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; + } + + ss << "+------------+------------+------------+------------+\n"; + + return ss.str(); +} + + +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h new file mode 100644 index 00000000..5eab0218 --- /dev/null +++ b/src/nnue/nnue_misc.h @@ -0,0 +1,63 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NNUE_MISC_H_INCLUDED +#define NNUE_MISC_H_INCLUDED + +#include +#include + +#include "../types.h" +#include "nnue_architecture.h" + +namespace Stockfish { + +class Position; + +namespace Eval::NNUE { + +struct EvalFile { + // Default net name, will use one of the EvalFileDefaultName* macros defined + // in evaluate.h + std::string defaultName; + // Selected net name, either via uci option or default + std::string current; + // Net description extracted from the net file + std::string netDescription; +}; + + +struct NnueEvalTrace { + static_assert(LayerStacks == PSQTBuckets); + + Value psqt[LayerStacks]; + Value positional[LayerStacks]; + std::size_t correctBucket; +}; + + +struct Networks; + + +std::string trace(Position& pos, const Networks& networks); +void hint_common_parent_position(const Position& pos, const Networks& networks); + +} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish + +#endif // #ifndef NNUE_MISC_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 16f45df1..f4ec8fb1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -27,15 +27,15 @@ #include #include #include -#include #include +#include #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" -#include "nnue/evaluate_nnue.h" #include "nnue/nnue_common.h" +#include "nnue/nnue_misc.h" #include "position.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -135,7 +135,8 @@ Search::Worker::Worker(SharedState& sharedState, manager(std::move(sm)), options(sharedState.options), threads(sharedState.threads), - tt(sharedState.tt) { + tt(sharedState.tt), + networks(sharedState.networks) { clear(); } @@ -566,8 +567,9 @@ Value Search::Worker::search( // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) - : value_draw(thisThread->nodes); + return (ss->ply >= MAX_PLY && !ss->inCheck) + ? evaluate(networks, pos, thisThread->optimism[us]) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -700,7 +702,7 @@ Value Search::Worker::search( { // 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); + Eval::NNUE::hint_common_parent_position(pos, networks); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -708,9 +710,9 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, networks); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -720,7 +722,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -877,7 +879,7 @@ Value Search::Worker::search( } } - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, networks); } moves_loop: // When in check, search starts here @@ -1413,8 +1415,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) - : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !ss->inCheck) + ? evaluate(networks, pos, thisThread->optimism[us]) + : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1445,7 +1448,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1458,7 +1461,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = (ss - 1)->currentMove != Move::null() - ? evaluate(pos, thisThread->optimism[us]) + ? evaluate(networks, pos, thisThread->optimism[us]) : -(ss - 1)->staticEval; ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); diff --git a/src/search.h b/src/search.h index bb9f63ff..4908e535 100644 --- a/src/search.h +++ b/src/search.h @@ -25,8 +25,8 @@ #include #include #include -#include #include +#include #include "misc.h" #include "movepick.h" @@ -37,6 +37,10 @@ namespace Stockfish { +namespace Eval::NNUE { +struct Networks; +} + // Different node types, used as a template parameter enum NodeType { NonPV, @@ -125,16 +129,20 @@ struct LimitsType { // The UCI stores the uci options, thread pool, and transposition table. // This struct is used to easily forward data to the Search::Worker class. struct SharedState { - SharedState(const OptionsMap& optionsMap, - ThreadPool& threadPool, - TranspositionTable& transpositionTable) : + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + const Eval::NNUE::Networks& nets) : options(optionsMap), threads(threadPool), - tt(transpositionTable) {} + tt(transpositionTable), + networks(nets) {} - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const Eval::NNUE::Networks& networks; }; class Worker; @@ -176,6 +184,7 @@ class NullSearchManager: public ISearchManager { void check_time(Search::Worker&) override {} }; + // Search::Worker is the class that does the actual search. // It is instantiated once per thread, and it is responsible for keeping track // of the search history, and storing data required for the search. @@ -247,9 +256,10 @@ class Worker { Tablebases::Config tbConfig; - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const Eval::NNUE::Networks& networks; friend class Stockfish::ThreadPool; friend class SearchManager; diff --git a/src/thread.cpp b/src/thread.cpp index b62f5d8a..a3823d0c 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -19,12 +19,12 @@ #include "thread.h" #include +#include #include #include #include #include #include -#include #include "misc.h" #include "movegen.h" @@ -62,6 +62,7 @@ Thread::~Thread() { stdThread.join(); } + // Wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); @@ -109,6 +110,13 @@ void Thread::idle_loop() { } } +Search::SearchManager* ThreadPool::main_manager() { + return static_cast(main_thread()->worker.get()->manager.get()); +} + +uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); } +uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); } + // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. diff --git a/src/thread.h b/src/thread.h index 0d4c252c..81fcc72a 100644 --- a/src/thread.h +++ b/src/thread.h @@ -33,6 +33,7 @@ namespace Stockfish { + class OptionsMap; using Value = int; @@ -83,15 +84,13 @@ class ThreadPool { void clear(); void set(Search::SharedState); - Search::SearchManager* main_manager() const { - return static_cast(main_thread()->worker.get()->manager.get()); - }; - Thread* main_thread() const { return threads.front(); } - uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); } - uint64_t tb_hits() const { return accumulate(&Search::Worker::tbHits); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; + Search::SearchManager* main_manager(); + Thread* main_thread() const { return threads.front(); } + uint64_t nodes_searched() const; + uint64_t tb_hits() const; + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; std::atomic_bool stop, abortedSearch, increaseDepth; diff --git a/src/uci.cpp b/src/uci.cpp index 357369bf..fda336b0 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,25 +22,25 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include "benchmark.h" #include "evaluate.h" #include "movegen.h" -#include "nnue/evaluate_nnue.h" -#include "nnue/nnue_architecture.h" +#include "nnue/network.h" +#include "nnue/nnue_common.h" +#include "perft.h" #include "position.h" #include "search.h" #include "syzygy/tbprobe.h" #include "types.h" #include "ucioption.h" -#include "perft.h" namespace Stockfish { @@ -48,17 +48,20 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKB constexpr int NormalizeToPawnValue = 356; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; + +namespace NN = Eval::NNUE; + + UCI::UCI(int argc, char** argv) : + networks(NN::Networks( + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::embeddedNNUEBig), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::embeddedNNUESmall))), cli(argc, argv) { - evalFiles = {{Eval::NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None", ""}}, - {Eval::NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None", ""}}}; - - options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - threads.set({options, threads, tt}); + threads.set({options, threads, tt, networks}); }); options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { @@ -80,14 +83,17 @@ UCI::UCI(int argc, char** argv) : options["SyzygyProbeDepth"] << Option(1, 1, 100); options["Syzygy50MoveRule"] << Option(true); options["SyzygyProbeLimit"] << Option(7, 0, 7); - options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) { - evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) { + networks.big.load(cli.binaryDirectory, o); }); - options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option&) { - evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { + networks.small.load(cli.binaryDirectory, o); }); - threads.set({options, threads, tt}); + networks.big.load(cli.binaryDirectory, options["EvalFile"]); + networks.small.load(cli.binaryDirectory, options["EvalFileSmall"]); + + threads.set({options, threads, tt, networks}); search_clear(); // After threads are up } @@ -157,7 +163,7 @@ void UCI::loop() { std::string f; if (is >> std::skipws >> f) filename = f; - Eval::NNUE::save_eval(filename, Eval::NNUE::Big, evalFiles); + networks.big.save(filename); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout @@ -218,7 +224,8 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits = parse_limits(pos, is); - Eval::NNUE::verify(options, evalFiles); + networks.big.verify(options["EvalFile"]); + networks.small.verify(options["EvalFileSmall"]); if (limits.perft) { @@ -283,9 +290,11 @@ void UCI::trace_eval(Position& pos) { Position p; p.set(pos.fen(), options["UCI_Chess960"], &states->back()); - Eval::NNUE::verify(options, evalFiles); + networks.big.verify(options["EvalFile"]); + networks.small.verify(options["EvalFileSmall"]); - sync_cout << "\n" << Eval::trace(p) << sync_endl; + + sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; } void UCI::search_clear() { diff --git a/src/uci.h b/src/uci.h index f25bb8d5..dd55862a 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,13 +22,13 @@ #include #include -#include "evaluate.h" #include "misc.h" +#include "nnue/network.h" #include "position.h" +#include "search.h" #include "thread.h" #include "tt.h" #include "ucioption.h" -#include "search.h" namespace Stockfish { @@ -53,8 +53,8 @@ class UCI { const std::string& working_directory() const { return cli.workingDirectory; } - OptionsMap options; - Eval::NNUE::EvalFiles evalFiles; + OptionsMap options; + Eval::NNUE::Networks networks; private: TranspositionTable tt; From daa3ef9148e8a141d2562900924d578d6462ba72 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 9 Mar 2024 19:49:59 +0300 Subject: [PATCH 610/678] Simplify increaseDepth to boolean expression Non-functional Simplification, maintaining the same logic as before. Big thanks to @peregrineshahin for helping with the code. Passed non-regression bounds: https://tests.stockfishchess.org/tests/view/65ec93860ec64f0526c42375 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 101088 W: 26196 L: 26047 D: 48845 Ptnml(0-2): 405, 11580, 26473, 11633, 453 closes https://github.com/official-stockfish/Stockfish/pull/5103 No functional change --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f4ec8fb1..a3e53e02 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -464,11 +464,10 @@ void Search::Worker::iterative_deepening() { else threads.stop = true; } - else if (!mainThread->ponder - && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.506) - threads.increaseDepth = false; else - threads.increaseDepth = true; + threads.increaseDepth = + mainThread->ponder + || mainThread->tm.elapsed(threads.nodes_searched()) <= totalTime * 0.506; } mainThread->iterValue[iterIdx] = bestValue; From 627974c99fcd5a3dcbd5a8e0eb12f2afeb2d0a9a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 12 Mar 2024 17:10:28 +0300 Subject: [PATCH 611/678] Search + Eval + Movepick Tune Passed STC: https://tests.stockfishchess.org/tests/view/65ef15220ec64f0526c44b04 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 24480 W: 6459 L: 6153 D: 11868 Ptnml(0-2): 101, 2798, 6184, 3008, 149 Passed LTC: https://tests.stockfishchess.org/tests/view/65ef4bac0ec64f0526c44f50 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 53316 W: 13561 L: 13203 D: 26552 Ptnml(0-2): 27, 5925, 14408, 6259, 39 closes https://github.com/official-stockfish/Stockfish/pull/5104 Bench: 1715522 --- src/evaluate.cpp | 8 ++++---- src/evaluate.h | 2 +- src/movepick.cpp | 16 ++++++++-------- src/search.cpp | 24 ++++++++++++------------ src/tt.cpp | 4 ++-- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 56abe6cb..f4d18d8e 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -59,15 +59,15 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, : networks.big.evaluate(pos, true, &nnueComplexity, false); // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; - nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 524; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 31950; int npm = pos.non_pawn_material() / 64; - int v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; + int v = (nnue * (927 + npm + 9 * pos.count()) + optimism * (159 + npm)) / 1000; // Damp down the evaluation linearly when shuffling int shuffling = pos.rule50_count(); - v = v * (200 - shuffling) / 214; + v = v * (195 - shuffling) / 228; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/evaluate.h b/src/evaluate.h index 754a92eb..bd11e0c1 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1139, PsqtOnlyThreshold = 2500; +constexpr inline int SmallNetThreshold = 1136, PsqtOnlyThreshold = 2656; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/movepick.cpp b/src/movepick.cpp index 33791922..c1119cf1 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -190,18 +190,18 @@ void MovePicker::score() { m.value += bool(pos.check_squares(pt) & to) * 16384; // bonus for escaping from capture - m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 50000 - : pt == ROOK && !(to & threatenedByMinor) ? 25000 - : !(to & threatenedByPawn) ? 15000 + m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51000 + : pt == ROOK && !(to & threatenedByMinor) ? 24950 + : !(to & threatenedByPawn) ? 14450 : 0) : 0; // malus for putting piece en prise m.value -= !(threatenedPieces & from) - ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 - + bool(to & threatenedByMinor) * 10000 - : pt == ROOK ? bool(to & threatenedByMinor) * 25000 - : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + ? (pt == QUEEN ? bool(to & threatenedByRook) * 48150 + + bool(to & threatenedByMinor) * 10650 + : pt == ROOK ? bool(to & threatenedByMinor) * 24500 + : pt != PAWN ? bool(to & threatenedByPawn) * 14950 : 0) : 0; } @@ -241,7 +241,7 @@ Move MovePicker::select(Pred filter) { // moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { - auto quiet_threshold = [](Depth d) { return -3330 * d; }; + auto quiet_threshold = [](Depth d) { return -3550 * d; }; top: switch (stage) diff --git a/src/search.cpp b/src/search.cpp index a3e53e02..5cc67968 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,8 +55,8 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 121 - 43 * noTtCutNode; - Value improvingDeduction = 3 * improving * futilityMult / 2; + Value futilityMult = 122 - 46 * noTtCutNode; + Value improvingDeduction = 57 * improving * futilityMult / 32; Value worseningDeduction = (331 + 45 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; @@ -69,7 +69,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 10759; + v += cv * std::abs(cv) / 11450; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -77,7 +77,7 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { int stat_bonus(Depth d) { return std::min(249 * d - 327, 1192); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(516 * d - 299, 1432); } +int stat_malus(Depth d) { return std::min(516 * d - 299, 1254); } // 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); } @@ -301,12 +301,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 12804; + delta = 9 + avg * avg / 12800; 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] = 131 * avg / (std::abs(avg) + 90); + optimism[us] = 130 * avg / (std::abs(avg) + 90); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -500,7 +500,7 @@ void Search::Worker::clear() { h->fill(-71); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((19.02 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((19.80 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -732,12 +732,12 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1621, 1237); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1621, 1238); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; 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 / 4; + << bonus / 2; } // Set up the improving flag, which is true if current static evaluation is @@ -828,7 +828,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 164 - 62 * improving; + probCutBeta = beta + 168 - 64 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -1139,7 +1139,7 @@ moves_loop: // When in check, search starts here + (*contHist[3])[movedPiece][move.to_sq()] - 4587; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 12372; + r -= ss->statScore / 14956; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1627,7 +1627,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1091 - delta * 759 / rootDelta) / 1024 + (!i && reductionScale > 952); + return (reductionScale + 1091 - delta * 759 / rootDelta) / 1024 + (!i && reductionScale > 950); } namespace { diff --git a/src/tt.cpp b/src/tt.cpp index 8ef06e63..e62e0c17 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -134,8 +134,8 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; for (int i = 1; i < ClusterSize; ++i) - if (replace->depth8 - replace->relative_age(generation8) - > tte[i].depth8 - tte[i].relative_age(generation8)) + if (replace->depth8 - replace->relative_age(generation8) * 2 + > tte[i].depth8 - tte[i].relative_age(generation8) * 2) replace = &tte[i]; return found = false, replace; From 55df0ee00914f6bb5e9ecce4ace47f00b758098c Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 12 Mar 2024 18:20:19 +0100 Subject: [PATCH 612/678] Fix Raspberry Pi Compilation Reported by @Torom over discord. > dev build fails on Raspberry Pi 5 with clang ``` clang++ -o stockfish benchmark.o bitboard.o evaluate.o main.o misc.o movegen.o movepick.o position.o search.o thread.o timeman.o tt.o uci.o ucioption.o tune.o tbprobe.o nnue_misc.o half_ka_v2_hm.o network.o -fprofile-instr-generate -latomic -lpthread -Wall -Wcast-qual -fno-exceptions -std=c++17 -fprofile-instr-generate -pedantic -Wextra -Wshadow -Wmissing-prototypes -Wconditional-uninitialized -DUSE_PTHREADS -DNDEBUG -O3 -funroll-loops -DIS_64BIT -DUSE_POPCNT -DUSE_NEON=8 -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD -DGIT_SHA=627974c9 -DGIT_DATE=20240312 -DARCH=armv8-dotprod -flto=full /tmp/lto-llvm-e9300e.o: in function `_GLOBAL__sub_I_network.cpp': ld-temp.o:(.text.startup+0x704c): relocation truncated to fit: R_AARCH64_LDST64_ABS_LO12_NC against symbol `gEmbeddedNNUEBigEnd' defined in .rodata section in /tmp/lto-llvm-e9300e.o /usr/bin/ld: ld-temp.o:(.text.startup+0x704c): warning: one possible cause of this error is that the symbol is being referenced in the indicated code as if it had a larger alignment than was declared where it was defined ld-temp.o:(.text.startup+0x7068): relocation truncated to fit: R_AARCH64_LDST64_ABS_LO12_NC against symbol `gEmbeddedNNUESmallEnd' defined in .rodata section in /tmp/lto-llvm-e9300e.o /usr/bin/ld: ld-temp.o:(.text.startup+0x7068): warning: one possible cause of this error is that the symbol is being referenced in the indicated code as if it had a larger alignment than was declared where it was defined clang: error: linker command failed with exit code 1 (use -v to see invocation) make[2]: *** [Makefile:1051: stockfish] Error 1 make[2]: Leaving directory '/home/torsten/chess/Stockfish_master/src' make[1]: *** [Makefile:1058: clang-profile-make] Error 2 make[1]: Leaving directory '/home/torsten/chess/Stockfish_master/src' make: *** [Makefile:886: profile-build] Error 2 ``` closes https://github.com/official-stockfish/Stockfish/pull/5106 No functional change --- src/Makefile | 2 +- src/nnue/network.cpp | 28 ++++++++++++++++++++++++---- src/nnue/network.h | 24 ++++++++---------------- src/uci.cpp | 4 ++-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/Makefile b/src/Makefile index bd04d2c4..75f31108 100644 --- a/src/Makefile +++ b/src/Makefile @@ -502,7 +502,7 @@ endif # In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. # Currently we don't know how to make PGO builds with the NDK yet. ifeq ($(COMP),ndk) - CXXFLAGS += -stdlib=libc++ -fPIE -mcmodel=large + CXXFLAGS += -stdlib=libc++ -fPIE comp=clang ifeq ($(arch),armv7) CXX=armv7a-linux-androideabi16-clang++ diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 5d4e0954..bea3e7cb 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -55,15 +55,33 @@ const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; const unsigned int gEmbeddedNNUESmallSize = 1; #endif + +struct EmbeddedNNUE { + EmbeddedNNUE(const unsigned char* embeddedData, + const unsigned char* embeddedEnd, + const unsigned int embeddedSize) : + data(embeddedData), + end(embeddedEnd), + size(embeddedSize) {} + const unsigned char* data; + const unsigned char* end; + const unsigned int size; +}; + +using namespace Stockfish::Eval::NNUE; + +EmbeddedNNUE get_embedded(EmbeddedNNUEType type) { + if (type == EmbeddedNNUEType::BIG) + return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); + else + return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); +} + } namespace Stockfish::Eval::NNUE { -const EmbeddedNNUE embeddedNNUEBig(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); -const EmbeddedNNUE - embeddedNNUESmall(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); - namespace Detail { @@ -302,6 +320,8 @@ void Network::load_internal() { } }; + const auto embedded = get_embedded(embeddedType); + MemoryBuffer buffer(const_cast(reinterpret_cast(embedded.data)), size_t(embedded.size)); diff --git a/src/nnue/network.h b/src/nnue/network.h index c1ed7717..21e1c622 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -34,27 +34,19 @@ namespace Stockfish::Eval::NNUE { -struct EmbeddedNNUE { - EmbeddedNNUE(const unsigned char* embeddedData, - const unsigned char* embeddedEnd, - const unsigned int embeddedSize) : - data(embeddedData), - end(embeddedEnd), - size(embeddedSize) {} - const unsigned char* data; - const unsigned char* end; - const unsigned int size; + +enum class EmbeddedNNUEType { + BIG, + SMALL, }; -extern const EmbeddedNNUE embeddedNNUEBig; -extern const EmbeddedNNUE embeddedNNUESmall; template class Network { public: - Network(EvalFile file, EmbeddedNNUE embeddedEval) : + Network(EvalFile file, EmbeddedNNUEType type) : evalFile(file), - embedded(embeddedEval) {} + embeddedType(type) {} void load(const std::string& rootDirectory, std::string evalfilePath); bool save(const std::optional& filename) const; @@ -92,8 +84,8 @@ class Network { // Evaluation function AlignedPtr network[LayerStacks]; - EvalFile evalFile; - EmbeddedNNUE embedded; + EvalFile evalFile; + EmbeddedNNUEType embeddedType; // Hash value of evaluation function structure static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); diff --git a/src/uci.cpp b/src/uci.cpp index fda336b0..cf0e3f09 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -54,8 +54,8 @@ namespace NN = Eval::NNUE; UCI::UCI(int argc, char** argv) : networks(NN::Networks( - NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::embeddedNNUEBig), - NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::embeddedNNUESmall))), + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))), cli(argc, argv) { options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); From ee2ee6bdc42af80430e7468da4208e379b40c393 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 13 Mar 2024 19:21:13 +0300 Subject: [PATCH 613/678] Give more bonuses to quiet move that caused a fail low Give extra bonus if search result is far below static evaluation of position. Passed STC: https://tests.stockfishchess.org/tests/view/65edf1250ec64f0526c43787 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 90816 W: 23713 L: 23307 D: 43796 Ptnml(0-2): 401, 10725, 22742, 11147, 393 Passed LTC: https://tests.stockfishchess.org/tests/view/65ef5ed70ec64f0526c450af LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 66618 W: 16950 L: 16565 D: 33103 Ptnml(0-2): 35, 7372, 18139, 7699, 64 closes https://github.com/official-stockfish/Stockfish/pull/5111 Bench: 2002517 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5cc67968..5bd59712 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1321,7 +1321,8 @@ moves_loop: // When in check, search starts here else if (!priorCapture && prevSq != SQ_NONE) { int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14446) - + ((ss - 1)->moveCount > 10); + + ((ss - 1)->moveCount > 11) + + (!ss->inCheck && bestValue <= ss->staticEval - 150); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] From 23493de08272226394fb69c4f31182b48b0e739e Mon Sep 17 00:00:00 2001 From: Lemmy <10430540+TierynnB@users.noreply.github.com> Date: Mon, 11 Mar 2024 07:57:22 +1000 Subject: [PATCH 614/678] Sudden Death - Improve TM Due to the 50 estimated move horizon, once a sudden death game got below 1 second, the time allocation for optimumTime would go into the negative and SF would start instamoving. To counter this, once limits.time is below 1 second, the move horizon will start decreasing, at a rate of 1 move per 20ms. This was just what seemed a reasonable rate of decay. Fishtest sudden death TC 5+0 https://tests.stockfishchess.org/tests/live_elo/65ee2cdf0ec64f0526c43bbb LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 3348 W: 1070 L: 727 D:1551 Ptnml(0-2): 46, 277, 738, 514, 99 Fishtest SD TC 10+0 https://tests.stockfishchess.org/tests/live_elo/65ee401e0ec64f0526c43cf7 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 3780 W: 1097 L: 808 D: 1875 Ptnml(0-2): 11, 353, 919, 550, 57 Neutral Non-Regression STC 10+0.1 https://tests.stockfishchess.org/tests/live_elo/65ee45ff0ec64f0526c43d68 LLR: 2.95 (-2.94,2.94) <-1.75, 0.25> Total: 123616 W: 32054 L: 31927 D:59635 Ptnml(0-2): 493, 14323, 32105, 14338, 549 Neutral Non-Regression LTC 60+0.6 https://tests.stockfishchess.org/tests/live_elo/65ef1eec0ec64f0526c44bc4 LLR: 2.95 (-2.94,2.94) <-1.75, 0.25> Total: 130482 W: 32961 L: 32855 D:64666 Ptnml(0-2): 88, 13412, 38123, 13542, 76 closes https://github.com/official-stockfish/Stockfish/pull/5112 Bench: 2002517 --- src/timeman.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/timeman.cpp b/src/timeman.cpp index 4607344e..229ff3e9 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -84,6 +84,12 @@ void TimeManagement::init(Search::LimitsType& limits, // Maximum move horizon of 50 moves int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + // if less than one second, gradually reduce mtg + if (limits.time[us] < 1000 && (double(mtg) / limits.time[us] > 0.05)) + { + mtg = limits.time[us] * 0.05; + } + // Make sure timeLeft is > 0 since we may use it as a divisor TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); From abd82396a1ceef4d49c8be30c366964bf18a74e2 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:55:56 +0800 Subject: [PATCH 615/678] Make effort part of RootMove struct Also includes several small cleanups. Passed STC: https://tests.stockfishchess.org/tests/view/65f15cfe0ec64f0526c473a0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 71136 W: 18456 L: 18273 D: 34407 Ptnml(0-2): 311, 8014, 18708, 8251, 284 closes https://github.com/official-stockfish/Stockfish/pull/5114 No functional change --- src/search.cpp | 13 ++++--------- src/search.h | 3 +-- src/thread.cpp | 2 -- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5bd59712..fc92d1a9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -214,7 +214,7 @@ void Search::Worker::start_searching() { // consumed, the user stops the search, or the maximum search depth is reached. void Search::Worker::iterative_deepening() { - SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + SearchManager* mainThread = (is_mainthread() ? main_manager() : nullptr); Move pv[MAX_PLY + 1]; @@ -426,9 +426,7 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - auto bestmove = rootMoves[0].pv[0]; - int nodesEffort = effort[bestmove.from_sq()][bestmove.to_sq()] * 100 - / std::max(size_t(1), size_t(nodes)); + int nodesEffort = rootMoves[0].effort * 100 / std::max(size_t(1), size_t(nodes)); double fallingEval = (1067 + 223 * (mainThread->bestPreviousAverageScore - bestValue) + 97 * (mainThread->iterValue[iterIdx] - bestValue)) @@ -450,9 +448,7 @@ void Search::Worker::iterative_deepening() { if (completedDepth >= 10 && nodesEffort >= 97 && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.739 && !mainThread->ponder) - { threads.stop = true; - } // Stop the search if we have exceeded the totalTime if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) @@ -1199,9 +1195,6 @@ moves_loop: // When in check, search starts here // Step 19. Undo move pos.undo_move(move); - if (rootNode) - effort[move.from_sq()][move.to_sq()] += nodes - nodeCount; - assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); // Step 20. Check for a new best move @@ -1216,6 +1209,8 @@ moves_loop: // When in check, search starts here RootMove& rm = *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); + rm.effort += nodes - nodeCount; + rm.averageScore = rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; diff --git a/src/search.h b/src/search.h index 4908e535..22f75ffd 100644 --- a/src/search.h +++ b/src/search.h @@ -89,6 +89,7 @@ struct RootMove { return m.score != score ? m.score < score : m.previousScore < previousScore; } + uint64_t effort = 0; Value score = -VALUE_INFINITE; Value previousScore = -VALUE_INFINITE; Value averageScore = -VALUE_INFINITE; @@ -230,8 +231,6 @@ class Worker { return static_cast(manager.get()); } - std::array, SQUARE_NB> effort; - LimitsType limits; size_t pvIdx, pvLast; diff --git a/src/thread.cpp b/src/thread.cpp index a3823d0c..d968271f 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -19,7 +19,6 @@ #include "thread.h" #include -#include #include #include #include @@ -211,7 +210,6 @@ void ThreadPool::start_thinking(const OptionsMap& options, th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); th->worker->rootState = setupStates->back(); th->worker->tbConfig = tbConfig; - th->worker->effort = {}; } main_thread()->start_searching(); From fb07281f5590bc216ecbacd468aa0d06fdead70c Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 14 Mar 2024 10:38:20 +0100 Subject: [PATCH 616/678] Fix false positives from ThreadSanitizer Since Linux Kernel 6.5 we are getting false positives from the ci, lower the ALSR entropy to disable ALSR, which works as a temporary workaround. https://github.com/google/sanitizers/issues/1716 https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762 closes https://github.com/official-stockfish/Stockfish/pull/5115 No functional change --- tests/instrumented.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 2a3eadc0..525c7e04 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -8,6 +8,13 @@ error() } trap 'error ${LINENO}' ERR +# Since Linux Kernel 6.5 we are getting false positives from the ci, +# lower the ALSR entropy to disable ALSR, which works as a temporary workaround. +# https://github.com/google/sanitizers/issues/1716 +# https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762 +sudo sysctl -w vm.mmap_rnd_bits=28 + + # define suitable post and prefixes for testing options case $1 in --valgrind) From ed604600042a4f2c0023ac9a189215e50fc7aa0f Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 15 Mar 2024 18:55:40 +0300 Subject: [PATCH 617/678] Clamp history bonus to stats range Before, one always had to keep track of the bonus one assigns to a history to stop the stats from overflowing. This is a quality of life improvement. Since this would often go unnoticed during benching. Passed non-regression bounds: https://tests.stockfishchess.org/tests/view/65ef2af40ec64f0526c44cbc LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 179232 W: 46513 L: 46450 D: 86269 Ptnml(0-2): 716, 20323, 47452, 20432, 693 closes https://github.com/official-stockfish/Stockfish/pull/5116 No functional change --- src/movepick.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 357918a9..a16fdcd2 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -19,17 +19,17 @@ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED +#include #include #include -#include #include #include #include #include // IWYU pragma: keep #include "movegen.h" -#include "types.h" #include "position.h" +#include "types.h" namespace Stockfish { @@ -69,10 +69,11 @@ class StatsEntry { operator const T&() const { return entry; } void operator<<(int bonus) { - assert(std::abs(bonus) <= D); // Ensure range is [-D, D] static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += bonus - entry * std::abs(bonus) / D; + // Make sure that bonus is in range [-D, D] + int clampedBonus = std::clamp(bonus, -D, D); + entry += clampedBonus - entry * std::abs(clampedBonus) / D; assert(std::abs(entry) <= D); } From 134e6d7bb400a372d168806e0f6f60d5e23a4cbf Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 17 Mar 2024 10:33:03 +0100 Subject: [PATCH 618/678] Consistent use of anonymous namespace Also change `bindThisThread` to match the current code style for function naming. closes https://github.com/official-stockfish/Stockfish/pull/5118 No functional change --- src/misc.cpp | 8 +++--- src/misc.h | 2 +- src/nnue/nnue_misc.cpp | 7 ++--- src/thread.cpp | 2 +- src/tt.cpp | 2 +- src/tune.cpp | 60 +++++++++++++++++++++++------------------- 6 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 4885a5cd..270d25ad 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -596,14 +596,15 @@ namespace WinProcGroup { #ifndef _WIN32 -void bindThisThread(size_t) {} +void bind_this_thread(size_t) {} #else +namespace { // Retrieves logical processor information using Windows-specific // API and returns the best node id for the thread with index idx. Original // code from Texel by Peter Österlund. -static int best_node(size_t idx) { +int best_node(size_t idx) { int threads = 0; int nodes = 0; @@ -668,10 +669,11 @@ static int best_node(size_t idx) { // then return -1 and let the OS to decide what to do. return idx < groups.size() ? groups[idx] : -1; } +} // Sets the group affinity of the current thread -void bindThisThread(size_t idx) { +void bind_this_thread(size_t idx) { // Use only local variables to be thread-safe int node = best_node(idx); diff --git a/src/misc.h b/src/misc.h index 9ad5c3ca..de34ee11 100644 --- a/src/misc.h +++ b/src/misc.h @@ -200,7 +200,7 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { // called to set group affinity for each thread. Original code from Texel by // Peter Österlund. namespace WinProcGroup { -void bindThisThread(size_t idx); +void bind_this_thread(size_t idx); } diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index c443aaf1..7005a610 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -51,10 +51,10 @@ void hint_common_parent_position(const Position& pos, const Networks& networks) networks.big.hint_common_access(pos, false); } - +namespace { // Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. -static void format_cp_compact(Value v, char* buffer) { +void format_cp_compact(Value v, char* buffer) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); @@ -90,7 +90,7 @@ static void format_cp_compact(Value v, char* buffer) { // Converts a Value into pawns, always keeping two decimals -static void format_cp_aligned_dot(Value v, std::stringstream& stream) { +void format_cp_aligned_dot(Value v, std::stringstream& stream) { const double pawns = std::abs(0.01 * UCI::to_cp(v)); @@ -99,6 +99,7 @@ static void format_cp_aligned_dot(Value v, std::stringstream& stream) { : ' ') << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; } +} // Returns a string with the value of each piece on a board, diff --git a/src/thread.cpp b/src/thread.cpp index d968271f..90add4ad 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -91,7 +91,7 @@ void Thread::idle_loop() { // just check if running threads are below a threshold, in this case, all this // NUMA machinery is not needed. if (nthreads > 8) - WinProcGroup::bindThisThread(idx); + WinProcGroup::bind_this_thread(idx); while (true) { diff --git a/src/tt.cpp b/src/tt.cpp index e62e0c17..9d4d2eca 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -95,7 +95,7 @@ void TranspositionTable::clear(size_t threadCount) { threads.emplace_back([this, idx, threadCount]() { // Thread binding gives faster search on systems with a first-touch policy if (threadCount > 8) - WinProcGroup::bindThisThread(idx); + WinProcGroup::bind_this_thread(idx); // Each thread will zero its part of the hash table const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx), diff --git a/src/tune.cpp b/src/tune.cpp index 88b3b791..3e5ebe5e 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -30,10 +30,39 @@ using std::string; namespace Stockfish { -bool Tune::update_on_last; -const Option* LastOption = nullptr; -OptionsMap* Tune::options; -static std::map TuneResults; +bool Tune::update_on_last; +const Option* LastOption = nullptr; +OptionsMap* Tune::options; + + +namespace { +std::map TuneResults; + +void on_tune(const Option& o) { + + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); +} + + +void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) { + + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; + + if (TuneResults.count(n)) + v = TuneResults[n]; + + (*options)[n] << Option(v, r(v).first, r(v).second, on_tune); + LastOption = &((*options)[n]); + + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," + << (r(v).second - r(v).first) / 20.0 << "," + << "0.0020" << std::endl; +} +} string Tune::next(string& names, bool pop) { @@ -54,29 +83,6 @@ string Tune::next(string& names, bool pop) { return name; } -static void on_tune(const Option& o) { - - if (!Tune::update_on_last || LastOption == &o) - Tune::read_options(); -} - -static void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) { - - // Do not generate option when there is nothing to tune (ie. min = max) - if (r(v).first == r(v).second) - return; - - if (TuneResults.count(n)) - v = TuneResults[n]; - - (*options)[n] << Option(v, r(v).first, r(v).second, on_tune); - LastOption = &((*options)[n]); - - // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," - << (r(v).second - r(v).first) / 20.0 << "," - << "0.0020" << std::endl; -} template<> void Tune::Entry::init_option() { From 117e08c26454f2107a13d35945e3508ca6c0de5d Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 17 Mar 2024 12:34:02 +0100 Subject: [PATCH 619/678] Fix header name in Makefile No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 75f31108..672171bc 100644 --- a/src/Makefile +++ b/src/Makefile @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.cpp + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h OBJS = $(notdir $(SRCS:.cpp=.o)) From 9b92ada935ddf920491156be22f609afaca4d840 Mon Sep 17 00:00:00 2001 From: Robert Nurnberg Date: Sun, 17 Mar 2024 15:39:01 +0100 Subject: [PATCH 620/678] Base WDL model on material count and normalize evals dynamically This PR proposes to change the parameter dependence of Stockfish's internal WDL model from full move counter to material count. In addition it ensures that an evaluation of 100 centipawns always corresponds to a 50% win probability at fishtest LTC, whereas for master this holds only at move number 32. See also https://github.com/official-stockfish/Stockfish/pull/4920 and the discussion therein. The new model was fitted based on about 340M positions extracted from 5.6M fishtest LTC games from the last three weeks, involving SF versions from e67cc979fd2c0e66dfc2b2f2daa0117458cfc462 (SF 16.1) to current master. The involved commands are for [WDL_model](https://github.com/official-stockfish/WDL_model) are: ``` ./updateWDL.sh --firstrev e67cc979fd2c0e66dfc2b2f2daa0117458cfc462 python scoreWDL.py updateWDL.json --plot save --pgnName update_material.png --momType "material" --momTarget 58 --materialMin 10 --modelFitting optimizeProbability ``` The anchor `58` for the material count value was chosen to be as close as possible to the observed average material count of fishtest LTC games at move 32 (`43`), while not changing the value of `NormalizeToPawnValue` compared to the move-based WDL model by more than 1. The patch only affects the displayed cp and wdl values. closes https://github.com/official-stockfish/Stockfish/pull/5121 No functional change --- src/evaluate.cpp | 4 +- src/nnue/nnue_misc.cpp | 18 ++++---- src/search.cpp | 7 +-- src/uci.cpp | 99 +++++++++++++++++++++++++----------------- src/uci.h | 6 +-- 5 files changed, 76 insertions(+), 58 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index f4d18d8e..c7adf509 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -92,11 +92,11 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { Value v = networks.big.evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; + ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v, pos) << " (white side)\n"; v = evaluate(networks, pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; + ss << "Final evaluation " << 0.01 * UCI::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; ss << "\n"; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 7005a610..725d90d2 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -54,11 +54,11 @@ void hint_common_parent_position(const Position& pos, const Networks& networks) namespace { // Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. -void format_cp_compact(Value v, char* buffer) { +void format_cp_compact(Value v, char* buffer, const Position& pos) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - int cp = std::abs(UCI::to_cp(v)); + int cp = std::abs(UCI::to_cp(v, pos)); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; @@ -90,9 +90,9 @@ void format_cp_compact(Value v, char* buffer) { // Converts a Value into pawns, always keeping two decimals -void format_cp_aligned_dot(Value v, std::stringstream& stream) { +void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) { - const double pawns = std::abs(0.01 * UCI::to_cp(v)); + const double pawns = std::abs(0.01 * UCI::to_cp(v, pos)); stream << (v < 0 ? '-' : v > 0 ? '+' @@ -114,7 +114,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { board[row][8 * 8 + 1] = '\0'; // A lambda to output one box of the board - auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { + auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) { const int x = int(file) * 8; const int y = (7 - int(rank)) * 3; for (int i = 1; i < 8; ++i) @@ -125,7 +125,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pc != NO_PIECE) board[y + 1][x + 4] = PieceToChar[pc]; if (value != VALUE_NONE) - format_cp_compact(value, &board[y + 2][x + 2]); + format_cp_compact(value, &board[y + 2][x + 2], pos); }; // We estimate the value of each piece by doing a differential evaluation from @@ -180,13 +180,13 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { { ss << "| " << bucket << " "; ss << " | "; - format_cp_aligned_dot(t.psqt[bucket], ss); + format_cp_aligned_dot(t.psqt[bucket], ss, pos); ss << " " << " | "; - format_cp_aligned_dot(t.positional[bucket], ss); + format_cp_aligned_dot(t.positional[bucket], ss, pos); ss << " " << " | "; - format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); ss << " " << " |"; if (bucket == t.correctBucket) diff --git a/src/search.cpp b/src/search.cpp index fc92d1a9..9929ec27 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -155,7 +155,8 @@ void Search::Worker::start_searching() { { rootMoves.emplace_back(Move::none()); sync_cout << "info depth 0 score " - << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; + << UCI::to_score(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos) + << sync_endl; } else { @@ -1898,10 +1899,10 @@ std::string SearchManager::pv(const Search::Worker& worker, ss << "info" << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCI::value(v); + << " score " << UCI::to_score(v, pos); if (worker.options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos.game_ply()); + ss << UCI::wdl(v, pos); if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact ss << (rootMoves[i].scoreLowerbound diff --git a/src/uci.cpp b/src/uci.cpp index cf0e3f09..cc03005f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "benchmark.h" @@ -44,9 +45,8 @@ namespace Stockfish { -constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int NormalizeToPawnValue = 356; -constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; namespace NN = Eval::NNUE; @@ -338,15 +338,43 @@ void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) } } -int UCI::to_cp(Value v) { return 100 * v / NormalizeToPawnValue; } +namespace { +std::pair win_rate_params(const Position& pos) { -std::string UCI::value(Value v) { + int material = pos.count() + 3 * pos.count() + 3 * pos.count() + + 5 * pos.count() + 9 * pos.count(); + + // The fitted model only uses data for material counts in [10, 78], and is anchored at count 58. + double m = std::clamp(material, 10, 78) / 58.0; + + // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model + constexpr double as[] = {-185.71965483, 504.85014385, -438.58295743, 474.04604627}; + constexpr double bs[] = {89.23542728, -137.02141296, 73.28669021, 47.53376190}; + + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + + return {a, b}; +} + +// The win rate model is 1 / (1 + exp((a - eval) / b)), where a = p_a(material) and b = p_b(material). +// It fits the LTC fishtest statistics rather accurately. +int win_rate_model(Value v, const Position& pos) { + + auto [a, b] = win_rate_params(pos); + + // Return the win rate in per mille units, rounded to the nearest integer. + return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); +} +} + +std::string UCI::to_score(Value v, const Position& pos) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); std::stringstream ss; if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << to_cp(v); + ss << "cp " << to_cp(v, pos); else if (std::abs(v) <= VALUE_TB) { const int ply = VALUE_TB - std::abs(v); // recompute ss->ply @@ -358,6 +386,30 @@ std::string UCI::value(Value v) { return ss.str(); } +// Turns a Value to an integer centipawn number, +// without treatment of mate and similar special scores. +int UCI::to_cp(Value v, const Position& pos) { + + // In general, the score can be defined via the the WDL as + // (log(1/L - 1) - log(1/W - 1)) / ((log(1/L - 1) + log(1/W - 1)) + // Based on our win_rate_model, this simply yields v / a. + + auto [a, b] = win_rate_params(pos); + + return std::round(100 * int(v) / a); +} + +std::string UCI::wdl(Value v, const Position& pos) { + std::stringstream ss; + + int wdl_w = win_rate_model(v, pos); + int wdl_l = win_rate_model(-v, pos); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} + std::string UCI::square(Square s) { return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } @@ -383,41 +435,6 @@ std::string UCI::move(Move m, bool chess960) { return move; } -namespace { -// The win rate model returns the probability of winning (in per mille units) given an -// eval and a game ply. It fits the LTC fishtest statistics rather accurately. -int win_rate_model(Value v, int ply) { - - // The fitted model only uses data for moves in [8, 120], and is anchored at move 32. - double m = std::clamp(ply / 2 + 1, 8, 120) / 32.0; - - // The coefficients of a third-order polynomial fit is based on the fishtest data - // for two parameters that need to transform eval to the argument of a logistic - // function. - constexpr double as[] = {-1.06249702, 7.42016937, 0.89425629, 348.60356174}; - constexpr double bs[] = {-5.33122190, 39.57831533, -90.84473771, 123.40620748}; - - // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at move 32. - static_assert(NormalizeToPawnValue == int(0.5 + as[0] + as[1] + as[2] + as[3])); - - double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; - double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - - // Return the win rate in per mille units, rounded to the nearest integer. - return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); -} -} - -std::string UCI::wdl(Value v, int ply) { - std::stringstream ss; - - int wdl_w = win_rate_model(v, ply); - int wdl_l = win_rate_model(-v, ply); - int wdl_d = 1000 - wdl_w - wdl_l; - ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; - - return ss.str(); -} Move UCI::to_move(const Position& pos, std::string& str) { if (str.length() == 5) diff --git a/src/uci.h b/src/uci.h index dd55862a..237928d9 100644 --- a/src/uci.h +++ b/src/uci.h @@ -42,11 +42,11 @@ class UCI { void loop(); - static int to_cp(Value v); - static std::string value(Value v); + static int to_cp(Value v, const Position& pos); + static std::string to_score(Value v, const Position& pos); static std::string square(Square s); static std::string move(Move m, bool chess960); - static std::string wdl(Value v, int ply); + static std::string wdl(Value v, const Position& pos); static Move to_move(const Position& pos, std::string& str); static Search::LimitsType parse_limits(const Position& pos, std::istream& is); From 1a6c22c5114c6ae9f916a69e0446b6c9eb864d72 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:50:27 +0700 Subject: [PATCH 621/678] Evaluation adjustment for different eval types Gives different eval scaling parameters for the three different types of evaluation (bignet, smallnet, psqtOnly). Passed STC: https://tests.stockfishchess.org/tests/view/65f4b0020ec64f0526c4a3bd LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 168064 W: 43507 L: 42987 D: 81570 Ptnml(0-2): 662, 19871, 42445, 20393, 661 Passed LTC: https://tests.stockfishchess.org/tests/view/65f6be1a0ec64f0526c4c361 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 162564 W: 41188 L: 40604 D: 80772 Ptnml(0-2): 120, 18112, 44216, 18732, 102 closes https://github.com/official-stockfish/Stockfish/pull/5122 Bench: 2113576 --- src/evaluate.cpp | 33 +++++++++++++++++++++++---------- src/evaluate.h | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c7adf509..3d1643fb 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -52,22 +52,35 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int simpleEval = simple_eval(pos, pos.side_to_move()); bool smallNet = std::abs(simpleEval) > SmallNetThreshold; bool psqtOnly = std::abs(simpleEval) > PsqtOnlyThreshold; - - int nnueComplexity; + int nnueComplexity; + int v; Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) : networks.big.evaluate(pos, true, &nnueComplexity, false); - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 524; - nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 31950; + const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, + int npmConstant, int evalDiv, int shufflingConstant, + int shufflingDiv) { + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / nnueDiv; - int npm = pos.non_pawn_material() / 64; - int v = (nnue * (927 + npm + 9 * pos.count()) + optimism * (159 + npm)) / 1000; + int npm = pos.non_pawn_material() / 64; + v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) + + optimism * (npmConstant + npm)) + / evalDiv; - // Damp down the evaluation linearly when shuffling - int shuffling = pos.rule50_count(); - v = v * (195 - shuffling) / 228; + // Damp down the evaluation linearly when shuffling + int shuffling = pos.rule50_count(); + v = v * (shufflingConstant - shuffling) / shufflingDiv; + }; + + if (!smallNet) + adjustEval(513, 32395, 919, 11, 145, 1036, 178, 204); + else if (psqtOnly) + adjustEval(517, 32857, 908, 7, 155, 1019, 224, 238); + else + adjustEval(499, 32793, 903, 9, 147, 1067, 208, 211); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/evaluate.h b/src/evaluate.h index bd11e0c1..29dff40d 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1136, PsqtOnlyThreshold = 2656; +constexpr inline int SmallNetThreshold = 1050, PsqtOnlyThreshold = 2500; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the From 8e61d70499a9cebf3b7d97cf74295be5261ac0af Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Tue, 19 Mar 2024 02:45:09 +0700 Subject: [PATCH 622/678] Remove reduction increase on repetition Passed non-reg STC: https://tests.stockfishchess.org/tests/view/65f89ae30ec64f0526c4e0ff LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 173568 W: 45005 L: 44936 D: 83627 Ptnml(0-2): 684, 19878, 45628, 19873, 721 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/65fa0f370ec64f0526c4f42d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 21138 W: 5432 L: 5216 D: 10490 Ptnml(0-2): 13, 2107, 6112, 2325, 12 closes https://github.com/official-stockfish/Stockfish/pull/5123 Bench: 2109005 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9929ec27..ca36c570 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1117,10 +1117,6 @@ moves_loop: // When in check, search starts here if (PvNode) r--; - // Increase reduction on repetition (~1 Elo) - if (move == (ss - 4)->currentMove && pos.has_repeated()) - r += 2; - // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) r++; From 7e427639ce2868cb31f36c9b1de3071f61a63618 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 20 Mar 2024 16:36:10 +0100 Subject: [PATCH 623/678] Add cmath header to movepick.h No functional change --- src/movepick.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/movepick.h b/src/movepick.h index a16fdcd2..b81f76e1 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From d99f89506bd0ed535fb1c55dbb7cc8f7c29444d4 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sun, 17 Mar 2024 11:20:41 +0800 Subject: [PATCH 624/678] VVLTC search tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This set of parameters was derived from 3 tuning attempts: https://tests.stockfishchess.org/tests/view/65d19ab61d8e83c78bfd8436 (80+0.8 x8, ~40k games) Then tuned with one of linrock's early L1-3072 nets: https://tests.stockfishchess.org/tests/view/65def7b04b19edc854ebdec8 (VVLTC, ~36k games) Starting from the result of this tuning, the parameters were then tuned with the current master net: https://tests.stockfishchess.org/tests/view/65f11c420ec64f0526c46fc4 (VVLTC, ~45k games) Additionally, at the start of the third tuning phase, 2 parameters were manually changed: Notably, the triple extension margin was decreased from 78 to 22. This idea was given by Vizvezdenec: https://tests.stockfishchess.org/tests/view/65f0a2360ec64f0526c46752. The PvNode extension margin was also adjusted from 50 to 40. This tune also differs from previous tuning attempts by tuning the evaluation thresholds for smallnet and psqt-only. The former was increased through the tuning, and this is hypothesized to scale better at VVLTC, although there is not much evidence of it. Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/65f6761d0ec64f0526c4be88 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 44688 W: 11421 L: 11140 D: 22127 Ptnml(0-2): 1, 4170, 13722, 4449, 2 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/65fa31a30ec64f0526c4f611 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 27450 W: 7057 L: 6778 D: 13615 Ptnml(0-2): 4, 2545, 8346, 2828, 2 STC Elo estimate: https://tests.stockfishchess.org/tests/view/65fd3e540ec64f0526c521ae Elo: -7.84 ± 1.8 (95%) LOS: 0.0% Total: 40000 W: 9899 L: 10802 D: 19299 Ptnml(0-2): 203, 5221, 10025, 4378, 173 nElo: -14.91 ± 3.4 (95%) PairsRatio: 0.84 closes https://github.com/official-stockfish/Stockfish/pull/5130 Bench: 1876107 --- src/evaluate.h | 2 +- src/search.cpp | 74 +++++++++++++++++++++++++------------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index 29dff40d..5f2b2c54 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1050, PsqtOnlyThreshold = 2500; +constexpr inline int SmallNetThreshold = 1165, PsqtOnlyThreshold = 2500; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/search.cpp b/src/search.cpp index ca36c570..f0e7f847 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,9 +55,9 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 122 - 46 * noTtCutNode; - Value improvingDeduction = 57 * improving * futilityMult / 32; - Value worseningDeduction = (331 + 45 * improving) * oppWorsening * futilityMult / 1024; + Value futilityMult = 118 - 44 * noTtCutNode; + Value improvingDeduction = 53 * improving * futilityMult / 32; + Value worseningDeduction = (309 + 47 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -69,15 +69,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 11450; + v += cv * std::abs(cv) / 11175; return std::clamp(v, 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(249 * d - 327, 1192); } +int stat_bonus(Depth d) { return std::min(223 * d - 332, 1258); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(516 * d - 299, 1254); } +int stat_malus(Depth d) { return std::min(536 * d - 299, 1353); } // 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); } @@ -302,12 +302,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 12800; + delta = 10 + avg * avg / 12493; 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] = 130 * avg / (std::abs(avg) + 90); + optimism[us] = 132 * avg / (std::abs(avg) + 89); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -494,7 +494,7 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-71); + h->fill(-67); for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((19.80 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); @@ -729,7 +729,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1621, 1238); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1578, 1291); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -752,7 +752,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 462 - (296 - 145 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 488 - (289 - 142 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -763,13 +763,13 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 287 + - (ss - 1)->statScore / 267 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16211 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16878 && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 20 * depth + 314 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) @@ -777,7 +777,7 @@ Value Search::Worker::search( assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 151, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -825,7 +825,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 168 - 64 * improving; + probCutBeta = beta + 170 - 64 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -881,7 +881,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 410; + probCutBeta = beta + 409; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -964,7 +964,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 298 + 288 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 297 + 284 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -972,7 +972,7 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -202 * depth)) + if (!pos.see_ge(move, -203 * depth)) continue; } else @@ -984,24 +984,24 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4125 * depth) + if (lmrDepth < 6 && history < -4040 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 5686; + lmrDepth += history / 5637; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 55 ? 153 : 58) - + 118 * lmrDepth + && ss->staticEval + (bestValue < ss->staticEval - 59 ? 141 : 58) + + 125 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1021,11 +1021,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 29) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (58 + 55 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (58 + 58 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1040,11 +1040,11 @@ moves_loop: // When in check, search starts here // We make sure to limit the extensions in some way to avoid a search explosion if (!PvNode && ss->multipleExtensions <= 16) { - extension = 2 + (value < singularBeta - 78 && !ttCapture); + extension = 2 + (value < singularBeta - 22 && !ttCapture); depth += depth < 14; } if (PvNode && !ttCapture && ss->multipleExtensions <= 5 - && value < singularBeta - 50) + && value < singularBeta - 37) extension = 2; } @@ -1079,7 +1079,7 @@ moves_loop: // When in check, search starts here else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4315) + > 4026) extension = 1; } @@ -1129,10 +1129,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4587; + + (*contHist[3])[movedPiece][move.to_sq()] - 4723; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 14956; + r -= ss->statScore / 13659; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1151,7 +1151,7 @@ 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 + 48 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 47 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1269,7 +1269,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13665 && value > -12276) + if (depth > 2 && depth < 12 && beta < 14206 && value > -12077) depth -= 2; assert(depth > 0); @@ -1312,7 +1312,7 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14446) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14963) + ((ss - 1)->moveCount > 11) + (!ss->inCheck && bestValue <= ss->staticEval - 150); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, @@ -1472,7 +1472,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 221; + futilityBase = ss->staticEval + 226; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1552,7 +1552,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -79)) + if (!pos.see_ge(move, -78)) continue; } @@ -1620,7 +1620,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1091 - delta * 759 / rootDelta) / 1024 + (!i && reductionScale > 950); + return (reductionScale + 1107 - delta * 725 / rootDelta) / 1024 + (!i && reductionScale > 956); } namespace { @@ -1709,7 +1709,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 167 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 5001d49f42033bcf36a6c57401f891791031b4d8 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 21 Mar 2024 01:25:59 -0700 Subject: [PATCH 625/678] Update nnue_feature_transformer.h Unroll update_accumulator_refresh to process two active indices simultaneously. The compiler might not unroll effectively because the number of active indices isn't known at compile time. STC https://tests.stockfishchess.org/tests/view/65faa8850ec64f0526c4fca9 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 130464 W: 33882 L: 33431 D: 63151 Ptnml(0-2): 539, 14591, 34501, 15082, 519 closes https://github.com/official-stockfish/Stockfish/pull/5125 No functional change --- src/nnue/nnue_feature_transformer.h | 33 +++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index b42f1604..888edebb 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -619,8 +619,22 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = biasesTile[k]; - for (const auto index : active) + int i = 0; + for (; i < int(active.size()) - 1; i += 2) { + IndexType index0 = active[i]; + IndexType index1 = active[i + 1]; + const IndexType offset0 = HalfDimensions * index0 + j * TileHeight; + const IndexType offset1 = HalfDimensions * index1 + j * TileHeight; + auto column0 = reinterpret_cast(&weights[offset0]); + auto column1 = reinterpret_cast(&weights[offset1]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], vec_add_16(column0[k], column1[k])); + } + for (; i < int(active.size()); ++i) + { + IndexType index = active[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; auto column = reinterpret_cast(&weights[offset]); @@ -639,8 +653,23 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_zero_psqt(); - for (const auto index : active) + int i = 0; + for (; i < int(active.size()) - 1; i += 2) { + IndexType index0 = active[i]; + IndexType index1 = active[i + 1]; + const IndexType offset0 = PSQTBuckets * index0 + j * PsqtTileHeight; + const IndexType offset1 = PSQTBuckets * index1 + j * PsqtTileHeight; + auto columnPsqt0 = reinterpret_cast(&psqtWeights[offset0]); + auto columnPsqt1 = reinterpret_cast(&psqtWeights[offset1]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = + vec_add_psqt_32(psqt[k], vec_add_psqt_32(columnPsqt0[k], columnPsqt1[k])); + } + for (; i < int(active.size()); ++i) + { + IndexType index = active[i]; const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); From 7998570414ba489b3dfe239b424c200041396e7f Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 23 Mar 2024 10:34:32 +0100 Subject: [PATCH 626/678] Add functionality to export small net Usage ``` export_net ``` closes https://github.com/official-stockfish/Stockfish/pull/5133 No functional change --- src/uci.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index cc03005f..25884f57 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -159,11 +159,16 @@ void UCI::loop() { sync_cout << compiler_info() << sync_endl; else if (token == "export_net") { - std::optional filename; - std::string f; - if (is >> std::skipws >> f) - filename = f; - networks.big.save(filename); + std::pair, std::string> files[2]; + + if (is >> std::skipws >> files[0].second) + files[0].first = files[0].second; + + if (is >> std::skipws >> files[1].second) + files[1].first = files[1].second; + + networks.big.save(files[0].first); + networks.small.save(files[1].first); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout From d49b3738bc89353a9318d54af400f02c91d2d69a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 24 Mar 2024 14:02:27 +0300 Subject: [PATCH 627/678] Tweak the stats bonus and malus For depth 1 we don't have a negative score anymore. Passed STC: https://tests.stockfishchess.org/tests/view/65fb055c0ec64f0526c5024f LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 117120 W: 30468 L: 30023 D: 56629 Ptnml(0-2): 526, 13759, 29539, 14216, 520 Passed LTC: https://tests.stockfishchess.org/tests/view/65fdca4b0ec64f0526c5293f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 54816 W: 13955 L: 13595 D: 27266 Ptnml(0-2): 30, 6046, 14897, 6404, 31 closes https://github.com/official-stockfish/Stockfish/pull/5134 Bench: 1876428 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f0e7f847..e1508a7f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -74,10 +74,10 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(223 * d - 332, 1258); } +int stat_bonus(Depth d) { return std::clamp(245 * d - 320, 0, 1296); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(536 * d - 299, 1353); } +int stat_malus(Depth d) { return (d < 4 ? 554 * d - 303 : 1203); } // 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); } @@ -1709,7 +1709,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From ed24e3a0a6e6958b26fefc4c43078c0bb46f5376 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sat, 23 Mar 2024 03:48:44 +0700 Subject: [PATCH 628/678] Remove material imbalance from nnue eval Passed non-reg STC: https://tests.stockfishchess.org/tests/view/65fdf11f0ec64f0526c52b57 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 76480 W: 19893 L: 19712 D: 36875 Ptnml(0-2): 339, 9107, 19157, 9308, 329 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/65fee22e0ec64f0526c53885 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 150948 W: 38078 L: 37988 D: 74882 Ptnml(0-2): 111, 16997, 41148, 17127, 91 closes https://github.com/official-stockfish/Stockfish/pull/5135 Bench: 2103324 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3d1643fb..bc705b85 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -63,7 +63,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int shufflingDiv) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; - nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / nnueDiv; + nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; int npm = pos.non_pawn_material() / 64; v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) From e636f73ab83da13fd352658e1eeecd369479c491 Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Wed, 27 Mar 2024 08:20:49 +0000 Subject: [PATCH 629/678] Vary time use with eval Adjust time use depending on the current eval. Passed STC : LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 101696 W: 26651 L: 26238 D: 48807 Ptnml(0-2): 400, 11602, 26459, 11959, 428 https://tests.stockfishchess.org/tests/live_elo/660187a50ec64f0526c557f6 Passed LTC : LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 60648 W: 15550 L: 15187 D: 29911 Ptnml(0-2): 40, 6356, 17171, 6715, 42 https://tests.stockfishchess.org/tests/live_elo/660298ed0ec64f0526c566d0 Values were found using two tunes with the final values taken from the ltc tune after 62k games : stc - https://tests.stockfishchess.org/tests/view/65fb526b0ec64f0526c50694 ltc - https://tests.stockfishchess.org/tests/view/65fd36e60ec64f0526c5214b Ideas for future work; * tune these values with the other TM adjustments * try narrower bands * calculate adjustment for exact eval by interpolation closes https://github.com/official-stockfish/Stockfish/pull/5138 No functional change --- src/search.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e1508a7f..f045bc47 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -53,6 +53,9 @@ using namespace Search; namespace { +static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971, + 1.002, 0.992, 0.947, 1.046, 1.001}; + // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 118 - 44 * noTtCutNode; @@ -438,9 +441,10 @@ void Search::Worker::iterative_deepening() { timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.495 : 0.687; double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); + int el = std::clamp((bestValue + 750) / 150, 0, 9); - double totalTime = - mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; + double totalTime = mainThread->tm.optimum() * fallingEval * reduction + * bestMoveInstability * EvalLevel[el]; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) From 0ef5d05102ce0632d7b076706d26eb4eba7fcb70 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 29 Mar 2024 00:17:37 +0300 Subject: [PATCH 630/678] Adjust best value after a pruned quiet move Logic somewhat similar to how we adjust best value after pruned captures in qsearch, but in search this patch does it after pruned quiet moves and also to not full scale of futility value but to smth in between best value and futility value. Passed STC: https://tests.stockfishchess.org/tests/view/6601cf900ec64f0526c55c30 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 59936 W: 15722 L: 15369 D: 28845 Ptnml(0-2): 182, 7097, 15112, 7340, 237 Passed LTC: https://tests.stockfishchess.org/tests/view/66029b2d0ec64f0526c566f1 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 118362 W: 29953 L: 29460 D: 58949 Ptnml(0-2): 68, 13159, 32249, 13622, 83 closes https://github.com/official-stockfish/Stockfish/pull/5141 bench 1772608 --- src/search.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f045bc47..dfb27285 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -995,12 +995,17 @@ moves_loop: // When in check, search starts here lmrDepth += history / 5637; + Value futilityValue = + ss->staticEval + (bestValue < ss->staticEval - 59 ? 141 : 58) + 125 * lmrDepth; + // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 59 ? 141 : 58) - + 125 * lmrDepth - <= alpha) + if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha) + { + if (bestValue <= futilityValue && abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY + && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) + bestValue = (bestValue + futilityValue * 3) / 4; continue; + } lmrDepth = std::max(lmrDepth, 0); From e13e4cfb8340cdb26a00679681a0f163c6b4f0a9 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 27 Mar 2024 02:58:59 -0700 Subject: [PATCH 631/678] Simplify NMP Condition Remove eval >= ss->staticEval condition for Null Move Pruning. Passed non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 44000 W: 11420 L: 11202 D: 21378 Ptnml(0-2): 174, 5243, 10978, 5401, 204 https://tests.stockfishchess.org/tests/live_elo/6603ee490ec64f0526c57984 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82956 W: 20978 L: 20818 D: 41160 Ptnml(0-2): 54, 9353, 22499, 9523, 49 https://tests.stockfishchess.org/tests/live_elo/660464b50ec64f0526c5804d closes https://github.com/official-stockfish/Stockfish/pull/5142 Bench 1759189 --- AUTHORS | 1 + src/search.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 40a38bd5..abae401c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -204,6 +204,7 @@ sf-x Shahin M. Shahin (peregrine) Shane Booth (shane31) Shawn Varghese (xXH4CKST3RXx) +Shawn Xu (xu-shawn) Siad Daboul (Topologist) Stefan Geschwentner (locutus2) Stefano Cardanobile (Stefano80) diff --git a/src/search.cpp b/src/search.cpp index dfb27285..fb5a2f00 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -774,8 +774,8 @@ Value Search::Worker::search( // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16878 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 20 * depth + 314 - && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly + && eval >= beta && ss->staticEval >= beta - 20 * depth + 314 && !excludedMove + && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); From 68d58d94da15d175269eaa06b3d224062cf72a70 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 29 Mar 2024 10:25:41 +0100 Subject: [PATCH 632/678] Fix usage of abs vs std::abs close https://github.com/official-stockfish/Stockfish/pull/5143 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fb5a2f00..3f882aab 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1001,7 +1001,7 @@ moves_loop: // When in check, search starts here // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha) { - if (bestValue <= futilityValue && abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY + if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) bestValue = (bestValue + futilityValue * 3) / 4; continue; From ec598b380db41fa54100cf3de51fe4be17eaf08b Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 29 Mar 2024 10:49:53 +0100 Subject: [PATCH 633/678] Improve prerelease creation workflow In the last couple of months we sometimes saw duplicated prereleases uploaded to GitHub, possibly due to some racy behavior when concurrent jobs create a prerelease. This now creates an empty prerelease at the beginning of the CI and the binaries are later just attached to this one. closes https://github.com/official-stockfish/Stockfish/pull/5144 No functional change --- .github/workflows/stockfish.yml | 38 ++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 22cd9af3..13d57f9e 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -16,6 +16,8 @@ jobs: if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')) runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + # returns null if no pre-release exists - name: Get Commit SHA of Latest Pre-release run: | @@ -23,14 +25,40 @@ jobs: sudo apt-get update sudo apt-get install -y curl jq - echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV + echo "COMMIT_SHA_TAG=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV - # delete old previous pre-release and tag - - uses: actions/checkout@v4 - - run: gh release delete ${{ env.COMMIT_SHA }} --cleanup-tag - if: env.COMMIT_SHA != 'null' + # delete old previous pre-release and tag + - run: gh release delete ${{ env.COMMIT_SHA_TAG }} --cleanup-tag + if: env.COMMIT_SHA_TAG != 'null' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Make sure that an old ci that still runs on master doesn't recreate a prerelease + - name: Check Pullable Commits + id: check_commits + run: | + git fetch + CHANGES=$(git rev-list HEAD..origin/master --count) + echo "CHANGES=$CHANGES" >> $GITHUB_ENV + + - name: Get last commit SHA + id: last_commit + run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV + + - name: Get commit date + id: commit_date + run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + + # Create a new pre-release, the other upload_binaries.yml will upload the binaries + # to this pre-release. + - name: Create Prerelease + if: github.ref_name == 'master' && env.CHANGES == '0' + uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 + with: + name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + prerelease: true + Matrix: runs-on: ubuntu-latest outputs: From c964942da225ace51e1446deb29e7f43bf21360e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 30 Mar 2024 10:54:36 +0100 Subject: [PATCH 634/678] Avoid a note related to an ABI change current master triggers a gcc note: parameter passing for argument of type 'std::pair' when C++17 is enabled changed to match C++14 in GCC 10.1 while this is inconsequential, and just informative https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111516 we can easily avoid it. closes https://github.com/official-stockfish/Stockfish/pull/5145 No functional change --- src/uci.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uci.cpp b/src/uci.cpp index 25884f57..ee95d5be 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -344,7 +344,13 @@ void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) } namespace { -std::pair win_rate_params(const Position& pos) { + +struct WinRateParams { + double a; + double b; +}; + +WinRateParams win_rate_params(const Position& pos) { int material = pos.count() + 3 * pos.count() + 3 * pos.count() + 5 * pos.count() + 9 * pos.count(); From 0716b845fdef8a20102b07eaec074b8da8162523 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Tue, 2 Apr 2024 04:38:54 +0100 Subject: [PATCH 635/678] Update NNUE architecture to SFNNv9 and net nn-ae6a388e4a1a.nnue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part 1: PyTorch Training, linrock Trained with a 10-stage sequence from scratch, starting in May 2023: https://github.com/linrock/nnue-tools/blob/master/exp-sequences/3072-10stage-SFNNv9.yml While the training methods were similar to the L1-2560 training sequence, the last two stages introduced min-v2 binpacks, where bestmove capture and in-check position scores were not zeroed during minimization, for compatibility with skipping SEE >= 0 positions and future research. Training data can be found at: https://robotmoon.com/nnue-training-data This net was tested at epoch 679 of the 10th training stage: https://tests.stockfishchess.org/tests/view/65f32e460ec64f0526c48dbc Part 2: SPSA Training, Viren6 The net was then SPSA tuned. This consisted of the output weights (32 * 8) and biases (8) as well as the L3 biases (32 * 8) and L2 biases (16 * 8), totalling 648 params in total. The SPSA tune can be found here: https://tests.stockfishchess.org/tests/view/65fc33ba0ec64f0526c512e3 With the help of Disservin , the initial weights were extracted with: https://github.com/Viren6/Stockfish/tree/new228 The net was saved with the tuned weights using: https://github.com/Viren6/Stockfish/tree/new241 Earlier nets of the SPSA failed STC compared to the base 3072 net of part 1: https://tests.stockfishchess.org/tests/view/65ff356e0ec64f0526c53c98 Therefore it is suspected that the SPSA at VVLTC has added extra scaling on top of the scaling of increasing the L1 size. Passed VVLTC 1: https://tests.stockfishchess.org/tests/view/6604a9020ec64f0526c583da LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 53042 W: 13554 L: 13256 D: 26232 Ptnml(0-2): 12, 5147, 15903, 5449, 10 Passed VVLTC 2: https://tests.stockfishchess.org/tests/view/660ad1b60ec64f0526c5dd23 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 17506 W: 4574 L: 4315 D: 8617 Ptnml(0-2): 1, 1567, 5362, 1818, 5 STC Elo estimate: https://tests.stockfishchess.org/tests/view/660b834d01aaec5069f87cb0 Elo: -7.66 ± 3.8 (95%) LOS: 0.0% Total: 9618 W: 2440 L: 2652 D: 4526 Ptnml(0-2): 80, 1281, 2261, 1145, 42 nElo: -13.94 ± 6.9 (95%) PairsRatio: 0.87 closes https://tests.stockfishchess.org/tests/view/660b834d01aaec5069f87cb0 bench 1823302 Co-Authored-By: Linmiao Xu --- src/evaluate.h | 2 +- src/nnue/nnue_architecture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index 5f2b2c54..97ca4c4b 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -35,7 +35,7 @@ constexpr inline int SmallNetThreshold = 1165, PsqtOnlyThreshold = 2500; // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-1ceb1ade0001.nnue" +#define EvalFileDefaultNameBig "nn-ae6a388e4a1a.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" namespace NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 05efb813..7f73f87f 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -38,7 +38,7 @@ namespace Stockfish::Eval::NNUE { using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensionsBig = 2560; +constexpr IndexType TransformedFeatureDimensionsBig = 3072; constexpr int L2Big = 15; constexpr int L3Big = 32; From 299707d2c2cbf1694bb21ed4a375b54ef35d719e Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 17 Mar 2024 12:33:14 +0100 Subject: [PATCH 636/678] Split UCI into UCIEngine and Engine This is another refactor which aims to decouple uci from stockfish. A new engine class manages all engine related logic and uci is a "small" wrapper around it. In the future we should also try to remove the need for the Position object in the uci and replace the options with an actual options struct instead of using a map. Also convert the std::string's in the Info structs a string_view. closes #5147 No functional change --- src/Makefile | 4 +- src/engine.cpp | 153 +++++++++++++++++++++++++++++++++++++++++ src/engine.h | 84 ++++++++++++++++++++++ src/evaluate.cpp | 4 +- src/main.cpp | 5 +- src/misc.cpp | 31 +++++---- src/misc.h | 10 +-- src/nnue/nnue_misc.cpp | 4 +- src/perft.h | 2 +- src/position.cpp | 6 +- src/search.cpp | 16 +++-- src/tune.h | 2 +- src/uci.cpp | 144 ++++++++++++++------------------------ src/uci.h | 25 +++---- 14 files changed, 341 insertions(+), 149 deletions(-) create mode 100644 src/engine.cpp create mode 100644 src/engine.h diff --git a/src/Makefile b/src/Makefile index 672171bc..6315bda8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,7 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/engine.cpp b/src/engine.cpp new file mode 100644 index 00000000..79a2c604 --- /dev/null +++ b/src/engine.cpp @@ -0,0 +1,153 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "engine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark.h" +#include "evaluate.h" +#include "movegen.h" +#include "nnue/network.h" +#include "nnue/nnue_common.h" +#include "perft.h" +#include "position.h" +#include "search.h" +#include "syzygy/tbprobe.h" +#include "types.h" +#include "ucioption.h" + +namespace Stockfish { + +namespace NN = Eval::NNUE; + +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + +Engine::Engine(std::string path) : + binaryDirectory(CommandLine::get_binary_directory(path)), + states(new std::deque(1)), + networks(NN::Networks( + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { + Tune::init(options); + pos.set(StartFEN, false, &states->back()); +} + +void Engine::go(const Search::LimitsType& limits) { + verify_networks(); + + if (limits.perft) + { + perft(pos.fen(), limits.perft, options["UCI_Chess960"]); + return; + } + + threads.start_thinking(options, pos, states, limits); +} +void Engine::stop() { threads.stop = true; } + +void Engine::search_clear() { + wait_for_search_finished(); + + tt.clear(options["Threads"]); + threads.clear(); + + // @TODO wont work multiple instances + Tablebases::init(options["SyzygyPath"]); // Free mapped files +} + +void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); } + +void Engine::set_position(const std::string& fen, const std::vector& moves) { + // Drop the old state and create a new one + states = StateListPtr(new std::deque(1)); + pos.set(fen, options["UCI_Chess960"], &states->back()); + + for (const auto& move : moves) + { + auto m = UCIEngine::to_move(pos, move); + + if (m == Move::none()) + break; + + states->emplace_back(); + pos.do_move(m, states->back()); + } +} + +// modifiers + +void Engine::resize_threads() { threads.set({options, threads, tt, networks}); } + +void Engine::set_tt_size(size_t mb) { + wait_for_search_finished(); + tt.resize(mb, options["Threads"]); +} + +void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } + +// network related + +void Engine::verify_networks() { + networks.big.verify(options["EvalFile"]); + networks.small.verify(options["EvalFileSmall"]); +} + +void Engine::load_networks() { + networks.big.load(binaryDirectory, options["EvalFile"]); + networks.small.load(binaryDirectory, options["EvalFileSmall"]); +} + +void Engine::load_big_network(const std::string& file) { networks.big.load(binaryDirectory, file); } + +void Engine::load_small_network(const std::string& file) { + networks.small.load(binaryDirectory, file); +} + +void Engine::save_network(const std::pair, std::string> files[2]) { + networks.big.save(files[0].first); + networks.small.save(files[1].first); +} + +// utility functions + +OptionsMap& Engine::get_options() { return options; } + +uint64_t Engine::nodes_searched() const { return threads.nodes_searched(); } + +void Engine::trace_eval() { + StateListPtr trace_states(new std::deque(1)); + Position p; + p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back()); + + verify_networks(); + + sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; +} + +} \ No newline at end of file diff --git a/src/engine.h b/src/engine.h new file mode 100644 index 00000000..6afc423d --- /dev/null +++ b/src/engine.h @@ -0,0 +1,84 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef ENGINE_H_INCLUDED +#define ENGINE_H_INCLUDED + +#include "misc.h" +#include "nnue/network.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +namespace Stockfish { + +class Engine { + public: + Engine(std::string path = ""); + ~Engine() { wait_for_search_finished(); } + + // non blocking call to start searching + void go(const Search::LimitsType&); + // non blocking call to stop searching + void stop(); + + // blocking call to wait for search to finish + void wait_for_search_finished(); + // set a new position, moves are in UCI format + void set_position(const std::string& fen, const std::vector& moves); + + // modifiers + + void resize_threads(); + void set_tt_size(size_t mb); + void set_ponderhit(bool); + void search_clear(); + + // network related + + void verify_networks(); + void load_networks(); + void load_big_network(const std::string& file); + void load_small_network(const std::string& file); + void save_network(const std::pair, std::string> files[2]); + + // utility functions + + void trace_eval(); + // nodes since last search clear + uint64_t nodes_searched() const; + OptionsMap& get_options(); + + private: + const std::string binaryDirectory; + + Position pos; + StateListPtr states; + + OptionsMap options; + ThreadPool threads; + TranspositionTable tt; + Eval::NNUE::Networks networks; +}; + +} // namespace Stockfish + + +#endif // #ifndef ENGINE_H_INCLUDED \ No newline at end of file diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bc705b85..dcbfedb4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -105,11 +105,11 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { Value v = networks.big.evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v, pos) << " (white side)\n"; + ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; v = evaluate(networks, pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << 0.01 * UCI::to_cp(v, pos) << " (white side)"; + ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; ss << "\n"; diff --git a/src/main.cpp b/src/main.cpp index 33d5d375..4e72c003 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,10 +34,7 @@ int main(int argc, char* argv[]) { Bitboards::init(); Position::init(); - UCI uci(argc, argv); - - Tune::init(uci.options); - + UCIEngine uci(argc, argv); uci.loop(); return 0; diff --git a/src/misc.cpp b/src/misc.cpp index 270d25ad..1abb81b1 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -723,13 +723,9 @@ void bind_this_thread(size_t idx) { #define GETCWD getcwd #endif -CommandLine::CommandLine(int _argc, char** _argv) : - argc(_argc), - argv(_argv) { - std::string pathSeparator; - // Extract the path+name of the executable binary - std::string argv0 = argv[0]; +std::string CommandLine::get_binary_directory(std::string argv0) { + std::string pathSeparator; #ifdef _WIN32 pathSeparator = "\\"; @@ -745,15 +741,11 @@ CommandLine::CommandLine(int _argc, char** _argv) : #endif // Extract the working directory - workingDirectory = ""; - char buff[40000]; - char* cwd = GETCWD(buff, 40000); - if (cwd) - workingDirectory = cwd; + auto workingDirectory = CommandLine::get_working_directory(); // Extract the binary directory path from argv0 - binaryDirectory = argv0; - size_t pos = binaryDirectory.find_last_of("\\/"); + auto binaryDirectory = argv0; + size_t pos = binaryDirectory.find_last_of("\\/"); if (pos == std::string::npos) binaryDirectory = "." + pathSeparator; else @@ -762,6 +754,19 @@ CommandLine::CommandLine(int _argc, char** _argv) : // Pattern replacement: "./" at the start of path is replaced by the working directory if (binaryDirectory.find("." + pathSeparator) == 0) binaryDirectory.replace(0, 1, workingDirectory); + + return binaryDirectory; } +std::string CommandLine::get_working_directory() { + std::string workingDirectory = ""; + char buff[40000]; + char* cwd = GETCWD(buff, 40000); + if (cwd) + workingDirectory = cwd; + + return workingDirectory; +} + + } // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index de34ee11..d75b236f 100644 --- a/src/misc.h +++ b/src/misc.h @@ -206,13 +206,15 @@ void bind_this_thread(size_t idx); struct CommandLine { public: - CommandLine(int, char**); + CommandLine(int _argc, char** _argv) : + argc(_argc), + argv(_argv) {} + + static std::string get_binary_directory(std::string argv0); + static std::string get_working_directory(); int argc; char** argv; - - std::string binaryDirectory; // path of the executable directory - std::string workingDirectory; // path of the working directory }; namespace Utility { diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 725d90d2..3fa6e1b6 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -58,7 +58,7 @@ void format_cp_compact(Value v, char* buffer, const Position& pos) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - int cp = std::abs(UCI::to_cp(v, pos)); + int cp = std::abs(UCIEngine::to_cp(v, pos)); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; @@ -92,7 +92,7 @@ void format_cp_compact(Value v, char* buffer, const Position& pos) { // Converts a Value into pawns, always keeping two decimals void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) { - const double pawns = std::abs(0.01 * UCI::to_cp(v, pos)); + const double pawns = std::abs(0.01 * UCIEngine::to_cp(v, pos)); stream << (v < 0 ? '-' : v > 0 ? '+' diff --git a/src/perft.h b/src/perft.h index 2edc3ad0..2dbab828 100644 --- a/src/perft.h +++ b/src/perft.h @@ -51,7 +51,7 @@ uint64_t perft(Position& pos, Depth depth) { pos.undo_move(m); } if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + sync_cout << UCIEngine::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; } return nodes; } diff --git a/src/position.cpp b/src/position.cpp index 2263afe7..fd167895 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -78,7 +78,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { << std::setw(16) << pos.key() << std::setfill(' ') << std::dec << "\nCheckers: "; for (Bitboard b = pos.checkers(); b;) - os << UCI::square(pop_lsb(b)) << " "; + os << UCIEngine::square(pop_lsb(b)) << " "; if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { @@ -431,8 +431,8 @@ string Position::fen() const { if (!can_castle(ANY_CASTLING)) ss << '-'; - ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50 - << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + ss << (ep_square() == SQ_NONE ? " - " : " " + UCIEngine::square(ep_square()) + " ") + << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; return ss.str(); } diff --git a/src/search.cpp b/src/search.cpp index 3f882aab..efc00750 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -158,7 +158,7 @@ void Search::Worker::start_searching() { { rootMoves.emplace_back(Move::none()); sync_cout << "info depth 0 score " - << UCI::to_score(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos) + << UCIEngine::to_score(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos) << sync_endl; } else @@ -204,11 +204,13 @@ void Search::Worker::start_searching() { sync_cout << main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth) << sync_endl; - sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + sync_cout << "bestmove " + << UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) - std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + std::cout << " ponder " + << UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); std::cout << sync_endl; } @@ -933,7 +935,7 @@ moves_loop: // When in check, search starts here if (rootNode && is_mainthread() && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) sync_cout << "info depth " << depth << " currmove " - << UCI::move(move, pos.is_chess960()) << " currmovenumber " + << UCIEngine::move(move, pos.is_chess960()) << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; if (PvNode) (ss + 1)->pv = nullptr; @@ -1904,10 +1906,10 @@ std::string SearchManager::pv(const Search::Worker& worker, ss << "info" << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCI::to_score(v, pos); + << " score " << UCIEngine::to_score(v, pos); if (worker.options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos); + ss << UCIEngine::wdl(v, pos); if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact ss << (rootMoves[i].scoreLowerbound @@ -1918,7 +1920,7 @@ std::string SearchManager::pv(const Search::Worker& worker, << " tbhits " << tbHits << " time " << time << " pv"; for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); + ss << " " << UCIEngine::move(m, pos.is_chess960()); } return ss.str(); diff --git a/src/tune.h b/src/tune.h index b88c085f..079614db 100644 --- a/src/tune.h +++ b/src/tune.h @@ -158,7 +158,7 @@ class Tune { for (auto& e : instance().list) e->init_option(); read_options(); - } // Deferred, due to UCI::Options access + } // Deferred, due to UCIEngine::Options access static void read_options() { for (auto& e : instance().list) e->read_option(); diff --git a/src/uci.cpp b/src/uci.cpp index ee95d5be..ed23c00a 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -32,6 +32,7 @@ #include #include "benchmark.h" +#include "engine.h" #include "evaluate.h" #include "movegen.h" #include "nnue/network.h" @@ -49,27 +50,19 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; -namespace NN = Eval::NNUE; - - -UCI::UCI(int argc, char** argv) : - networks(NN::Networks( - NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), - NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))), +UCIEngine::UCIEngine(int argc, char** argv) : + engine(argv[0]), cli(argc, argv) { + auto& options = engine.get_options(); + options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); - options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - threads.set({options, threads, tt, networks}); - }); + options["Threads"] << Option(1, 1, 1024, [this](const Option&) { engine.resize_threads(); }); - options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { - threads.main_thread()->wait_for_search_finished(); - tt.resize(o, options["Threads"]); - }); + options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { engine.set_tt_size(o); }); - options["Clear Hash"] << Option([this](const Option&) { search_clear(); }); + options["Clear Hash"] << Option([this](const Option&) { engine.search_clear(); }); options["Ponder"] << Option(false); options["MultiPV"] << Option(1, 1, MAX_MOVES); options["Skill Level"] << Option(20, 0, 20); @@ -83,22 +76,17 @@ UCI::UCI(int argc, char** argv) : options["SyzygyProbeDepth"] << Option(1, 1, 100); options["Syzygy50MoveRule"] << Option(true); options["SyzygyProbeLimit"] << Option(7, 0, 7); - options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) { - networks.big.load(cli.binaryDirectory, o); - }); - options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { - networks.small.load(cli.binaryDirectory, o); - }); + options["EvalFile"] << Option(EvalFileDefaultNameBig, + [this](const Option& o) { engine.load_big_network(o); }); + options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, + [this](const Option& o) { engine.load_small_network(o); }); - networks.big.load(cli.binaryDirectory, options["EvalFile"]); - networks.small.load(cli.binaryDirectory, options["EvalFileSmall"]); - - threads.set({options, threads, tt, networks}); - - search_clear(); // After threads are up + engine.load_networks(); + engine.resize_threads(); + engine.search_clear(); // After threads are up } -void UCI::loop() { +void UCIEngine::loop() { Position pos; std::string token, cmd; @@ -121,27 +109,27 @@ void UCI::loop() { is >> std::skipws >> token; if (token == "quit" || token == "stop") - threads.stop = true; + engine.stop(); // The GUI sends 'ponderhit' to tell that the user has played the expected move. // So, 'ponderhit' is sent if pondering was done on the same move that the user // has played. The search should continue, but should also switch from pondering // to the normal search. else if (token == "ponderhit") - threads.main_manager()->ponder = false; // Switch to the normal search + engine.set_ponderhit(false); else if (token == "uci") sync_cout << "id name " << engine_info(true) << "\n" - << options << "\nuciok" << sync_endl; + << engine.get_options() << "\nuciok" << sync_endl; else if (token == "setoption") setoption(is); else if (token == "go") - go(pos, is, states); + go(pos, is); else if (token == "position") - position(pos, is, states); + position(is); else if (token == "ucinewgame") - search_clear(); + engine.search_clear(); else if (token == "isready") sync_cout << "readyok" << sync_endl; @@ -150,11 +138,11 @@ void UCI::loop() { else if (token == "flip") pos.flip(); else if (token == "bench") - bench(pos, is, states); + bench(pos, is); else if (token == "d") sync_cout << pos << sync_endl; else if (token == "eval") - trace_eval(pos); + engine.trace_eval(); else if (token == "compiler") sync_cout << compiler_info() << sync_endl; else if (token == "export_net") @@ -167,8 +155,7 @@ void UCI::loop() { if (is >> std::skipws >> files[1].second) files[1].first = files[1].second; - networks.big.save(files[0].first); - networks.small.save(files[1].first); + engine.save_network(files); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout @@ -186,7 +173,7 @@ void UCI::loop() { } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } -Search::LimitsType UCI::parse_limits(const Position& pos, std::istream& is) { +Search::LimitsType UCIEngine::parse_limits(const Position& pos, std::istream& is) { Search::LimitsType limits; std::string token; @@ -225,23 +212,13 @@ Search::LimitsType UCI::parse_limits(const Position& pos, std::istream& is) { return limits; } -void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { +void UCIEngine::go(Position& pos, std::istringstream& is) { Search::LimitsType limits = parse_limits(pos, is); - - networks.big.verify(options["EvalFile"]); - networks.small.verify(options["EvalFileSmall"]); - - if (limits.perft) - { - perft(pos.fen(), limits.perft, options["UCI_Chess960"]); - return; - } - - threads.start_thinking(options, pos, states, limits); + engine.go(limits); } -void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { +void UCIEngine::bench(Position& pos, std::istream& args) { std::string token; uint64_t num, nodes = 0, cnt = 1; @@ -263,20 +240,20 @@ void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { << std::endl; if (token == "go") { - go(pos, is, states); - threads.main_thread()->wait_for_search_finished(); - nodes += threads.nodes_searched(); + go(pos, is); + engine.wait_for_search_finished(); + nodes += engine.nodes_searched(); } else - trace_eval(pos); + engine.trace_eval(); } else if (token == "setoption") setoption(is); else if (token == "position") - position(pos, is, states); + position(is); else if (token == "ucinewgame") { - search_clear(); // Search::clear() may take a while + engine.search_clear(); // search_clear may take a while elapsed = now(); } } @@ -290,33 +267,13 @@ void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; } -void UCI::trace_eval(Position& pos) { - StateListPtr states(new std::deque(1)); - Position p; - p.set(pos.fen(), options["UCI_Chess960"], &states->back()); - networks.big.verify(options["EvalFile"]); - networks.small.verify(options["EvalFileSmall"]); - - - sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; +void UCIEngine::setoption(std::istringstream& is) { + engine.wait_for_search_finished(); + engine.get_options().setoption(is); } -void UCI::search_clear() { - threads.main_thread()->wait_for_search_finished(); - - tt.clear(options["Threads"]); - threads.clear(); - Tablebases::init(options["SyzygyPath"]); // Free mapped files -} - -void UCI::setoption(std::istringstream& is) { - threads.main_thread()->wait_for_search_finished(); - options.setoption(is); -} - -void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) { - Move m; +void UCIEngine::position(std::istringstream& is) { std::string token, fen; is >> token; @@ -332,15 +289,14 @@ void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) else return; - states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one - pos.set(fen, options["UCI_Chess960"], &states->back()); + std::vector moves; - // Parse the move list, if any - while (is >> token && (m = to_move(pos, token)) != Move::none()) + while (is >> token) { - states->emplace_back(); - pos.do_move(m, states->back()); + moves.push_back(token); } + + engine.set_position(fen, moves); } namespace { @@ -379,7 +335,7 @@ int win_rate_model(Value v, const Position& pos) { } } -std::string UCI::to_score(Value v, const Position& pos) { +std::string UCIEngine::to_score(Value v, const Position& pos) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); std::stringstream ss; @@ -399,7 +355,7 @@ std::string UCI::to_score(Value v, const Position& pos) { // Turns a Value to an integer centipawn number, // without treatment of mate and similar special scores. -int UCI::to_cp(Value v, const Position& pos) { +int UCIEngine::to_cp(Value v, const Position& pos) { // In general, the score can be defined via the the WDL as // (log(1/L - 1) - log(1/W - 1)) / ((log(1/L - 1) + log(1/W - 1)) @@ -410,7 +366,7 @@ int UCI::to_cp(Value v, const Position& pos) { return std::round(100 * int(v) / a); } -std::string UCI::wdl(Value v, const Position& pos) { +std::string UCIEngine::wdl(Value v, const Position& pos) { std::stringstream ss; int wdl_w = win_rate_model(v, pos); @@ -421,11 +377,11 @@ std::string UCI::wdl(Value v, const Position& pos) { return ss.str(); } -std::string UCI::square(Square s) { +std::string UCIEngine::square(Square s) { return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } -std::string UCI::move(Move m, bool chess960) { +std::string UCIEngine::move(Move m, bool chess960) { if (m == Move::none()) return "(none)"; @@ -447,7 +403,7 @@ std::string UCI::move(Move m, bool chess960) { } -Move UCI::to_move(const Position& pos, std::string& str) { +Move UCIEngine::to_move(const Position& pos, std::string str) { if (str.length() == 5) str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased diff --git a/src/uci.h b/src/uci.h index 237928d9..c4e90b48 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,6 +22,7 @@ #include #include +#include "engine.h" #include "misc.h" #include "nnue/network.h" #include "position.h" @@ -36,9 +37,9 @@ class Move; enum Square : int; using Value = int; -class UCI { +class UCIEngine { public: - UCI(int argc, char** argv); + UCIEngine(int argc, char** argv); void loop(); @@ -47,25 +48,17 @@ class UCI { static std::string square(Square s); static std::string move(Move m, bool chess960); static std::string wdl(Value v, const Position& pos); - static Move to_move(const Position& pos, std::string& str); + static Move to_move(const Position& pos, std::string str); static Search::LimitsType parse_limits(const Position& pos, std::istream& is); - const std::string& working_directory() const { return cli.workingDirectory; } - - OptionsMap options; - Eval::NNUE::Networks networks; - private: - TranspositionTable tt; - ThreadPool threads; - CommandLine cli; + Engine engine; + CommandLine cli; - void go(Position& pos, std::istringstream& is, StateListPtr& states); - void bench(Position& pos, std::istream& args, StateListPtr& states); - void position(Position& pos, std::istringstream& is, StateListPtr& states); - void trace_eval(Position& pos); - void search_clear(); + void go(Position& pos, std::istringstream& is); + void bench(Position& pos, std::istream& args); + void position(std::istringstream& is); void setoption(std::istringstream& is); }; From 9032c6cbe74ccf7e8963755501e7e6cc473ae471 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 23 Mar 2024 10:22:20 +0100 Subject: [PATCH 637/678] Transform search output to engine callbacks Part 2 of the Split UCI into UCIEngine and Engine refactor. This creates function callbacks for search to use when an update should occur. The benching in uci.cpp for example does this to extract the total nodes searched. No functional change --- src/Makefile | 4 +- src/engine.cpp | 42 +++++++++++-------- src/engine.h | 26 +++++++++--- src/main.cpp | 5 ++- src/score.cpp | 48 +++++++++++++++++++++ src/score.h | 69 +++++++++++++++++++++++++++++++ src/search.cpp | 82 +++++++++++++++++++----------------- src/search.h | 55 ++++++++++++++++++++++--- src/thread.cpp | 16 +++---- src/thread.h | 2 +- src/uci.cpp | 110 +++++++++++++++++++++++++++++++++++++++---------- src/uci.h | 17 +++++--- 12 files changed, 372 insertions(+), 104 deletions(-) create mode 100644 src/score.cpp create mode 100644 src/score.h diff --git a/src/Makefile b/src/Makefile index 6315bda8..550f5404 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,7 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/engine.cpp b/src/engine.cpp index 79a2c604..12fa5c3f 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -18,21 +18,15 @@ #include "engine.h" -#include -#include -#include -#include -#include -#include #include #include -#include -#include +#include +#include +#include #include -#include "benchmark.h" #include "evaluate.h" -#include "movegen.h" +#include "misc.h" #include "nnue/network.h" #include "nnue/nnue_common.h" #include "perft.h" @@ -40,6 +34,7 @@ #include "search.h" #include "syzygy/tbprobe.h" #include "types.h" +#include "uci.h" #include "ucioption.h" namespace Stockfish { @@ -54,7 +49,6 @@ Engine::Engine(std::string path) : networks(NN::Networks( NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { - Tune::init(options); pos.set(StartFEN, false, &states->back()); } @@ -77,10 +71,26 @@ void Engine::search_clear() { tt.clear(options["Threads"]); threads.clear(); - // @TODO wont work multiple instances + // @TODO wont work with multiple instances Tablebases::init(options["SyzygyPath"]); // Free mapped files } +void Engine::set_on_update_no_moves(std::function&& f) { + updateContext.onUpdateNoMoves = std::move(f); +} + +void Engine::set_on_update_full(std::function&& f) { + updateContext.onUpdateFull = std::move(f); +} + +void Engine::set_on_iter(std::function&& f) { + updateContext.onIter = std::move(f); +} + +void Engine::set_on_bestmove(std::function&& f) { + updateContext.onBestmove = std::move(f); +} + void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); } void Engine::set_position(const std::string& fen, const std::vector& moves) { @@ -102,7 +112,7 @@ void Engine::set_position(const std::string& fen, const std::vector // modifiers -void Engine::resize_threads() { threads.set({options, threads, tt, networks}); } +void Engine::resize_threads() { threads.set({options, threads, tt, networks}, updateContext); } void Engine::set_tt_size(size_t mb) { wait_for_search_finished(); @@ -113,7 +123,7 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } // network related -void Engine::verify_networks() { +void Engine::verify_networks() const { networks.big.verify(options["EvalFile"]); networks.small.verify(options["EvalFileSmall"]); } @@ -138,9 +148,7 @@ void Engine::save_network(const std::pair, std::strin OptionsMap& Engine::get_options() { return options; } -uint64_t Engine::nodes_searched() const { return threads.nodes_searched(); } - -void Engine::trace_eval() { +void Engine::trace_eval() const { StateListPtr trace_states(new std::deque(1)); Position p; p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back()); diff --git a/src/engine.h b/src/engine.h index 6afc423d..f74209d9 100644 --- a/src/engine.h +++ b/src/engine.h @@ -19,7 +19,14 @@ #ifndef ENGINE_H_INCLUDED #define ENGINE_H_INCLUDED -#include "misc.h" +#include +#include +#include +#include +#include +#include +#include + #include "nnue/network.h" #include "position.h" #include "search.h" @@ -31,6 +38,10 @@ namespace Stockfish { class Engine { public: + using InfoShort = Search::InfoShort; + using InfoFull = Search::InfoFull; + using InfoIter = Search::InfoIteration; + Engine(std::string path = ""); ~Engine() { wait_for_search_finished(); } @@ -51,9 +62,14 @@ class Engine { void set_ponderhit(bool); void search_clear(); + void set_on_update_no_moves(std::function&&); + void set_on_update_full(std::function&&); + void set_on_iter(std::function&&); + void set_on_bestmove(std::function&&); + // network related - void verify_networks(); + void verify_networks() const; void load_networks(); void load_big_network(const std::string& file); void load_small_network(const std::string& file); @@ -61,9 +77,7 @@ class Engine { // utility functions - void trace_eval(); - // nodes since last search clear - uint64_t nodes_searched() const; + void trace_eval() const; OptionsMap& get_options(); private: @@ -76,6 +90,8 @@ class Engine { ThreadPool threads; TranspositionTable tt; Eval::NNUE::Networks networks; + + Search::SearchManager::UpdateContext updateContext; }; } // namespace Stockfish diff --git a/src/main.cpp b/src/main.cpp index 4e72c003..a6a3d1c4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,9 @@ #include "bitboard.h" #include "misc.h" #include "position.h" -#include "tune.h" #include "types.h" #include "uci.h" +#include "tune.h" using namespace Stockfish; @@ -35,6 +35,9 @@ int main(int argc, char* argv[]) { Position::init(); UCIEngine uci(argc, argv); + + Tune::init(uci.engine_options()); + uci.loop(); return 0; diff --git a/src/score.cpp b/src/score.cpp new file mode 100644 index 00000000..d1a8a6ab --- /dev/null +++ b/src/score.cpp @@ -0,0 +1,48 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "score.h" + +#include +#include +#include + +#include "uci.h" + +namespace Stockfish { + +Score::Score(Value v, const Position& pos) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + + if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + { + score = InternalUnits{UCIEngine::to_cp(v, pos)}; + } + else if (std::abs(v) <= VALUE_TB) + { + auto distance = VALUE_TB - std::abs(v); + score = (v > 0) ? TBWin{distance} : TBWin{-distance}; + } + else + { + auto distance = VALUE_MATE - std::abs(v); + score = (v > 0) ? Mate{distance} : Mate{-distance}; + } +} + +} \ No newline at end of file diff --git a/src/score.h b/src/score.h new file mode 100644 index 00000000..b94d9f7f --- /dev/null +++ b/src/score.h @@ -0,0 +1,69 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SCORE_H_INCLUDED +#define SCORE_H_INCLUDED + +#include +#include + +#include "types.h" + +namespace Stockfish { + +class Position; + +class Score { + public: + struct Mate { + int plies; + }; + + struct TBWin { + int plies; + }; + + struct InternalUnits { + int value; + }; + + Score() = default; + Score(Value v, const Position& pos); + + template + bool is() const { + return std::holds_alternative(score); + } + + template + T get() const { + return std::get(score); + } + + template + decltype(auto) visit(F&& f) const { + return std::visit(std::forward(f), score); + } + + private: + std::variant score; +}; + +} + +#endif // #ifndef SCORE_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index efc00750..51cd1ae1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -26,8 +26,7 @@ #include #include #include -#include -#include +#include #include #include "evaluate.h" @@ -157,9 +156,8 @@ void Search::Worker::start_searching() { if (rootMoves.empty()) { rootMoves.emplace_back(Move::none()); - sync_cout << "info depth 0 score " - << UCIEngine::to_score(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos) - << sync_endl; + main_manager()->updates.onUpdateNoMoves( + {0, {rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos}}); } else { @@ -201,18 +199,16 @@ void Search::Worker::start_searching() { // Send again PV info if we have a new best thread if (bestThread != this) - sync_cout << main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth) - << sync_endl; + main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth); - sync_cout << "bestmove " - << UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + std::string ponder; if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) - std::cout << " ponder " - << UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + ponder = UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); - std::cout << sync_endl; + auto bestmove = UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + main_manager()->updates.onBestmove(bestmove, ponder); } // Main iterative deepening loop. It calls search() @@ -345,7 +341,7 @@ void Search::Worker::iterative_deepening() { // the UI) before a re-search. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) - sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; + main_manager()->pv(*this, threads, tt, rootDepth); // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. @@ -382,7 +378,7 @@ void Search::Worker::iterative_deepening() { // had time to fully search other root-moves. Thus we suppress this output and // below pick a proven score/PV for this thread (from the previous iteration). && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) - sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; + main_manager()->pv(*this, threads, tt, rootDepth); } if (!threads.stop) @@ -934,9 +930,10 @@ moves_loop: // When in check, search starts here if (rootNode && is_mainthread() && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) - sync_cout << "info depth " << depth << " currmove " - << UCIEngine::move(move, pos.is_chess960()) << " currmovenumber " - << moveCount + thisThread->pvIdx << sync_endl; + { + main_manager()->updates.onIter( + {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); + } if (PvNode) (ss + 1)->pv = nullptr; @@ -1871,11 +1868,10 @@ void SearchManager::check_time(Search::Worker& worker) { worker.threads.stop = worker.threads.abortedSearch = true; } -std::string SearchManager::pv(const Search::Worker& worker, - const ThreadPool& threads, - const TranspositionTable& tt, - Depth depth) const { - std::stringstream ss; +void SearchManager::pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const { const auto nodes = threads.nodes_searched(); const auto& rootMoves = worker.rootMoves; @@ -1901,29 +1897,39 @@ std::string SearchManager::pv(const Search::Worker& worker, bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB; v = tb ? rootMoves[i].tbScore : v; - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; + std::string pv; + for (Move m : rootMoves[i].pv) + pv += UCIEngine::move(m, pos.is_chess960()) + " "; - ss << "info" - << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCIEngine::to_score(v, pos); + // remove last whitespace + if (!pv.empty()) + pv.pop_back(); - if (worker.options["UCI_ShowWDL"]) - ss << UCIEngine::wdl(v, pos); + auto wdl = worker.options["UCI_ShowWDL"] ? UCIEngine::wdl(v, pos) : ""; + auto bound = rootMoves[i].scoreLowerbound + ? "lowerbound" + : (rootMoves[i].scoreUpperbound ? "upperbound" : ""); + + InfoFull info; + + info.depth = d; + info.selDepth = rootMoves[i].selDepth; + info.multiPV = i + 1; + info.score = {v, pos}; + info.wdl = wdl; if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound - ? " lowerbound" - : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + info.bound = bound; - ss << " nodes " << nodes << " nps " << nodes * 1000 / time << " hashfull " << tt.hashfull() - << " tbhits " << tbHits << " time " << time << " pv"; + info.timeMs = time; + info.nodes = nodes; + info.nps = nodes * 1000 / time; + info.tbHits = tbHits; + info.pv = pv; + info.hashfull = tt.hashfull(); - for (Move m : rootMoves[i].pv) - ss << " " << UCIEngine::move(m, pos.is_chess960()); + updates.onUpdateFull(info); } - - return ss.str(); } // Called in case we have no ponder move before exiting the search, diff --git a/src/search.h b/src/search.h index 22f75ffd..d1464840 100644 --- a/src/search.h +++ b/src/search.h @@ -24,13 +24,15 @@ #include #include #include +#include #include -#include +#include #include #include "misc.h" #include "movepick.h" #include "position.h" +#include "score.h" #include "syzygy/tbprobe.h" #include "timeman.h" #include "types.h" @@ -139,7 +141,6 @@ struct SharedState { tt(transpositionTable), networks(nets) {} - const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; @@ -156,16 +157,56 @@ class ISearchManager { virtual void check_time(Search::Worker&) = 0; }; +struct InfoShort { + int depth; + Score score; +}; + +struct InfoFull: InfoShort { + int selDepth; + size_t multiPV; + std::string_view wdl; + std::string_view bound; + size_t timeMs; + size_t nodes; + size_t nps; + size_t tbHits; + std::string_view pv; + int hashfull; +}; + +struct InfoIteration { + int depth; + std::string_view currmove; + size_t currmovenumber; +}; + // SearchManager manages the search from the main thread. It is responsible for // keeping track of the time, and storing data strictly related to the main thread. class SearchManager: public ISearchManager { public: + using UpdateShort = std::function; + using UpdateFull = std::function; + using UpdateIter = std::function; + using UpdateBestmove = std::function; + + struct UpdateContext { + UpdateShort onUpdateNoMoves; + UpdateFull onUpdateFull; + UpdateIter onIter; + UpdateBestmove onBestmove; + }; + + + SearchManager(const UpdateContext& updateContext) : + updates(updateContext) {} + void check_time(Search::Worker& worker) override; - std::string pv(const Search::Worker& worker, - const ThreadPool& threads, - const TranspositionTable& tt, - Depth depth) const; + void pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const; Stockfish::TimeManagement tm; int callsCnt; @@ -178,6 +219,8 @@ class SearchManager: public ISearchManager { bool stopOnPonderhit; size_t id; + + const UpdateContext& updates; }; class NullSearchManager: public ISearchManager { diff --git a/src/thread.cpp b/src/thread.cpp index 90add4ad..85a2bcbb 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -119,7 +119,8 @@ uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. -void ThreadPool::set(Search::SharedState sharedState) { +void ThreadPool::set(Search::SharedState sharedState, + const Search::SearchManager::UpdateContext& updateContext) { if (threads.size() > 0) // destroy any existing thread(s) { @@ -133,14 +134,15 @@ void ThreadPool::set(Search::SharedState sharedState) { if (requested > 0) // create new thread(s) { - threads.push_back(new Thread( - sharedState, std::unique_ptr(new Search::SearchManager()), 0)); - + auto manager = std::make_unique(updateContext); + threads.push_back(new Thread(sharedState, std::move(manager), 0)); while (threads.size() < requested) - threads.push_back(new Thread( - sharedState, std::unique_ptr(new Search::NullSearchManager()), - threads.size())); + { + auto null_manager = std::make_unique(); + threads.push_back(new Thread(sharedState, std::move(null_manager), threads.size())); + } + clear(); main_thread()->wait_for_search_finished(); diff --git a/src/thread.h b/src/thread.h index 81fcc72a..223652ae 100644 --- a/src/thread.h +++ b/src/thread.h @@ -82,7 +82,7 @@ class ThreadPool { void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType); void clear(); - void set(Search::SharedState); + void set(Search::SharedState, const Search::SearchManager::UpdateContext&); Search::SearchManager* main_manager(); Thread* main_thread() const { return threads.front(); } diff --git a/src/uci.cpp b/src/uci.cpp index ed23c00a..d6936d38 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -19,15 +19,14 @@ #include "uci.h" #include -#include #include #include #include -#include #include #include #include #include +#include #include #include @@ -35,10 +34,8 @@ #include "engine.h" #include "evaluate.h" #include "movegen.h" -#include "nnue/network.h" -#include "nnue/nnue_common.h" -#include "perft.h" #include "position.h" +#include "score.h" #include "search.h" #include "syzygy/tbprobe.h" #include "types.h" @@ -49,6 +46,13 @@ namespace Stockfish { constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +template +struct overload: Ts... { + using Ts::operator()...; +}; + +template +overload(Ts...) -> overload; UCIEngine::UCIEngine(int argc, char** argv) : engine(argv[0]), @@ -81,6 +85,12 @@ UCIEngine::UCIEngine(int argc, char** argv) : options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { engine.load_small_network(o); }); + + engine.set_on_iter([](const auto& i) { on_iter(i); }); + engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); }); + engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); + engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); }); + engine.load_networks(); engine.resize_threads(); engine.search_clear(); // After threads are up @@ -221,6 +231,13 @@ void UCIEngine::go(Position& pos, std::istringstream& is) { void UCIEngine::bench(Position& pos, std::istream& args) { std::string token; uint64_t num, nodes = 0, cnt = 1; + uint64_t nodesSearched = 0; + const auto& options = engine.get_options(); + + engine.set_on_update_full([&](const auto& i) { + nodesSearched = i.nodes; + on_update_full(i, options["UCI_ShowWDL"]); + }); std::vector list = setup_bench(pos, args); @@ -242,7 +259,8 @@ void UCIEngine::bench(Position& pos, std::istream& args) { { go(pos, is); engine.wait_for_search_finished(); - nodes += engine.nodes_searched(); + nodes += nodesSearched; + nodesSearched = 0; } else engine.trace_eval(); @@ -265,6 +283,9 @@ void UCIEngine::bench(Position& pos, std::istream& args) { std::cerr << "\n===========================" << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; + + // reset callback, to not capture a dangling reference to nodesSearched + engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); } @@ -335,22 +356,22 @@ int win_rate_model(Value v, const Position& pos) { } } -std::string UCIEngine::to_score(Value v, const Position& pos) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); +std::string UCIEngine::format_score(const Score& s) { + constexpr int TB_CP = 20000; + const auto format = + overload{[](Score::Mate mate) -> std::string { + auto m = (mate.plies > 0 ? (mate.plies + 1) : -mate.plies) / 2; + return std::string("mate ") + std::to_string(m); + }, + [](Score::TBWin tb) -> std::string { + return std::string("cp ") + + std::to_string((tb.plies > 0 ? TB_CP - tb.plies : -TB_CP + tb.plies)); + }, + [](Score::InternalUnits units) -> std::string { + return std::string("cp ") + std::to_string(units.value); + }}; - std::stringstream ss; - - if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << to_cp(v, pos); - else if (std::abs(v) <= VALUE_TB) - { - const int ply = VALUE_TB - std::abs(v); // recompute ss->ply - ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); - } - else - ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; - - return ss.str(); + return s.visit(format); } // Turns a Value to an integer centipawn number, @@ -414,4 +435,51 @@ Move UCIEngine::to_move(const Position& pos, std::string str) { return Move::none(); } +void UCIEngine::on_update_no_moves(const Engine::InfoShort& info) { + sync_cout << "info depth" << info.depth << " score " << format_score(info.score) << sync_endl; +} + +void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) { + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " seldepth " << info.selDepth // + << " multipv " << info.multiPV // + << " score " << format_score(info.score); // + + if (showWDL) + ss << " wdl " << info.wdl; + + if (!info.bound.empty()) + ss << " " << info.bound; + + ss << " nodes " << info.nodes // + << " nps " << info.nps // + << " hashfull " << info.hashfull // + << " tbhits " << info.tbHits // + << " time " << info.timeMs // + << " pv " << info.pv; // + + sync_cout << ss.str() << sync_endl; +} + +void UCIEngine::on_iter(const Engine::InfoIter& info) { + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " currmove " << info.currmove // + << " currmovenumber " << info.currmovenumber; // + + sync_cout << ss.str() << sync_endl; +} + +void UCIEngine::on_bestmove(std::string_view bestmove, std::string_view ponder) { + sync_cout << "bestmove " << bestmove; + if (!ponder.empty()) + std::cout << " ponder " << ponder; + std::cout << sync_endl; +} + } // namespace Stockfish diff --git a/src/uci.h b/src/uci.h index c4e90b48..fa8c57fd 100644 --- a/src/uci.h +++ b/src/uci.h @@ -21,19 +21,17 @@ #include #include +#include #include "engine.h" #include "misc.h" -#include "nnue/network.h" -#include "position.h" #include "search.h" -#include "thread.h" -#include "tt.h" -#include "ucioption.h" namespace Stockfish { +class Position; class Move; +class Score; enum Square : int; using Value = int; @@ -44,7 +42,7 @@ class UCIEngine { void loop(); static int to_cp(Value v, const Position& pos); - static std::string to_score(Value v, const Position& pos); + static std::string format_score(const Score& s); static std::string square(Square s); static std::string move(Move m, bool chess960); static std::string wdl(Value v, const Position& pos); @@ -52,6 +50,8 @@ class UCIEngine { static Search::LimitsType parse_limits(const Position& pos, std::istream& is); + auto& engine_options() { return engine.get_options(); } + private: Engine engine; CommandLine cli; @@ -60,6 +60,11 @@ class UCIEngine { void bench(Position& pos, std::istream& args); void position(std::istringstream& is); void setoption(std::istringstream& is); + + static void on_update_no_moves(const Engine::InfoShort& info); + static void on_update_full(const Engine::InfoFull& info, bool showWDL); + static void on_iter(const Engine::InfoIter& info); + static void on_bestmove(std::string_view bestmove, std::string_view ponder); }; } // namespace Stockfish From 1adf8e1ae662f2eecc5d652ece7b9f53014cf057 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:39:29 +0800 Subject: [PATCH 638/678] VVLTC search tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parameters were tuned in 3 stages: * Using an earlier L1-3072 net, and with triple extension margin manually set to 0: https://tests.stockfishchess.org/tests/view/65ffdf5d0ec64f0526c544f2 (~30k games) * Continue tuning, but with the previous master net (L1-2560). https://tests.stockfishchess.org/tests/view/660663f00ec64f0526c59c41 (~27k games) * Starting with the parameters from step 2, use the current L1-3072 net, and allow the triple extension margin to be tuned starting from 0: https://tests.stockfishchess.org/tests/view/660c16b8216a13d9498e7536 (40k games) Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/66115eacbfeb43334bf7eddd LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 27138 W: 7045 L: 6789 D: 13304 Ptnml(0-2): 1, 2421, 8471, 2673, 3 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/661483623eb00c8ccc0049c1 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 26242 W: 6807 L: 6535 D: 12900 Ptnml(0-2): 0, 2353, 8143, 2625, 0 STC Elo estimate: https://tests.stockfishchess.org/tests/view/66175ca55a4693796d96608c Elo: -10.53 ± 2.4 (95%) LOS: 0.0% Total: 21584 W: 5294 L: 5948 D: 10342 Ptnml(0-2): 102, 2937, 5363, 2293, 97 nElo: -19.99 ± 4.7 (95%) PairsRatio: 0.79 closes https://github.com/official-stockfish/Stockfish/pull/5162 Bench: 1381387 --- src/evaluate.h | 2 +- src/search.cpp | 76 +++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index 97ca4c4b..da9c7074 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1165, PsqtOnlyThreshold = 2500; +constexpr inline int SmallNetThreshold = 1274, PsqtOnlyThreshold = 2389; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/search.cpp b/src/search.cpp index 51cd1ae1..a131c958 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -58,8 +58,8 @@ static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 118 - 44 * noTtCutNode; - Value improvingDeduction = 53 * improving * futilityMult / 32; - Value worseningDeduction = (309 + 47 * improving) * oppWorsening * futilityMult / 1024; + Value improvingDeduction = 52 * improving * futilityMult / 32; + Value worseningDeduction = (310 + 48 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -71,15 +71,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 11175; + v += cv * std::abs(cv) / 9260; return std::clamp(v, 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::clamp(245 * d - 320, 0, 1296); } +int stat_bonus(Depth d) { return std::clamp(211 * d - 315, 0, 1291); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 554 * d - 303 : 1203); } +int stat_malus(Depth d) { return (d < 4 ? 572 * d - 285 : 1372); } // 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); } @@ -303,12 +303,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 10 + avg * avg / 12493; + delta = 11 + avg * avg / 11254; 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] = 132 * avg / (std::abs(avg) + 89); + optimism[us] = 125 * avg / (std::abs(avg) + 91); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -496,10 +496,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-67); + h->fill(-65); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((19.80 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((20.14 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -731,7 +731,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1578, 1291); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1644, 1384); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -754,7 +754,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 488 - (289 - 142 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 471 - (276 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -765,21 +765,21 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 267 + - (ss - 1)->statScore / 284 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16878 - && eval >= beta && ss->staticEval >= beta - 20 * depth + 314 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 18001 + && eval >= beta && ss->staticEval >= beta - 21 * depth + 315 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -827,7 +827,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 170 - 64 * improving; + probCutBeta = beta + 169 - 63 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -883,7 +883,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 409; + probCutBeta = beta + 436; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -967,7 +967,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 297 + 284 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 287 + 277 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -975,7 +975,7 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -203 * depth)) + if (!pos.see_ge(move, -199 * depth)) continue; } else @@ -987,15 +987,15 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4040 * depth) + if (lmrDepth < 6 && history < -4173 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 5637; + lmrDepth += history / 5285; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 59 ? 141 : 58) + 125 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 58) + 131 * lmrDepth; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha) @@ -1009,7 +1009,7 @@ 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, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1029,11 +1029,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (58 + 58 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (64 + 59 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1048,11 +1048,11 @@ moves_loop: // When in check, search starts here // We make sure to limit the extensions in some way to avoid a search explosion if (!PvNode && ss->multipleExtensions <= 16) { - extension = 2 + (value < singularBeta - 22 && !ttCapture); + extension = 2 + (value < singularBeta - 11 && !ttCapture); depth += depth < 14; } if (PvNode && !ttCapture && ss->multipleExtensions <= 5 - && value < singularBeta - 37) + && value < singularBeta - 38) extension = 2; } @@ -1087,7 +1087,7 @@ moves_loop: // When in check, search starts here else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4026) + > 3807) extension = 1; } @@ -1137,10 +1137,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4723; + + (*contHist[3])[movedPiece][move.to_sq()] - 5007; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 13659; + r -= ss->statScore / 12901; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1159,7 +1159,7 @@ 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 + 47 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 42 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1277,7 +1277,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 14206 && value > -12077) + if (depth > 2 && depth < 12 && beta < 13132 && value > -13295) depth -= 2; assert(depth > 0); @@ -1320,9 +1320,9 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14963) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) + ((ss - 1)->moveCount > 11) - + (!ss->inCheck && bestValue <= ss->staticEval - 150); + + (!ss->inCheck && bestValue <= ss->staticEval - 144); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1480,7 +1480,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 226; + futilityBase = ss->staticEval + 246; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1560,7 +1560,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -78)) + if (!pos.see_ge(move, -79)) continue; } @@ -1628,7 +1628,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1107 - delta * 725 / rootDelta) / 1024 + (!i && reductionScale > 956); + return (reductionScale + 1123 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); } namespace { @@ -1717,7 +1717,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 185 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 94484db6e83ad791b8782fd120f32db2ab72bf11 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 8 Apr 2024 13:07:41 -0700 Subject: [PATCH 639/678] Avoid permuting inputs during transform() Avoid permuting inputs during transform() and instead do it once at load time. Affects AVX2 and newer Intel architectures only. https://tests.stockfishchess.org/tests/view/661306613eb00c8ccc0033c7 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 108480 W: 28319 L: 27898 D: 52263 Ptnml(0-2): 436, 12259, 28438, 12662, 445 speedups measured such as e.g. ``` Result of 100 runs ================== base (./stockfish.master ) = 1241128 +/- 3757 test (./stockfish.patch ) = 1247713 +/- 3689 diff = +6585 +/- 2583 speedup = +0.0053 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/5160 No functional change --- src/nnue/nnue_feature_transformer.h | 78 +++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 888edebb..3101c8d2 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -60,10 +60,9 @@ using psqt_vec_t = __m256i; #define vec_set_16(a) _mm512_set1_epi16(a) #define vec_max_16(a, b) _mm512_max_epi16(a, b) #define vec_min_16(a, b) _mm512_min_epi16(a, b) -inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { - vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7)); - return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted); -} + // Inverse permuted at load time + #define vec_msb_pack_16(a, b) \ + _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7)) #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a, b) _mm256_store_si256(a, b) #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) @@ -84,10 +83,9 @@ using psqt_vec_t = __m256i; #define vec_set_16(a) _mm256_set1_epi16(a) #define vec_max_16(a, b) _mm256_max_epi16(a, b) #define vec_min_16(a, b) _mm256_min_epi16(a, b) -inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { - vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7)); - return _mm256_permute4x64_epi64(compacted, 0b11011000); -} + // Inverse permuted at load time + #define vec_msb_pack_16(a, b) \ + _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7)) #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a, b) _mm256_store_si256(a, b) #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) @@ -229,6 +227,62 @@ class FeatureTransformer { 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, tmp1; + 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 + } + + static constexpr void inverse_order_packs([[maybe_unused]] uint64_t* v) { +#if defined(USE_AVX512) // Inverse _mm512_packs_epi16 ordering + uint64_t tmp0, tmp1; + 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*)) const { +#if defined(USE_AVX2) + #if defined(USE_AVX512) + constexpr IndexType di = 16; + #else + constexpr IndexType di = 8; + #endif + uint64_t* b = reinterpret_cast(const_cast(&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(const_cast(&weights[j * HalfDimensions])); + for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(uint64_t); + i += di) + order_fn(&w[i]); + } +#endif + } + // Read network parameters bool read_parameters(std::istream& stream) { @@ -236,16 +290,20 @@ class FeatureTransformer { read_leb_128(stream, weights, HalfDimensions * InputDimensions); read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + permute_weights(inverse_order_packs); return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { + permute_weights(order_packs); + write_leb_128(stream, biases, HalfDimensions); write_leb_128(stream, weights, HalfDimensions * InputDimensions); write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + permute_weights(inverse_order_packs); return !stream.fail(); } @@ -276,8 +334,8 @@ class FeatureTransformer { static_assert((HalfDimensions / 2) % OutputChunkSize == 0); constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - vec_t Zero = vec_zero(); - vec_t One = vec_set_16(127); + const vec_t Zero = vec_zero(); + const vec_t One = vec_set_16(127); const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); const vec_t* in1 = From de2244284b301c5bf15b248b5e3538aee92bb295 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 5 Apr 2024 11:34:11 +0200 Subject: [PATCH 640/678] Remove COMPILER from Makefile The same functionality is available by using COMPCXX and having another variable which does the same is just confusing. There was only one mention on Stockfish Wiki about this which has been changed to COMPCXX. closes https://github.com/official-stockfish/Stockfish/pull/5154 No functional change --- .github/workflows/arm_compilation.yml | 4 ++-- .github/workflows/compilation.yml | 6 +++--- .github/workflows/sanitizers.yml | 4 ++-- .github/workflows/tests.yml | 6 +++--- .github/workflows/upload_binaries.yml | 2 +- src/Makefile | 5 ----- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/workflows/arm_compilation.yml b/.github/workflows/arm_compilation.yml index ef141971..3934ac2d 100644 --- a/.github/workflows/arm_compilation.yml +++ b/.github/workflows/arm_compilation.yml @@ -10,7 +10,7 @@ jobs: name: ${{ matrix.config.name }} ${{ matrix.binaries }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EMU: ${{ matrix.config.emu }} EXT: ${{ matrix.config.ext }} @@ -62,7 +62,7 @@ jobs: if [ $COMP == ndk ]; then export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH fi - $COMPILER -v + $COMPCXX -v - name: Test help target run: make help diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 964b5f05..3524d5e9 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -10,7 +10,7 @@ jobs: name: ${{ matrix.config.name }} ${{ matrix.binaries }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} NAME: ${{ matrix.config.simple_name }} @@ -50,7 +50,7 @@ jobs: run: make net - name: Check compiler - run: $COMPILER -v + run: $COMPCXX -v - name: Test help target run: make help @@ -59,7 +59,7 @@ jobs: run: git --version - name: Check compiler - run: $COMPILER -v + run: $COMPCXX -v - name: Show g++ cpu info if: runner.os != 'macOS' diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 7ab1f997..612f1275 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -6,7 +6,7 @@ jobs: name: ${{ matrix.sanitizers.name }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: @@ -47,7 +47,7 @@ jobs: run: make net - name: Check compiler - run: $COMPILER -v + run: $COMPCXX -v - name: Test help target run: make help diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 702e86e5..328c9cf9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ jobs: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: @@ -172,9 +172,9 @@ jobs: if [ $COMP == ndk ]; then export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH fi - $COMPILER -v + $COMPCXX -v else - echo "$COMPILER -v" > script.sh + echo "$COMPCXX -v" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder fi diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 015b514c..acf91a8f 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -11,7 +11,7 @@ jobs: name: ${{ matrix.config.name }} ${{ matrix.binaries }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} NAME: ${{ matrix.config.simple_name }} diff --git a/src/Makefile b/src/Makefile index 550f5404..45f38b01 100644 --- a/src/Makefile +++ b/src/Makefile @@ -546,11 +546,6 @@ else endif endif -### Travis CI script uses COMPILER to overwrite CXX -ifdef COMPILER - COMPCXX=$(COMPILER) -endif - ### Allow overwriting CXX from command line ifdef COMPCXX CXX=$(COMPCXX) From d6bdcec52c33a67970721c4443136b42265a6148 Mon Sep 17 00:00:00 2001 From: gab8192 Date: Wed, 3 Apr 2024 23:41:24 +0200 Subject: [PATCH 641/678] Remove an useless assignment The assignment (ss + 1)->excludedMove = Move::none() can be simplified away because when that line is reached, (ss + 1)->excludedMove is always already none. The only moment stack[x]->excludedMove is modified, is during singular search, but it is reset to none right after the singular search is finished. closes https://github.com/official-stockfish/Stockfish/pull/5153 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a131c958..3d84eb36 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -585,7 +585,7 @@ Value Search::Worker::search( assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss + 1)->excludedMove = bestMove = Move::none(); + bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; ss->multipleExtensions = (ss - 1)->multipleExtensions; From 249eec67152d334d76c0f981907a6f5787289443 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 11 Apr 2024 14:00:46 +0300 Subject: [PATCH 642/678] Simplify the depth-dependent part of the best value adjustment formula in main search Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 139648 W: 36171 L: 36061 D: 67416 Ptnml(0-2): 545, 16685, 35282, 16739, 573 https://tests.stockfishchess.org/tests/view/660d953b8ff4a059828d625d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 222894 W: 56519 L: 56505 D: 109870 Ptnml(0-2): 112, 25145, 60971, 25055, 164 https://tests.stockfishchess.org/tests/view/660fd4afbfeb43334bf7d558 closes https://github.com/official-stockfish/Stockfish/pull/5164 bench: 1479416 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3d84eb36..24805aa7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -585,7 +585,7 @@ Value Search::Worker::search( assert(0 <= ss->ply && ss->ply < MAX_PLY); - bestMove = Move::none(); + bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; ss->multipleExtensions = (ss - 1)->multipleExtensions; @@ -1307,7 +1307,7 @@ moves_loop: // When in check, search starts here // Adjust best value for fail high cases at non-pv nodes if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) - bestValue = (bestValue * (depth + 2) + beta) / (depth + 3); + bestValue = (bestValue * depth + beta) / (depth + 1); if (!moveCount) bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; From e58b3b4665469a793a0976d7a28f61fcd771b565 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Fri, 12 Apr 2024 08:39:39 +0200 Subject: [PATCH 643/678] Fix wrong mate sign introduced yesterday by the UCI refactoring 9032c6cbe fixes #5166 closes https://github.com/official-stockfish/Stockfish/pull/5167 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index d6936d38..a328ccb0 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -360,12 +360,12 @@ std::string UCIEngine::format_score(const Score& s) { constexpr int TB_CP = 20000; const auto format = overload{[](Score::Mate mate) -> std::string { - auto m = (mate.plies > 0 ? (mate.plies + 1) : -mate.plies) / 2; + auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; return std::string("mate ") + std::to_string(m); }, [](Score::TBWin tb) -> std::string { return std::string("cp ") - + std::to_string((tb.plies > 0 ? TB_CP - tb.plies : -TB_CP + tb.plies)); + + std::to_string((tb.plies > 0 ? TB_CP - tb.plies : -TB_CP - tb.plies)); }, [](Score::InternalUnits units) -> std::string { return std::string("cp ") + std::to_string(units.value); From 14f6eab07d1d1e1a59372974e5534128676e9440 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:32:31 +0300 Subject: [PATCH 644/678] Fix some more UCI output further fall-out of the refactoring, fixes: * the position object in UCI is not never getting updated if position token is used * duplicate string of " wdl " See also: https://discord.com/channels/435943710472011776/1032922913499783169/1228227522945351690 https://discord.com/channels/435943710472011776/813919248455827515/1228288106449338398 closes https://github.com/official-stockfish/Stockfish/pull/5168 No functional change Co-Authored-By: disservin <45608332+disservin@users.noreply.github.com> --- src/uci.cpp | 9 +++++---- src/uci.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index a328ccb0..a15bc7d4 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -137,7 +137,7 @@ void UCIEngine::loop() { else if (token == "go") go(pos, is); else if (token == "position") - position(is); + position(pos, is); else if (token == "ucinewgame") engine.search_clear(); else if (token == "isready") @@ -268,7 +268,7 @@ void UCIEngine::bench(Position& pos, std::istream& args) { else if (token == "setoption") setoption(is); else if (token == "position") - position(is); + position(pos, is); else if (token == "ucinewgame") { engine.search_clear(); // search_clear may take a while @@ -294,7 +294,7 @@ void UCIEngine::setoption(std::istringstream& is) { engine.get_options().setoption(is); } -void UCIEngine::position(std::istringstream& is) { +void UCIEngine::position(Position& pos, std::istringstream& is) { std::string token, fen; is >> token; @@ -317,6 +317,7 @@ void UCIEngine::position(std::istringstream& is) { moves.push_back(token); } + pos.set(fen, engine.get_options()["UCI_Chess960"], pos.state()); engine.set_position(fen, moves); } @@ -393,7 +394,7 @@ std::string UCIEngine::wdl(Value v, const Position& pos) { int wdl_w = win_rate_model(v, pos); int wdl_l = win_rate_model(-v, pos); int wdl_d = 1000 - wdl_w - wdl_l; - ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + ss << wdl_w << " " << wdl_d << " " << wdl_l; return ss.str(); } diff --git a/src/uci.h b/src/uci.h index fa8c57fd..fa359db4 100644 --- a/src/uci.h +++ b/src/uci.h @@ -58,7 +58,7 @@ class UCIEngine { void go(Position& pos, std::istringstream& is); void bench(Position& pos, std::istream& args); - void position(std::istringstream& is); + void position(Position& pos, std::istringstream& is); void setoption(std::istringstream& is); static void on_update_no_moves(const Engine::InfoShort& info); From 4912f5b0b5f2656bc5fcdb0af480765ad5aa8932 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 12 Apr 2024 19:11:10 +0200 Subject: [PATCH 645/678] Remove duplicated Position object in UCIEngine Also fixes searchmoves. Drop the need of a Position object in uci.cpp. A side note, it is still required for the static functions, but these should be moved to a different namespace/class later on, since sf kinda relies on them. closes https://github.com/official-stockfish/Stockfish/pull/5169 No functional change --- src/benchmark.cpp | 6 ++---- src/benchmark.h | 4 +--- src/engine.cpp | 16 ++++++++++++++-- src/engine.h | 3 +++ src/search.h | 11 ++++++----- src/thread.cpp | 16 +++++++++++++--- src/uci.cpp | 48 +++++++++++++++++++++++------------------------ src/uci.h | 9 +++++---- 8 files changed, 67 insertions(+), 46 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 50f8612d..267a6b4b 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -23,8 +23,6 @@ #include #include -#include "position.h" - namespace { // clang-format off @@ -108,7 +106,7 @@ namespace Stockfish { // bench 64 1 100000 default nodes : search default positions for 100K nodes each // bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec // bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" -std::vector setup_bench(const Position& current, std::istream& is) { +std::vector setup_bench(const std::string& currentFen, std::istream& is) { std::vector fens, list; std::string go, token; @@ -126,7 +124,7 @@ std::vector setup_bench(const Position& current, std::istream& is) fens = Defaults; else if (fenFile == "current") - fens.push_back(current.fen()); + fens.push_back(currentFen); else { diff --git a/src/benchmark.h b/src/benchmark.h index 86f8a0ad..8905fcb1 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -25,9 +25,7 @@ namespace Stockfish { -class Position; - -std::vector setup_bench(const Position&, std::istream&); +std::vector setup_bench(const std::string&, std::istream&); } // namespace Stockfish diff --git a/src/engine.cpp b/src/engine.cpp index 12fa5c3f..325b971e 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "evaluate.h" #include "misc.h" @@ -146,8 +148,6 @@ void Engine::save_network(const std::pair, std::strin // utility functions -OptionsMap& Engine::get_options() { return options; } - void Engine::trace_eval() const { StateListPtr trace_states(new std::deque(1)); Position p; @@ -158,4 +158,16 @@ void Engine::trace_eval() const { sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; } +OptionsMap& Engine::get_options() { return options; } + +std::string Engine::fen() const { return pos.fen(); } + +void Engine::flip() { pos.flip(); } + +std::string Engine::visualize() const { + std::stringstream ss; + ss << pos; + return ss.str(); +} + } \ No newline at end of file diff --git a/src/engine.h b/src/engine.h index f74209d9..7122ee59 100644 --- a/src/engine.h +++ b/src/engine.h @@ -79,6 +79,9 @@ class Engine { void trace_eval() const; OptionsMap& get_options(); + std::string fen() const; + void flip(); + std::string visualize() const; private: const std::string binaryDirectory; diff --git a/src/search.h b/src/search.h index d1464840..d30a06fe 100644 --- a/src/search.h +++ b/src/search.h @@ -28,6 +28,7 @@ #include #include #include +#include #include "misc.h" #include "movepick.h" @@ -121,11 +122,11 @@ struct LimitsType { bool use_time_management() const { return time[WHITE] || time[BLACK]; } - std::vector searchmoves; - TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; - int movestogo, depth, mate, perft, infinite; - uint64_t nodes; - bool ponderMode; + std::vector searchmoves; + TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; + bool ponderMode; }; diff --git a/src/thread.cpp b/src/thread.cpp index 85a2bcbb..1438c9f9 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "misc.h" #include "movegen.h" @@ -33,6 +34,7 @@ #include "tt.h" #include "types.h" #include "ucioption.h" +#include "uci.h" namespace Stockfish { @@ -182,10 +184,18 @@ void ThreadPool::start_thinking(const OptionsMap& options, increaseDepth = true; Search::RootMoves rootMoves; + const auto legalmoves = MoveList(pos); - for (const auto& m : MoveList(pos)) - if (limits.searchmoves.empty() - || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + for (const auto& uciMove : limits.searchmoves) + { + auto move = UCIEngine::to_move(pos, uciMove); + + if (std::find(legalmoves.begin(), legalmoves.end(), move) != legalmoves.end()) + rootMoves.emplace_back(move); + } + + if (rootMoves.empty()) + for (const auto& m : legalmoves) rootMoves.emplace_back(m); Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves); diff --git a/src/uci.cpp b/src/uci.cpp index a15bc7d4..8f697836 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,8 +22,6 @@ #include #include #include -#include -#include #include #include #include @@ -98,11 +96,7 @@ UCIEngine::UCIEngine(int argc, char** argv) : void UCIEngine::loop() { - Position pos; - std::string token, cmd; - StateListPtr states(new std::deque(1)); - - pos.set(StartFEN, false, &states->back()); + std::string token, cmd; for (int i = 1; i < cli.argc; ++i) cmd += std::string(cli.argv[i]) + " "; @@ -135,9 +129,9 @@ void UCIEngine::loop() { else if (token == "setoption") setoption(is); else if (token == "go") - go(pos, is); + go(is); else if (token == "position") - position(pos, is); + position(is); else if (token == "ucinewgame") engine.search_clear(); else if (token == "isready") @@ -146,11 +140,11 @@ void UCIEngine::loop() { // Add custom non-UCI commands, mainly for debugging purposes. // These commands must not be used during a search! else if (token == "flip") - pos.flip(); + engine.flip(); else if (token == "bench") - bench(pos, is); + bench(is); else if (token == "d") - sync_cout << pos << sync_endl; + sync_cout << engine.visualize() << sync_endl; else if (token == "eval") engine.trace_eval(); else if (token == "compiler") @@ -183,7 +177,7 @@ void UCIEngine::loop() { } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } -Search::LimitsType UCIEngine::parse_limits(const Position& pos, std::istream& is) { +Search::LimitsType UCIEngine::parse_limits(std::istream& is) { Search::LimitsType limits; std::string token; @@ -192,7 +186,7 @@ Search::LimitsType UCIEngine::parse_limits(const Position& pos, std::istream& is while (is >> token) if (token == "searchmoves") // Needs to be the last command on the line while (is >> token) - limits.searchmoves.push_back(to_move(pos, token)); + limits.searchmoves.push_back(to_lower(token)); else if (token == "wtime") is >> limits.time[WHITE]; @@ -222,13 +216,13 @@ Search::LimitsType UCIEngine::parse_limits(const Position& pos, std::istream& is return limits; } -void UCIEngine::go(Position& pos, std::istringstream& is) { +void UCIEngine::go(std::istringstream& is) { - Search::LimitsType limits = parse_limits(pos, is); + Search::LimitsType limits = parse_limits(is); engine.go(limits); } -void UCIEngine::bench(Position& pos, std::istream& args) { +void UCIEngine::bench(std::istream& args) { std::string token; uint64_t num, nodes = 0, cnt = 1; uint64_t nodesSearched = 0; @@ -239,7 +233,7 @@ void UCIEngine::bench(Position& pos, std::istream& args) { on_update_full(i, options["UCI_ShowWDL"]); }); - std::vector list = setup_bench(pos, args); + std::vector list = setup_bench(engine.fen(), args); num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); @@ -253,11 +247,11 @@ void UCIEngine::bench(Position& pos, std::istream& args) { if (token == "go" || token == "eval") { - std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" + std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << engine.fen() << ")" << std::endl; if (token == "go") { - go(pos, is); + go(is); engine.wait_for_search_finished(); nodes += nodesSearched; nodesSearched = 0; @@ -268,7 +262,7 @@ void UCIEngine::bench(Position& pos, std::istream& args) { else if (token == "setoption") setoption(is); else if (token == "position") - position(pos, is); + position(is); else if (token == "ucinewgame") { engine.search_clear(); // search_clear may take a while @@ -294,7 +288,7 @@ void UCIEngine::setoption(std::istringstream& is) { engine.get_options().setoption(is); } -void UCIEngine::position(Position& pos, std::istringstream& is) { +void UCIEngine::position(std::istringstream& is) { std::string token, fen; is >> token; @@ -317,7 +311,6 @@ void UCIEngine::position(Position& pos, std::istringstream& is) { moves.push_back(token); } - pos.set(fen, engine.get_options()["UCI_Chess960"], pos.state()); engine.set_position(fen, moves); } @@ -425,9 +418,14 @@ std::string UCIEngine::move(Move m, bool chess960) { } +std::string UCIEngine::to_lower(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return std::tolower(c); }); + + return str; +} + Move UCIEngine::to_move(const Position& pos, std::string str) { - if (str.length() == 5) - str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased + str = to_lower(str); for (const auto& m : MoveList(pos)) if (str == move(m, pos.is_chess960())) diff --git a/src/uci.h b/src/uci.h index fa359db4..ee8c2814 100644 --- a/src/uci.h +++ b/src/uci.h @@ -46,9 +46,10 @@ class UCIEngine { static std::string square(Square s); static std::string move(Move m, bool chess960); static std::string wdl(Value v, const Position& pos); + static std::string to_lower(std::string str); static Move to_move(const Position& pos, std::string str); - static Search::LimitsType parse_limits(const Position& pos, std::istream& is); + static Search::LimitsType parse_limits(std::istream& is); auto& engine_options() { return engine.get_options(); } @@ -56,9 +57,9 @@ class UCIEngine { Engine engine; CommandLine cli; - void go(Position& pos, std::istringstream& is); - void bench(Position& pos, std::istream& args); - void position(Position& pos, std::istringstream& is); + void go(std::istringstream& is); + void bench(std::istream& args); + void position(std::istringstream& is); void setoption(std::istringstream& is); static void on_update_no_moves(const Engine::InfoShort& info); From c55ae376f62de80fd20822954aaa6c7cd23eb2fa Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 13 Apr 2024 21:54:10 +0200 Subject: [PATCH 646/678] Fix wrong sign for 200 TB score Fix another case of 9032c6cbe74ccf7e8963755501e7e6cc473ae471 * TB values can have a distance of 0, mainly when we are in a tb position but haven't found mate. * Add a missing whitespace to UCIEngine::on_update_no_moves() Closes https://github.com/official-stockfish/Stockfish/pull/5172 No functional change --- src/score.cpp | 2 +- src/score.h | 7 ++++--- src/uci.cpp | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/score.cpp b/src/score.cpp index d1a8a6ab..292f5340 100644 --- a/src/score.cpp +++ b/src/score.cpp @@ -36,7 +36,7 @@ Score::Score(Value v, const Position& pos) { else if (std::abs(v) <= VALUE_TB) { auto distance = VALUE_TB - std::abs(v); - score = (v > 0) ? TBWin{distance} : TBWin{-distance}; + score = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false}; } else { diff --git a/src/score.h b/src/score.h index b94d9f7f..2eb40f7e 100644 --- a/src/score.h +++ b/src/score.h @@ -34,8 +34,9 @@ class Score { int plies; }; - struct TBWin { - int plies; + struct Tablebase { + int plies; + bool win; }; struct InternalUnits { @@ -61,7 +62,7 @@ class Score { } private: - std::variant score; + std::variant score; }; } diff --git a/src/uci.cpp b/src/uci.cpp index 8f697836..8e20207b 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -357,9 +357,9 @@ std::string UCIEngine::format_score(const Score& s) { auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; return std::string("mate ") + std::to_string(m); }, - [](Score::TBWin tb) -> std::string { + [](Score::Tablebase tb) -> std::string { return std::string("cp ") - + std::to_string((tb.plies > 0 ? TB_CP - tb.plies : -TB_CP - tb.plies)); + + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); }, [](Score::InternalUnits units) -> std::string { return std::string("cp ") + std::to_string(units.value); @@ -435,7 +435,7 @@ Move UCIEngine::to_move(const Position& pos, std::string str) { } void UCIEngine::on_update_no_moves(const Engine::InfoShort& info) { - sync_cout << "info depth" << info.depth << " score " << format_score(info.score) << sync_endl; + sync_cout << "info depth " << info.depth << " score " << format_score(info.score) << sync_endl; } void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) { From 432995ad82119070afa0bf720eb65d800bcbf817 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 7 Apr 2024 14:26:23 +0200 Subject: [PATCH 647/678] Update outdated comments closes https://github.com/official-stockfish/Stockfish/pull/5158 No functional change --- src/position.cpp | 1 - src/search.cpp | 4 ++-- src/tt.cpp | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index fd167895..78e62bda 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -744,7 +744,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update board and piece lists remove_piece(capsq); - // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; diff --git a/src/search.cpp b/src/search.cpp index 24805aa7..0eb0f45e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1083,7 +1083,7 @@ moves_loop: // When in check, search starts here extension = -1; } - // Recapture extensions (~0 Elo on STC, ~1 Elo on LTC) + // Extension for capturing the previous moved piece (~0 Elo on STC, ~1 Elo on LTC) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] @@ -1147,7 +1147,7 @@ moves_loop: // When in check, search starts here { // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension - // beyond the first move depth. This may lead to hidden multiple extensions. + // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r, newDepth + 1)); diff --git a/src/tt.cpp b/src/tt.cpp index 9d4d2eca..41ed4591 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -67,7 +67,7 @@ uint8_t TTEntry::relative_age(const uint8_t generation8) const { // Sets the size of the transposition table, -// measured in megabytes. Transposition table consists of a power of 2 number +// measured in megabytes. Transposition table consists // of clusters and each cluster consists of ClusterSize number of TTEntry. void TranspositionTable::resize(size_t mbSize, int threadCount) { aligned_large_pages_free(table); From d3fc1d835e5144cc98d6a7658fb8cfd9370792f1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 10 Apr 2024 23:10:07 +0200 Subject: [PATCH 648/678] Refactor elapsed time checks in search Small improvement of the elapsed time usage in search, makes the code easier to read overall. Also Search::Worker::iterative_deepening() now only checks the elapsed time once, instead of 3 times in a row. Non Regression STC: https://tests.stockfishchess.org/tests/view/6617005d5a4693796d965c3c LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 61024 W: 16002 L: 15806 D: 29216 Ptnml(0-2): 243, 6874, 16102, 7030, 263 closes https://github.com/official-stockfish/Stockfish/pull/5163 No functional change --- src/search.cpp | 28 +++++++++++++++------------- src/search.h | 2 ++ src/timeman.cpp | 3 --- src/timeman.h | 6 ++++-- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0eb0f45e..00636865 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -340,7 +340,7 @@ void Search::Worker::iterative_deepening() { // When failing high/low give some update (without cluttering // the UI) before a re-search. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + && elapsed() > 3000) main_manager()->pv(*this, threads, tt, rootDepth); // In case of failing low/high increase aspiration window and @@ -371,8 +371,7 @@ void Search::Worker::iterative_deepening() { std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); if (mainThread - && (threads.stop || pvIdx + 1 == multiPV - || mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + && (threads.stop || pvIdx + 1 == multiPV || elapsed() > 3000) // A thread that aborted search can have mated-in/TB-loss PV and score // that cannot be trusted, i.e. it can be delayed or refuted if we would have // had time to fully search other root-moves. Thus we suppress this output and @@ -448,13 +447,14 @@ void Search::Worker::iterative_deepening() { if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); - if (completedDepth >= 10 && nodesEffort >= 97 - && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.739 + auto elapsedTime = elapsed(); + + if (completedDepth >= 10 && nodesEffort >= 97 && elapsedTime > totalTime * 0.739 && !mainThread->ponder) threads.stop = true; // Stop the search if we have exceeded the totalTime - if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) + if (elapsedTime > totalTime) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". @@ -464,9 +464,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = - mainThread->ponder - || mainThread->tm.elapsed(threads.nodes_searched()) <= totalTime * 0.506; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.506; } mainThread->iterValue[iterIdx] = bestValue; @@ -928,8 +926,7 @@ moves_loop: // When in check, search starts here ss->moveCount = ++moveCount; - if (rootNode && is_mainthread() - && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) + if (rootNode && is_mainthread() && elapsed() > 3000) { main_manager()->updates.onIter( {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); @@ -1631,6 +1628,11 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { return (reductionScale + 1123 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); } +TimePoint Search::Worker::elapsed() const { + return main_manager()->tm.elapsed([this]() { return threads.nodes_searched(); }); +} + + namespace { // Adjusts a mate or TB score from "plies to mate from the root" // to "plies to mate from the current position". Standard scores are unchanged. @@ -1845,7 +1847,7 @@ void SearchManager::check_time(Search::Worker& worker) { static TimePoint lastInfoTime = now(); - TimePoint elapsed = tm.elapsed(worker.threads.nodes_searched()); + TimePoint elapsed = tm.elapsed([&worker]() { return worker.threads.nodes_searched(); }); TimePoint tick = worker.limits.startTime + elapsed; if (tick - lastInfoTime >= 1000) @@ -1877,7 +1879,7 @@ void SearchManager::pv(const Search::Worker& worker, const auto& rootMoves = worker.rootMoves; const auto& pos = worker.rootPos; size_t pvIdx = worker.pvIdx; - TimePoint time = tm.elapsed(nodes) + 1; + TimePoint time = tm.elapsed([nodes]() { return nodes; }) + 1; size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); diff --git a/src/search.h b/src/search.h index d30a06fe..3ceaf5dd 100644 --- a/src/search.h +++ b/src/search.h @@ -275,6 +275,8 @@ class Worker { return static_cast(manager.get()); } + TimePoint elapsed() const; + LimitsType limits; size_t pvIdx, pvLast; diff --git a/src/timeman.cpp b/src/timeman.cpp index 229ff3e9..c651745f 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -30,9 +30,6 @@ namespace Stockfish { TimePoint TimeManagement::optimum() const { return optimumTime; } TimePoint TimeManagement::maximum() const { return maximumTime; } -TimePoint TimeManagement::elapsed(size_t nodes) const { - return useNodesTime ? TimePoint(nodes) : now() - startTime; -} void TimeManagement::clear() { availableNodes = 0; // When in 'nodes as time' mode diff --git a/src/timeman.h b/src/timeman.h index b07712a2..35c3cfc0 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -19,7 +19,6 @@ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED -#include #include #include "misc.h" @@ -41,7 +40,10 @@ class TimeManagement { TimePoint optimum() const; TimePoint maximum() const; - TimePoint elapsed(std::size_t nodes) const; + template + TimePoint elapsed(FUNC nodes) const { + return useNodesTime ? TimePoint(nodes()) : now() - startTime; + } void clear(); void advance_nodes_time(std::int64_t nodes); From 9021a61807ae8f869ffd7ba55d1b4f0404379dca Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 12 Apr 2024 00:00:59 +0300 Subject: [PATCH 649/678] Trivial cleanup Make naming and declaration of futilityValue in search consistent between different places. closes https://github.com/official-stockfish/Stockfish/pull/5165 No functional change. --- src/search.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 00636865..6813c1a5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -963,11 +963,11 @@ moves_loop: // When in check, search starts here if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { Piece capturedPiece = pos.piece_on(move.to_sq()); - int futilityEval = - ss->staticEval + 287 + 277 * lmrDepth + PieceValue[capturedPiece] + Value futilityValue = + ss->staticEval + 288 + 277 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; - if (futilityEval < alpha) + if (futilityValue <= alpha) continue; } @@ -1389,7 +1389,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Key posKey; Move ttMove, move, bestMove; Depth ttDepth; - Value bestValue, value, ttValue, futilityValue, futilityBase; + Value bestValue, value, ttValue, futilityBase; bool pvHit, givesCheck, capture; int moveCount; Color us = pos.side_to_move(); @@ -1518,7 +1518,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (moveCount > 2) continue; - futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())]; + 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) From d0e72c19fa878645afd3d2f573a2587b02e26d47 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:55:28 +0700 Subject: [PATCH 650/678] fix clang compiler warning for avx512 build Initialize variable in constexpr function to get rid of clang compiler warning for avx512 build. closes https://github.com/official-stockfish/Stockfish/pull/5176 Non-functional change --- src/nnue/nnue_feature_transformer.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 3101c8d2..0a0f4217 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -229,8 +229,7 @@ class FeatureTransformer { static constexpr void order_packs([[maybe_unused]] uint64_t* v) { #if defined(USE_AVX512) // _mm512_packs_epi16 ordering - uint64_t tmp0, tmp1; - tmp0 = v[2], tmp1 = v[3]; + 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; @@ -246,8 +245,7 @@ class FeatureTransformer { static constexpr void inverse_order_packs([[maybe_unused]] uint64_t* v) { #if defined(USE_AVX512) // Inverse _mm512_packs_epi16 ordering - uint64_t tmp0, tmp1; - tmp0 = v[2], tmp1 = v[3]; + 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; From 6fc7da44ad9c7e2ba6062d5c79daafd29a4dcd6f Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Tue, 16 Apr 2024 08:23:42 +0200 Subject: [PATCH 651/678] update the WDL model The patch only changes the displayed cp and wdl values. closes https://github.com/official-stockfish/Stockfish/pull/5178 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 8e20207b..c707f6dc 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -330,8 +330,8 @@ WinRateParams win_rate_params(const Position& pos) { double m = std::clamp(material, 10, 78) / 58.0; // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model - constexpr double as[] = {-185.71965483, 504.85014385, -438.58295743, 474.04604627}; - constexpr double bs[] = {89.23542728, -137.02141296, 73.28669021, 47.53376190}; + constexpr double as[] = {-150.77043883, 394.96159472, -321.73403766, 406.15850091}; + constexpr double bs[] = {62.33245393, -91.02264855, 45.88486850, 51.63461272}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From 1a8de45b8c2887e8d5efe61498f3acccf5f36116 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 20 Apr 2024 15:33:07 +0200 Subject: [PATCH 652/678] Improve CI the recent refactoring has shown some limitations of our testing, hence we add a couple of more tests including: * expected mate score * expected mated score * expected in TB win score * expected in TB loss score * expected info line output * expected info line output (wdl) closes https://github.com/official-stockfish/Stockfish/pull/5181 No functional change --- .github/workflows/sanitizers.yml | 3 + tests/instrumented.sh | 116 +++++++++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 612f1275..78260a18 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -31,6 +31,9 @@ jobs: - name: Run under valgrind-thread make_option: "" instrumented_option: valgrind-thread + - name: Run non-instrumented + make_option: "" + instrumented_option: none defaults: run: working-directory: src diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 525c7e04..ac534c16 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -21,14 +21,14 @@ case $1 in echo "valgrind testing started" prefix='' exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full' - postfix='1>/dev/null' + postfix='' threads="1" ;; --valgrind-thread) echo "valgrind-thread testing started" prefix='' exeprefix='valgrind --fair-sched=try --error-exitcode=42' - postfix='1>/dev/null' + postfix='' threads="2" ;; --sanitizer-undefined) @@ -112,7 +112,12 @@ diff $network verify.nnue # more general testing, following an uci protocol exchange cat << EOF > game.exp set timeout 240 + # to correctly catch eof we need the following line + # expect_before timeout { exit 2 } eof { exit 3 } + expect_before timeout { exit 2 } + spawn $exeprefix ./stockfish + expect "Stockfish" send "uci\n" expect "uciok" @@ -125,27 +130,101 @@ cat << EOF > game.exp send "go nodes 1000\n" expect "bestmove" + send "ucinewgame\n" send "position startpos moves e2e4 e7e6\n" send "go nodes 1000\n" expect "bestmove" + send "ucinewgame\n" send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n" send "go depth 10\n" expect "bestmove" - send "setoption name UCI_ShowWDL value true\n" - send "position startpos\n" + send "ucinewgame\n" + send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n" send "flip\n" - send "go depth 5\n" + send "go depth 10\n" expect "bestmove" - send "setoption name Skill Level value 10\n" + send "ucinewgame\n" send "position startpos\n" send "go depth 5\n" + expect -re {info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect "bestmove" + + send "ucinewgame\n" + send "setoption name UCI_ShowWDL value true\n" + send "position startpos\n" + send "go depth 9\n" + expect -re {info depth 1 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 2 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 3 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 4 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 5 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 6 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 7 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 8 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 9 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} expect "bestmove" send "setoption name Clear Hash\n" + send "ucinewgame\n" + send "position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6\n" + send "go depth 18\n" + expect "score mate 1" + expect "pv d5e6" + expect "bestmove d5e6" + + send "ucinewgame\n" + send "position fen 2brrb2/8/p7/Q7/1p1kpPp1/1P1pN1K1/3P4/8 b - -\n" + send "go depth 18\n" + expect "score mate -1" + expect "bestmove" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" + send "go depth 18\n" + expect "score mate 2 * pv c6d7 * f7f5" + expect "bestmove c6d7" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" + send "go mate 2\n" + expect "score mate 2 * pv c6d7" + expect "bestmove c6d7" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" + send "go nodes 10000\n" + expect "score mate 2 * pv c6d7 * f7f5" + expect "bestmove c6d7" + + send "ucinewgame\n" + send "position fen 1NR2B2/5p2/5p2/1p1kpp2/1P2rp2/2P1pB2/2P1P1K1/8 b - - \n" + send "go depth 18\n" + expect "score mate -2" + expect "pv d5e6 c8d8" + expect "bestmove d5e6" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7 f2f1q\n" + send "go depth 18\n" + expect "score mate 1 * pv f7f5" + expect "bestmove f7f5" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" + send "go depth 18 searchmoves c6d7\n" + expect "score mate 2 * pv c6d7 * f7f5" + expect "bestmove c6d7" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7\n" + send "go depth 18 searchmoves e3e2\n" + expect "score mate -1 * pv e3e2 f7f5" + expect "bestmove e3e2" + send "setoption name EvalFile value verify.nnue\n" send "position startpos\n" send "go depth 5\n" @@ -154,6 +233,13 @@ cat << EOF > game.exp send "setoption name MultiPV value 4\n" send "position startpos\n" send "go depth 5\n" + expect "bestmove" + + send "setoption name Skill Level value 10\n" + send "position startpos\n" + send "go depth 5\n" + expect "bestmove" + send "setoption name Skill Level value 20\n" send "quit\n" expect eof @@ -171,17 +257,30 @@ fi cat << EOF > syzygy.exp set timeout 240 + # to correctly catch eof we need the following line + # expect_before timeout { exit 2 } eof { exit 3 } + expect_before timeout { exit 2 } spawn $exeprefix ./stockfish + expect "Stockfish" send "uci\n" send "setoption name SyzygyPath value ../tests/syzygy/\n" - expect "info string Found 35 tablebases" {} timeout {exit 1} + expect "info string Found 35 tablebases" send "bench 128 1 8 default depth\n" + expect "Nodes searched :" send "ucinewgame\n" send "position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\n" send "go depth 5\n" + expect -re {score cp 20000|score mate} expect "bestmove" + send "ucinewgame\n" send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\n" send "go depth 5\n" + expect -re {score cp 20000|score mate} + expect "bestmove" + send "ucinewgame\n" + send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 b - - 0 1\n" + send "go depth 5\n" + expect -re {score cp -20000|score mate} expect "bestmove" send "quit\n" expect eof @@ -194,6 +293,9 @@ EOF for exp in game.exp syzygy.exp do + echo "======== $exp ==============" + cat $exp + echo "============================" echo "$prefix expect $exp $postfix" eval "$prefix expect $exp $postfix" From 56a9cc512e5ffb2310ad6e4676c77ce0485f31f3 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 20 Apr 2024 20:37:39 +0200 Subject: [PATCH 653/678] Move ALSR change to CI Workflow file It makes more sense to not (potentially) change the developers alsr entropy setting to make the test run through. This should be an active choice even if the test then might fail locally for them. closes https://github.com/official-stockfish/Stockfish/pull/5182 No functional change --- .github/workflows/sanitizers.yml | 8 ++++++++ tests/instrumented.sh | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 78260a18..b75c06cf 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -58,6 +58,14 @@ jobs: - name: Check git run: git --version + # Since Linux Kernel 6.5 we are getting false positives from the ci, + # lower the ALSR entropy to disable ALSR, which works as a temporary workaround. + # https://github.com/google/sanitizers/issues/1716 + # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762 + + - name: Lower ALSR entropy + run: sudo sysctl -w vm.mmap_rnd_bits=28 + # Sanitizers - name: ${{ matrix.sanitizers.name }} diff --git a/tests/instrumented.sh b/tests/instrumented.sh index ac534c16..4c63fc57 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -8,13 +8,6 @@ error() } trap 'error ${LINENO}' ERR -# Since Linux Kernel 6.5 we are getting false positives from the ci, -# lower the ALSR entropy to disable ALSR, which works as a temporary workaround. -# https://github.com/google/sanitizers/issues/1716 -# https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762 -sudo sysctl -w vm.mmap_rnd_bits=28 - - # define suitable post and prefixes for testing options case $1 in --valgrind) From d47aa639bd614b37a59f87e6ab68496580f0cf3e Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Thu, 18 Apr 2024 18:50:09 +0800 Subject: [PATCH 654/678] Tweak TT aging and replacement strategies We change the definition of "age" from "age of this position" to "age of this TT entry". In this way, despite being on the same position, when we save into TT, we always prefer the new entry as compared to the old one. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 152256 W: 39597 L: 39110 D: 73549 Ptnml(0-2): 556, 17562, 39398, 18063, 549 https://tests.stockfishchess.org/tests/view/6620faee3fe04ce4cefbf215 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 51564 W: 13242 L: 12895 D: 25427 Ptnml(0-2): 24, 5464, 14463, 5803, 28 https://tests.stockfishchess.org/tests/view/66231ab53fe04ce4cefc153e closes #5184 Bench 1479416 --- src/tt.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index 41ed4591..4885a781 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -40,7 +40,8 @@ void TTEntry::save( move16 = m; // Overwrite less valuable entries (cheapest checks first) - if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) + if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4 + || relative_age(generation8)) { assert(d > DEPTH_OFFSET); assert(d < 256 + DEPTH_OFFSET); @@ -123,13 +124,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16 || !tte[i].depth8) - { - constexpr uint8_t lowerBits = GENERATION_DELTA - 1; - - // Refresh with new generation, keeping the lower bits the same. - tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & lowerBits)); - return found = bool(tte[i].depth8), &tte[i]; - } + return found = bool(tte[i].depth8), &tte[i]; // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; From ddd250b9d655117920dd65a973cea2f8b3c57fce Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 22 Apr 2024 19:24:10 +0200 Subject: [PATCH 655/678] Restore NPS output for Perft Previously it was possible to also get the node counter after running a bench with perft, i.e. `./stockfish bench 1 1 5 current perft`, caused by a small regression from the uci refactoring. ``` Nodes searched: 4865609 =========================== Total time (ms) : 18 Nodes searched : 4865609 Nodes/second : 270311611 ```` closes https://github.com/official-stockfish/Stockfish/pull/5188 No functional change --- src/benchmark.cpp | 2 +- src/benchmark.h | 2 +- src/engine.cpp | 14 ++++++++------ src/engine.h | 4 ++++ src/perft.h | 7 +++---- src/uci.cpp | 26 ++++++++++++++++++++++---- src/uci.h | 10 ++++++---- 7 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 267a6b4b..3622ac8a 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -93,7 +93,7 @@ const std::vector Defaults = { } // namespace -namespace Stockfish { +namespace Stockfish::Benchmark { // Builds a list of UCI commands to be run by bench. There // are five parameters: TT size in MB, number of search threads that diff --git a/src/benchmark.h b/src/benchmark.h index 8905fcb1..b1eba40f 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -23,7 +23,7 @@ #include #include -namespace Stockfish { +namespace Stockfish::Benchmark { std::vector setup_bench(const std::string&, std::istream&); diff --git a/src/engine.cpp b/src/engine.cpp index 325b971e..4625e00a 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "evaluate.h" #include "misc.h" @@ -54,14 +55,15 @@ Engine::Engine(std::string path) : pos.set(StartFEN, false, &states->back()); } -void Engine::go(const Search::LimitsType& limits) { +std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { verify_networks(); - if (limits.perft) - { - perft(pos.fen(), limits.perft, options["UCI_Chess960"]); - return; - } + return Benchmark::perft(fen, depth, isChess960); +} + +void Engine::go(const Search::LimitsType& limits) { + assert(limits.perft == 0); + verify_networks(); threads.start_thinking(options, pos, states, limits); } diff --git a/src/engine.h b/src/engine.h index 7122ee59..041f5678 100644 --- a/src/engine.h +++ b/src/engine.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "nnue/network.h" #include "position.h" @@ -33,6 +34,7 @@ #include "thread.h" #include "tt.h" #include "ucioption.h" +#include "syzygy/tbprobe.h" // for Stockfish::Depth namespace Stockfish { @@ -45,6 +47,8 @@ class Engine { Engine(std::string path = ""); ~Engine() { wait_for_search_finished(); } + std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960); + // non blocking call to start searching void go(const Search::LimitsType&); // non blocking call to stop searching diff --git a/src/perft.h b/src/perft.h index 2dbab828..e907742d 100644 --- a/src/perft.h +++ b/src/perft.h @@ -26,7 +26,7 @@ #include "types.h" #include "uci.h" -namespace Stockfish { +namespace Stockfish::Benchmark { // Utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. @@ -56,13 +56,12 @@ uint64_t perft(Position& pos, Depth depth) { return nodes; } -inline void perft(const std::string& fen, Depth depth, bool isChess960) { +inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) { StateListPtr states(new std::deque(1)); Position p; p.set(fen, isChess960, &states->back()); - uint64_t nodes = perft(p, depth); - sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return perft(p, depth); } } diff --git a/src/uci.cpp b/src/uci.cpp index c707f6dc..cb686a02 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -219,7 +219,11 @@ Search::LimitsType UCIEngine::parse_limits(std::istream& is) { void UCIEngine::go(std::istringstream& is) { Search::LimitsType limits = parse_limits(is); - engine.go(limits); + + if (limits.perft) + perft(limits); + else + engine.go(limits); } void UCIEngine::bench(std::istream& args) { @@ -233,7 +237,7 @@ void UCIEngine::bench(std::istream& args) { on_update_full(i, options["UCI_ShowWDL"]); }); - std::vector list = setup_bench(engine.fen(), args); + std::vector list = Benchmark::setup_bench(engine.fen(), args); num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); @@ -251,8 +255,16 @@ void UCIEngine::bench(std::istream& args) { << std::endl; if (token == "go") { - go(is); - engine.wait_for_search_finished(); + Search::LimitsType limits = parse_limits(is); + + if (limits.perft) + nodes = perft(limits); + else + { + engine.go(limits); + engine.wait_for_search_finished(); + } + nodes += nodesSearched; nodesSearched = 0; } @@ -288,6 +300,12 @@ void UCIEngine::setoption(std::istringstream& is) { engine.get_options().setoption(is); } +std::uint64_t UCIEngine::perft(const Search::LimitsType& limits) { + auto nodes = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return nodes; +} + void UCIEngine::position(std::istringstream& is) { std::string token, fen; diff --git a/src/uci.h b/src/uci.h index ee8c2814..55d580f9 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "engine.h" #include "misc.h" @@ -57,10 +58,11 @@ class UCIEngine { Engine engine; CommandLine cli; - void go(std::istringstream& is); - void bench(std::istream& args); - void position(std::istringstream& is); - void setoption(std::istringstream& is); + void go(std::istringstream& is); + void bench(std::istream& args); + void position(std::istringstream& is); + void setoption(std::istringstream& is); + std::uint64_t perft(const Search::LimitsType&); static void on_update_no_moves(const Engine::InfoShort& info); static void on_update_full(const Engine::InfoFull& info, bool showWDL); From fcba524793222fcdb1ca4254697b15e168f39ad2 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 22 Apr 2024 14:34:22 +0300 Subject: [PATCH 656/678] Tune Search Parameters Parameters Tune, adding also another tunable parameter (npmDiv) to be variable for different nets (bignet, smallnet, psqtOnly smallnet). P.s: The changed values are only the parameters where there is agreement among the different time controls, so in other words, the tunings are telling us that changing these specific values to this specific direction is good in all time controls, so there shouldn't be a high risk of regressing at longer time controls. Passed STC: LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 39552 W: 10329 L: 9999 D: 19224 Ptnml(0-2): 156, 4592, 9989, 4844, 195 https://tests.stockfishchess.org/tests/view/661be9a0bd68065432a088c0 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 56394 W: 14439 L: 14078 D: 27877 Ptnml(0-2): 30, 6152, 15480, 6497, 38 https://tests.stockfishchess.org/tests/view/661c746296961e72eb565406 closes https://github.com/official-stockfish/Stockfish/pull/5187 Bench: 1836777 --- src/evaluate.cpp | 14 +++++++------- src/movepick.cpp | 10 +++++----- src/search.cpp | 46 +++++++++++++++++++++++----------------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index dcbfedb4..ec120a48 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -58,14 +58,14 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) : networks.big.evaluate(pos, true, &nnueComplexity, false); - const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, - int npmConstant, int evalDiv, int shufflingConstant, - int shufflingDiv) { + const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, + int pawnCountMul, int npmConstant, int evalDiv, + int shufflingConstant, int shufflingDiv) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; - int npm = pos.non_pawn_material() / 64; + int npm = pos.non_pawn_material() / npmDiv; v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) + optimism * (npmConstant + npm)) / evalDiv; @@ -76,11 +76,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, }; if (!smallNet) - adjustEval(513, 32395, 919, 11, 145, 1036, 178, 204); + adjustEval(524, 32395, 66, 942, 11, 139, 1058, 178, 204); else if (psqtOnly) - adjustEval(517, 32857, 908, 7, 155, 1019, 224, 238); + adjustEval(517, 32857, 65, 908, 7, 155, 1006, 224, 238); else - adjustEval(499, 32793, 903, 9, 147, 1067, 208, 211); + adjustEval(515, 32793, 63, 944, 9, 140, 1067, 206, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/movepick.cpp b/src/movepick.cpp index c1119cf1..4a93662d 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -190,8 +190,8 @@ void MovePicker::score() { m.value += bool(pos.check_squares(pt) & to) * 16384; // bonus for escaping from capture - m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51000 - : pt == ROOK && !(to & threatenedByMinor) ? 24950 + m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51700 + : pt == ROOK && !(to & threatenedByMinor) ? 25600 : !(to & threatenedByPawn) ? 14450 : 0) : 0; @@ -200,7 +200,7 @@ void MovePicker::score() { m.value -= !(threatenedPieces & from) ? (pt == QUEEN ? bool(to & threatenedByRook) * 48150 + bool(to & threatenedByMinor) * 10650 - : pt == ROOK ? bool(to & threatenedByMinor) * 24500 + : pt == ROOK ? bool(to & threatenedByMinor) * 24335 : pt != PAWN ? bool(to & threatenedByPawn) * 14950 : 0) : 0; @@ -241,7 +241,7 @@ Move MovePicker::select(Pred filter) { // moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { - auto quiet_threshold = [](Depth d) { return -3550 * d; }; + auto quiet_threshold = [](Depth d) { return -3560 * d; }; top: switch (stage) @@ -310,7 +310,7 @@ top: return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) { - if ((cur - 1)->value > -8000 || (cur - 1)->value <= quiet_threshold(depth)) + if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) return *(cur - 1); // Remaining quiets are bad diff --git a/src/search.cpp b/src/search.cpp index 6813c1a5..183b7bce 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -57,9 +57,9 @@ static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 118 - 44 * noTtCutNode; + Value futilityMult = 118 - 45 * noTtCutNode; Value improvingDeduction = 52 * improving * futilityMult / 32; - Value worseningDeduction = (310 + 48 * improving) * oppWorsening * futilityMult / 1024; + Value worseningDeduction = (316 + 48 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -76,10 +76,10 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(211 * d - 315, 0, 1291); } +int stat_bonus(Depth d) { return std::clamp(214 * d - 318, 16, 1304); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 572 * d - 285 : 1372); } +int stat_malus(Depth d) { return (d < 4 ? 572 * d - 284 : 1355); } // 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); } @@ -303,12 +303,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 11 + avg * avg / 11254; + delta = 10 + avg * avg / 11480; 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] = 125 * avg / (std::abs(avg) + 91); + optimism[us] = 122 * avg / (std::abs(avg) + 92); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -752,7 +752,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 471 - (276 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 471 - (275 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -763,14 +763,14 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 284 + - (ss - 1)->statScore / 286 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 18001 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 315 && !excludedMove + && eval >= beta && ss->staticEval >= beta - 21 * depth + 312 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { @@ -881,7 +881,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 436; + probCutBeta = beta + 452; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -964,7 +964,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.piece_on(move.to_sq()); Value futilityValue = - ss->staticEval + 288 + 277 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 285 + 277 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityValue <= alpha) @@ -972,7 +972,7 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -199 * depth)) + if (!pos.see_ge(move, -203 * depth)) continue; } else @@ -992,10 +992,10 @@ moves_loop: // When in check, search starts here lmrDepth += history / 5285; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 58) + 131 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 57) + 131 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 14 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1006,7 +1006,7 @@ 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, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1026,11 +1026,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (64 + 59 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (65 + 59 * (ss->ttPv && !PvNode)) * depth / 63; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1134,10 +1134,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 5007; + + (*contHist[3])[movedPiece][move.to_sq()] - 5024; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 12901; + r -= ss->statScore / 13182; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1274,7 +1274,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13132 && value > -13295) + if (depth > 2 && depth < 12 && beta < 13546 && value > -13478) depth -= 2; assert(depth > 0); @@ -1319,7 +1319,7 @@ moves_loop: // When in check, search starts here { int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) + ((ss - 1)->moveCount > 11) - + (!ss->inCheck && bestValue <= ss->staticEval - 144); + + (!ss->inCheck && bestValue <= ss->staticEval - 142); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1477,7 +1477,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 246; + futilityBase = ss->staticEval + 250; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1625,7 +1625,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1123 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); + return (reductionScale + 1150 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); } TimePoint Search::Worker::elapsed() const { From 49ef4c935a5cb0e4d94096e6354caa06b36b3e3c Mon Sep 17 00:00:00 2001 From: gab8192 Date: Sat, 20 Apr 2024 21:26:00 +0200 Subject: [PATCH 657/678] Implement accumulator refresh table For each thread persist an accumulator cache for the network, where each cache contains multiple entries for each of the possible king squares. When the accumulator needs to be refreshed, the cached entry is used to more efficiently update the accumulator, instead of rebuilding it from scratch. This idea, was first described by Luecx (author of Koivisto) and is commonly referred to as "Finny Tables". When the accumulator needs to be refreshed, instead of filling it with biases and adding every piece from scratch, we... 1. Take the `AccumulatorRefreshEntry` associated with the new king bucket 2. Calculate the features to activate and deactivate (from differences between bitboards in the entry and bitboards of the actual position) 3. Apply the updates on the refresh entry 4. Copy the content of the refresh entry accumulator to the accumulator we were refreshing 5. Copy the bitboards from the position to the refresh entry, to match the newly updated accumulator Results at STC: https://tests.stockfishchess.org/tests/view/662301573fe04ce4cefc1386 (first version) https://tests.stockfishchess.org/tests/view/6627fa063fe04ce4cefc6560 (final) Non-Regression between first and final: https://tests.stockfishchess.org/tests/view/662801e33fe04ce4cefc660a STC SMP: https://tests.stockfishchess.org/tests/view/662808133fe04ce4cefc667c closes https://github.com/official-stockfish/Stockfish/pull/5183 No functional change --- src/evaluate.cpp | 19 ++- src/evaluate.h | 8 +- src/nnue/features/half_ka_v2_hm.cpp | 4 +- src/nnue/features/half_ka_v2_hm.h | 8 +- src/nnue/network.cpp | 45 ++++--- src/nnue/network.h | 24 ++-- src/nnue/nnue_accumulator.h | 70 +++++++++- src/nnue/nnue_feature_transformer.h | 192 ++++++++++++++++++++++++++-- src/nnue/nnue_misc.cpp | 17 ++- src/nnue/nnue_misc.h | 9 +- src/search.cpp | 26 ++-- src/search.h | 7 +- 12 files changed, 349 insertions(+), 80 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index ec120a48..f5746ca5 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -25,12 +25,14 @@ #include #include #include +#include #include "nnue/network.h" #include "nnue/nnue_misc.h" #include "position.h" #include "types.h" #include "uci.h" +#include "nnue/nnue_accumulator.h" namespace Stockfish { @@ -45,7 +47,10 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int optimism) { +Value Eval::evaluate(const Eval::NNUE::Networks& networks, + const Position& pos, + Eval::NNUE::AccumulatorCaches& caches, + int optimism) { assert(!pos.checkers()); @@ -55,8 +60,8 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int nnueComplexity; int v; - Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) - : networks.big.evaluate(pos, true, &nnueComplexity, false); + Value nnue = smallNet ? networks.small.evaluate(pos, nullptr, true, &nnueComplexity, psqtOnly) + : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, int pawnCountMul, int npmConstant, int evalDiv, @@ -94,20 +99,22 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, // Trace scores are from white's point of view std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { + auto caches = std::make_unique(); + if (pos.checkers()) return "Final evaluation: none (in check)"; std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos, networks) << '\n'; + ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v = networks.big.evaluate(pos, false); + Value v = networks.big.evaluate(pos, &caches->big, false); v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; - v = evaluate(networks, pos, VALUE_ZERO); + v = evaluate(networks, pos, *caches, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index da9c7074..38615ff7 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -40,14 +40,16 @@ constexpr inline int SmallNetThreshold = 1274, PsqtOnlyThreshold = 2389; namespace NNUE { struct Networks; +struct AccumulatorCaches; } std::string trace(Position& pos, const Eval::NNUE::Networks& networks); int simple_eval(const Position& pos, Color c); -Value evaluate(const NNUE::Networks& networks, const Position& pos, int optimism); - - +Value evaluate(const NNUE::Networks& networks, + const Position& pos, + Eval::NNUE::AccumulatorCaches& caches, + int optimism); } // namespace Eval } // namespace Stockfish diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5789db48..71782a7b 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -23,7 +23,7 @@ #include "../../bitboard.h" #include "../../position.h" #include "../../types.h" -#include "../nnue_common.h" +#include "../nnue_accumulator.h" namespace Stockfish::Eval::NNUE::Features { @@ -49,6 +49,8 @@ void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) // Explicit template instantiations template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); +template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); // Get a list of indices for recently changed features template diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 8363184f..96349704 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -63,10 +63,6 @@ class HalfKAv2_hm { {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}}; - // Index of a feature for a given king position and another piece on some square - template - static IndexType make_index(Square s, Piece pc, Square ksq); - public: // Feature name static constexpr const char* Name = "HalfKAv2_hm(Friend)"; @@ -126,6 +122,10 @@ class HalfKAv2_hm { static constexpr IndexType MaxActiveDimensions = 32; using IndexList = ValueList; + // Index of a feature for a given king position and another piece on some square + template + static IndexType make_index(Square s, Piece pc, Square ksq); + // Get a list of indices for active features template static void append_active_indices(const Position& pos, IndexList& active); diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index bea3e7cb..656ad97a 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -186,10 +186,11 @@ bool Network::save(const std::optional& filename template -Value Network::evaluate(const Position& pos, - bool adjusted, - int* complexity, - bool psqtOnly) const { +Value Network::evaluate(const Position& pos, + AccumulatorCaches::Cache* cache, + bool adjusted, + int* complexity, + bool psqtOnly) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -197,20 +198,21 @@ Value Network::evaluate(const Position& pos, constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType transformedFeatures - [FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket, psqtOnly); + const auto psqt = + featureTransformer->transform(pos, cache, transformedFeatures, bucket, psqtOnly); const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0; if (complexity) @@ -255,26 +257,29 @@ void Network::verify(std::string evalfilePath) const { template -void Network::hint_common_access(const Position& pos, bool psqtOnl) const { - featureTransformer->hint_common_access(pos, psqtOnl); +void Network::hint_common_access(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnl) const { + featureTransformer->hint_common_access(pos, cache, psqtOnl); } - template -NnueEvalTrace Network::trace_evaluate(const Position& pos) const { +NnueEvalTrace +Network::trace_evaluate(const Position& pos, + AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType transformedFeatures - [FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); @@ -284,7 +289,7 @@ NnueEvalTrace Network::trace_evaluate(const Position& pos) co for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, transformedFeatures, bucket, false); + featureTransformer->transform(pos, cache, transformedFeatures, bucket, false); const auto positional = network[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); diff --git a/src/nnue/network.h b/src/nnue/network.h index 21e1c622..df59732d 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -31,10 +31,10 @@ #include "nnue_architecture.h" #include "nnue_feature_transformer.h" #include "nnue_misc.h" +#include "nnue_accumulator.h" namespace Stockfish::Eval::NNUE { - enum class EmbeddedNNUEType { BIG, SMALL, @@ -43,6 +43,8 @@ enum class EmbeddedNNUEType { template class Network { + static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions; + public: Network(EvalFile file, EmbeddedNNUEType type) : evalFile(file), @@ -51,17 +53,20 @@ class Network { void load(const std::string& rootDirectory, std::string evalfilePath); bool save(const std::optional& filename) const; - - Value evaluate(const Position& pos, - bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false) const; + Value evaluate(const Position& pos, + AccumulatorCaches::Cache* cache, + bool adjusted = false, + int* complexity = nullptr, + bool psqtOnly = false) const; - void hint_common_access(const Position& pos, bool psqtOnl) const; + void hint_common_access(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnl) const; void verify(std::string evalfilePath) const; - NnueEvalTrace trace_evaluate(const Position& pos) const; + NnueEvalTrace trace_evaluate(const Position& pos, + AccumulatorCaches::Cache* cache) const; private: void load_user_net(const std::string&, const std::string&); @@ -89,6 +94,9 @@ class Network { // Hash value of evaluation function structure static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); + + template + friend struct AccumulatorCaches::Cache; }; // Definitions of the network types diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index c0746b4e..8d73dbef 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -28,13 +28,75 @@ namespace Stockfish::Eval::NNUE { +using BiasType = std::int16_t; +using PSQTWeightType = std::int32_t; +using IndexType = std::uint32_t; + // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[2][Size]; - std::int32_t psqtAccumulation[2][PSQTBuckets]; - bool computed[2]; - bool computedPSQT[2]; + std::int16_t accumulation[COLOR_NB][Size]; + std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; + bool computed[COLOR_NB]; + bool computedPSQT[COLOR_NB]; +}; + + +// AccumulatorCaches struct provides per-thread accumulator caches, where each +// cache contains multiple entries for each of the possible king squares. +// When the accumulator needs to be refreshed, the cached entry is used to more +// efficiently update the accumulator, instead of rebuilding it from scratch. +// This idea, was first described by Luecx (author of Koivisto) and +// is commonly referred to as "Finny Tables". +struct AccumulatorCaches { + + template + struct alignas(CacheLineSize) Cache { + + struct alignas(CacheLineSize) Entry { + BiasType accumulation[COLOR_NB][Size]; + PSQTWeightType psqtAccumulation[COLOR_NB][PSQTBuckets]; + Bitboard byColorBB[COLOR_NB][COLOR_NB]; + Bitboard byTypeBB[COLOR_NB][PIECE_TYPE_NB]; + + // To initialize a refresh entry, we set all its bitboards empty, + // so we put the biases in the accumulation, without any weights on top + void clear(const BiasType* biases) { + + std::memset(byColorBB, 0, sizeof(byColorBB)); + std::memset(byTypeBB, 0, sizeof(byTypeBB)); + + std::memcpy(accumulation[WHITE], biases, Size * sizeof(BiasType)); + std::memcpy(accumulation[BLACK], biases, Size * sizeof(BiasType)); + + std::memset(psqtAccumulation, 0, sizeof(psqtAccumulation)); + } + }; + + template + void clear(const Network& network) { + for (auto& entry : entries) + entry.clear(network.featureTransformer->biases); + } + + void clear(const BiasType* biases) { + for (auto& entry : entries) + entry.clear(biases); + } + + Entry& operator[](Square sq) { return entries[sq]; } + + std::array entries; + }; + + template + void clear(const Networks& networks) { + big.clear(networks.big); + } + + // When adding a new cache for a network, i.e. the smallnet + // the appropriate condition must be added to FeatureTransformer::update_accumulator_refresh. + Cache big; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 0a0f4217..88f0e403 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -195,10 +195,10 @@ template StateInfo::*accPtr> class FeatureTransformer { - private: // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; + private: #ifdef VECTOR static constexpr int NumRegs = BestRegisterCount(); @@ -306,10 +306,13 @@ class FeatureTransformer { } // Convert input features - std::int32_t - transform(const Position& pos, OutputType* output, int bucket, bool psqtOnly) const { - update_accumulator(pos, psqtOnly); - update_accumulator(pos, psqtOnly); + std::int32_t transform(const Position& pos, + AccumulatorCaches::Cache* cache, + OutputType* output, + int bucket, + bool psqtOnly) const { + update_accumulator(pos, cache, psqtOnly); + update_accumulator(pos, cache, psqtOnly); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; @@ -371,9 +374,11 @@ class FeatureTransformer { return psqt; } // end of function transform() - void hint_common_access(const Position& pos, bool psqtOnly) const { - hint_common_access_for_perspective(pos, psqtOnly); - hint_common_access_for_perspective(pos, psqtOnly); + void hint_common_access(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnly) const { + hint_common_access_for_perspective(pos, cache, psqtOnly); + hint_common_access_for_perspective(pos, cache, psqtOnly); } private: @@ -650,7 +655,161 @@ class FeatureTransformer { } template - void update_accumulator_refresh(const Position& pos, bool psqtOnly) const { + void update_accumulator_refresh_cache(const Position& pos, + AccumulatorCaches::Cache* cache) const { + assert(cache != nullptr); + + Square ksq = pos.square(Perspective); + + auto& entry = (*cache)[ksq]; + + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = true; + accumulator.computedPSQT[Perspective] = true; + + FeatureSet::IndexList removed, added; + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = + entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + } + } + +#ifdef VECTOR + vec_t acc[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; + + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto entryTile = + reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = entryTile[k]; + + for (int i = 0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + for (int i = 0; i < int(removed.size()); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + for (IndexType k = 0; k < NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + auto entryTilePsqt = reinterpret_cast( + &entry.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = entryTilePsqt[k]; + + for (int i = 0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + for (int i = 0; i < int(removed.size()); ++i) + { + IndexType index = removed[i]; + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&entryTilePsqt[k], psqt[k]); + } + +#else + + for (const auto index : added) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + } + for (const auto index : removed) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] -= weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + } + +#endif + + // The accumulator of the refresh entry has been updated. + // Now copy its content to the actual accumulator we were refreshing + + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation[Perspective], + sizeof(int32_t) * PSQTBuckets); + + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], + sizeof(BiasType) * HalfDimensions); + + for (Color c : {WHITE, BLACK}) + entry.byColorBB[Perspective][c] = pos.pieces(c); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.byTypeBB[Perspective][pt] = pos.pieces(pt); + } + + template + void + update_accumulator_refresh(const Position& pos, + [[maybe_unused]] AccumulatorCaches::Cache* cache, + bool psqtOnly) const { + + // When we are refreshing the accumulator of the big net, + // redirect to the version of refresh that uses the refresh table. + // Using the cache for the small net is not beneficial. + if constexpr (HalfDimensions == Eval::NNUE::TransformedFeatureDimensionsBig) + { + update_accumulator_refresh_cache(pos, cache); + return; + } + #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array // is defined in the VECTOR code below, once in each branch @@ -764,7 +923,9 @@ class FeatureTransformer { } template - void hint_common_access_for_perspective(const Position& pos, bool psqtOnly) const { + void hint_common_access_for_perspective(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnly) const { // Works like update_accumulator, but performs less work. // Updates ONLY the accumulator for pos. @@ -787,11 +948,13 @@ class FeatureTransformer { psqtOnly); } else - update_accumulator_refresh(pos, psqtOnly); + update_accumulator_refresh(pos, cache, psqtOnly); } template - void update_accumulator(const Position& pos, bool psqtOnly) const { + void update_accumulator(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnly) const { auto [oldest_st, next] = try_find_computed_accumulator(pos, psqtOnly); @@ -813,9 +976,12 @@ class FeatureTransformer { psqtOnly); } else - update_accumulator_refresh(pos, psqtOnly); + update_accumulator_refresh(pos, cache, psqtOnly); } + template + friend struct AccumulatorCaches::Cache; + alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 3fa6e1b6..51838fef 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -42,13 +42,15 @@ namespace Stockfish::Eval::NNUE { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -void hint_common_parent_position(const Position& pos, const Networks& networks) { +void hint_common_parent_position(const Position& pos, + const Networks& networks, + AccumulatorCaches& caches) { int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, nullptr, simpleEvalAbs > Eval::PsqtOnlyThreshold); else - networks.big.hint_common_access(pos, false); + networks.big.hint_common_access(pos, &caches.big, false); } namespace { @@ -104,7 +106,8 @@ void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& p // Returns a string with the value of each piece on a board, // and a table for (PSQT, Layers) values bucket by bucket. -std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { +std::string +trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) { std::stringstream ss; @@ -130,7 +133,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - Value base = networks.big.evaluate(pos); + Value base = networks.big.evaluate(pos, &caches.big); base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= FILE_H; ++f) @@ -149,7 +152,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = false; - Value eval = networks.big.evaluate(pos); + Value eval = networks.big.evaluate(pos, &caches.big); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; @@ -167,7 +170,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << board[row] << '\n'; ss << '\n'; - auto t = networks.big.trace_evaluate(pos); + auto t = networks.big.trace_evaluate(pos, &caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index 5eab0218..27a93f88 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -50,12 +50,13 @@ struct NnueEvalTrace { std::size_t correctBucket; }; - struct Networks; +struct AccumulatorCaches; - -std::string trace(Position& pos, const Networks& networks); -void hint_common_parent_position(const Position& pos, const Networks& networks); +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 diff --git a/src/search.cpp b/src/search.cpp index 183b7bce..893daab2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -33,6 +33,8 @@ #include "misc.h" #include "movegen.h" #include "movepick.h" +#include "nnue/network.h" +#include "nnue/nnue_accumulator.h" #include "nnue/nnue_common.h" #include "nnue/nnue_misc.h" #include "position.h" @@ -135,6 +137,7 @@ Search::Worker::Worker(SharedState& sharedState, // Unpack the SharedState struct into member variables thread_idx(thread_id), manager(std::move(sm)), + refreshTable(), options(sharedState.options), threads(sharedState.threads), tt(sharedState.tt), @@ -143,6 +146,10 @@ Search::Worker::Worker(SharedState& sharedState, } void Search::Worker::start_searching() { + + // Initialize accumulator refresh entries + refreshTable.clear(networks); + // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) { @@ -564,7 +571,7 @@ Value Search::Worker::search( if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) - ? evaluate(networks, pos, thisThread->optimism[us]) + ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score @@ -698,7 +705,7 @@ Value Search::Worker::search( { // 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); + Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -706,9 +713,9 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, refreshTable, thisThread->optimism[us]); else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos, networks); + Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -718,7 +725,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, refreshTable, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -875,7 +882,7 @@ Value Search::Worker::search( } } - Eval::NNUE::hint_common_parent_position(pos, networks); + Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable); } moves_loop: // When in check, search starts here @@ -1413,7 +1420,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) - ? evaluate(networks, pos, thisThread->optimism[us]) + ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1445,7 +1452,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); + unadjustedStaticEval = + evaluate(networks, pos, refreshTable, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1458,7 +1466,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = (ss - 1)->currentMove != Move::null() - ? evaluate(networks, pos, thisThread->optimism[us]) + ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) : -(ss - 1)->staticEval; ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); diff --git a/src/search.h b/src/search.h index 3ceaf5dd..0fd778b4 100644 --- a/src/search.h +++ b/src/search.h @@ -26,9 +26,9 @@ #include #include #include +#include #include #include -#include #include "misc.h" #include "movepick.h" @@ -37,6 +37,7 @@ #include "syzygy/tbprobe.h" #include "timeman.h" #include "types.h" +#include "nnue/nnue_accumulator.h" namespace Stockfish { @@ -301,6 +302,10 @@ class Worker { Tablebases::Config tbConfig; + // Used by NNUE + + Eval::NNUE::AccumulatorCaches refreshTable; + const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; From 886ed90ec3599cdf0dc4e7d07b0543a27028c6c0 Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:27:40 +0100 Subject: [PATCH 658/678] Use less time on recaptures Credit for the idea goes to peregrine on discord. Passed STC 10+0.1: https://tests.stockfishchess.org/tests/view/662652623fe04ce4cefc48cf LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 75712 W: 19793 L: 19423 D: 36496 Ptnml(0-2): 258, 8487, 20023, 8803, 285 Passed LTC 60+0.6: https://tests.stockfishchess.org/tests/view/6627495e3fe04ce4cefc59b6 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 49788 W: 12743 L: 12404 D: 24641 Ptnml(0-2): 29, 5141, 14215, 5480, 29 The code was updated slightly and tested for non-regression against the original code at STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 41952 W: 10912 L: 10698 D: 20342 Ptnml(0-2): 133, 4825, 10835, 5061, 122 https://tests.stockfishchess.org/tests/view/662d84f56115ff6764c7e438 closes https://github.com/official-stockfish/Stockfish/pull/5189 Bench: 1836777 --- src/engine.cpp | 12 ++++++++++-- src/engine.h | 11 +++++++---- src/search.cpp | 7 ++++--- src/search.h | 4 ++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 4625e00a..72a37ce9 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -53,6 +53,7 @@ Engine::Engine(std::string path) : NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { pos.set(StartFEN, false, &states->back()); + capSq = SQ_NONE; } std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { @@ -61,9 +62,10 @@ std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960 return Benchmark::perft(fen, depth, isChess960); } -void Engine::go(const Search::LimitsType& limits) { +void Engine::go(Search::LimitsType& limits) { assert(limits.perft == 0); verify_networks(); + limits.capSq = capSq; threads.start_thinking(options, pos, states, limits); } @@ -102,6 +104,7 @@ void Engine::set_position(const std::string& fen, const std::vector states = StateListPtr(new std::deque(1)); pos.set(fen, options["UCI_Chess960"], &states->back()); + capSq = SQ_NONE; for (const auto& move : moves) { auto m = UCIEngine::to_move(pos, move); @@ -111,6 +114,11 @@ void Engine::set_position(const std::string& fen, const std::vector states->emplace_back(); pos.do_move(m, states->back()); + + capSq = SQ_NONE; + DirtyPiece& dp = states->back().dirtyPiece; + if (dp.dirty_num > 1 && dp.to[1] == SQ_NONE) + capSq = m.to_sq(); } } @@ -172,4 +180,4 @@ std::string Engine::visualize() const { return ss.str(); } -} \ No newline at end of file +} diff --git a/src/engine.h b/src/engine.h index 041f5678..64a814cb 100644 --- a/src/engine.h +++ b/src/engine.h @@ -20,24 +20,26 @@ #define ENGINE_H_INCLUDED #include +#include #include #include #include #include #include #include -#include #include "nnue/network.h" #include "position.h" #include "search.h" +#include "syzygy/tbprobe.h" // for Stockfish::Depth #include "thread.h" #include "tt.h" #include "ucioption.h" -#include "syzygy/tbprobe.h" // for Stockfish::Depth namespace Stockfish { +enum Square : int; + class Engine { public: using InfoShort = Search::InfoShort; @@ -50,7 +52,7 @@ class Engine { std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960); // non blocking call to start searching - void go(const Search::LimitsType&); + void go(Search::LimitsType&); // non blocking call to stop searching void stop(); @@ -92,6 +94,7 @@ class Engine { Position pos; StateListPtr states; + Square capSq; OptionsMap options; ThreadPool threads; @@ -104,4 +107,4 @@ class Engine { } // namespace Stockfish -#endif // #ifndef ENGINE_H_INCLUDED \ No newline at end of file +#endif // #ifndef ENGINE_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 893daab2..396e5aa0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -54,8 +54,8 @@ using namespace Search; namespace { -static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971, - 1.002, 0.992, 0.947, 1.046, 1.001}; +static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, + 0.942, 0.933, 0.890, 0.984, 0.941}; // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { @@ -446,9 +446,10 @@ void Search::Worker::iterative_deepening() { double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); int el = std::clamp((bestValue + 750) / 150, 0, 9); + double recapture = limits.capSq == rootMoves[0].pv[0].to_sq() ? 0.955 : 1.005; double totalTime = mainThread->tm.optimum() * fallingEval * reduction - * bestMoveInstability * EvalLevel[el]; + * bestMoveInstability * EvalLevel[el] * recapture; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) diff --git a/src/search.h b/src/search.h index 0fd778b4..9b3528c8 100644 --- a/src/search.h +++ b/src/search.h @@ -109,8 +109,7 @@ struct RootMove { using RootMoves = std::vector; -// LimitsType struct stores information sent by GUI about available time to -// search the current move, maximum depth/time, or if we are in analysis mode. +// LimitsType struct stores information sent by the caller about the analysis required. struct LimitsType { // Init explicitly due to broken value-initialization of non POD in MSVC @@ -128,6 +127,7 @@ struct LimitsType { int movestogo, depth, mate, perft, infinite; uint64_t nodes; bool ponderMode; + Square capSq; }; From 3502c8ae426506453ca64e87e48d962b327c2356 Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 25 Apr 2024 19:20:57 +0200 Subject: [PATCH 659/678] Fix missing initialization of AccumulatorCaches in Eval::trace Add a constructor to `AccumulatorCaches` instead of just calling `clear(networks)` to prevent similar issues from appearing in the future. fixes https://github.com/official-stockfish/Stockfish/issues/5190 closes https://github.com/official-stockfish/Stockfish/pull/5191 No functional change --- src/evaluate.cpp | 2 +- src/nnue/nnue_accumulator.h | 5 +++++ src/search.cpp | 4 ++-- src/search.h | 7 +++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index f5746ca5..6e101e78 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -99,7 +99,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Trace scores are from white's point of view std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { - auto caches = std::make_unique(); + auto caches = std::make_unique(networks); if (pos.checkers()) return "Final evaluation: none (in check)"; diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 8d73dbef..f6538568 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -50,6 +50,11 @@ struct alignas(CacheLineSize) Accumulator { // is commonly referred to as "Finny Tables". struct AccumulatorCaches { + template + AccumulatorCaches(const Networks& networks) { + clear(networks); + } + template struct alignas(CacheLineSize) Cache { diff --git a/src/search.cpp b/src/search.cpp index 396e5aa0..11373707 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -137,11 +137,11 @@ Search::Worker::Worker(SharedState& sharedState, // Unpack the SharedState struct into member variables thread_idx(thread_id), manager(std::move(sm)), - refreshTable(), options(sharedState.options), threads(sharedState.threads), tt(sharedState.tt), - networks(sharedState.networks) { + networks(sharedState.networks), + refreshTable(networks) { clear(); } diff --git a/src/search.h b/src/search.h index 9b3528c8..444e3b8b 100644 --- a/src/search.h +++ b/src/search.h @@ -302,15 +302,14 @@ class Worker { Tablebases::Config tbConfig; - // Used by NNUE - - Eval::NNUE::AccumulatorCaches refreshTable; - const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; const Eval::NNUE::Networks& networks; + // Used by NNUE + Eval::NNUE::AccumulatorCaches refreshTable; + friend class Stockfish::ThreadPool; friend class SearchManager; }; From bc45cbc820a53a9fc405c06ca67bd7be3970344e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 27 Apr 2024 18:09:45 +0200 Subject: [PATCH 660/678] Output some basic info about the used networks Adds size in memory as well as layer sizes as in info string NNUE evaluation using nn-ae6a388e4a1a.nnue (132MiB, (22528, 3072, 15, 32, 1)) info string NNUE evaluation using nn-baff1ede1f90.nnue (6MiB, (22528, 128, 15, 32, 1)) For example, the size in MiB is useful to keep the fishtest memory sizes up-to-date, the L1-L3 sizes give a useful hint about the architecture used. closes https://github.com/official-stockfish/Stockfish/pull/5193 No functional change --- src/nnue/network.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 656ad97a..42320bae 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -252,7 +252,11 @@ void Network::verify(std::string evalfilePath) const { exit(EXIT_FAILURE); } - sync_cout << "info string NNUE evaluation using " << evalfilePath << sync_endl; + size_t size = sizeof(*featureTransformer) + sizeof(*network) * LayerStacks; + sync_cout << "info string NNUE evaluation using " << evalfilePath << " (" + << size / (1024 * 1024) << "MiB, (" << featureTransformer->InputDimensions << ", " + << network[0]->TransformedFeatureDimensions << ", " << network[0]->FC_0_OUTPUTS + << ", " << network[0]->FC_1_OUTPUTS << ", 1))" << sync_endl; } From 940a3a7383f48cea7aacbbe335671aa0d3ead1ae Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 25 Apr 2024 18:20:08 -0700 Subject: [PATCH 661/678] Cache small net w/ psqtOnly support Caching the small net in the same way as the big net allows them to share the same code path and completely removes update_accumulator_refresh(). STC: https://tests.stockfishchess.org/tests/view/662bfb5ed46f72253dcfed85 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 151712 W: 39252 L: 39158 D: 73302 Ptnml(0-2): 565, 17474, 39683, 17570, 564 closes https://github.com/official-stockfish/Stockfish/pull/5194 Bench: 1836777 --- src/evaluate.cpp | 2 +- src/nnue/network.cpp | 4 +- src/nnue/network.h | 2 +- src/nnue/nnue_accumulator.h | 6 +- src/nnue/nnue_feature_transformer.h | 267 ++++++++-------------------- src/nnue/nnue_misc.cpp | 2 +- 6 files changed, 88 insertions(+), 195 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6e101e78..345925f6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -60,7 +60,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int nnueComplexity; int v; - Value nnue = smallNet ? networks.small.evaluate(pos, nullptr, true, &nnueComplexity, psqtOnly) + Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 42320bae..2eca18bd 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -263,8 +263,8 @@ void Network::verify(std::string evalfilePath) const { template void Network::hint_common_access(const Position& pos, AccumulatorCaches::Cache* cache, - bool psqtOnl) const { - featureTransformer->hint_common_access(pos, cache, psqtOnl); + bool psqtOnly) const { + featureTransformer->hint_common_access(pos, cache, psqtOnly); } template diff --git a/src/nnue/network.h b/src/nnue/network.h index df59732d..053b7d19 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -62,7 +62,7 @@ class Network { void hint_common_access(const Position& pos, AccumulatorCaches::Cache* cache, - bool psqtOnl) const; + bool psqtOnly) const; void verify(std::string evalfilePath) const; NnueEvalTrace trace_evaluate(const Position& pos, diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index f6538568..dd313958 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -63,6 +63,7 @@ struct AccumulatorCaches { PSQTWeightType psqtAccumulation[COLOR_NB][PSQTBuckets]; Bitboard byColorBB[COLOR_NB][COLOR_NB]; Bitboard byTypeBB[COLOR_NB][PIECE_TYPE_NB]; + bool psqtOnly; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top @@ -70,6 +71,7 @@ struct AccumulatorCaches { std::memset(byColorBB, 0, sizeof(byColorBB)); std::memset(byTypeBB, 0, sizeof(byTypeBB)); + psqtOnly = false; std::memcpy(accumulation[WHITE], biases, Size * sizeof(BiasType)); std::memcpy(accumulation[BLACK], biases, Size * sizeof(BiasType)); @@ -97,11 +99,11 @@ struct AccumulatorCaches { template void clear(const Networks& networks) { big.clear(networks.big); + small.clear(networks.small); } - // When adding a new cache for a network, i.e. the smallnet - // the appropriate condition must be added to FeatureTransformer::update_accumulator_refresh. Cache big; + Cache small; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 88f0e403..60957ebe 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -656,75 +656,84 @@ class FeatureTransformer { template void update_accumulator_refresh_cache(const Position& pos, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache* cache, + bool psqtOnly) const { assert(cache != nullptr); Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq]; - - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = true; - accumulator.computedPSQT[Perspective] = true; - FeatureSet::IndexList removed, added; - for (Color c : {WHITE, BLACK}) - { - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = - entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; - while (toRemove) + if (entry.psqtOnly && !psqtOnly) + { + entry.clear(biases); + FeatureSet::append_active_indices(pos, added); + } + else + { + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) - { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = + entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } } } } + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = !psqtOnly; + accumulator.computedPSQT[Perspective] = true; + #ifdef VECTOR vec_t acc[NumRegs]; psqt_vec_t psqt[NumPsqtRegs]; - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto entryTile = - reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = entryTile[k]; - - for (int i = 0; i < int(added.size()); ++i) + if (!psqtOnly) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + auto entryTile = + reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = entryTile[k]; - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + for (int i = 0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + for (int i = 0; i < int(removed.size()); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + for (IndexType k = 0; k < NumRegs; k++) + vec_store(&entryTile[k], acc[k]); } - for (int i = 0; i < int(removed.size()); ++i) - { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - for (IndexType k = 0; k < NumRegs; k++) - vec_store(&entryTile[k], acc[k]); - } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -760,18 +769,24 @@ class FeatureTransformer { for (const auto index : added) { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] += weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] += weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } for (const auto index : removed) { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] -= weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] -= weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; @@ -782,144 +797,20 @@ class FeatureTransformer { // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing + if (!psqtOnly) + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], + sizeof(BiasType) * HalfDimensions); + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation[Perspective], sizeof(int32_t) * PSQTBuckets); - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], - sizeof(BiasType) * HalfDimensions); - for (Color c : {WHITE, BLACK}) entry.byColorBB[Perspective][c] = pos.pieces(c); for (PieceType pt = PAWN; pt <= KING; ++pt) entry.byTypeBB[Perspective][pt] = pos.pieces(pt); - } - template - void - update_accumulator_refresh(const Position& pos, - [[maybe_unused]] AccumulatorCaches::Cache* cache, - bool psqtOnly) const { - - // When we are refreshing the accumulator of the big net, - // redirect to the version of refresh that uses the refresh table. - // Using the cache for the small net is not beneficial. - if constexpr (HalfDimensions == Eval::NNUE::TransformedFeatureDimensionsBig) - { - update_accumulator_refresh_cache(pos, cache); - return; - } - -#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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; -#endif - - // Refresh the accumulator - // Could be extracted to a separate function because it's done in 2 places, - // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = !psqtOnly; - accumulator.computedPSQT[Perspective] = true; - FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, active); - -#ifdef VECTOR - if (!psqtOnly) - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - int i = 0; - for (; i < int(active.size()) - 1; i += 2) - { - IndexType index0 = active[i]; - IndexType index1 = active[i + 1]; - const IndexType offset0 = HalfDimensions * index0 + j * TileHeight; - const IndexType offset1 = HalfDimensions * index1 + j * TileHeight; - auto column0 = reinterpret_cast(&weights[offset0]); - auto column1 = reinterpret_cast(&weights[offset1]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_add_16(column0[k], column1[k])); - } - for (; i < int(active.size()); ++i) - { - IndexType index = active[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - auto accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_zero_psqt(); - - int i = 0; - for (; i < int(active.size()) - 1; i += 2) - { - IndexType index0 = active[i]; - IndexType index1 = active[i + 1]; - const IndexType offset0 = PSQTBuckets * index0 + j * PsqtTileHeight; - const IndexType offset1 = PSQTBuckets * index1 + j * PsqtTileHeight; - auto columnPsqt0 = reinterpret_cast(&psqtWeights[offset0]); - auto columnPsqt1 = reinterpret_cast(&psqtWeights[offset1]); - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = - vec_add_psqt_32(psqt[k], vec_add_psqt_32(columnPsqt0[k], columnPsqt1[k])); - } - for (; i < int(active.size()); ++i) - { - IndexType index = active[i]; - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - -#else - if (!psqtOnly) - std::memcpy(accumulator.accumulation[Perspective], biases, - HalfDimensions * sizeof(BiasType)); - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; - - for (const auto index : active) - { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; - } - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += - psqtWeights[index * PSQTBuckets + k]; - } -#endif + entry.psqtOnly = psqtOnly; } template @@ -948,7 +839,7 @@ class FeatureTransformer { psqtOnly); } else - update_accumulator_refresh(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache, psqtOnly); } template @@ -976,7 +867,7 @@ class FeatureTransformer { psqtOnly); } else - update_accumulator_refresh(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache, psqtOnly); } template diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 51838fef..e92dcc71 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -48,7 +48,7 @@ void hint_common_parent_position(const Position& pos, int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, nullptr, simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, &caches.small, simpleEvalAbs > Eval::PsqtOnlyThreshold); else networks.big.hint_common_access(pos, &caches.big, false); } From a129c0695be921acfbb3f5c966eef756d0b6f843 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 28 Apr 2024 10:28:25 -0700 Subject: [PATCH 662/678] Combine remove and add in update_accumulator_refresh_cache() Combine remove and add in update_accumulator_refresh_cache(). Move remove before add to match other parts of the code. STC: https://tests.stockfishchess.org/tests/view/662d96dc6115ff6764c7f4ca LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 364032 W: 94421 L: 93624 D: 175987 Ptnml(0-2): 1261, 41983, 94811, 42620, 1341 closes https://github.com/official-stockfish/Stockfish/pull/5194 Bench: 1836777 --- src/evaluate.cpp | 5 +- src/nnue/nnue_accumulator.h | 2 +- src/nnue/nnue_feature_transformer.h | 71 +++++++++++++++++------------ src/nnue/nnue_misc.cpp | 3 +- 4 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 345925f6..fe6b83aa 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -60,8 +60,9 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int nnueComplexity; int v; - Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) - : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); + Value nnue = smallNet + ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) + : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, int pawnCountMul, int npmConstant, int evalDiv, diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index dd313958..a2b3b989 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -102,7 +102,7 @@ struct AccumulatorCaches { small.clear(networks.small); } - Cache big; + Cache big; Cache small; }; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 60957ebe..6b3f78a9 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -660,8 +660,8 @@ class FeatureTransformer { bool psqtOnly) const { assert(cache != nullptr); - Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq]; + Square ksq = pos.square(Perspective); + auto& entry = (*cache)[ksq]; FeatureSet::IndexList removed, added; if (entry.psqtOnly && !psqtOnly) @@ -712,16 +712,20 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; - for (int i = 0; i < int(added.size()); ++i) + int i0 = 0; + for (; i0 < int(std::min(removed.size(), added.size())); ++i0) { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + IndexType indexR = removed[i0]; + const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; + auto columnR = reinterpret_cast(&weights[offsetR]); + IndexType indexA = added[i0]; + const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; + auto columnA = reinterpret_cast(&weights[offsetA]); for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); } - for (int i = 0; i < int(removed.size()); ++i) + for (int i = i0; i < int(removed.size()); ++i) { IndexType index = removed[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; @@ -730,6 +734,15 @@ class FeatureTransformer { for (unsigned k = 0; k < NumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } + for (int i = i0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } for (IndexType k = 0; k < NumRegs; k++) vec_store(&entryTile[k], acc[k]); @@ -742,15 +755,6 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; - for (int i = 0; i < int(added.size()); ++i) - { - IndexType index = added[i]; - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } for (int i = 0; i < int(removed.size()); ++i) { IndexType index = removed[i]; @@ -760,6 +764,15 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); } + for (int i = 0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&entryTilePsqt[k], psqt[k]); @@ -767,18 +780,6 @@ class FeatureTransformer { #else - for (const auto index : added) - { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] += weights[offset + j]; - } - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } for (const auto index : removed) { if (!psqtOnly) @@ -791,6 +792,18 @@ class FeatureTransformer { for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; } + for (const auto index : added) + { + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] += weights[offset + j]; + } + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + } #endif diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index e92dcc71..21685d0f 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -48,7 +48,8 @@ void hint_common_parent_position(const Position& pos, int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, &caches.small, simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, &caches.small, + simpleEvalAbs > Eval::PsqtOnlyThreshold); else networks.big.hint_common_access(pos, &caches.big, false); } From 834e8ff619b212baf402c3922f8fde9af979cd0c Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 28 Apr 2024 08:53:28 +0800 Subject: [PATCH 663/678] Penalise the TT move in multicut Passed STC: LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 185504 W: 48079 L: 47533 D: 89892 Ptnml(0-2): 716, 21866, 46988, 22520, 662 https://tests.stockfishchess.org/tests/view/662d9e1d6115ff6764c7f83d Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 75612 W: 19351 L: 18948 D: 37313 Ptnml(0-2): 46, 8363, 20592, 8752, 53 https://tests.stockfishchess.org/tests/view/662dc9dc6115ff6764c80fea closes https://github.com/official-stockfish/Stockfish/pull/5195 Bench: 1415435 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 11373707..ad59b35a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1067,7 +1067,12 @@ moves_loop: // When in check, search starts here // we assume this expected cut-node is not singular (multiple moves fail high), // and we can prune the whole subtree by returning a softbound. else if (singularBeta >= beta) + { + if (!ttCapture) + update_quiet_stats(pos, ss, *this, ttMove, -stat_malus(depth)); + return singularBeta; + } // Negative extensions // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, From 48a3b7c0ee7d32441a5a4519c85bd1e93e467f6e Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 28 Apr 2024 16:04:28 +0200 Subject: [PATCH 664/678] Simplify non-pawn material divisor to a constant Passed STC: https://tests.stockfishchess.org/tests/view/662942603fe04ce4cefc7aba LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 272832 W: 70456 L: 70497 D: 131879 Ptnml(0-2): 1020, 32619, 69154, 32628, 995 Passed LTC: https://tests.stockfishchess.org/tests/view/662dfe3b6115ff6764c829eb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 100254 W: 25446 L: 25303 D: 49505 Ptnml(0-2): 121, 11292, 27166, 11419, 129 closes https://github.com/official-stockfish/Stockfish/pull/5198 Bench: 1544645 --- src/evaluate.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index fe6b83aa..1d41f3a2 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -64,14 +64,14 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); - const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, - int pawnCountMul, int npmConstant, int evalDiv, - int shufflingConstant, int shufflingDiv) { + const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, + int npmConstant, int evalDiv, int shufflingConstant, + int shufflingDiv) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; - int npm = pos.non_pawn_material() / npmDiv; + int npm = pos.non_pawn_material() / 64; v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) + optimism * (npmConstant + npm)) / evalDiv; @@ -82,11 +82,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, }; if (!smallNet) - adjustEval(524, 32395, 66, 942, 11, 139, 1058, 178, 204); + adjustEval(524, 32395, 942, 11, 139, 1058, 178, 204); else if (psqtOnly) - adjustEval(517, 32857, 65, 908, 7, 155, 1006, 224, 238); + adjustEval(517, 32857, 908, 7, 155, 1006, 224, 238); else - adjustEval(515, 32793, 63, 944, 9, 140, 1067, 206, 206); + adjustEval(515, 32793, 944, 9, 140, 1067, 206, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 0fe64286457549d2f80cd7792088375aaa9bee55 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 28 Apr 2024 16:53:47 +0200 Subject: [PATCH 665/678] More reduction at cut nodes which are not a former PV node But the tt move and first killer are excluded. This idea is based on following LMR condition tuning https://tests.stockfishchess.org/tests/view/66228bed3fe04ce4cefc0c71 by using only the two largest terms P[0] and P[1]. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 173248 W: 45091 L: 44565 D: 83592 Ptnml(0-2): 693, 20534, 43673, 21002, 722 https://tests.stockfishchess.org/tests/view/6629603b3fe04ce4cefc7d37 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 722394 W: 183231 L: 181487 D: 357676 Ptnml(0-2): 462, 80650, 197252, 82348, 485 https://tests.stockfishchess.org/tests/view/662cbe45d46f72253dcff7bf closes https://github.com/official-stockfish/Stockfish/pull/5199 Bench: 1619613 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index ad59b35a..3718c378 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1123,6 +1123,9 @@ moves_loop: // When in check, search starts here if (ss->ttPv) r -= 1 + (ttValue > alpha) + (tte->depth() >= depth); + else if (cutNode && move != ttMove && move != ss->killers[0]) + r++; + // Increase reduction for cut nodes (~4 Elo) if (cutNode) r += 2 - (tte->depth() >= depth && ss->ttPv); From 5d720325596699ceba2743776cb39f9cea1754f5 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sat, 20 Apr 2024 00:29:01 -0500 Subject: [PATCH 666/678] Use capture history to better judge which sacrifices to explore This idea has been bouncing around a while. @Vizvezdenec tried it a couple years ago in Stockfish without results, but its recent arrival in Ethereal inspired him and thence me to try it afresh in Stockfish. (Also factor out the now-common code with futpruning for captures.) STC: https://tests.stockfishchess.org/tests/view/662355bc3fe04ce4cefc18ac LLR: 2.92 (-2.94,2.94) <0.00,2.00> Total: 45760 W: 11970 L: 11640 D: 22150 Ptnml(0-2): 124, 5371, 11625, 5571, 189 LTC: https://tests.stockfishchess.org/tests/view/662dda396115ff6764c817c9 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 243828 W: 62042 L: 61287 D: 120499 Ptnml(0-2): 211, 27202, 66329, 27965, 207 closes https://github.com/official-stockfish/Stockfish/pull/5200 Bench: 1480008 --- src/search.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3718c378..e4f170be 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -967,20 +967,22 @@ moves_loop: // When in check, search starts here if (capture || givesCheck) { + Piece capturedPiece = pos.piece_on(move.to_sq()); + int captHist = + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; + // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Piece capturedPiece = pos.piece_on(move.to_sq()); - Value futilityValue = - ss->staticEval + 285 + 277 * lmrDepth + PieceValue[capturedPiece] - + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] - / 7; + Value futilityValue = ss->staticEval + 285 + 277 * lmrDepth + + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -203 * depth)) + int seeHist = std::clamp(captHist / 32, -199 * depth, 199 * depth); + if (!pos.see_ge(move, -203 * depth - seeHist)) continue; } else From eb20de36c05b4101af37b2bf3783c570a47bb1cc Mon Sep 17 00:00:00 2001 From: Ciekce <44617491+Ciekce@users.noreply.github.com> Date: Mon, 29 Apr 2024 01:45:56 +0100 Subject: [PATCH 667/678] Avoid unnecessary creation of accumulator cache Saves a (currently) 800 KB allocation and deallocation when running `eval`, not particularly significant and zero impact on play but not necessary either. closes https://github.com/official-stockfish/Stockfish/pull/5201 No functional change --- AUTHORS | 1 + src/evaluate.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index abae401c..36b2b6f7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -46,6 +46,7 @@ Bryan Cross (crossbr) candirufish Chess13234 Chris Cain (ceebo) +Ciekce clefrks Clemens L. (rn5f107s2) Cody Ho (aesrentai) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 1d41f3a2..e3aa249c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -100,11 +100,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Trace scores are from white's point of view std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { - auto caches = std::make_unique(networks); - if (pos.checkers()) return "Final evaluation: none (in check)"; + auto caches = std::make_unique(networks); + std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; From 6a9b8a0c7b913b9d4c4474bae7804184d20e8c4a Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:33:59 +0800 Subject: [PATCH 668/678] Optimise NNUE Accumulator updates Passed STC: https://tests.stockfishchess.org/tests/view/662e3c6a5e9274400985a741 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 86176 W: 22284 L: 21905 D: 41987 Ptnml(0-2): 254, 9572, 23051, 9963, 248 closes https://github.com/official-stockfish/Stockfish/pull/5202 No functional change --- src/nnue/nnue_feature_transformer.h | 76 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 6b3f78a9..402a47a8 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -404,19 +404,25 @@ class FeatureTransformer { return {st, next}; } - // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. + // NOTE: The parameter states_to_update is an array of position states. // All states must be sequential, that is states_to_update[i] must either be reachable - // by repeatedly applying ->previous from states_to_update[i+1] or - // states_to_update[i] == nullptr. + // by repeatedly applying ->previous from states_to_update[i+1]. // computed_st must be reachable by repeatedly applying ->previous on - // states_to_update[0], if not nullptr. + // states_to_update[0]. template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N], bool psqtOnly) const { static_assert(N > 0); - assert(states_to_update[N - 1] == nullptr); + assert([&]() { + for (size_t i = 0; i < N; ++i) + { + if (states_to_update[i] == nullptr) + return false; + } + return true; + }()); #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array @@ -425,11 +431,7 @@ class FeatureTransformer { psqt_vec_t psqt[NumPsqtRegs]; #endif - if (states_to_update[0] == nullptr) - return; - // Update incrementally going back through states_to_update. - // Gather all features to be updated. const Square ksq = pos.square(Perspective); @@ -437,28 +439,18 @@ class FeatureTransformer { // 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. - FeatureSet::IndexList removed[N - 1], added[N - 1]; + FeatureSet::IndexList removed[N], added[N]; + for (int i = N - 1; i >= 0; --i) { - int i = - N - - 2; // Last potential state to update. Skip last element because it must be nullptr. - while (states_to_update[i] == nullptr) - --i; + (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; + (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; - StateInfo* st2 = states_to_update[i]; + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - for (; i >= 0; --i) - { - (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; - (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; - - const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - - for (; st2 != end_state; st2 = st2->previous) - FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, - removed[i], added[i]); - } + for (StateInfo* st2 = states_to_update[i]; st2 != end_state; st2 = st2->previous) + FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, removed[i], + added[i]); } StateInfo* st = computed_st; @@ -466,8 +458,7 @@ class FeatureTransformer { // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. #ifdef VECTOR - if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2) - && added[0].size() == 1) + if (N == 1 && (removed[0].size() == 1 || removed[0].size() == 2) && added[0].size() == 1) { assert(states_to_update[0]); @@ -541,7 +532,7 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_load(&accTileIn[k]); - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType i = 0; i < N; ++i) { // Difference calculation for the deactivated features for (const auto index : removed[i]) @@ -578,7 +569,7 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType i = 0; i < N; ++i) { // Difference calculation for the deactivated features for (const auto index : removed[i]) @@ -608,7 +599,7 @@ class FeatureTransformer { } } #else - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType i = 0; i < N; ++i) { if (!psqtOnly) std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], @@ -847,8 +838,8 @@ class FeatureTransformer { || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) { // Only update current position accumulator to minimize work. - StateInfo* states_to_update[2] = {pos.state(), nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update, + StateInfo* states_to_update[1] = {pos.state()}; + update_accumulator_incremental(pos, oldest_st, states_to_update, psqtOnly); } else @@ -873,11 +864,20 @@ class FeatureTransformer { // 1. for the current position // 2. the next accumulator after the computed one // The heuristic may change in the future. - StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(), - nullptr}; + if (next == pos.state()) + { + StateInfo* states_to_update[1] = {next}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update, + psqtOnly); + } + else + { + StateInfo* states_to_update[2] = {next, pos.state()}; + + update_accumulator_incremental(pos, oldest_st, states_to_update, + psqtOnly); + } } else update_accumulator_refresh_cache(pos, cache, psqtOnly); From be142337d843ef3afc675e27628ab8e896c32cce Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 29 Apr 2024 20:37:54 -0700 Subject: [PATCH 669/678] Accumulator cache bugfix and cleanup STC: https://tests.stockfishchess.org/tests/view/663068913a05f1bf7a511dc2 LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 70304 W: 18211 L: 18026 D: 34067 Ptnml(0-2): 232, 7966, 18582, 8129, 243 1) Fixes a bug introduced in https://github.com/official-stockfish/Stockfish/pull/5194. Only one psqtOnly flag was used for two perspectives which was causing wrong entries to be cleared and marked. 2) The finny caches should be cleared like histories and not at the start of every search. closes https://github.com/official-stockfish/Stockfish/pull/5203 No functional change --- src/nnue/nnue_accumulator.h | 28 ++++++++++++--------------- src/nnue/nnue_feature_transformer.h | 30 ++++++++++++++--------------- src/search.cpp | 5 ++--- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index a2b3b989..179feba5 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -59,31 +59,27 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Cache { struct alignas(CacheLineSize) Entry { - BiasType accumulation[COLOR_NB][Size]; - PSQTWeightType psqtAccumulation[COLOR_NB][PSQTBuckets]; - Bitboard byColorBB[COLOR_NB][COLOR_NB]; - Bitboard byTypeBB[COLOR_NB][PIECE_TYPE_NB]; + BiasType accumulation[Size]; + PSQTWeightType psqtAccumulation[PSQTBuckets]; + Bitboard byColorBB[COLOR_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; bool psqtOnly; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top void clear(const BiasType* biases) { - std::memset(byColorBB, 0, sizeof(byColorBB)); - std::memset(byTypeBB, 0, sizeof(byTypeBB)); - psqtOnly = false; - - std::memcpy(accumulation[WHITE], biases, Size * sizeof(BiasType)); - std::memcpy(accumulation[BLACK], biases, Size * sizeof(BiasType)); - - std::memset(psqtAccumulation, 0, sizeof(psqtAccumulation)); + std::memcpy(accumulation, biases, sizeof(accumulation)); + std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, + sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } }; template void clear(const Network& network) { - for (auto& entry : entries) - entry.clear(network.featureTransformer->biases); + for (auto& entries1D : entries) + for (auto& entry : entries1D) + entry.clear(network.featureTransformer->biases); } void clear(const BiasType* biases) { @@ -91,9 +87,9 @@ struct AccumulatorCaches { entry.clear(biases); } - Entry& operator[](Square sq) { return entries[sq]; } + std::array& operator[](Square sq) { return entries[sq]; } - std::array entries; + std::array, SQUARE_NB> entries; }; template diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 402a47a8..4647ecd0 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -652,7 +652,7 @@ class FeatureTransformer { assert(cache != nullptr); Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq]; + auto& entry = (*cache)[ksq][Perspective]; FeatureSet::IndexList removed, added; if (entry.psqtOnly && !psqtOnly) @@ -666,9 +666,8 @@ class FeatureTransformer { { for (PieceType pt = PAWN; pt <= KING; ++pt) { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = - entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; const Bitboard newBB = pos.pieces(c, pt); Bitboard toRemove = oldBB & ~newBB; Bitboard toAdd = newBB & ~oldBB; @@ -698,8 +697,7 @@ class FeatureTransformer { if (!psqtOnly) for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - auto entryTile = - reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); + auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; @@ -741,8 +739,8 @@ class FeatureTransformer { for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - auto entryTilePsqt = reinterpret_cast( - &entry.psqtAccumulation[Perspective][j * PsqtTileHeight]); + auto entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; @@ -777,11 +775,11 @@ class FeatureTransformer { { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] -= weights[offset + j]; + entry.accumulation[j] -= weights[offset + j]; } for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + entry.psqtAccumulation[k] -= psqtWeights[index * PSQTBuckets + k]; } for (const auto index : added) { @@ -789,11 +787,11 @@ class FeatureTransformer { { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] += weights[offset + j]; + entry.accumulation[j] += weights[offset + j]; } for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; } #endif @@ -802,17 +800,17 @@ class FeatureTransformer { // Now copy its content to the actual accumulator we were refreshing if (!psqtOnly) - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, sizeof(BiasType) * HalfDimensions); - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation[Perspective], + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, sizeof(int32_t) * PSQTBuckets); for (Color c : {WHITE, BLACK}) - entry.byColorBB[Perspective][c] = pos.pieces(c); + entry.byColorBB[c] = pos.pieces(c); for (PieceType pt = PAWN; pt <= KING; ++pt) - entry.byTypeBB[Perspective][pt] = pos.pieces(pt); + entry.byTypeBB[pt] = pos.pieces(pt); entry.psqtOnly = psqtOnly; } diff --git a/src/search.cpp b/src/search.cpp index e4f170be..b8e515f0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -147,9 +147,6 @@ Search::Worker::Worker(SharedState& sharedState, void Search::Worker::start_searching() { - // Initialize accumulator refresh entries - refreshTable.clear(networks); - // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) { @@ -506,6 +503,8 @@ void Search::Worker::clear() { for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((20.14 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + + refreshTable.clear(networks); } From be026bdcb2c71501dffab4a04dabef682661e664 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 1 May 2024 15:10:23 +0200 Subject: [PATCH 670/678] Clear Workers after changing the network ensures internal state (e.g. accumulator cache) is consistent with network closes https://github.com/official-stockfish/Stockfish/pull/5204 No functional change --- src/engine.cpp | 10 +++++++--- src/thread.cpp | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 72a37ce9..e8da24aa 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -141,14 +141,18 @@ void Engine::verify_networks() const { } void Engine::load_networks() { - networks.big.load(binaryDirectory, options["EvalFile"]); - networks.small.load(binaryDirectory, options["EvalFileSmall"]); + load_big_network(options["EvalFile"]); + load_small_network(options["EvalFileSmall"]); } -void Engine::load_big_network(const std::string& file) { networks.big.load(binaryDirectory, file); } +void Engine::load_big_network(const std::string& file) { + networks.big.load(binaryDirectory, file); + threads.clear(); +} void Engine::load_small_network(const std::string& file) { networks.small.load(binaryDirectory, file); + threads.clear(); } void Engine::save_network(const std::pair, std::string> files[2]) { diff --git a/src/thread.cpp b/src/thread.cpp index 1438c9f9..9052654b 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -161,6 +161,9 @@ void ThreadPool::clear() { for (Thread* th : threads) th->worker->clear(); + if (threads.size() == 0) + return; + main_manager()->callsCnt = 0; main_manager()->bestPreviousScore = VALUE_INFINITE; main_manager()->bestPreviousAverageScore = VALUE_INFINITE; From 8ee9905d8beddc01fa70e39c439b076c2d661acb Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sat, 4 May 2024 09:52:27 +0800 Subject: [PATCH 671/678] Remove PSQT-only mode Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94208 W: 24270 L: 24112 D: 45826 Ptnml(0-2): 286, 11186, 24009, 11330, 293 https://tests.stockfishchess.org/tests/view/6635ddd773559a8aa8582826 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 114960 W: 29107 L: 28982 D: 56871 Ptnml(0-2): 37, 12683, 31924, 12790, 46 https://tests.stockfishchess.org/tests/view/663604a973559a8aa85881ed closes #5214 Bench 1653939 --- src/evaluate.cpp | 8 +- src/evaluate.h | 2 +- src/nnue/network.cpp | 21 +- src/nnue/network.h | 6 +- src/nnue/nnue_accumulator.h | 2 - src/nnue/nnue_feature_transformer.h | 346 ++++++++++++---------------- src/nnue/nnue_misc.cpp | 13 +- src/position.cpp | 18 +- 8 files changed, 172 insertions(+), 244 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e3aa249c..11999b55 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -56,13 +56,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int simpleEval = simple_eval(pos, pos.side_to_move()); bool smallNet = std::abs(simpleEval) > SmallNetThreshold; - bool psqtOnly = std::abs(simpleEval) > PsqtOnlyThreshold; int nnueComplexity; int v; - Value nnue = smallNet - ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) - : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); + Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) + : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, int npmConstant, int evalDiv, int shufflingConstant, @@ -83,8 +81,6 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, if (!smallNet) adjustEval(524, 32395, 942, 11, 139, 1058, 178, 204); - else if (psqtOnly) - adjustEval(517, 32857, 908, 7, 155, 1006, 224, 238); else adjustEval(515, 32793, 944, 9, 140, 1067, 206, 206); diff --git a/src/evaluate.h b/src/evaluate.h index 38615ff7..2d244ff6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1274, PsqtOnlyThreshold = 2389; +constexpr inline int SmallNetThreshold = 1274; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 2eca18bd..de2c7eca 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -189,8 +189,7 @@ template Value Network::evaluate(const Position& pos, AccumulatorCaches::Cache* cache, bool adjusted, - int* complexity, - bool psqtOnly) const { + int* complexity) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -210,13 +209,12 @@ Value Network::evaluate(const Position& ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = - featureTransformer->transform(pos, cache, transformedFeatures, bucket, psqtOnly); - const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0; + const int bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket); + const auto positional = network[bucket]->propagate(transformedFeatures); if (complexity) - *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; + *complexity = std::abs(psqt - positional) / OutputScale; // Give more value to positional evaluation when adjusted flag is set if (adjusted) @@ -261,10 +259,9 @@ void Network::verify(std::string evalfilePath) const { template -void Network::hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { - featureTransformer->hint_common_access(pos, cache, psqtOnly); +void Network::hint_common_access( + const Position& pos, AccumulatorCaches::Cache* cache) const { + featureTransformer->hint_common_access(pos, cache); } template @@ -293,7 +290,7 @@ Network::trace_evaluate(const Position& for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, cache, transformedFeatures, bucket, false); + featureTransformer->transform(pos, cache, transformedFeatures, bucket); const auto positional = network[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); diff --git a/src/nnue/network.h b/src/nnue/network.h index 053b7d19..23f56663 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -56,13 +56,11 @@ class Network { Value evaluate(const Position& pos, AccumulatorCaches::Cache* cache, bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false) const; + int* complexity = nullptr) const; void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const; + AccumulatorCaches::Cache* cache) const; void verify(std::string evalfilePath) const; NnueEvalTrace trace_evaluate(const Position& pos, diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 179feba5..b8dcf1e4 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -38,7 +38,6 @@ struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[COLOR_NB][Size]; std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; bool computed[COLOR_NB]; - bool computedPSQT[COLOR_NB]; }; @@ -63,7 +62,6 @@ struct AccumulatorCaches { PSQTWeightType psqtAccumulation[PSQTBuckets]; Bitboard byColorBB[COLOR_NB]; Bitboard byTypeBB[PIECE_TYPE_NB]; - bool psqtOnly; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 4647ecd0..018b715e 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -309,10 +309,9 @@ class FeatureTransformer { std::int32_t transform(const Position& pos, AccumulatorCaches::Cache* cache, OutputType* output, - int bucket, - bool psqtOnly) const { - update_accumulator(pos, cache, psqtOnly); - update_accumulator(pos, cache, psqtOnly); + int bucket) const { + update_accumulator(pos, cache); + update_accumulator(pos, cache); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; @@ -320,9 +319,6 @@ class FeatureTransformer { (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; - if (psqtOnly) - return psqt; - const auto& accumulation = (pos.state()->*accPtr).accumulation; for (IndexType p = 0; p < 2; ++p) @@ -375,23 +371,20 @@ class FeatureTransformer { } // end of function transform() void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { - hint_common_access_for_perspective(pos, cache, psqtOnly); - hint_common_access_for_perspective(pos, cache, psqtOnly); + AccumulatorCaches::Cache* cache) const { + hint_common_access_for_perspective(pos, cache); + hint_common_access_for_perspective(pos, cache); } private: template [[nodiscard]] std::pair - try_find_computed_accumulator(const Position& pos, bool psqtOnly) const { + 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(), *next = nullptr; int gain = FeatureSet::refresh_cost(pos); - while (st->previous - && (!(st->*accPtr).computedPSQT[Perspective] - || (!psqtOnly && !(st->*accPtr).computed[Perspective]))) + 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. @@ -412,8 +405,7 @@ class FeatureTransformer { template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, - StateInfo* states_to_update[N], - bool psqtOnly) const { + StateInfo* states_to_update[N]) const { static_assert(N > 0); assert([&]() { for (size_t i = 0; i < N; ++i) @@ -443,8 +435,7 @@ class FeatureTransformer { for (int i = N - 1; i >= 0; --i) { - (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; - (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; + (states_to_update[i]->*accPtr).computed[Perspective] = true; const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; @@ -462,34 +453,31 @@ class FeatureTransformer { { assert(states_to_update[0]); - if (!psqtOnly) + auto accIn = + reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); + auto accOut = reinterpret_cast( + &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); + + if (removed[0].size() == 1) { - auto accIn = - reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); - auto accOut = reinterpret_cast( - &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); - const IndexType offsetR0 = HalfDimensions * removed[0][0]; - auto columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0][0]; - auto columnA = reinterpret_cast(&weights[offsetA]); - - if (removed[0].size() == 1) - { - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); - } - else - { - const IndexType offsetR1 = HalfDimensions * removed[0][1]; - auto columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); - } + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); } auto accPsqtIn = @@ -523,43 +511,41 @@ class FeatureTransformer { } else { - if (!psqtOnly) - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator + auto accTileIn = reinterpret_cast( + &(st->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTileIn[k]); + + for (IndexType i = 0; i < N; ++i) { - // Load accumulator - auto accTileIn = reinterpret_cast( - &(st->*accPtr).accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTileIn[k]); - - for (IndexType i = 0; i < N; ++i) + // Difference calculation for the deactivated features + for (const auto index : removed[i]) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - auto accTileOut = - reinterpret_cast(&(states_to_update[i]->*accPtr) - .accumulation[Perspective][j * TileHeight]); + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTileOut[k], acc[k]); + acc[k] = vec_sub_16(acc[k], column[k]); } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); } + } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -601,10 +587,8 @@ class FeatureTransformer { #else for (IndexType i = 0; i < N; ++i) { - if (!psqtOnly) - std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], - (st->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); + std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], + (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = @@ -615,12 +599,9 @@ class FeatureTransformer { // Difference calculation for the deactivated features for (const auto index : removed[i]) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] -= @@ -630,12 +611,9 @@ class FeatureTransformer { // Difference calculation for the activated features for (const auto index : added[i]) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] += @@ -647,95 +625,84 @@ class FeatureTransformer { template void update_accumulator_refresh_cache(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { + AccumulatorCaches::Cache* cache) const { assert(cache != nullptr); Square ksq = pos.square(Perspective); auto& entry = (*cache)[ksq][Perspective]; FeatureSet::IndexList removed, added; - if (entry.psqtOnly && !psqtOnly) + for (Color c : {WHITE, BLACK}) { - entry.clear(biases); - FeatureSet::append_active_indices(pos, added); - } - else - { - for (Color c : {WHITE, BLACK}) + for (PieceType pt = PAWN; pt <= KING; ++pt) { - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; - while (toRemove) - { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) - { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); - } + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); } } } - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = !psqtOnly; - accumulator.computedPSQT[Perspective] = true; + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = true; #ifdef VECTOR vec_t acc[NumRegs]; psqt_vec_t psqt[NumPsqtRegs]; - if (!psqtOnly) - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = entryTile[k]; + + int i0 = 0; + for (; i0 < int(std::min(removed.size(), added.size())); ++i0) { - auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = entryTile[k]; + IndexType indexR = removed[i0]; + const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; + auto columnR = reinterpret_cast(&weights[offsetR]); + IndexType indexA = added[i0]; + const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; + auto columnA = reinterpret_cast(&weights[offsetA]); - int i0 = 0; - for (; i0 < int(std::min(removed.size(), added.size())); ++i0) - { - IndexType indexR = removed[i0]; - const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; - auto columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i0]; - const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; - auto columnA = reinterpret_cast(&weights[offsetA]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); - } - for (int i = i0; i < int(removed.size()); ++i) - { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - for (int i = i0; i < int(added.size()); ++i) - { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - for (IndexType k = 0; k < NumRegs; k++) - vec_store(&entryTile[k], acc[k]); + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); } + for (int i = i0; i < int(removed.size()); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (int i = i0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + for (IndexType k = 0; k < NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -771,24 +738,18 @@ class FeatureTransformer { for (const auto index : removed) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] -= weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[k] -= psqtWeights[index * PSQTBuckets + k]; } for (const auto index : added) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] += weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; @@ -799,9 +760,8 @@ class FeatureTransformer { // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing - if (!psqtOnly) - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, - sizeof(BiasType) * HalfDimensions); + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, + sizeof(BiasType) * HalfDimensions); std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, sizeof(int32_t) * PSQTBuckets); @@ -811,14 +771,11 @@ class FeatureTransformer { for (PieceType pt = PAWN; pt <= KING; ++pt) entry.byTypeBB[pt] = pos.pieces(pt); - - entry.psqtOnly = psqtOnly; } template void hint_common_access_for_perspective(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { + AccumulatorCaches::Cache* cache) const { // Works like update_accumulator, but performs less work. // Updates ONLY the accumulator for pos. @@ -826,33 +783,28 @@ class FeatureTransformer { // Look for a usable accumulator of an earlier position. We keep track // of the estimated gain in terms of features to be added/subtracted. // Fast early exit. - if ((pos.state()->*accPtr).computed[Perspective] - || (psqtOnly && (pos.state()->*accPtr).computedPSQT[Perspective])) + if ((pos.state()->*accPtr).computed[Perspective]) return; - auto [oldest_st, _] = try_find_computed_accumulator(pos, psqtOnly); + auto [oldest_st, _] = try_find_computed_accumulator(pos); - if ((oldest_st->*accPtr).computed[Perspective] - || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) + if ((oldest_st->*accPtr).computed[Perspective]) { // Only update current position accumulator to minimize work. StateInfo* states_to_update[1] = {pos.state()}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update); } else - update_accumulator_refresh_cache(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache); } template void update_accumulator(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { + AccumulatorCaches::Cache* cache) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos, psqtOnly); + auto [oldest_st, next] = try_find_computed_accumulator(pos); - if ((oldest_st->*accPtr).computed[Perspective] - || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) + if ((oldest_st->*accPtr).computed[Perspective]) { if (next == nullptr) return; @@ -866,19 +818,17 @@ class FeatureTransformer { { StateInfo* states_to_update[1] = {next}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update); } else { StateInfo* states_to_update[2] = {next, pos.state()}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update); } } else - update_accumulator_refresh_cache(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache); } template diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 21685d0f..bf73a58b 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -48,10 +48,9 @@ void hint_common_parent_position(const Position& pos, int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, &caches.small, - simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, &caches.small); else - networks.big.hint_common_access(pos, &caches.big, false); + networks.big.hint_common_access(pos, &caches.big); } namespace { @@ -149,18 +148,14 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat auto st = pos.state(); pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; Value eval = networks.big.evaluate(pos, &caches.big); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); diff --git a/src/position.cpp b/src/position.cpp index 78e62bda..b46ba029 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -680,11 +680,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = - st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; auto& dp = st->dirtyPiece; dp.dirty_num = 1; @@ -968,13 +965,10 @@ void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { newSt.previous = st; 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->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = - st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = - false; + 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) { From 351a2e22dd8ad8bc3b2204e1e80d4d5860a778d6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 4 May 2024 10:33:26 +0300 Subject: [PATCH 672/678] Add extra bonuses to some moves that forced a fail low The previous patch on this idea was giving bonuses to this moves if best value of search is far below current static evaluation. This patch does similar thing but adds extra bonus when best value of search is far below static evaluation before previous move. Passed STC: https://tests.stockfishchess.org/tests/view/66355fc819566d64b481d6a4 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 454144 W: 116575 L: 115656 D: 221913 Ptnml(0-2): 1060, 53410, 117215, 54325, 1062 Passed LTC: https://tests.stockfishchess.org/tests/view/6635c61a73559a8aa858012d LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 136578 W: 34858 L: 34335 D: 67385 closes https://github.com/official-stockfish/Stockfish/pull/5209 Bench: 1614825 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b8e515f0..cd80e939 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1335,8 +1335,8 @@ moves_loop: // When in check, search starts here else if (!priorCapture && prevSq != SQ_NONE) { int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) - + ((ss - 1)->moveCount > 11) - + (!ss->inCheck && bestValue <= ss->staticEval - 142); + + ((ss - 1)->moveCount > 11) + (!ss->inCheck && bestValue <= ss->staticEval - 142) + + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 77); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] From 741aaf8a38c75535e01a3f5506877654547ebb33 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Sat, 4 May 2024 17:29:23 +0100 Subject: [PATCH 673/678] Introduce Quadruple Extensions This patch introduces quadruple extensions, with the new condition of not ttPv. It also generalises all margins, so that extensions can still occur if conditions are only partially fulfilled, but with a stricter margin. Failed STC: LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 16096 W: 3984 L: 4228 D: 7884 Ptnml(0-2): 72, 2067, 4002, 1847, 60 https://tests.stockfishchess.org/tests/view/66316422d01fb9ac9bcdbdcd Passed VVLTC 1: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 96660 W: 24550 L: 24210 D: 47900 Ptnml(0-2): 5, 8776, 30426, 9120, 3 https://tests.stockfishchess.org/tests/view/66361f2c74fa3f41ef2ee091 Passed VVLTC 2: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 80546 W: 20495 L: 20120 D: 39931 Ptnml(0-2): 6, 7477, 24929, 7858, 3 https://tests.stockfishchess.org/tests/view/66350cf739ba8e443112b3fa closes https://github.com/official-stockfish/Stockfish/pull/5211 bench 2233743 --- src/search.cpp | 21 +++++++++------------ src/search.h | 1 - 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cd80e939..06d31510 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -593,7 +593,6 @@ Value Search::Worker::search( bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; - ss->multipleExtensions = (ss - 1)->multipleExtensions; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; @@ -1049,17 +1048,16 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - extension = 1; + int doubleMargin = 251 * PvNode - 241 * !ttCapture; + int tripleMargin = + 135 + 234 * PvNode - 248 * !ttCapture + 124 * (ss->ttPv || !ttCapture); + int quadMargin = 447 + 354 * PvNode - 300 * !ttCapture + 206 * ss->ttPv; - // We make sure to limit the extensions in some way to avoid a search explosion - if (!PvNode && ss->multipleExtensions <= 16) - { - extension = 2 + (value < singularBeta - 11 && !ttCapture); - depth += depth < 14; - } - if (PvNode && !ttCapture && ss->multipleExtensions <= 5 - && value < singularBeta - 38) - extension = 2; + extension = 1 + (value < singularBeta - doubleMargin) + + (value < singularBeta - tripleMargin) + + (value < singularBeta - quadMargin); + + depth += ((!PvNode) && (depth < 14)); } // Multi-cut pruning @@ -1104,7 +1102,6 @@ moves_loop: // When in check, search starts here // Add extension to new depth newDepth += extension; - ss->multipleExtensions = (ss - 1)->multipleExtensions + (extension >= 2); // Speculative prefetch as early as possible prefetch(tt.first_entry(pos.key_after(move))); diff --git a/src/search.h b/src/search.h index 444e3b8b..cb73a5af 100644 --- a/src/search.h +++ b/src/search.h @@ -74,7 +74,6 @@ struct Stack { bool inCheck; bool ttPv; bool ttHit; - int multipleExtensions; int cutoffCnt; }; From d712ed38d1c6c9c76ad375efbd4b8a0469200c0b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 4 May 2024 21:43:07 +0300 Subject: [PATCH 674/678] Simplify shuffling and optimism divisors to constants Shuffling divisor and Optimism divisors passed STC & LTC separately: shuf STC: https://tests.stockfishchess.org/tests/view/66356316b4e9bdbc7228b995 shuf LTC: https://tests.stockfishchess.org/tests/view/6635815a73559a8aa857c1dc opt STC: https://tests.stockfishchess.org/tests/view/66356326b4e9bdbc7228b9a0 opt LTC: https://tests.stockfishchess.org/tests/view/663615c673559a8aa8589f8a And then passed LTC together: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 178278 W: 45039 L: 44979 D: 88260 Ptnml(0-2): 43, 19776, 49460, 19798, 62 https://tests.stockfishchess.org/tests/view/66363f19cdb7cf5da64e22a3 closes https://github.com/official-stockfish/Stockfish/pull/5212 Bench: 2198243 --- src/evaluate.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 11999b55..5be7e7a1 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -62,11 +62,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); - const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, - int npmConstant, int evalDiv, int shufflingConstant, - int shufflingDiv) { + const auto adjustEval = [&](int nnueDiv, int pawnCountConstant, int pawnCountMul, + int npmConstant, int evalDiv, int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; int npm = pos.non_pawn_material() / 64; @@ -76,13 +75,13 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Damp down the evaluation linearly when shuffling int shuffling = pos.rule50_count(); - v = v * (shufflingConstant - shuffling) / shufflingDiv; + v = v * (shufflingConstant - shuffling) / 207; }; if (!smallNet) - adjustEval(524, 32395, 942, 11, 139, 1058, 178, 204); + adjustEval(32395, 942, 11, 139, 1058, 178); else - adjustEval(515, 32793, 944, 9, 140, 1067, 206, 206); + adjustEval(32793, 944, 9, 140, 1067, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 6da1590de0980ca569827e2905f5b423e1a00a52 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Wed, 1 May 2024 18:31:38 +0800 Subject: [PATCH 675/678] Some history fixes and tidy-up This adds the functions `update_refutations` and `update_quiet_histories` to better distinguish the two. `update_quiet_stats` now just calls both of these functions. The functional side of this patch is two-fold: 1. Stop refutations being updated when we carry out multicut 2. Update pawn history every time we update other quiet histories Yellow STC: LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 238976 W: 61506 L: 61415 D: 116055 Ptnml(0-2): 846, 28628, 60456, 28705, 853 https://tests.stockfishchess.org/tests/view/66321b5ed01fb9ac9bcdca83 However, it passed in <-1.75, 0.25> bounds: $ python3 sprt.py --wins 61506 --losses 61415 --draws 116055 --elo0 -1.75 --elo1 0.25 ELO: 0.132 +- 0.998 [-0.865, 1.13] LLR: 4.15 [-1.75, 0.25] (-2.94, 2.94) H1 Accepted Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 399126 W: 100730 L: 100896 D: 197500 Ptnml(0-2): 116, 44328, 110843, 44158, 118 https://tests.stockfishchess.org/tests/view/66357b0473559a8aa857ba6f closes #5215 Bench 2370967 --- src/search.cpp | 51 +++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 06d31510..43f18af2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -114,8 +114,11 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_quiet_stats( +void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerThread, Move move); +void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); void update_all_stats(const Position& pos, Stack* ss, Search::Worker& workerThread, @@ -1068,7 +1071,7 @@ moves_loop: // When in check, search starts here else if (singularBeta >= beta) { if (!ttCapture) - update_quiet_stats(pos, ss, *this, ttMove, -stat_malus(depth)); + update_quiet_histories(pos, ss, *this, ttMove, -stat_malus(depth)); return singularBeta; } @@ -1724,7 +1727,6 @@ void update_all_stats(const Position& pos, int captureCount, Depth depth) { - Color us = pos.side_to_move(); CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; @@ -1737,23 +1739,11 @@ void update_all_stats(const Position& pos, int bestMoveBonus = bestValue > beta + 185 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus - // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); - int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; - // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) - { - workerThread - .pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] - << -quietMoveMalus; - - workerThread.mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; - update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - quietsSearched[i].to_sq(), -quietMoveMalus); - } + update_quiet_histories(pos, ss, workerThread, quietsSearched[i], -quietMoveMalus); } else { @@ -1794,10 +1784,8 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { } } - // Updates move sorting heuristics -void update_quiet_stats( - const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { +void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerThread, Move move) { // Update killers if (ss->killers[0] != move) @@ -1806,10 +1794,6 @@ void update_quiet_stats( ss->killers[0] = move; } - Color us = pos.side_to_move(); - workerThread.mainHistory[us][move.from_to()] << bonus; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); - // Update countermove history if (((ss - 1)->currentMove).is_ok()) { @@ -1817,6 +1801,27 @@ void update_quiet_stats( workerThread.counterMoves[pos.piece_on(prevSq)][prevSq] = move; } } + +void update_quiet_histories( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { + + Color us = pos.side_to_move(); + workerThread.mainHistory[us][move.from_to()] << bonus; + + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); + + int pIndex = pawn_structure_index(pos); + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus; +} + +// Updates move sorting heuristics +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { + + update_refutations(pos, ss, workerThread, move); + update_quiet_histories(pos, ss, workerThread, move, bonus); +} + } // When playing with strength handicap, choose the best move among a set of RootMoves From f1612612457fd90f9842b2432d795ee6e2e26ebc Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 5 May 2024 05:20:05 +0300 Subject: [PATCH 676/678] Adjust history usage in moves loop pruning After experiments with conthist 5 addition failed really bad divions by 2 passed as a gainer. Passed STC: https://tests.stockfishchess.org/tests/view/6636d7114b68b70d858035ce LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 35936 W: 9287 L: 8976 D: 17673 Ptnml(0-2): 81, 4129, 9234, 4446, 78 Passed LTC: https://tests.stockfishchess.org/tests/view/6636ddb64b68b70d858040a8 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 82428 W: 21035 L: 20622 D: 40771 Ptnml(0-2): 29, 8985, 22775, 9394, 31 closes https://github.com/official-stockfish/Stockfish/pull/5217 Bench: 2309253 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 43f18af2..a60f4d36 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -991,7 +991,7 @@ moves_loop: // When in check, search starts here int history = (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] / 2 + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) From 61f12a4c383a76c5304aa2cf9cb6e47d5aae0606 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Wed, 1 May 2024 15:54:17 +0800 Subject: [PATCH 677/678] Simplify accumulator refreshes Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/6631f5d5d01fb9ac9bcdc7d0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 57472 W: 14979 L: 14784 D: 27709 Ptnml(0-2): 185, 6486, 15192, 6695, 178 closes https://github.com/official-stockfish/Stockfish/pull/5207 No functional change --- src/nnue/nnue_feature_transformer.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 018b715e..2b11adef 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -668,20 +668,20 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; - int i0 = 0; - for (; i0 < int(std::min(removed.size(), added.size())); ++i0) + int i = 0; + for (; i < int(std::min(removed.size(), added.size())); ++i) { - IndexType indexR = removed[i0]; + IndexType indexR = removed[i]; const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; auto columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i0]; + IndexType indexA = added[i]; const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; auto columnA = reinterpret_cast(&weights[offsetA]); for (unsigned k = 0; k < NumRegs; ++k) acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); } - for (int i = i0; i < int(removed.size()); ++i) + for (; i < int(removed.size()); ++i) { IndexType index = removed[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; @@ -690,7 +690,7 @@ class FeatureTransformer { for (unsigned k = 0; k < NumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } - for (int i = i0; i < int(added.size()); ++i) + for (; i < int(added.size()); ++i) { IndexType index = added[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; From 070e564c389eb2c263f3982060ab5899b67d0a62 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sun, 5 May 2024 07:36:48 +0800 Subject: [PATCH 678/678] VVLTC search tune This patch is the result of two tuning stages: 1. ~32k games at 60+0.6 th8: https://tests.stockfishchess.org/tests/view/662d9dea6115ff6764c7f817 2. ~193k games at 80+0.8 th6, based on PR #5211: https://tests.stockfishchess.org/tests/view/663587e273559a8aa857ca00. Based on extensive VVLTC tuning and testing both before and after #5211, it is observed that introduction of new extensions positively affected the search tune results. Passed VVLTC 70+0.7 th7 1st sprt: https://tests.stockfishchess.org/tests/view/6636c6f04b68b70d85801409 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 18566 W: 4864 L: 4620 D: 9082 Ptnml(0-2): 0, 1608, 5827, 1844, 4 Passed VVLTC 70+0.7 th7 2nd sprt: https://tests.stockfishchess.org/tests/view/6636d4b84b68b70d85802ab7 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 43142 W: 11141 L: 10838 D: 21163 Ptnml(0-2): 4, 3915, 13427, 4224, 1 Passed VVLTC 70+0.7 3rd sprt: https://tests.stockfishchess.org/tests/view/66376b4f9819650825aa230b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 40322 W: 10374 L: 10076 D: 19872 Ptnml(0-2): 1, 3660, 12544, 3952, 4 The first two sprts were run against passed #5211. The third sprt was run against latest master. closes https://github.com/official-stockfish/Stockfish/pull/5216 Bench: 2180675 --- src/search.cpp | 80 +++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a60f4d36..6830e4b1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -59,9 +59,9 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 118 - 45 * noTtCutNode; - Value improvingDeduction = 52 * improving * futilityMult / 32; - Value worseningDeduction = (316 + 48 * improving) * oppWorsening * futilityMult / 1024; + Value futilityMult = 126 - 46 * noTtCutNode; + Value improvingDeduction = 58 * improving * futilityMult / 32; + Value worseningDeduction = (323 + 52 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -73,15 +73,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 9260; + v += cv * std::abs(cv) / 7350; return std::clamp(v, 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::clamp(214 * d - 318, 16, 1304); } +int stat_bonus(Depth d) { return std::clamp(208 * d - 297, 16, 1406); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 572 * d - 284 : 1355); } +int stat_malus(Depth d) { return (d < 4 ? 520 * d - 312 : 1479); } // 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); } @@ -310,12 +310,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 10 + avg * avg / 11480; + delta = 10 + avg * avg / 9530; 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] = 122 * avg / (std::abs(avg) + 92); + optimism[us] = 119 * avg / (std::abs(avg) + 88); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -502,10 +502,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-65); + h->fill(-60); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((20.14 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((18.93 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks); } @@ -738,7 +738,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1644, 1384); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1796, 1526); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -761,7 +761,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 471 - (275 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 433 - (302 - 141 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -770,23 +770,23 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 12 + if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 286 + - (ss - 1)->statScore / 254 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 18001 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 312 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16993 + && eval >= beta && ss->staticEval >= beta - 19 * depth + 326 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 134, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -834,7 +834,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 169 - 63 * improving; + probCutBeta = beta + 159 - 66 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -890,7 +890,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 452; + probCutBeta = beta + 420; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -975,15 +975,15 @@ moves_loop: // When in check, search starts here // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 285 + 277 * lmrDepth + Value futilityValue = ss->staticEval + 295 + 280 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -199 * depth, 199 * depth); - if (!pos.see_ge(move, -203 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -197 * depth, 196 * depth); + if (!pos.see_ge(move, -186 * depth - seeHist)) continue; } else @@ -995,18 +995,18 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4173 * depth) + if (lmrDepth < 6 && history < -4081 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 5285; + lmrDepth += history / 4768; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 57) + 131 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 52 ? 134 : 54) + 142 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 14 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1017,7 +1017,7 @@ 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, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -28 * lmrDepth * lmrDepth)) continue; } } @@ -1037,11 +1037,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (65 + 59 * (ss->ttPv && !PvNode)) * depth / 63; + Value singularBeta = ttValue - (65 + 52 * (ss->ttPv && !PvNode)) * depth / 63; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1099,7 +1099,7 @@ moves_loop: // When in check, search starts here else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 3807) + > 4016) extension = 1; } @@ -1151,10 +1151,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 5024; + + (*contHist[3])[movedPiece][move.to_sq()] - 5078; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 13182; + r -= ss->statScore / 12076; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1173,7 +1173,7 @@ 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 + 42 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1291,7 +1291,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13546 && value > -13478) + if (depth > 2 && depth < 13 && beta < 15868 && value > -14630) depth -= 2; assert(depth > 0); @@ -1334,8 +1334,8 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) - + ((ss - 1)->moveCount > 11) + (!ss->inCheck && bestValue <= ss->staticEval - 142) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14455) + + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 130) + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 77); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1495,7 +1495,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 250; + futilityBase = ss->staticEval + 270; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1575,7 +1575,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -79)) + if (!pos.see_ge(move, -69)) continue; } @@ -1643,7 +1643,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1150 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); + return (reductionScale + 1318 - delta * 760 / rootDelta) / 1024 + (!i && reductionScale > 1066); } TimePoint Search::Worker::elapsed() const { @@ -1736,7 +1736,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 185 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 165 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus);