diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index c414c51fc..f72451f5b 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -8,11 +8,11 @@ "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 --" + "sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-lin/sde -future --" }, { - "name": "MacOS 13 Apple Clang", - "os": "macos-13", + "name": "macOS 15 Apple Clang", + "os": "macos-15-intel", "simple_name": "macos", "compiler": "clang++", "comp": "clang", @@ -20,7 +20,7 @@ "archive_ext": "tar" }, { - "name": "MacOS 14 Apple Clang M1", + "name": "macOS 14 Apple Clang M1", "os": "macos-14", "simple_name": "macos-m1", "compiler": "clang++", @@ -38,7 +38,7 @@ "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 --", + "sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-win/sde.exe -future --", "archive_ext": "zip" }, { @@ -61,8 +61,8 @@ "x86-64-bmi2", "x86-64-avxvnni", "x86-64-avx512", - "x86-64-vnni256", "x86-64-vnni512", + "x86-64-avx512icl", "apple-silicon", "armv8", "armv8-dotprod" @@ -105,13 +105,13 @@ } }, { - "binaries": "x86-64-vnni256", + "binaries": "x86-64-vnni512", "config": { "os": "macos-14" } }, { - "binaries": "x86-64-vnni512", + "binaries": "x86-64-avx512icl", "config": { "os": "macos-14" } @@ -119,25 +119,25 @@ { "binaries": "x86-64-avxvnni", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { "binaries": "x86-64-avx512", "config": { - "os": "macos-13" - } - }, - { - "binaries": "x86-64-vnni256", - "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { "binaries": "x86-64-vnni512", "config": { - "os": "macos-13" + "os": "macos-15-intel" + } + }, + { + "binaries": "x86-64-avx512icl", + "config": { + "os": "macos-15-intel" } }, { @@ -176,18 +176,18 @@ "os": "windows-11-arm" } }, - { - "binaries": "x86-64-vnni256", - "config": { - "os": "windows-11-arm" - } - }, { "binaries": "x86-64-vnni512", "config": { "os": "windows-11-arm" } }, + { + "binaries": "x86-64-avx512icl", + "config": { + "os": "windows-11-arm" + } + }, { "binaries": "apple-silicon", "config": { @@ -215,7 +215,7 @@ { "binaries": "apple-silicon", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { @@ -239,7 +239,7 @@ { "binaries": "armv8", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { @@ -269,7 +269,7 @@ { "binaries": "armv8-dotprod", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 67b2e1c55..7805b24d6 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -43,10 +43,10 @@ jobs: - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 + uses: petarpetrovt/setup-sde@f0fa5971dc275704531e94264dd23250c442aa41 # @v2.4 with: environmentVariableName: SDE_DIR - sdeVersion: 9.27.0 + sdeVersion: 9.33.0 - name: Download the used network from the fishtest framework run: make net diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml index 85c2be3e7..43d35ca18 100644 --- a/.github/workflows/matetrack.yml +++ b/.github/workflows/matetrack.yml @@ -24,7 +24,7 @@ jobs: with: repository: vondele/matetrack path: matetrack - ref: 4f8a80860ed8f3607f05a9195df8b40203bdc360 + ref: 2d96fa3373f90edb032b7ea7468473fb9e6f0343 persist-credentials: false - name: matetrack install deps @@ -47,25 +47,44 @@ jobs: wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-wdl/ wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-dtz/ - - name: Run matetrack + - name: Run matetrack th1 working-directory: matetrack run: | - python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout.out - ! grep "issues were detected" matecheckout.out > /dev/null + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout1.out + ! grep "issues were detected" matecheckout1.out > /dev/null - - name: Run matetrack with --syzygy50MoveRule false + - name: Run matetrack th4 + working-directory: matetrack + run: | + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 --threads 4 | tee matecheckout4.out + ! grep "issues were detected" matecheckout4.out > /dev/null + + - name: Run matetrack th1 with --syzygy50MoveRule false working-directory: matetrack run: | grep 5men cursed.epd > cursed5.epd - python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --syzygy50MoveRule false | tee matecheckcursed.out - ! grep "issues were detected" matecheckcursed.out > /dev/null + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --syzygy50MoveRule false | tee matecheckcursed1.out + ! grep "issues were detected" matecheckcursed1.out > /dev/null - - name: Verify mate and TB win count for matecheckcursed.out + - name: Run matetrack th4 with --syzygy50MoveRule false working-directory: matetrack run: | - mates=$(grep "Found mates:" matecheckcursed.out | awk '{print $3}') - tbwins=$(grep "Found TB wins:" matecheckcursed.out | awk '{print $4}') + grep 5men cursed.epd > cursed5.epd + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --threads 4 --syzygy50MoveRule false | tee matecheckcursed4.out + ! grep "issues were detected" matecheckcursed4.out > /dev/null + + - name: Verify mate and TB win count for matecheckcursed[14].out + working-directory: matetrack + run: | + mates=$(grep "Found mates:" matecheckcursed1.out | awk '{print $3}') + tbwins=$(grep "Found TB wins:" matecheckcursed1.out | awk '{print $4}') if [ $(($mates + $tbwins)) -ne 32 ]; then - echo "Sum of mates and TB wins is not 32 in matecheckcursed.out" >&2 + echo "Sum of mates and TB wins is not 32 in matecheckcursed1.out" >&2 + exit 1 + fi + mates=$(grep "Found mates:" matecheckcursed4.out | awk '{print $3}') + tbwins=$(grep "Found TB wins:" matecheckcursed4.out | awk '{print $4}') + if [ $(($mates + $tbwins)) -ne 32 ]; then + echo "Sum of mates and TB wins is not 32 in matecheckcursed4.out" >&2 exit 1 fi diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 950435f30..d9b783eb6 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -42,7 +42,7 @@ jobs: - name: Run with glibcxx assertions make_option: "" cxx_extra_flags: -D_GLIBCXX_ASSERTIONS - instrumented_option: non + instrumented_option: none defaults: run: working-directory: src diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 95ca12092..c2280f0b4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,21 +56,21 @@ jobs: base_image: "ppc64le/alpine:latest" platform: linux/ppc64le shell: bash - - name: MacOS 13 Apple Clang - os: macos-13 + - name: macOS 15 Apple Clang + os: macos-15-intel compiler: clang++ comp: clang run_64bit_tests: true shell: bash - - name: MacOS 14 Apple Clang M1 + - 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 + - name: macOS 15 GCC 11 + os: macos-15-intel compiler: g++-11 comp: gcc run_64bit_tests: true diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 1067f6e76..073e40a13 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -12,20 +12,17 @@ on: jobs: Artifacts: name: ${{ matrix.config.name }} ${{ matrix.binaries }} - runs-on: ${{ matrix.config.os }} + runs-on: ubuntu-latest env: - COMPCXX: ${{ 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 }} + shell: bash steps: - uses: actions/checkout@v4 with: @@ -37,13 +34,6 @@ jobs: 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 @@ -69,13 +59,13 @@ jobs: cp CONTRIBUTING.md ../stockfish/ - name: Create tar - if: runner.os != 'Windows' + if: ${{ !startsWith(matrix.config.os, 'windows') }} run: | chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT tar -cvf stockfish-$NAME-$BINARY.tar stockfish - name: Create zip - if: runner.os == 'Windows' + if: ${{ startsWith(matrix.config.os, 'windows') }} run: | zip -r stockfish-$NAME-$BINARY.zip stockfish diff --git a/AUTHORS b/AUTHORS index b43a9e5b6..4729afeab 100644 --- a/AUTHORS +++ b/AUTHORS @@ -31,6 +31,8 @@ Andy Duplain Antoine Champion (antoinechampion) Aram Tumanian (atumanian) Arjun Temurnikar +Aron Petkovski (fury) +Arseniy Surkov (codedeliveryservice) Artem Solopiy (EntityFX) Auguste Pop Balazs Szilagyi @@ -56,10 +58,11 @@ Ciekce clefrks Clemens L. (rn5f107s2) Cody Ho (aesrentai) +CSTENTOR Dale Weiler (graphitemaster) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) -Daniel Monroe (Ergodice) +Daniel Monroe (daniel-monroe) Daniel Samek (DanSamek) Dan Schmidt (dfannius) Dariusz Orzechowski (dorzechowski) @@ -123,6 +126,7 @@ Jonathan McDermid (jonathanmcdermid) Joost VandeVondele (vondele) Joseph Ellis (jhellis3) Joseph R. Prostko +Jost Triller (tsoj) Jörg Oster (joergoster) Julian Willemer (NightlyKing) jundery @@ -130,6 +134,7 @@ Justin Blanchard (UncombedCoconut) Kelly Wilson Ken Takusagawa Kenneth Lee (kennethlee33) +kevlu8 Kian E (KJE-98) kinderchocolate Kiran Panditrao (Krgp) @@ -178,6 +183,7 @@ Nathan Rugg (nmrugg) Nguyen Pham (nguyenpham) Nicklas Persson (NicklasPersson) Nick Pelling (nickpelling) +Nicolas Duhamel (nikloskoda) Niklas Fiekas (niklasf) Nikolay Kostov (NikolayIT) Norman Schmidt (FireFather) @@ -211,6 +217,7 @@ Ronald de Man (syzygy1, syzygy) Ron Britvich (Britvich) rqs Rui Coelho (ruicoelhopedro) +rustam-cpp Ryan Schmitt Ryan Takker Sami Kiminki (skiminki) @@ -230,12 +237,15 @@ Stefano Di Martino (StefanoD) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Stephen Touset (stouset) +Stockfisher69 +Styx (styxdoto) Syine Mineta (MinetaS) Taras Vuk (TarasVuk) Thanar2 thaspel theo77186 TierynnB +Timothy Herchen (anematode) Ting-Hsuan Huang (fffelix-huang) Tobias Steinmann Tomasz Sobczyk (Sopel97) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index e8c8f23f2..67dd60ca5 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -31,7 +31,7 @@ set_arch_loongarch64() { if check_flags 'lasx'; then true_arch='loongarch64-lasx' elif check_flags 'lsx'; then - true_arch='lonngarch64-lsx' + true_arch='loongarch64-lsx' else true_arch='loongarch64' fi @@ -42,9 +42,11 @@ set_arch_x86_64() { if check_flags 'avx512f' 'avx512cd' 'avx512vl' 'avx512dq' 'avx512bw' 'avx512ifma' 'avx512vbmi' 'avx512vbmi2' 'avx512vpopcntdq' 'avx512bitalg' 'avx512vnni' 'vpclmulqdq' 'gfni' 'vaes'; then true_arch='x86-64-avx512icl' elif check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then - true_arch='x86-64-vnni256' + true_arch='x86-64-vnni512' elif check_flags 'avx512f' 'avx512bw'; then true_arch='x86-64-avx512' + elif check_flags 'avxvnni'; then + true_arch='x86-64-avxvnni' elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then true_arch='x86-64-bmi2' elif check_flags 'avx2'; then @@ -57,7 +59,7 @@ set_arch_x86_64() { } set_arch_ppc_64() { - if $(grep -q -w "altivec" /proc/cpuinfo); then + if grep -q -w "altivec" /proc/cpuinfo; then power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo) if [ "0$power" -gt 7 ]; then # VSX started with POWER8 @@ -83,7 +85,7 @@ case $uname_s in 'x86_64') 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 + if [ "$true_arch" = 'x86-64-avx512' ]; then file_arch='x86-64-bmi2' fi ;; diff --git a/src/Makefile b/src/Makefile index f61a0f9a8..bb3a515b2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -98,7 +98,6 @@ VPATH = syzygy:nnue:nnue/features # avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 # avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX # avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 -# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands # vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 # avx512icl = yes/no --- ... multiple ... --- Use All AVX-512 features available on both Intel Ice Lake and AMD Zen 4 # altivec = yes/no --- -maltivec --- Use PowerPC Altivec SIMD extension @@ -130,7 +129,7 @@ endif # explicitly check for the list of supported architectures (as listed with make help), # the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true` ifeq ($(ARCH), $(filter $(ARCH), \ - x86-64-avx512icl x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni \ + x86-64-avx512icl x86-64-vnni512 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-64-altivec ppc-64-vsx ppc-32 e2k \ armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \ @@ -155,7 +154,6 @@ sse41 = no avx2 = no avxvnni = no avx512 = no -vnni256 = no vnni512 = no avx512icl = no altivec = no @@ -272,17 +270,6 @@ ifeq ($(findstring -avx512,$(ARCH)),-avx512) avx512 = yes endif -ifeq ($(findstring -vnni256,$(ARCH)),-vnni256) - popcnt = yes - sse = yes - sse2 = yes - ssse3 = yes - sse41 = yes - avx2 = yes - pext = yes - vnni256 = yes -endif - ifeq ($(findstring -vnni512,$(ARCH)),-vnni512) popcnt = yes sse = yes @@ -593,6 +580,10 @@ endif # Locate the version in the same directory as the compiler used, # with fallback to a generic one if it can't be located LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))llvm-profdata +# for icx +ifeq ($(wildcard $(LLVM_PROFDATA)),) + LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))/compiler/llvm-profdata +endif ifeq ($(wildcard $(LLVM_PROFDATA)),) LLVM_PROFDATA := llvm-profdata endif @@ -723,17 +714,10 @@ ifeq ($(avx512),yes) endif endif -ifeq ($(vnni256),yes) - CXXFLAGS += -DUSE_VNNI - ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) - CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256 - endif -endif - ifeq ($(vnni512),yes) CXXFLAGS += -DUSE_VNNI ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) - CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512 + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl endif endif @@ -911,9 +895,8 @@ help: echo "Supported archs:" && \ echo "" && \ echo "native > select the best architecture for the host processor (default)" && \ - echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lane or AMD Zen 4" && \ + echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lake or AMD Zen 4" && \ echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \ - echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \ echo "x86-64-avx512 > x86 64-bit with avx512 support" && \ echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" && \ echo "x86-64-bmi2 > x86 64-bit with bmi2 support" && \ @@ -1058,7 +1041,6 @@ config-sanity: net echo "avx2: '$(avx2)'" && \ echo "avxvnni: '$(avxvnni)'" && \ echo "avx512: '$(avx512)'" && \ - echo "vnni256: '$(vnni256)'" && \ echo "vnni512: '$(vnni512)'" && \ echo "avx512icl: '$(avx512icl)'" && \ echo "altivec: '$(altivec)'" && \ @@ -1096,7 +1078,6 @@ config-sanity: net (test "$(sse41)" = "yes" || test "$(sse41)" = "no") && \ (test "$(avx2)" = "yes" || test "$(avx2)" = "no") && \ (test "$(avx512)" = "yes" || test "$(avx512)" = "no") && \ - (test "$(vnni256)" = "yes" || test "$(vnni256)" = "no") && \ (test "$(vnni512)" = "yes" || test "$(vnni512)" = "no") && \ (test "$(avx512icl)" = "yes" || test "$(avx512icl)" = "no") && \ (test "$(altivec)" = "yes" || test "$(altivec)" = "no") && \ @@ -1150,7 +1131,7 @@ icx-profile-make: all icx-profile-use: - $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw + $(XCRUN) $(LLVM_PROFDATA) merge -output=stockfish.profdata *.profraw $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ EXTRALDFLAGS='-fprofile-use ' \ diff --git a/src/bitboard.h b/src/bitboard.h index f959bcb86..27ef89c0e 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -179,11 +179,6 @@ inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } -// 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. diff --git a/src/history.h b/src/history.h index 46914789e..940e98991 100644 --- a/src/history.h +++ b/src/history.h @@ -33,7 +33,7 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; @@ -44,14 +44,12 @@ static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, "CORRECTION_HISTORY_SIZE has to be a power of 2"); -enum PawnHistoryType { - Normal, - Correction -}; +inline int pawn_history_index(const Position& pos) { + return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); +} -template -inline int pawn_structure_index(const Position& pos) { - return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); +inline int pawn_correction_history_index(const Position& pos) { + return pos.pawn_key() & (CORRECTION_HISTORY_SIZE - 1); } inline int minor_piece_index(const Position& pos) { @@ -103,7 +101,7 @@ using Stats = MultiArray, Sizes...>; // 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 https://www.chessprogramming.org/Butterfly_Boards (~11 elo) +// see https://www.chessprogramming.org/Butterfly_Boards using ButterflyHistory = Stats; // LowPlyHistory is adressed by play and move's from and to squares, used @@ -120,7 +118,6 @@ 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) using ContinuationHistory = MultiArray; // PawnHistory is addressed by the pawn structure and a move's [piece][to] diff --git a/src/movegen.cpp b/src/movegen.cpp index 10adc70be..697a83cdf 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -37,6 +37,7 @@ namespace { #if defined(USE_AVX512ICL) inline Move* write_moves(Move* moveList, uint32_t mask, __m512i vector) { + // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4 _mm512_storeu_si512(reinterpret_cast<__m512i*>(moveList), _mm512_maskz_compress_epi16(mask, vector)); return moveList + popcount(mask); diff --git a/src/movepick.cpp b/src/movepick.cpp index 79b6f55a2..2eec3556b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -115,8 +115,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo 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)); } // Assigns a numerical value to each move in a list, used for sorting. @@ -129,13 +128,15 @@ ExtMove* MovePicker::score(MoveList& ml) { Color us = pos.side_to_move(); - [[maybe_unused]] Bitboard threatByLesser[QUEEN + 1]; + [[maybe_unused]] Bitboard threatByLesser[KING + 1]; if constexpr (Type == QUIETS) { + threatByLesser[PAWN] = 0; threatByLesser[KNIGHT] = threatByLesser[BISHOP] = pos.attacks_by(~us); threatByLesser[ROOK] = pos.attacks_by(~us) | pos.attacks_by(~us) | threatByLesser[KNIGHT]; threatByLesser[QUEEN] = pos.attacks_by(~us) | threatByLesser[ROOK]; + threatByLesser[KING] = pos.attacks_by(~us) | threatByLesser[QUEEN]; } ExtMove* it = cur; @@ -158,7 +159,7 @@ ExtMove* MovePicker::score(MoveList& ml) { { // histories m.value = 2 * (*mainHistory)[us][m.from_to()]; - m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; + m.value += 2 * (*pawnHistory)[pawn_history_index(pos)][pc][to]; m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to]; @@ -170,12 +171,10 @@ ExtMove* MovePicker::score(MoveList& ml) { // penalty for moving to a square threatened by a lesser piece // or bonus for escaping an attack by a lesser piece. - if (KNIGHT <= pt && pt <= QUEEN) - { - static constexpr int bonus[QUEEN + 1] = {0, 0, 144, 144, 256, 517}; - int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); - m.value += bonus[pt] * v; - } + static constexpr int bonus[KING + 1] = {0, 0, 144, 144, 256, 517, 10000}; + int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); + m.value += bonus[pt] * v; + if (ply < LOW_PLY_HISTORY_SIZE) m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); @@ -189,7 +188,7 @@ ExtMove* MovePicker::score(MoveList& ml) { { m.value = (*mainHistory)[us][m.from_to()] + (*continuationHistory[0])[pc][to]; if (ply < LOW_PLY_HISTORY_SIZE) - m.value += 2 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); + m.value += (*lowPlyHistory)[ply][m.from_to()]; } } } @@ -316,18 +315,4 @@ top: void MovePicker::skip_quiet_moves() { skipQuiets = true; } -// this function must be called after all quiet moves and captures have been generated -bool MovePicker::can_move_king_or_pawn() const { - // SEE negative captures shouldn't be returned in GOOD_CAPTURE stage - assert(stage > GOOD_CAPTURE && stage != EVASION_INIT); - - for (const ExtMove* m = moves; m < endGenerated; ++m) - { - PieceType movedPieceType = type_of(pos.moved_piece(*m)); - if ((movedPieceType == PAWN || movedPieceType == KING) && pos.legal(*m)) - return true; - } - return false; -} - } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 9d6c02b0e..5b3190594 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -50,7 +50,6 @@ class MovePicker { MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(); void skip_quiet_moves(); - bool can_move_king_or_pawn() const; private: template diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index e77c98f8c..472da834f 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -22,6 +22,7 @@ #define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED #include +#include #include #include @@ -82,7 +83,37 @@ void find_nnz(const std::int32_t* RESTRICT input, std::uint16_t* RESTRICT out, IndexType& count_out) { - #ifdef USE_AVX512 + #if defined(USE_AVX512ICL) + + constexpr IndexType SimdWidthIn = 16; // 512 bits / 32 bits + constexpr IndexType SimdWidthOut = 32; // 512 bits / 16 bits + constexpr IndexType NumChunks = InputDimensions / SimdWidthOut; + const __m512i increment = _mm512_set1_epi16(SimdWidthOut); + __m512i base = _mm512_set_epi16( // Same permute order as _mm512_packus_epi32() + 31, 30, 29, 28, 15, 14, 13, 12, 27, 26, 25, 24, 11, 10, 9, 8, 23, 22, 21, 20, 7, 6, 5, 4, 19, + 18, 17, 16, 3, 2, 1, 0); + + IndexType count = 0; + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m512i inputV0 = _mm512_load_si512(input + i * 2 * SimdWidthIn); + const __m512i inputV1 = _mm512_load_si512(input + i * 2 * SimdWidthIn + SimdWidthIn); + + // Get a bitmask and gather non zero indices + const __m512i inputV01 = _mm512_packus_epi32(inputV0, inputV1); + const __mmask32 nnzMask = _mm512_test_epi16_mask(inputV01, inputV01); + + // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4 + __m512i nnz = _mm512_maskz_compress_epi16(nnzMask, base); + _mm512_storeu_si512(out + count, nnz); + + count += popcount(nnzMask); + base = _mm512_add_epi16(base, increment); + } + count_out = count; + + #elif defined(USE_AVX512) + constexpr IndexType SimdWidth = 16; // 512 bits / 32 bits constexpr IndexType NumChunks = InputDimensions / SimdWidth; const __m512i increment = _mm512_set1_epi32(SimdWidth); @@ -217,11 +248,13 @@ class AffineTransformSparseInput { #if defined(USE_AVX512) using invec_t = __m512i; using outvec_t = __m512i; + #define vec_add_32 _mm512_add_epi32 #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_add_32 _mm256_add_epi32 #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32 #elif defined(USE_SSSE3) @@ -240,12 +273,19 @@ class AffineTransformSparseInput { #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 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 NumAccums = OutputDimensions / OutputSimdWidth; + // If we're using high-latency dot product instructions, split the accumulators + // to create 3 separate dependency chains and merge at the end + constexpr IndexType NumRegs = + #if defined(USE_VNNI) + 3 * NumAccums; + #else + NumAccums; + #endif + std::uint16_t nnz[NumChunks]; + IndexType count; const auto input32 = reinterpret_cast(input); @@ -254,24 +294,61 @@ class AffineTransformSparseInput { const outvec_t* biasvec = reinterpret_cast(biases); outvec_t acc[NumRegs]; - for (IndexType k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < NumAccums; ++k) acc[k] = biasvec[k]; - for (IndexType j = 0; j < count; ++j) + const auto* start = nnz; + const auto* end = nnz + count; + + // convince GCC to not do weird pointer arithmetic in the following loop + const std::int8_t* weights_cp = weights; + #if defined(USE_VNNI) + for (IndexType k = NumAccums; k < NumRegs; ++k) + acc[k] = vec_zero(); + + while (start < end - 2) { - 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) + const std::ptrdiff_t i0 = *start++; + const std::ptrdiff_t i1 = *start++; + const std::ptrdiff_t i2 = *start++; + const invec_t in0 = vec_set_32(input32[i0]); + const invec_t in1 = vec_set_32(input32[i1]); + const invec_t in2 = vec_set_32(input32[i2]); + const auto col0 = + reinterpret_cast(&weights_cp[i0 * OutputDimensions * ChunkSize]); + const auto col1 = + reinterpret_cast(&weights_cp[i1 * OutputDimensions * ChunkSize]); + const auto col2 = + reinterpret_cast(&weights_cp[i2 * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumAccums; ++k) + { + vec_add_dpbusd_32(acc[k], in0, col0[k]); + vec_add_dpbusd_32(acc[k + NumAccums], in1, col1[k]); + vec_add_dpbusd_32(acc[k + 2 * NumAccums], in2, col2[k]); + } + } + for (IndexType k = 0; k < NumAccums; ++k) + acc[k] = vec_add_32(vec_add_32(acc[k], acc[k + NumAccums]), acc[k + 2 * NumAccums]); + #endif + while (start < end) + { + const std::ptrdiff_t i = *start++; + const invec_t in = vec_set_32(input32[i]); + const auto col = + reinterpret_cast(&weights_cp[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumAccums; ++k) vec_add_dpbusd_32(acc[k], in, col[k]); } outvec_t* outptr = reinterpret_cast(output); - for (IndexType k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < NumAccums; ++k) outptr[k] = acc[k]; + #undef vec_set_32 #undef vec_add_dpbusd_32 + #ifdef vec_add_32 + #undef vec_add_32 + #endif #else // Use dense implementation for the other architectures. affine_transform_non_ssse3( diff --git a/src/perft.h b/src/perft.h index 60774d82d..4a49201d5 100644 --- a/src/perft.h +++ b/src/perft.h @@ -57,9 +57,9 @@ uint64_t perft(Position& pos, Depth depth) { } inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) { - StateListPtr states(new std::deque(1)); - Position p; - p.set(fen, isChess960, &states->back()); + StateInfo st; + Position p; + p.set(fen, isChess960, &st); return perft(p, depth); } diff --git a/src/position.cpp b/src/position.cpp index 5e2c27822..d0cad3e7f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -79,7 +79,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { for (Bitboard b = pos.checkers(); b;) os << UCIEngine::square(pop_lsb(b)) << " "; - if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + if (Tablebases::MaxCardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { StateInfo st; @@ -717,6 +717,8 @@ DirtyPiece Position::do_move(Move m, Piece pc = piece_on(from); Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + bool checkEP = false; + DirtyPiece dp; dp.pc = pc; dp.from = from; @@ -804,21 +806,14 @@ DirtyPiece Position::do_move(Move m, // Move the piece. The tricky Chess960 castling is handled earlier if (m.type_of() != CASTLING) - { - 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 - && (attacks_bb(to - pawn_push(us), us) & pieces(them, PAWN))) - { - st->epSquare = to - pawn_push(us); - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - } + // Check later if the en passant square needs to be set + if ((int(to) ^ int(from)) == 16) + checkEP = true; else if (m.type_of() == PROMOTION) { @@ -863,11 +858,6 @@ DirtyPiece Position::do_move(Move m, st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } - // Update the key with the final value - st->key = k; - if (tt) - prefetch(tt->first_entry(key())); - // Set capture piece st->capturedPiece = captured; @@ -879,6 +869,63 @@ DirtyPiece Position::do_move(Move m, // Update king attacks used for fast check detection set_check_info(); + // Accurate e.p. info is needed for correct zobrist key generation and 3-fold checking + while (checkEP) + { + auto updateEpSquare = [&] { + st->epSquare = to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + }; + + Bitboard pawns = attacks_bb(to - pawn_push(us), us) & pieces(them, PAWN); + + // If there are no pawns attacking the ep square, ep is not possible + if (!pawns) + break; + + // If there are checkers other than the to be captured pawn, ep is never legal + if (checkers() & ~square_bb(to)) + break; + + if (more_than_one(pawns)) + { + // If there are two pawns potentially being abled to capture and at least one + // is not pinned, ep is legal as there are no horizontal exposed checks + if (!more_than_one(blockers_for_king(them) & pawns)) + { + updateEpSquare(); + break; + } + + // If there is no pawn on our king's file, and thus both pawns are pinned + // by bishops, ep is not legal as the king square must be in front of the to square. + // And because the ep square and the king are not on a common diagonal, either ep capture + // would expose the king to a check from one of the bishops + if (!(file_bb(square(them)) & pawns)) + break; + + // Otherwise remove the pawn on the king file, as an ep capture by it can never be legal and the + // check below relies on there only being one pawn + pawns &= ~file_bb(square(them)); + } + + Square ksq = square(them); + Square capsq = to; + Bitboard occupied = (pieces() ^ lsb(pawns) ^ capsq) | (to - pawn_push(us)); + + // If our king is not attacked after making the move, ep is legal. + if (!(attacks_bb(ksq, occupied) & pieces(us, QUEEN, ROOK)) + && !(attacks_bb(ksq, occupied) & pieces(us, QUEEN, BISHOP))) + updateEpSquare(); + + break; + } + + // Update the key with the final value + st->key = k; + if (tt) + prefetch(tt->first_entry(key())); + // 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. @@ -1058,6 +1105,8 @@ bool Position::see_ge(Move m, int threshold) const { Square from = m.from_sq(), to = m.to_sq(); + assert(piece_on(from) != NO_PIECE); + int swap = PieceValue[piece_on(to)] - threshold; if (swap < 0) return false; diff --git a/src/position.h b/src/position.h index cf6b1c472..dde496fe0 100644 --- a/src/position.h +++ b/src/position.h @@ -103,10 +103,9 @@ class Position { 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; + bool can_castle(CastlingRights cr) const; + bool castling_impeded(CastlingRights cr) const; + Square castling_rook_square(CastlingRights cr) const; // Checking Bitboard checkers() const; @@ -184,8 +183,7 @@ class Position { Square& rfrom, Square& rto, DirtyPiece* const dp = nullptr); - template - Key adjust_key50(Key k) const; + Key adjust_key50(Key k) const; // Data members Piece board[SQUARE_NB]; @@ -248,10 +246,6 @@ inline Square Position::ep_square() const { return st->epSquare; } 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); -} - 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]; @@ -288,11 +282,10 @@ inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; } 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); + return st->rule50 < 14 ? k : k ^ make_key((st->rule50 - 14) / 8); } inline Key Position::pawn_key() const { return st->pawnKey; } diff --git a/src/search.cpp b/src/search.cpp index cad5d7f3b..150bf5f67 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -73,18 +73,22 @@ using SearchedList = ValueList; // so changing them or adding conditions that are similar requires // tests at these types of time controls. +// (*Scaler) All tuned parameters at time controls shorter than +// optimized for require verifications at longer time controls + int correction_value(const Worker& w, const Position& pos, const Stack* const ss) { const Color us = pos.side_to_move(); const auto m = (ss - 1)->currentMove; - const auto pcv = w.pawnCorrectionHistory[pawn_structure_index(pos)][us]; + const auto pcv = w.pawnCorrectionHistory[pawn_correction_history_index(pos)][us]; const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us]; const auto wnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us]; const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; const auto cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - : 0; + + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] + : 8; - return 7696 * pcv + 7689 * micv + 9708 * (wnpcv + bnpcv) + 6978 * cntcv; + return 9536 * pcv + 8494 * micv + 10132 * (wnpcv + bnpcv) + 7156 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -100,19 +104,22 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - static constexpr int nonPawnWeight = 172; + constexpr int nonPawnWeight = 165; - workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 111 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 151 / 128; + workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] << bonus * nonPawnWeight / 128; if (m.is_ok()) - (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 141 / 128; + { + const Square to = m.to_sq(); + const Piece pc = pos.piece_on(m.to_sq()); + (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 137 / 128; + (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 64 / 128; + } } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -131,8 +138,7 @@ void update_all_stats(const Position& pos, SearchedList& quietsSearched, SearchedList& capturesSearched, Depth depth, - Move TTMove, - int moveCount); + Move TTMove); } // namespace @@ -330,7 +336,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(86); + lowPlyHistory.fill(97); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -366,7 +372,7 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11134; + delta = 5 + threadIdx % 8 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 9000; Value avg = rootMoves[pvIdx].averageScore; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); @@ -416,7 +422,7 @@ void Search::Worker::iterative_deepening() { // otherwise exit the loop. if (bestValue <= alpha) { - beta = (alpha + beta) / 2; + beta = alpha; alpha = std::max(bestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; @@ -425,7 +431,8 @@ void Search::Worker::iterative_deepening() { } else if (bestValue >= beta) { - beta = std::min(bestValue + delta, VALUE_INFINITE); + alpha = std::max(beta - delta, alpha); + beta = std::min(bestValue + delta, VALUE_INFINITE); ++failedHighCnt; } else @@ -507,29 +514,29 @@ void Search::Worker::iterative_deepening() { rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); double fallingEval = - (11.396 + 2.035 * (mainThread->bestPreviousAverageScore - bestValue) - + 0.968 * (mainThread->iterValue[iterIdx] - bestValue)) + (11.325 + 2.115 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.987 * (mainThread->iterValue[iterIdx] - bestValue)) / 100.0; - fallingEval = std::clamp(fallingEval, 0.5786, 1.6752); + fallingEval = std::clamp(fallingEval, 0.5688, 1.5698); // If the bestMove is stable over several iterations, reduce time accordingly - double k = 0.527; - double center = lastBestMoveDepth + 11; - timeReduction = 0.8 + 0.84 / (1.077 + std::exp(-k * (completedDepth - center))); + double k = 0.5189; + double center = lastBestMoveDepth + 11.57; + timeReduction = 0.723 + 0.79 / (1.104 + std::exp(-k * (completedDepth - center))); double reduction = - (1.4540 + mainThread->previousTimeReduction) / (2.1593 * timeReduction); - double bestMoveInstability = 0.9929 + 1.8519 * totBestMoveChanges / threads.size(); + (1.455 + mainThread->previousTimeReduction) / (2.2375 * timeReduction); + double bestMoveInstability = 1.04 + 1.8956 * totBestMoveChanges / threads.size(); 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); + totalTime = std::min(502.0, totalTime); auto elapsedTime = elapsed(); - if (completedDepth >= 10 && nodesEffort >= 97056 && elapsedTime > totalTime * 0.6540 + if (completedDepth >= 10 && nodesEffort >= 92425 && elapsedTime > totalTime * 0.666 && !mainThread->ponder) threads.stop = true; @@ -544,7 +551,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.5138; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.503; } mainThread->iterValue[iterIdx] = bestValue; @@ -564,14 +571,22 @@ void Search::Worker::iterative_deepening() { } -void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st) { - do_move(pos, move, st, pos.gives_check(move)); +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss) { + do_move(pos, move, st, pos.gives_check(move), ss); } -void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck) { - DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); +void Search::Worker::do_move( + Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { + bool capture = pos.capture_stage(move); + DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); nodes.fetch_add(1, std::memory_order_relaxed); accumulatorStack.push(dp); + if (ss != nullptr) + { + ss->currentMove = move; + ss->continuationHistory = &continuationHistory[ss->inCheck][capture][dp.pc][move.to_sq()]; + ss->continuationCorrectionHistory = &continuationCorrectionHistory[dp.pc][move.to_sq()]; + } } void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } @@ -586,9 +601,9 @@ void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(67); - captureHistory.fill(-688); - pawnHistory.fill(-1287); + mainHistory.fill(68); + captureHistory.fill(-689); + pawnHistory.fill(-1238); pawnCorrectionHistory.fill(5); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory.fill(0); @@ -603,10 +618,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-473); + h.fill(-529); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2796 / 128.0 * std::log(i)); + reductions[i] = int(2809 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -623,10 +638,7 @@ Value Search::Worker::search( // Dive into quiescence search when the depth reaches zero if (depth <= 0) - { - constexpr auto nt = PvNode ? PV : NonPV; - return qsearch(pos, ss, alpha, beta); - } + return qsearch(pos, ss, alpha, beta); // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); @@ -721,7 +733,10 @@ Value Search::Worker::search( if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) - && (cutNode == (ttData.value >= beta) || depth > 5)) + && (cutNode == (ttData.value >= beta) || depth > 5) + // avoid a TT cutoff if the rule50 count is high and the TT move is zeroing + && (depth > 8 || ttData.move == Move::none() || pos.rule50_count() < 80 + || (!ttCapture && type_of(pos.moved_piece(ttData.move)) != PAWN))) { // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) @@ -729,24 +744,24 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(125 * depth - 77, 1157)); + std::min(130 * depth - 71, 1043)); // Extra penalty for early quiet moves of the previous ply - if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2301); + if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2142); } // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. - if (pos.rule50_count() < 90) + if (pos.rule50_count() < 96) { if (depth >= 8 && ttData.move && pos.pseudo_legal(ttData.move) && pos.legal(ttData.move) && !is_decisive(ttData.value)) { - do_move(pos, ttData.move, st); + pos.do_move(ttData.move, st); Key nextPosKey = pos.key(); auto [ttHitNext, ttDataNext, ttWriterNext] = tt.probe(nextPosKey); - undo_move(pos, ttData.move); + pos.undo_move(ttData.move); // Check that the ttValue after the tt move would also trigger a cutoff if (!is_valid(ttDataNext.value)) @@ -849,61 +864,60 @@ Value Search::Worker::search( } // Use static evaluation difference to improve quiet move ordering - if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture && !ttHit) + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1858, 1492) + 661; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1057 / 1024; - if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << bonus * 1266 / 1024; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -2023, 1563) + 583; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 944 / 1024; + if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN + && ((ss - 1)->currentMove).type_of() != PROMOTION) + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] + << bonus * 1438 / 1024; } // 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 go back until we weren't in check) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = ss->staticEval > (ss - 2)->staticEval; - + improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 175) + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 486 - 325 * depth * depth) + if (!PvNode && eval < alpha - 514 - 294 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 93 - 20 * (cutNode && !ss->ttHit); + Value futilityMult = 91 - 21 * !ss->ttHit; - return futilityMult * d // - - improving * futilityMult * 2 // - - opponentWorsening * futilityMult / 3 // - + (ss - 1)->statScore / 376 // - + std::abs(correctionValue) / 168639; + return futilityMult * d // + - 2094 * improving * futilityMult / 1024 // + - 1324 * opponentWorsening * futilityMult / 4096 // + + (ss - 1)->statScore / 331 // + + std::abs(correctionValue) / 158105; }; if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) - return beta + (eval - beta) / 3; + return (2 * beta + eval) / 3; } // Step 9. Null move search with verification search - if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 19 * depth + 389 && !excludedMove && pos.non_pawn_material(us) - && ss->ply >= nmpMinPly && !is_loss(beta)) + if (cutNode && ss->staticEval >= beta - 18 * depth + 390 && !excludedMove + && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { - assert(eval - beta >= 0); + assert((ss - 1)->currentMove != Move::null()); // Null move dynamic reduction based on depth - Depth R = 7 + depth / 3; + Depth R = 6 + depth / 3; ss->currentMove = Move::null(); ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; @@ -936,18 +950,18 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta + 94; + improving |= ss->staticEval >= beta; // Step 10. Internal iterative reductions - // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. + // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. // (*Scaler) Especially if they make IIR less aggressive. - if (!allNode && depth >= 6 && !ttData.move) + if (!allNode && depth >= 6 && !ttData.move && priorReduction <= 3) depth--; // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 201 - 58 * improving; + probCutBeta = beta + 224 - 64 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -957,7 +971,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth probCutDepth = std::max(depth - 5, 0); + Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 306, 0, depth); while ((move = mp.next_move()) != Move::none()) { @@ -968,15 +982,7 @@ Value Search::Worker::search( assert(pos.capture_stage(move)); - movedPiece = pos.moved_piece(move); - - do_move(pos, move, st); - - ss->currentMove = move; - ss->continuationHistory = - &continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; - ss->continuationCorrectionHistory = - &continuationCorrectionHistory[movedPiece][move.to_sq()]; + do_move(pos, move, st, ss); // Perform a preliminary qsearch to verify that the move holds value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); @@ -1004,7 +1010,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 400; + probCutBeta = beta + 418; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; @@ -1055,8 +1061,6 @@ moves_loop: // When in check, search starts here movedPiece = pos.moved_piece(move); givesCheck = pos.gives_check(move); - (ss + 1)->quietMoveStreak = (!capture && !givesCheck) ? (ss->quietMoveStreak + 1) : 0; - // Calculate new depth for this move newDepth = depth - 1; @@ -1068,7 +1072,7 @@ moves_loop: // When in check, search starts here // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 968; + r += 946; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1087,52 +1091,44 @@ moves_loop: // When in check, search starts here int captHist = captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; // Futility pruning for captures - if (!givesCheck && lmrDepth < 7 && !ss->inCheck) + if (!givesCheck && lmrDepth < 7) { - Value futilityValue = ss->staticEval + 232 + 224 * lmrDepth - + PieceValue[capturedPiece] + 131 * captHist / 1024; + Value futilityValue = ss->staticEval + 231 + 211 * lmrDepth + + PieceValue[capturedPiece] + 130 * captHist / 1024; + if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks - int seeHist = std::clamp(captHist / 31, -137 * depth, 125 * depth); - if (!pos.see_ge(move, -158 * depth - seeHist)) - { - bool mayStalemateTrap = - depth > 2 && alpha < 0 && pos.non_pawn_material(us) == PieceValue[movedPiece] - && PieceValue[movedPiece] >= RookValue - // it can't be stalemate if we moved a piece adjacent to the king - && !(attacks_bb(pos.square(us)) & move.from_sq()) - && !mp.can_move_king_or_pawn(); - - // avoid pruning sacrifices of our last piece for stalemate - if (!mayStalemateTrap) - continue; - } + // Avoid pruning sacrifices of our last piece for stalemate + int margin = std::max(157 * depth + captHist / 29, 0); + if ((alpha >= VALUE_DRAW || pos.non_pawn_material(us) != PieceValue[movedPiece]) + && !pos.see_ge(move, -margin)) + continue; } else { int history = (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; + + pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4229 * depth) + if (history < -4312 * depth) continue; - history += 68 * mainHistory[us][move.from_to()] / 32; + history += 76 * mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3388; + // (*Scaler): Generally, a lower divisor scales well + lmrDepth += history / 3220; - Value baseFutility = (bestMove ? 46 : 230); - Value futilityValue = - ss->staticEval + baseFutility + 117 * lmrDepth + 102 * (ss->staticEval > alpha); + Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth + + 90 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning // scales well with respect to time and threads - if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) && !is_win(futilityValue)) @@ -1159,12 +1155,11 @@ moves_loop: // When in check, search starts here // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. - if (!rootNode && move == ttData.move && !excludedMove - && depth >= 6 - (completedDepth > 27) + ss->ttPv && is_valid(ttData.value) - && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) + if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv + && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (58 + 76 * (ss->ttPv && !PvNode)) * depth / 57; + Value singularBeta = ttData.value - (56 + 81 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1173,11 +1168,11 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 248400; - int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj - - 997 * ttMoveHistory / 131072 - (ss->ply > rootDepth) * 47; - int tripleMargin = 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv - corrValAdj - - (ss->ply * 2 > rootDepth * 3) * 54; + int corrValAdj = std::abs(correctionValue) / 229958; + int doubleMargin = -4 + 198 * PvNode - 212 * !ttCapture - corrValAdj + - 921 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 45; + int tripleMargin = 76 + 308 * PvNode - 250 * !ttCapture + 92 * ss->ttPv - corrValAdj + - (ss->ply * 2 > rootDepth * 3) * 52; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1192,7 +1187,10 @@ moves_loop: // When in check, search starts here // singular (multiple moves fail high), and we can prune the whole // subtree by returning a softbound. else if (value >= beta && !is_decisive(value)) + { + ttMoveHistory << std::max(-400 - 100 * depth, -4000); return value; + } // Negative extensions // If other moves failed high over (ttValue - margin) without the @@ -1212,50 +1210,41 @@ moves_loop: // When in check, search starts here } // Step 16. Make the move - do_move(pos, move, st, givesCheck); + do_move(pos, move, st, givesCheck, ss); // Add extension to new depth newDepth += extension; - - // Update the current move (this must be done after singular extension search) - ss->currentMove = move; - ss->continuationHistory = - &continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; - ss->continuationCorrectionHistory = - &continuationCorrectionHistory[movedPiece][move.to_sq()]; uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2437 + PvNode * 926 + (ttData.value > alpha) * 901 - + (ttData.depth >= depth) * (943 + cutNode * 1180); + r -= 2618 + PvNode * 991 + (ttData.value > alpha) * 903 + + (ttData.depth >= depth) * (978 + cutNode * 1051); // These reduction adjustments have no proven non-linear scaling - r += 650; // Base reduction offset to compensate for other tweaks + r += 843; // Base reduction offset to compensate for other tweaks r -= moveCount * 66; - r -= std::abs(correctionValue) / 28047; + r -= std::abs(correctionValue) / 30450; // Increase reduction for cut nodes if (cutNode) - r += 2864 + 966 * !ttData.move; + r += 3094 + 1056 * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) - r += 1350; + r += 1415; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 1036 + allNode * 848; - - r += (ss + 1)->quietMoveStreak * 50; + r += 1051 + allNode * 814; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2006; + r -= 2018; if (capture) - ss->statScore = 826 * int(PieceValue[pos.captured_piece()]) / 128 + ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else ss->statScore = 2 * mainHistory[us][move.from_to()] @@ -1263,7 +1252,7 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 826 / 8192; + r -= ss->statScore * 794 / 8192; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1273,9 +1262,7 @@ moves_loop: // When in check, search starts here // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = std::max(1, std::min(newDepth - r / 1024, - newDepth + !allNode + (PvNode && !bestMove))) - + PvNode; + Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + 2)) + PvNode; ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); @@ -1284,11 +1271,11 @@ moves_loop: // When in check, search starts here // Do a full-depth search when reduced LMR search fails high // (*Scaler) Usually doing more shallower searches // doesn't scale well to longer TCs - if (value > alpha && d < newDepth) + if (value > alpha) { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 42 + 2 * newDepth); + const bool doDeeperSearch = d < newDepth && value > (bestValue + 43 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1297,10 +1284,8 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - update_continuation_histories(ss, movedPiece, move.to_sq(), 1508); + update_continuation_histories(ss, movedPiece, move.to_sq(), 1365); } - else if (value > alpha && value < bestValue + 9) - newDepth--; } // Step 18. Full-depth search when LMR is skipped @@ -1308,11 +1293,11 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1128; + r += 1118; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3200) - (r > 4600 && newDepth > 2), !cutNode); + newDepth - (r > 3212) - (r > 4784 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1323,7 +1308,7 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && rootDepth > 8) + if (move == ttData.move && ttData.depth > 1 && rootDepth > 8) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); @@ -1394,7 +1379,7 @@ moves_loop: // When in check, search starts here // In case we have an alternative move equal in eval to the current bestmove, // promote it to bestmove by pretending it just exceeds alpha (but not beta). - int inc = (value == bestValue && ss->ply + 2 >= rootDepth && (int(nodes) & 15) == 0 + int inc = (value == bestValue && ss->ply + 2 >= rootDepth && (int(nodes) & 14) == 0 && !is_win(std::abs(value) + 1)); if (value + inc > bestValue) @@ -1410,14 +1395,14 @@ moves_loop: // When in check, search starts here if (value >= beta) { - // (* Scaler) Especially if they make cutoffCnt increment more often. + // (*Scaler) Especially if they make cutoffCnt increment more often. ss->cutoffCnt += (extension < 2) || PvNode; assert(value >= beta); // Fail high break; } // Reduce other moves if we have found at least one score improvement - if (depth > 2 && depth < 16 && !is_decisive(value)) + if (depth > 2 && depth < 14 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1455,33 +1440,33 @@ moves_loop: // When in check, search starts here else if (bestMove) { update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, - ttData.move, moveCount); + ttData.move); if (!PvNode) - ttMoveHistory << (bestMove == ttData.move ? 800 : -879); + ttMoveHistory << (bestMove == ttData.move ? 809 : -865); } // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -220; - bonusScale += std::min(-(ss - 1)->statScore / 103, 323); - bonusScale += std::min(73 * depth, 531); - bonusScale += 174 * ((ss - 1)->moveCount > 8); - bonusScale += 144 * (!ss->inCheck && bestValue <= ss->staticEval - 104); - bonusScale += 128 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82); + int bonusScale = -228; + bonusScale -= (ss - 1)->statScore / 104; + bonusScale += std::min(63 * depth, 508); + bonusScale += 184 * ((ss - 1)->moveCount > 8); + bonusScale += 143 * (!ss->inCheck && bestValue <= ss->staticEval - 92); + bonusScale += 149 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 70); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(159 * depth - 94, 1501) * bonusScale; + const int scaledBonus = std::min(144 * depth - 92, 1365) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 412 / 32768); + scaledBonus * 400 / 32768); - mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 203 / 32768; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 220 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1040 / 32768; + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] + << scaledBonus * 1164 / 32768; } // Bonus for prior capture countermove that caused the fail low @@ -1489,7 +1474,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1080; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 964; } if (PvNode) @@ -1516,9 +1501,11 @@ moves_loop: // When in check, search starts here && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low { - auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, - -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); - update_correction_history(pos, ss, *this, bonus); + auto bonus = + std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + update_correction_history(pos, ss, *this, + (1088 - 180 * (bestValue > ss->staticEval)) * bonus / 1024); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1642,7 +1629,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 376; + futilityBase = ss->staticEval + 352; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1699,29 +1686,17 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } } - // Continuation history based pruning - if (!capture - && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] - + pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)][move.to_sq()] - <= 6218) + // Skip non-captures + if (!capture) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -74)) + if (!pos.see_ge(move, -78)) continue; } // Step 7. Make and search the move - Piece movedPiece = pos.moved_piece(move); - - do_move(pos, move, st, givesCheck); - - // Update the current move - ss->currentMove = move; - ss->continuationHistory = - &continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; - ss->continuationCorrectionHistory = - &continuationCorrectionHistory[movedPiece][move.to_sq()]; + do_move(pos, move, st, givesCheck, ss); value = -qsearch(pos, ss + 1, -beta, -alpha); undo_move(pos, move); @@ -1789,7 +1764,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; - return reductionScale - delta * 794 / rootDelta + !i * reductionScale * 205 / 512 + 1086; + return reductionScale - delta * 757 / rootDelta + !i * reductionScale * 218 / 512 + 1200; } // elapsed() returns the time elapsed since the search started. If the @@ -1878,42 +1853,41 @@ void update_all_stats(const Position& pos, SearchedList& quietsSearched, SearchedList& capturesSearched, Depth depth, - Move ttMove, - int moveCount) { + Move ttMove) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(143 * depth - 89, 1496) + 302 * (bestMove == ttMove); - int malus = std::min(737 * depth - 179, 3141) - 30 * moveCount; + int bonus = std::min(151 * depth - 91, 1730) + 302 * (bestMove == ttMove); + int malus = std::min(951 * depth - 156, 2468) - 30 * quietsSearched.size(); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1059 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 957 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1310 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus); } else { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1213 / 1024; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus; } // Extra penalty for a quiet early move that was not a TT move in // previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 580 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 503 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1388 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1157 / 1024; } } @@ -1922,7 +1896,7 @@ void update_all_stats(const Position& pos, // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { static constexpr std::array conthist_bonuses = { - {{1, 1092}, {2, 631}, {3, 294}, {4, 517}, {5, 126}, {6, 445}}}; + {{1, 1157}, {2, 648}, {3, 288}, {4, 576}, {5, 140}, {6, 441}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1930,7 +1904,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { if (ss->inCheck && i > 2) break; if (((ss - i)->currentMove).is_ok()) - (*(ss - i)->continuationHistory)[pc][to] << bonus * weight / 1024; + (*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 88 * (i < 2); } } @@ -1943,14 +1917,13 @@ void update_quiet_histories( workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 792 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 761 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), - bonus * (bonus > 0 ? 1082 : 784) / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); - int pIndex = pawn_structure_index(pos); + int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << bonus * (bonus > 0 ? 705 : 450) / 1024; + << (bonus * (bonus > 0 ? 800 : 500) / 1024) + 70; } } diff --git a/src/search.h b/src/search.h index e0055f2e9..13b6842fa 100644 --- a/src/search.h +++ b/src/search.h @@ -77,7 +77,6 @@ struct Stack { bool ttHit; int cutoffCnt; int reduction; - int quietMoveStreak; }; @@ -331,8 +330,9 @@ class Worker { private: void iterative_deepening(); - void do_move(Position& pos, const Move move, StateInfo& st); - void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck); + void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss); + void + do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss); void do_null_move(Position& pos, StateInfo& st); void undo_move(Position& pos, const Move move); void undo_null_move(Position& pos); diff --git a/src/tune.h b/src/tune.h index 4dab6bf45..d3c9ebaa3 100644 --- a/src/tune.h +++ b/src/tune.h @@ -170,11 +170,20 @@ class Tune { static OptionsMap* options; }; +template +constexpr void tune_check_args(Args&&...) { + static_assert((!std::is_fundamental_v && ...), "TUNE macro arguments wrong"); +} + // 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 TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__) +#define TUNE(...) \ + int UNIQUE(p, __LINE__) = []() -> int { \ + tune_check_args(__VA_ARGS__); \ + return Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ + }(); #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true diff --git a/src/uci.cpp b/src/uci.cpp index c4d9bbaaa..a34e266c7 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -637,12 +637,12 @@ void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) { << " multipv " << info.multiPV // << " score " << format_score(info.score); // - if (showWDL) - ss << " wdl " << info.wdl; - if (!info.bound.empty()) ss << " " << info.bound; + if (showWDL) + ss << " wdl " << info.wdl; + ss << " nodes " << info.nodes // << " nps " << info.nps // << " hashfull " << info.hashfull // diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 8933f30d4..9f2dfc286 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -111,7 +111,7 @@ Option::Option(OnChange f) : max(0), on_change(std::move(f)) {} -Option::Option(double v, int minv, int maxv, OnChange f) : +Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), @@ -155,7 +155,7 @@ Option& Option::operator=(const std::string& v) { if ((type != "button" && type != "string" && v.empty()) || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (std::stof(v) < min || std::stof(v) > max))) + || (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) return *this; if (type == "combo") @@ -203,7 +203,7 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { } else if (o.type == "spin") - os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " + os << " default " << stoi(o.defaultValue) << " min " << o.min << " max " << o.max; break; diff --git a/src/ucioption.h b/src/ucioption.h index 3d7386c30..0c957fda1 100644 --- a/src/ucioption.h +++ b/src/ucioption.h @@ -43,7 +43,7 @@ class Option { Option(OnChange = nullptr); Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); - Option(double v, int minv, int maxv, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); Option(const char* v, const char* cur, OnChange = nullptr); Option& operator=(const std::string&); diff --git a/tests/perft.sh b/tests/perft.sh index 97c462c57..bb32bee56 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -16,7 +16,7 @@ EXPECT_SCRIPT=$(mktemp) cat << 'EOF' > $EXPECT_SCRIPT #!/usr/bin/expect -f -set timeout 30 +set timeout 120 lassign [lrange $argv 0 4] pos depth result chess960 logfile log_file -noappend $logfile spawn ./stockfish