From 0fd186fb28e7e1e5f2cc5ef8388115c950eaad9e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 1 Jul 2023 12:18:46 +0200 Subject: [PATCH 001/326] 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 002/326] 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 003/326] 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 004/326] 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 005/326] 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 006/326] 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 007/326] 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 008/326] 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 009/326] 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 010/326] 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 011/326] 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 012/326] 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 013/326] 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 014/326] 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 015/326] 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 016/326] 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 017/326] 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 018/326] 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 019/326] 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 020/326] 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 021/326] 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 022/326] 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 023/326] 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 024/326] 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 025/326] 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 026/326] 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 027/326] 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 028/326] 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 029/326] 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 030/326] 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 031/326] 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 032/326] 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 033/326] 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 034/326] 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 035/326] 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 036/326] 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 037/326] 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 038/326] 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 039/326] 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 040/326] 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 041/326] 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 042/326] 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 043/326] 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 044/326] 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 045/326] 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 046/326] 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 047/326] 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 048/326] 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 049/326] 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 050/326] 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 051/326] 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 052/326] 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 053/326] 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 054/326] 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 055/326] 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 056/326] 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 057/326] 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 058/326] 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 059/326] 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 060/326] 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 061/326] 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 062/326] 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 063/326] 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 064/326] 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 065/326] 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 066/326] 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 067/326] 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 068/326] 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 069/326] 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 070/326] 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 071/326] 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 072/326] 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 073/326] 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 074/326] 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 075/326] 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 076/326] 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 077/326] 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 078/326] 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 079/326] 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 080/326] 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 081/326] 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 082/326] 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 083/326] 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 084/326] 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 085/326] 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 086/326] 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 087/326] 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 088/326] 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 089/326] 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 090/326] 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 091/326] 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 092/326] 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 093/326] 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 094/326] 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 095/326] 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 096/326] 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 097/326] 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 098/326] 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 099/326] 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 100/326] 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 101/326] 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 102/326] 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 103/326] 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 104/326] 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 105/326] 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 106/326] 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 107/326] 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 108/326] 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 109/326] 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 110/326] 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 111/326] 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 112/326] 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 113/326] 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 114/326] 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 115/326] 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 116/326] 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 117/326] 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 118/326] 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 119/326] 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 120/326] 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 121/326] 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 122/326] 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 123/326] 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 124/326] 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 125/326] 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 126/326] 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 127/326] 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 128/326] 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 129/326] 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 130/326] 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 131/326] 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 132/326] 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 133/326] 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 134/326] 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 135/326] 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 136/326] 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 137/326] 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 138/326] 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 139/326] 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 140/326] 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 141/326] 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 142/326] 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 143/326] 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 144/326] 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 145/326] 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 146/326] 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 147/326] 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 148/326] 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 149/326] 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 150/326] 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 151/326] 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 152/326] 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 153/326] 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 154/326] 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 155/326] 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 156/326] 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 157/326] 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 158/326] 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 159/326] 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 160/326] 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 161/326] 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 162/326] 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 163/326] 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 164/326] 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 165/326] 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 166/326] 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 167/326] 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 168/326] 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 169/326] 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 170/326] 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 171/326] 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 172/326] 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 173/326] 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 174/326] 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 175/326] 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 176/326] 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 177/326] 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 178/326] 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 179/326] 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 180/326] 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 181/326] 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 182/326] 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 183/326] 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 184/326] 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 185/326] 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 186/326] 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 187/326] 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 188/326] 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 189/326] 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 190/326] 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 191/326] 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 192/326] 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 193/326] 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 194/326] 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 195/326] 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 196/326] 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 197/326] 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 198/326] 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 199/326] 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 200/326] 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 201/326] 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 202/326] 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 203/326] 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 204/326] 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 205/326] 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 206/326] 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 207/326] 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 208/326] 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 209/326] 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 210/326] 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 211/326] 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 212/326] 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 213/326] 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 214/326] 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 215/326] 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 216/326] 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 217/326] 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 218/326] 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 219/326] 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 220/326] 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 221/326] 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 222/326] 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 223/326] 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 224/326] 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 225/326] 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 226/326] 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 227/326] 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 228/326] 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 229/326] 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 230/326] 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 231/326] 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 232/326] 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 233/326] 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 234/326] 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 235/326] 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 236/326] 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 237/326] 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 238/326] 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 239/326] 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 240/326] 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 241/326] 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 242/326] 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 243/326] 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 244/326] 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 245/326] 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 246/326] 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 247/326] 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 248/326] 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 249/326] 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 250/326] 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 251/326] 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 252/326] 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 253/326] 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 254/326] 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 255/326] 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 256/326] 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 257/326] 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 258/326] 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 259/326] 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 260/326] 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 261/326] 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 262/326] 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 263/326] 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 264/326] 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 265/326] 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 266/326] 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 267/326] 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 268/326] 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 269/326] 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 270/326] 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 271/326] 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 272/326] 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 273/326] 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 274/326] 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 275/326] 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 276/326] 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 277/326] 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 278/326] 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 279/326] 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 280/326] 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 281/326] 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 282/326] 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 283/326] 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 284/326] 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 285/326] 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 286/326] 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 287/326] 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 288/326] 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 289/326] 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 290/326] 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 291/326] 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 292/326] 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 293/326] 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 294/326] 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 295/326] 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 296/326] 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 297/326] 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 298/326] 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 299/326] 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 300/326] 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 301/326] 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 302/326] 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 303/326] 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 304/326] 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 305/326] 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 306/326] 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 307/326] 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 308/326] 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 309/326] 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 310/326] 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 311/326] 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 312/326] 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 313/326] 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 314/326] 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 315/326] 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 316/326] 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 317/326] 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 318/326] 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 319/326] 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 320/326] 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 321/326] 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 322/326] 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 323/326] 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 324/326] 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 325/326] 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 326/326] 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;