]> git.sesse.net Git - stockfish/commitdiff
Fix compilation after recent merge. master
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 25 Dec 2023 17:48:26 +0000 (18:48 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 25 Dec 2023 17:48:26 +0000 (18:48 +0100)
86 files changed:
.clang-format [new file with mode: 0644]
.github/ISSUE_TEMPLATE/BUG-REPORT.yml [new file with mode: 0644]
.github/ISSUE_TEMPLATE/config.yml [new file with mode: 0644]
.github/workflows/codeql.yml [new file with mode: 0644]
.github/workflows/libcxx17.imp [new file with mode: 0644]
.github/workflows/stockfish.yml [new file with mode: 0644]
.github/workflows/stockfish_analyzers.yml [new file with mode: 0644]
.github/workflows/stockfish_arm_binaries.yml [new file with mode: 0644]
.github/workflows/stockfish_binaries.yml [new file with mode: 0644]
.github/workflows/stockfish_compile_test.yml [new file with mode: 0644]
.github/workflows/stockfish_format_check.yml [new file with mode: 0644]
.github/workflows/stockfish_sanitizers.yml [new file with mode: 0644]
.github/workflows/stockfish_test.yml [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.travis.yml [deleted file]
AUTHORS
CITATION.cff [new file with mode: 0644]
CONTRIBUTING.md [new file with mode: 0644]
Copying.txt
README.md [new file with mode: 0644]
Readme.md [deleted file]
Top CPU Contributors.txt
appveyor.yml [deleted file]
scripts/get_native_properties.sh [new file with mode: 0755]
src/Makefile
src/benchmark.cpp
src/benchmark.h [new file with mode: 0644]
src/bitbase.cpp [deleted file]
src/bitboard.cpp
src/bitboard.h
src/client.cpp [new file with mode: 0644]
src/endgame.cpp [deleted file]
src/endgame.h [deleted file]
src/evaluate.cpp
src/evaluate.h
src/hashprobe.h [new file with mode: 0644]
src/hashprobe.proto [new file with mode: 0644]
src/incbin/UNLICENCE [new file with mode: 0644]
src/incbin/incbin.h [new file with mode: 0644]
src/main.cpp
src/material.cpp [deleted file]
src/material.h [deleted file]
src/misc.cpp
src/misc.h
src/movegen.cpp
src/movegen.h
src/movepick.cpp
src/movepick.h
src/nnue/evaluate_nnue.cpp [new file with mode: 0644]
src/nnue/evaluate_nnue.h [new file with mode: 0644]
src/nnue/features/half_ka_v2_hm.cpp [new file with mode: 0644]
src/nnue/features/half_ka_v2_hm.h [new file with mode: 0644]
src/nnue/layers/affine_transform.h [new file with mode: 0644]
src/nnue/layers/affine_transform_sparse_input.h [new file with mode: 0644]
src/nnue/layers/clipped_relu.h [new file with mode: 0644]
src/nnue/layers/simd.h [new file with mode: 0644]
src/nnue/layers/sqr_clipped_relu.h [new file with mode: 0644]
src/nnue/nnue_accumulator.h [new file with mode: 0644]
src/nnue/nnue_architecture.h [new file with mode: 0644]
src/nnue/nnue_common.h [new file with mode: 0644]
src/nnue/nnue_feature_transformer.h [new file with mode: 0644]
src/pawns.cpp [deleted file]
src/pawns.h [deleted file]
src/position.cpp
src/position.h
src/psqt.cpp [deleted file]
src/search.cpp
src/search.h
src/syzygy/tbprobe.cpp
src/syzygy/tbprobe.h
src/thread.cpp
src/thread.h
src/thread_win32_osx.h
src/timeman.cpp
src/timeman.h
src/tt.cpp
src/tt.h
src/tune.cpp [new file with mode: 0644]
src/tune.h [new file with mode: 0644]
src/types.h
src/uci.cpp
src/uci.h
src/ucioption.cpp
tests/instrumented.sh
tests/reprosearch.sh
tests/signature.sh

diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..c71f036
--- /dev/null
@@ -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/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml
new file mode 100644 (file)
index 0000000..e46d2bf
--- /dev/null
@@ -0,0 +1,65 @@
+name: Report issue
+description: Create a report to help us fix issues with the engine
+body:
+- type: textarea
+  attributes:
+    label: Describe the issue
+    description: A clear and concise description of what you're experiencing.
+  validations:
+    required: true
+
+- type: textarea
+  attributes:
+    label: Expected behavior
+    description: A clear and concise description of what you expected to happen.
+  validations:
+    required: true
+
+- type: textarea
+  attributes:
+    label: Steps to reproduce
+    description: |
+      Steps to reproduce the behavior.
+      You can also use this section to paste the command line output.
+    placeholder: |
+      ```
+      position startpos moves g2g4 e7e5 f2f3
+      go mate 1
+      info string NNUE evaluation using nn-6877cd24400e.nnue enabled
+      info depth 1 seldepth 1 multipv 1 score mate 1 nodes 33 nps 11000 tbhits 0 time 3 pv d8h4
+      bestmove d8h4
+      ```
+  validations:
+    required: true
+
+- type: textarea
+  attributes:
+    label: Anything else?
+    description: |
+      Anything that will give us more context about the issue you are encountering.
+      You can also use this section to propose ideas on how to solve the issue. 
+  validations:
+    required: false
+
+- type: dropdown
+  attributes:
+    label: Operating system
+    options:
+      - All
+      - Windows
+      - Linux
+      - MacOS
+      - Android
+      - Other or N/A
+  validations:
+    required: true
+
+- type: input
+  attributes:
+    label: Stockfish version
+    description: |
+      This can be found by running the engine.
+      You can also use the commit ID.
+    placeholder: Stockfish 15 / e6e324e
+  validations:
+    required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644 (file)
index 0000000..1f8694d
--- /dev/null
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Discord server
+    url: https://discord.gg/GWDRS3kU6R
+    about: Feel free to ask for support or have a chat with us in our Discord server!
+  - name: Discussions, Q&A, ideas, show us something...
+    url: https://github.com/official-stockfish/Stockfish/discussions/new
+    about: Do you have an idea for Stockfish? Do you want to show something that you made? Please open a discussion about it!
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644 (file)
index 0000000..054be90
--- /dev/null
@@ -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@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.
+
+        # 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}}"
diff --git a/.github/workflows/libcxx17.imp b/.github/workflows/libcxx17.imp
new file mode 100644 (file)
index 0000000..7bdcf5b
--- /dev/null
@@ -0,0 +1,21 @@
+[
+    # Mappings for libcxx's internal headers
+    { include: [ "<__fwd/fstream.h>", private, "<iosfwd>", public ] },
+    { include: [ "<__fwd/ios.h>", private, "<iosfwd>", public ] },
+    { include: [ "<__fwd/istream.h>", private, "<iosfwd>", public ] },
+    { include: [ "<__fwd/ostream.h>", private, "<iosfwd>", public ] },
+    { include: [ "<__fwd/sstream.h>", private, "<iosfwd>", public ] },
+    { include: [ "<__fwd/streambuf.h>", private, "<iosfwd>", public ] },
+    { include: [ "<__fwd/string_view.h>", private, "<string_view>", public ] },
+
+    # Mappings for includes between public headers
+    { include: [ "<ios>", public, "<iostream>", public ] },
+    { include: [ "<streambuf>", public, "<iostream>", public ] },
+    { include: [ "<istream>", public, "<iostream>", public ] },
+    { include: [ "<ostream>", public, "<iostream>", public ] },
+    { include: [ "<iosfwd>", public, "<iostream>", public ] },
+
+    # Missing mappings in include-what-you-use's libcxx.imp
+    { include: ["@<__condition_variable/.*>", private, "<condition_variable>", public ] },
+    { include: ["@<__mutex/.*>", private, "<mutex>", public ] },
+]
diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml
new file mode 100644 (file)
index 0000000..e8db523
--- /dev/null
@@ -0,0 +1,48 @@
+name: Stockfish
+on:
+  push:
+    tags:        
+      - '*' 
+    branches:
+      - master
+      - tools
+      - github_ci
+  pull_request:
+    branches:
+      - master
+      - tools
+jobs:
+  Prerelease:
+    if: github.ref == 'refs/heads/master'
+    runs-on: ubuntu-latest
+    steps:
+      # returns null if no pre-release exists
+      - name: Get Commit SHA of Latest Pre-release
+        run: |
+          # Install required packages
+          sudo apt-get update
+          sudo apt-get install -y curl jq
+
+          echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV
+
+      # delete old previous pre-release and tag
+      - uses: dev-drprasad/delete-tag-and-release@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
+  Sanitizers:
+    uses: ./.github/workflows/stockfish_sanitizers.yml
+  Tests:
+    uses: ./.github/workflows/stockfish_test.yml
+  Compiles:
+    uses: ./.github/workflows/stockfish_compile_test.yml
+  Binaries:
+    if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')
+    uses: ./.github/workflows/stockfish_binaries.yml
+  ARM_Binaries:
+    if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')
+    uses: ./.github/workflows/stockfish_arm_binaries.yml
diff --git a/.github/workflows/stockfish_analyzers.yml b/.github/workflows/stockfish_analyzers.yml
new file mode 100644 (file)
index 0000000..f54cdd7
--- /dev/null
@@ -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@v4
+        with:
+          path: Stockfish
+
+      - name: Checkout include-what-you-use
+        uses: actions/checkout@v4
+        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/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml
new file mode 100644 (file)
index 0000000..203c00f
--- /dev/null
@@ -0,0 +1,170 @@
+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
new file mode 100644 (file)
index 0000000..5b3a522
--- /dev/null
@@ -0,0 +1,214 @@
+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: 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
+        exclude:
+          - binaries: x86-64-avxvnni
+            config: { ubuntu-20.04 }
+          - 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 }
+    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 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-${{ 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
new file mode 100644 (file)
index 0000000..1adc3e3
--- /dev/null
@@ -0,0 +1,100 @@
+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 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
+        run: |
+          make clean
+          make -j2 ARCH=x86-64-avx2 build
+
+      - name: Compile x86-64-bmi2 build
+        run: |
+          make clean
+          make -j2 ARCH=x86-64-bmi2 build
+
+      - name: Compile x86-64-avx512 build
+        run: |
+          make clean
+          make -j2 ARCH=x86-64-avx512 build
+
+      - name: Compile x86-64-vnni512 build
+        run: |
+          make clean
+          make -j2 ARCH=x86-64-vnni512 build
+
+      - name: Compile x86-64-vnni256 build
+        run: |
+          make clean
+          make -j2 ARCH=x86-64-vnni256 build
diff --git a/.github/workflows/stockfish_format_check.yml b/.github/workflows/stockfish_format_check.yml
new file mode 100644 (file)
index 0000000..7a47ab6
--- /dev/null
@@ -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@v4
+        with:
+          ref: ${{ github.event.pull_request.head.sha }}
+
+      - name: Run clang-format style check
+        uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e  # @v4.11.0
+        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  # @v2.4.3
+        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  # @v2.4.3
+        with:
+          message: |
+            _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
+          create_if_not_exists: false
+          comment_tag: execution
+          mode: delete
diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml
new file mode 100644 (file)
index 0000000..e3f0461
--- /dev/null
@@ -0,0 +1,64 @@
+name: Stockfish
+on:
+  workflow_call:
+jobs:
+  Stockfish:
+    name: ${{ matrix.sanitizers.name }}
+    runs-on: ${{ matrix.config.os }}
+    env:
+      COMPILER: ${{ matrix.config.compiler }}
+      COMP: ${{ matrix.config.comp }}
+      CXXFLAGS: "-Werror"
+    strategy:
+      matrix:
+        config:
+          - name: Ubuntu 22.04 GCC
+            os: ubuntu-22.04
+            compiler: g++
+            comp: gcc
+            shell: bash
+        sanitizers:
+          - name: Run with thread sanitizer
+            make_option: sanitize=thread
+            instrumented_option: sanitizer-thread
+          - name: Run with UB sanitizer
+            make_option: sanitize=undefined
+            instrumented_option: sanitizer-undefined
+          - name: Run under valgrind
+            make_option: ""
+            instrumented_option: valgrind
+          - name: Run under valgrind-thread
+            make_option: ""
+            instrumented_option: valgrind-thread
+    defaults:
+      run:
+        working-directory: src
+        shell: ${{ matrix.config.shell }}
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Download required linux packages
+        run: |
+          sudo apt update
+          sudo apt install expect valgrind g++-multilib
+
+      - 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
+
+      # Sanitizers
+
+      - name: ${{ matrix.sanitizers.name }}
+        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
+          ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }}
diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml
new file mode 100644 (file)
index 0000000..cff3ef1
--- /dev/null
@@ -0,0 +1,350 @@
+name: Stockfish
+on:
+  workflow_call:
+jobs:
+  Stockfish:
+    name: ${{ matrix.config.name }}
+    runs-on: ${{ matrix.config.os }}
+    env:
+      COMPILER: ${{ matrix.config.compiler }}
+      COMP: ${{ matrix.config.comp }}
+      CXXFLAGS: "-Werror"
+    strategy:
+      matrix:
+        config:
+          - name: Ubuntu 20.04 GCC
+            os: ubuntu-20.04
+            compiler: g++
+            comp: gcc
+            run_32bit_tests: true
+            run_64bit_tests: true
+            shell: bash
+          - name: Ubuntu 20.04 Clang
+            os: ubuntu-20.04
+            compiler: clang++
+            comp: clang
+            run_32bit_tests: true
+            run_64bit_tests: true
+            shell: bash
+          - name: Android NDK aarch64
+            os: ubuntu-22.04
+            compiler: aarch64-linux-android21-clang++
+            comp: ndk
+            run_armv8_tests: true
+            shell: bash
+          - name: Android NDK arm
+            os: ubuntu-22.04
+            compiler: armv7a-linux-androideabi21-clang++
+            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++
+            comp: clang
+            run_64bit_tests: true
+            shell: bash
+          - name: MacOS 13 GCC 11
+            os: macos-13
+            compiler: g++-11
+            comp: gcc
+            run_64bit_tests: true
+            shell: bash
+          - name: Windows 2022 Mingw-w64 GCC x86_64
+            os: windows-2022
+            compiler: g++
+            comp: mingw
+            run_64bit_tests: true
+            msys_sys: mingw64
+            msys_env: x86_64-gcc
+            shell: msys2 {0}
+          - name: Windows 2022 Mingw-w64 GCC i686
+            os: windows-2022
+            compiler: g++
+            comp: mingw
+            run_32bit_tests: true
+            msys_sys: mingw32
+            msys_env: i686-gcc
+            shell: msys2 {0}
+          - name: Windows 2022 Mingw-w64 Clang x86_64
+            os: windows-2022
+            compiler: clang++
+            comp: clang
+            run_64bit_tests: true
+            msys_sys: clang64
+            msys_env: clang-x86_64-clang
+            shell: msys2 {0}
+    defaults:
+      run:
+        working-directory: src
+        shell: ${{ matrix.config.shell }}
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Download required linux packages
+        if: runner.os == 'Linux'
+        run: |
+          sudo apt update
+          sudo apt install expect valgrind g++-multilib qemu-user-static
+
+      - 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: Set up QEMU
+        if: matrix.config.base_image
+        uses: docker/setup-qemu-action@v3
+
+      - name: Set up Docker Buildx
+        if: matrix.config.base_image
+        uses: docker/setup-buildx-action@v3
+
+      - 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", "script.sh"]
+          EOF
+
+      - 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
+        with:
+          msystem: ${{ matrix.config.msys_sys }}
+          install: mingw-w64-${{ matrix.config.msys_env }} make git expect
+
+      - name: Download the used network from the fishtest framework
+        run: make net
+
+      - name: Extract the bench number from the commit history
+        run: |
+          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: |
+          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
+
+      - name: Test help target
+        run: make help
+
+      - name: Check git
+        run: git --version
+
+      # x86-32 tests
+
+      - name: Test debug x86-32 build
+        if: matrix.config.run_32bit_tests
+        run: |
+          export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
+          make clean
+          make -j2 ARCH=x86-32 optimize=no debug=yes build
+          ../tests/signature.sh $benchref
+
+      - name: Test x86-32 build
+        if: matrix.config.run_32bit_tests
+        run: |
+          make clean
+          make -j2 ARCH=x86-32 build
+          ../tests/signature.sh $benchref
+
+      - name: Test x86-32-sse41-popcnt build
+        if: matrix.config.run_32bit_tests
+        run: |
+          make clean
+          make -j2 ARCH=x86-32-sse41-popcnt build
+          ../tests/signature.sh $benchref
+
+      - name: Test x86-32-sse2 build
+        if: matrix.config.run_32bit_tests
+        run: |
+          make clean
+          make -j2 ARCH=x86-32-sse2 build
+          ../tests/signature.sh $benchref
+
+      - name: Test general-32 build
+        if: matrix.config.run_32bit_tests
+        run: |
+          make clean
+          make -j2 ARCH=general-32 build
+          ../tests/signature.sh $benchref
+
+      # x86-64 tests
+
+      - name: Test debug x86-64-avx2 build
+        if: matrix.config.run_64bit_tests
+        run: |
+          export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
+          make clean
+          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
+        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
+        run: |
+          make clean
+          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: |
+          make clean
+          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: |
+          make clean
+          make -j2 ARCH=x86-64-ssse3 build
+          ../tests/signature.sh $benchref
+
+      - name: Test x86-64-sse3-popcnt build
+        if: matrix.config.run_64bit_tests
+        run: |
+          make clean
+          make -j2 ARCH=x86-64-sse3-popcnt build
+          ../tests/signature.sh $benchref
+
+      - name: Test x86-64 build
+        if: matrix.config.run_64bit_tests
+        run: |
+          make clean
+          make -j2 ARCH=x86-64 build
+          ../tests/signature.sh $benchref
+
+      - name: Test general-64 build
+        if: matrix.config.run_64bit_tests
+        run: |
+          make clean
+          make -j2 ARCH=general-64 build
+          ../tests/signature.sh $benchref
+
+      # armv8 tests
+
+      - name: Test armv8 build
+        if: matrix.config.run_armv8_tests
+        run: |
+          export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+          export LDFLAGS="-static -Wno-unused-command-line-argument"
+          make clean
+          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
+        if: matrix.config.run_armv7_tests
+        run: |
+          export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+          export LDFLAGS="-static -Wno-unused-command-line-argument"
+          make clean
+          make -j2 ARCH=armv7 build
+          ../tests/signature.sh $benchref
+
+      - name: Test armv7-neon build
+        if: matrix.config.run_armv7_tests
+        run: |
+          export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+          export LDFLAGS="-static -Wno-unused-command-line-argument"
+          make clean
+          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" > script.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" > script.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
+        if: matrix.config.run_64bit_tests
+        run: |
+          make clean
+          make -j2 ARCH=x86-64-avx2 build
+          ../tests/perft.sh
+          ../tests/reprosearch.sh
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..8981efc
--- /dev/null
@@ -0,0 +1,12 @@
+# Files from build
+**/*.o
+**/*.s
+src/.depend
+
+# Built binary
+src/stockfish*
+src/-lstdc++.res
+
+# Neural network for the NNUE evaluation
+**/*.nnue
+
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644 (file)
index e2b42e6..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-language: cpp
-dist: xenial
-
-matrix:
-  include:
-    - os: linux
-      compiler: gcc
-      addons:
-        apt:
-          sources: ['ubuntu-toolchain-r-test']
-          packages: ['g++-8', 'g++-8-multilib', 'g++-multilib', 'valgrind', 'expect', 'curl']
-      env:
-        - COMPILER=g++-8
-        - COMP=gcc
-
-    - os: linux
-      compiler: clang
-      addons:
-        apt:
-          sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-6.0']
-          packages: ['clang-6.0', 'llvm-6.0-dev', 'g++-multilib', 'valgrind', 'expect', 'curl']
-      env:
-        - COMPILER=clang++-6.0
-        - COMP=clang
-        - LDFLAGS=-fuse-ld=lld
-
-    - os: osx
-      compiler: gcc
-      env:
-        - COMPILER=g++
-        - COMP=gcc
-
-    - os: osx
-      compiler: clang
-      env:
-        - COMPILER=clang++ V='Apple LLVM 9.4.1' # Apple LLVM version 9.1.0 (clang-902.0.39.2)
-        - COMP=clang
-
-branches:
-  only:
-   - master
-
-before_script:
-  - cd src
-
-script:
-  # Obtain bench reference from git log
-  - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
-  - export benchref=$(cat git_sig)
-  - echo "Reference bench:" $benchref
-  #
-  # Verify bench number against various builds
-  - export CXXFLAGS=-Werror
-  - make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref
-  - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref
-  - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref
-
-  #
-  # Check perft and reproducible search
-  - ../tests/perft.sh
-  - ../tests/reprosearch.sh
-  #
-  # Valgrind
-  #
-  - export CXXFLAGS="-O1 -fno-inline"
-  - if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi
-  - if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi
-  #
-  # Sanitizer
-  #
-  # Use g++-8 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc
-  - if [[ "$COMPILER" == "g++-8" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi
-  - if [[ "$COMPILER" == "g++-8" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread    optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi
diff --git a/AUTHORS b/AUTHORS
index a9f141f961edfb7e46b90a768238b13e7020a655..cedee2f3536cd808545301dd009493b7e880112a 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,10 +1,15 @@
-# List of authors for Stockfish, as of January 7, 2020
-
+# Founders of the Stockfish project and Fishtest infrastructure
 Tord Romstad (romstad)
 Marco Costalba (mcostalba)
 Joona Kiiski (zamar)
 Gary Linscott (glinscott)
 
+# Authors and inventors of NNUE, training, and NNUE port
+Yu Nasu (ynasu87)
+Motohiro Isozaki (yaneurao)
+Hisayori Noda (nodchip)
+
+# All other authors of Stockfish code (in alphabetical order)
 Aditya (absimaldata)
 Adrian Petrescu (apetresc)
 Ajith Chandy Jose (ajithcj)
@@ -12,31 +17,52 @@ Alain Savard (Rocky640)
 Alayan Feh (Alayan-stk-2)
 Alexander Kure
 Alexander Pagel (Lolligerhans)
+Alfredo Menezes (lonfom169)
 Ali AlZhrani (Cooffe)
+Andreas Matthies (Matthies)
+Andrei Vetrov (proukornew)
 Andrew Grant (AndyGrant)
 Andrey Neporada (nepal)
 Andy Duplain
+Antoine Champion (antoinechampion)
 Aram Tumanian (atumanian)
 Arjun Temurnikar
+Artem Solopiy (EntityFX)
 Auguste Pop
+Balazs Szilagyi
 Balint Pfliegel
+Ben Chaney (Chaneybenjamini)
 Ben Koshy (BKSpurgeon)
 Bill Henry (VoyagerOne)
 Bojun Guo (noobpwnftw, Nooby)
+borg323
+Boštjan Mejak (PedanticHacker)
 braich
 Brian Sheppard (SapphireBrand, briansheppard-toast)
+Bruno de Melo Costa (BM123499)
+Bruno Pellanda (pellanda)
 Bryan Cross (crossbr)
 candirufish
 Chess13234
 Chris Cain (ceebo)
-Dan Schmidt (dfannius)
+clefrks
+Cody Ho (aesrentai)
+Dale Weiler (graphitemaster)
 Daniel Axtens (daxtens)
 Daniel Dugovic (ddugovic)
-Dariusz Orzechowski
+Daniel Monroe (Ergodice)
+Dan Schmidt (dfannius)
+Dariusz Orzechowski (dorzechowski)
+David (dav1312)
 David Zar
 Daylen Yang (daylen)
+Deshawn Mohan-Smith (GoldenRare)
+Dieter Dobbelaere (ddobbelaere)
 DiscanX
+Dominik Schlösser (domschl)
 double-beep
+Douglas Matos Gomes (dsmsgms)
+Dubslow
 Eduardo Cáceres (eduherminio)
 Eelco de Groot (KingDefender)
 Elvin Liu (solarlight2)
@@ -44,46 +70,61 @@ erbsenzaehler
 Ernesto Gatti
 Fabian Beuke (madnight)
 Fabian Fichter (ianfab)
+Fanael Linithien (Fanael)
 fanon
 Fauzi Akram Dabat (FauziAkram)
 Felix Wittmann
 gamander
+Gabriele Lombardo (gabe)
+Gary Heckman (gheckman)
+George Sobala (gsobala)
 gguliash
+Giacomo Lorenzetti (G-Lorenz)
 Gian-Carlo Pascutto (gcp)
+Goh CJ (cj5716)
 Gontran Lemaire (gonlem)
 Goodkov Vasiliy Aleksandrovich (goodkov)
 Gregor Cramer
 GuardianRM
-Günther Demetz (pb00067, pb00068)
 Guy Vreuls (gvreuls)
+Günther Demetz (pb00067, pb00068)
 Henri Wiechers
 Hiraoka Takuya (HiraokaTakuya)
 homoSapiensSapiens
 Hongzhi Cheng
 Ivan Ivec (IIvec)
 Jacques B. (Timshel)
+Jake Senne (w1wwwwww)
 Jan Ondruš (hxim)
-Jared Kish (Kurtbusch)
+Jared Kish (Kurtbusch, kurt22i)
 Jarrod Torriero (DU-jdto)
-Jean Gauthier (OuaisBla)
+Jasper Shovelton (Beanie496)
 Jean-Francois Romang (jromang)
+Jean Gauthier (OuaisBla)
 Jekaa
 Jerry Donald Watson (jerrydonaldwatson)
+jjoshua2
+Jonathan Buladas Dumale (SFisGOD)
 Jonathan Calovski (Mysseno)
-Jonathan Dumale (SFisGOD)
+Jonathan McDermid (jonathanmcdermid)
 Joost VandeVondele (vondele)
-Jörg Oster (joergoster)
 Joseph Ellis (jhellis3)
 Joseph R. Prostko
+Jörg Oster (joergoster)
+Julian Willemer (NightlyKing)
 jundery
 Justin Blanchard (UncombedCoconut)
 Kelly Wilson
 Ken Takusagawa
+Kian E (KJE-98)
 kinderchocolate
 Kiran Panditrao (Krgp)
 Kojirion
+Krystian Kuzniarek (kuzkry)
 Leonardo Ljubičić (ICCF World Champion)
 Leonid Pechenik (lp--)
+Liam Keegan (lkeegan)
+Linmiao Xu (linrock)
 Linus Arver (listx)
 loco-loco
 Lub van den Berg (ElbertoOne)
@@ -94,8 +135,12 @@ Maciej Żenczykowski (zenczykowski)
 Malcolm Campbell (xoto10)
 Mark Tenzer (31m059)
 marotear
+Matt Ginsberg (mattginsberg)
 Matthew Lai (matthewlai)
 Matthew Sullivan (Matt14916)
+Max A. (Disservin)
+Maxim Masiutin (maximmasiutin)
+Maxim Molchanov (Maxim)
 Michael An (man)
 Michael Byrne (MichaelB7)
 Michael Chaly (Vizvezdenec)
@@ -104,60 +149,89 @@ Michael Whiteley (protonspring)
 Michel Van den Bergh (vdbergh)
 Miguel Lahoz (miguel-l)
 Mikael Bäckman (mbootsector)
+Mike Babigian (Farseer)
 Mira
 Miroslav Fontán (Hexik)
 Moez Jellouli (MJZ1977)
 Mohammed Li (tthsqe12)
+Muzhen J (XInTheDark)
 Nathan Rugg (nmrugg)
-Nick Pelling (nickpelling)
+Nguyen Pham (nguyenpham)
 Nicklas Persson (NicklasPersson)
+Nick Pelling (nickpelling)
 Niklas Fiekas (niklasf)
 Nikolay Kostov (NikolayIT)
+Norman Schmidt (FireFather)
+notruck
+Ofek Shochat (OfekShochat, ghostway)
 Ondrej Mosnáček (WOnder93)
+Ondřej Mišina (AndrovT)
 Oskar Werkelin Ahlin
 Pablo Vazquez
 Panthee
 Pascal Romaret
 Pasquale Pigazzini (ppigazzini)
 Patrick Jansen (mibere)
-pellanda
+Peter Schneider (pschneider1968)
 Peter Zsifkovits (CoffeeOne)
+PikaCat
+Praveen Kumar Tummala (praveentml)
+Prokop Randáček (ProkopRandacek)
+Rahul Dsilva (silversolver1)
 Ralph Stößer (Ralph Stoesser)
 Raminder Singh
 renouve
-Reuven Peleg
-Richard Lloyd
+Reuven Peleg (R-Peleg)
+Richard Lloyd (Richard-Lloyd)
+rn5f107s2
+Robert Nürnberg (robertnurnberg)
 Rodrigo Exterckötter Tjäder
-Ron Britvich (Britvich)
+Rodrigo Roim (roim)
 Ronald de Man (syzygy1, syzygy)
+Ron Britvich (Britvich)
+rqs
+Rui Coelho (ruicoelhopedro)
 Ryan Schmitt
 Ryan Takker
 Sami Kiminki (skiminki)
 Sebastian Buchwald (UniQP)
 Sergei Antonov (saproj)
 Sergei Ivanov (svivanov72)
+Sergio Vieri (sergiovieri)
 sf-x
+Shahin M. Shahin (peregrine)
 Shane Booth (shane31)
+Shawn Varghese (xXH4CKST3RXx)
+Siad Daboul (Topologist)
 Stefan Geschwentner (locutus2)
 Stefano Cardanobile (Stefano80)
+Stefano Di Martino (StefanoD)
 Steinar Gunderson (sesse)
 Stéphane Nicolet (snicolet)
+Stephen Touset (stouset)
+Syine Mineta (MinetaS)
+Taras Vuk (TarasVuk)
 Thanar2
 thaspel
 theo77186
+Ting-Hsuan Huang (fffelix-huang)
+Tomasz Sobczyk (Sopel97)
 Tom Truscott
 Tom Vijlbrief (tomtor)
 Torsten Franz (torfranz, tfranzer)
+Torsten Hellwig (Torom)
 Tracey Emery (basepr1me)
+tttak
+Unai Corzo (unaiic)
 Uri Blass (uriblass)
 Vince Negri (cuddlestmonkey)
-
+Viren
+windfishballad
+xefoci7612
+Xiang Wang (KatyushaScarlet)
+zz4032
 
 # Additionally, we acknowledge the authors and maintainers of fishtest,
-# an amazing and essential framework for the development of Stockfish!
+# an amazing and essential framework for Stockfish development!
 #
-# https://github.com/glinscott/fishtest/blob/master/AUTHORS
-
-
-
-
+# https://github.com/official-stockfish/fishtest/blob/master/AUTHORS
diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644 (file)
index 0000000..bc0889a
--- /dev/null
@@ -0,0 +1,23 @@
+# This CITATION.cff file was generated with cffinit.
+# Visit https://bit.ly/cffinit to generate yours today!
+
+cff-version: 1.2.0
+title: Stockfish
+message: >-
+  Please cite this software using the metadata from this
+  file.
+type: software
+authors:
+  - name: The Stockfish developers (see AUTHORS file)
+repository-code: 'https://github.com/official-stockfish/Stockfish'
+url: 'https://stockfishchess.org/'
+repository-artifact: 'https://stockfishchess.org/download/'
+abstract: Stockfish is a free and strong UCI chess engine.
+keywords:
+  - chess
+  - artificial intelligence (AI)
+  - tree search
+  - alpha-beta search
+  - neural networks (NN)
+  - efficiently updatable neural networks (NNUE)
+license: GPL-3.0
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..9e72e1d
--- /dev/null
@@ -0,0 +1,87 @@
+# 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
+
+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
+
+- 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/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
index 818433ecc0e094a4db1023c68b33f24344643ad8..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7 100644 (file)
-                    GNU GENERAL PUBLIC LICENSE\r
-                       Version 3, 29 June 2007\r
-\r
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\r
- Everyone is permitted to copy and distribute verbatim copies\r
- of this license document, but changing it is not allowed.\r
-\r
-                            Preamble\r
-\r
-  The GNU General Public License is a free, copyleft license for\r
-software and other kinds of works.\r
-\r
-  The licenses for most software and other practical works are designed\r
-to take away your freedom to share and change the works.  By contrast,\r
-the GNU General Public License is intended to guarantee your freedom to\r
-share and change all versions of a program--to make sure it remains free\r
-software for all its users.  We, the Free Software Foundation, use the\r
-GNU General Public License for most of our software; it applies also to\r
-any other work released this way by its authors.  You can apply it to\r
-your programs, too.\r
-\r
-  When we speak of free software, we are referring to freedom, not\r
-price.  Our General Public Licenses are designed to make sure that you\r
-have the freedom to distribute copies of free software (and charge for\r
-them if you wish), that you receive source code or can get it if you\r
-want it, that you can change the software or use pieces of it in new\r
-free programs, and that you know you can do these things.\r
-\r
-  To protect your rights, we need to prevent others from denying you\r
-these rights or asking you to surrender the rights.  Therefore, you have\r
-certain responsibilities if you distribute copies of the software, or if\r
-you modify it: responsibilities to respect the freedom of others.\r
-\r
-  For example, if you distribute copies of such a program, whether\r
-gratis or for a fee, you must pass on to the recipients the same\r
-freedoms that you received.  You must make sure that they, too, receive\r
-or can get the source code.  And you must show them these terms so they\r
-know their rights.\r
-\r
-  Developers that use the GNU GPL protect your rights with two steps:\r
-(1) assert copyright on the software, and (2) offer you this License\r
-giving you legal permission to copy, distribute and/or modify it.\r
-\r
-  For the developers' and authors' protection, the GPL clearly explains\r
-that there is no warranty for this free software.  For both users' and\r
-authors' sake, the GPL requires that modified versions be marked as\r
-changed, so that their problems will not be attributed erroneously to\r
-authors of previous versions.\r
-\r
-  Some devices are designed to deny users access to install or run\r
-modified versions of the software inside them, although the manufacturer\r
-can do so.  This is fundamentally incompatible with the aim of\r
-protecting users' freedom to change the software.  The systematic\r
-pattern of such abuse occurs in the area of products for individuals to\r
-use, which is precisely where it is most unacceptable.  Therefore, we\r
-have designed this version of the GPL to prohibit the practice for those\r
-products.  If such problems arise substantially in other domains, we\r
-stand ready to extend this provision to those domains in future versions\r
-of the GPL, as needed to protect the freedom of users.\r
-\r
-  Finally, every program is threatened constantly by software patents.\r
-States should not allow patents to restrict development and use of\r
-software on general-purpose computers, but in those that do, we wish to\r
-avoid the special danger that patents applied to a free program could\r
-make it effectively proprietary.  To prevent this, the GPL assures that\r
-patents cannot be used to render the program non-free.\r
-\r
-  The precise terms and conditions for copying, distribution and\r
-modification follow.\r
-\r
-                       TERMS AND CONDITIONS\r
-\r
-  0. Definitions.\r
-\r
-  "This License" refers to version 3 of the GNU General Public License.\r
-\r
-  "Copyright" also means copyright-like laws that apply to other kinds of\r
-works, such as semiconductor masks.\r
-\r
-  "The Program" refers to any copyrightable work licensed under this\r
-License.  Each licensee is addressed as "you".  "Licensees" and\r
-"recipients" may be individuals or organizations.\r
-\r
-  To "modify" a work means to copy from or adapt all or part of the work\r
-in a fashion requiring copyright permission, other than the making of an\r
-exact copy.  The resulting work is called a "modified version" of the\r
-earlier work or a work "based on" the earlier work.\r
-\r
-  A "covered work" means either the unmodified Program or a work based\r
-on the Program.\r
-\r
-  To "propagate" a work means to do anything with it that, without\r
-permission, would make you directly or secondarily liable for\r
-infringement under applicable copyright law, except executing it on a\r
-computer or modifying a private copy.  Propagation includes copying,\r
-distribution (with or without modification), making available to the\r
-public, and in some countries other activities as well.\r
-\r
-  To "convey" a work means any kind of propagation that enables other\r
-parties to make or receive copies.  Mere interaction with a user through\r
-a computer network, with no transfer of a copy, is not conveying.\r
-\r
-  An interactive user interface displays "Appropriate Legal Notices"\r
-to the extent that it includes a convenient and prominently visible\r
-feature that (1) displays an appropriate copyright notice, and (2)\r
-tells the user that there is no warranty for the work (except to the\r
-extent that warranties are provided), that licensees may convey the\r
-work under this License, and how to view a copy of this License.  If\r
-the interface presents a list of user commands or options, such as a\r
-menu, a prominent item in the list meets this criterion.\r
-\r
-  1. Source Code.\r
-\r
-  The "source code" for a work means the preferred form of the work\r
-for making modifications to it.  "Object code" means any non-source\r
-form of a work.\r
-\r
-  A "Standard Interface" means an interface that either is an official\r
-standard defined by a recognized standards body, or, in the case of\r
-interfaces specified for a particular programming language, one that\r
-is widely used among developers working in that language.\r
-\r
-  The "System Libraries" of an executable work include anything, other\r
-than the work as a whole, that (a) is included in the normal form of\r
-packaging a Major Component, but which is not part of that Major\r
-Component, and (b) serves only to enable use of the work with that\r
-Major Component, or to implement a Standard Interface for which an\r
-implementation is available to the public in source code form.  A\r
-"Major Component", in this context, means a major essential component\r
-(kernel, window system, and so on) of the specific operating system\r
-(if any) on which the executable work runs, or a compiler used to\r
-produce the work, or an object code interpreter used to run it.\r
-\r
-  The "Corresponding Source" for a work in object code form means all\r
-the source code needed to generate, install, and (for an executable\r
-work) run the object code and to modify the work, including scripts to\r
-control those activities.  However, it does not include the work's\r
-System Libraries, or general-purpose tools or generally available free\r
-programs which are used unmodified in performing those activities but\r
-which are not part of the work.  For example, Corresponding Source\r
-includes interface definition files associated with source files for\r
-the work, and the source code for shared libraries and dynamically\r
-linked subprograms that the work is specifically designed to require,\r
-such as by intimate data communication or control flow between those\r
-subprograms and other parts of the work.\r
-\r
-  The Corresponding Source need not include anything that users\r
-can regenerate automatically from other parts of the Corresponding\r
-Source.\r
-\r
-  The Corresponding Source for a work in source code form is that\r
-same work.\r
-\r
-  2. Basic Permissions.\r
-\r
-  All rights granted under this License are granted for the term of\r
-copyright on the Program, and are irrevocable provided the stated\r
-conditions are met.  This License explicitly affirms your unlimited\r
-permission to run the unmodified Program.  The output from running a\r
-covered work is covered by this License only if the output, given its\r
-content, constitutes a covered work.  This License acknowledges your\r
-rights of fair use or other equivalent, as provided by copyright law.\r
-\r
-  You may make, run and propagate covered works that you do not\r
-convey, without conditions so long as your license otherwise remains\r
-in force.  You may convey covered works to others for the sole purpose\r
-of having them make modifications exclusively for you, or provide you\r
-with facilities for running those works, provided that you comply with\r
-the terms of this License in conveying all material for which you do\r
-not control copyright.  Those thus making or running the covered works\r
-for you must do so exclusively on your behalf, under your direction\r
-and control, on terms that prohibit them from making any copies of\r
-your copyrighted material outside their relationship with you.\r
-\r
-  Conveying under any other circumstances is permitted solely under\r
-the conditions stated below.  Sublicensing is not allowed; section 10\r
-makes it unnecessary.\r
-\r
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\r
-\r
-  No covered work shall be deemed part of an effective technological\r
-measure under any applicable law fulfilling obligations under article\r
-11 of the WIPO copyright treaty adopted on 20 December 1996, or\r
-similar laws prohibiting or restricting circumvention of such\r
-measures.\r
-\r
-  When you convey a covered work, you waive any legal power to forbid\r
-circumvention of technological measures to the extent such circumvention\r
-is effected by exercising rights under this License with respect to\r
-the covered work, and you disclaim any intention to limit operation or\r
-modification of the work as a means of enforcing, against the work's\r
-users, your or third parties' legal rights to forbid circumvention of\r
-technological measures.\r
-\r
-  4. Conveying Verbatim Copies.\r
-\r
-  You may convey verbatim copies of the Program's source code as you\r
-receive it, in any medium, provided that you conspicuously and\r
-appropriately publish on each copy an appropriate copyright notice;\r
-keep intact all notices stating that this License and any\r
-non-permissive terms added in accord with section 7 apply to the code;\r
-keep intact all notices of the absence of any warranty; and give all\r
-recipients a copy of this License along with the Program.\r
-\r
-  You may charge any price or no price for each copy that you convey,\r
-and you may offer support or warranty protection for a fee.\r
-\r
-  5. Conveying Modified Source Versions.\r
-\r
-  You may convey a work based on the Program, or the modifications to\r
-produce it from the Program, in the form of source code under the\r
-terms of section 4, provided that you also meet all of these conditions:\r
-\r
-    a) The work must carry prominent notices stating that you modified\r
-    it, and giving a relevant date.\r
-\r
-    b) The work must carry prominent notices stating that it is\r
-    released under this License and any conditions added under section\r
-    7.  This requirement modifies the requirement in section 4 to\r
-    "keep intact all notices".\r
-\r
-    c) You must license the entire work, as a whole, under this\r
-    License to anyone who comes into possession of a copy.  This\r
-    License will therefore apply, along with any applicable section 7\r
-    additional terms, to the whole of the work, and all its parts,\r
-    regardless of how they are packaged.  This License gives no\r
-    permission to license the work in any other way, but it does not\r
-    invalidate such permission if you have separately received it.\r
-\r
-    d) If the work has interactive user interfaces, each must display\r
-    Appropriate Legal Notices; however, if the Program has interactive\r
-    interfaces that do not display Appropriate Legal Notices, your\r
-    work need not make them do so.\r
-\r
-  A compilation of a covered work with other separate and independent\r
-works, which are not by their nature extensions of the covered work,\r
-and which are not combined with it such as to form a larger program,\r
-in or on a volume of a storage or distribution medium, is called an\r
-"aggregate" if the compilation and its resulting copyright are not\r
-used to limit the access or legal rights of the compilation's users\r
-beyond what the individual works permit.  Inclusion of a covered work\r
-in an aggregate does not cause this License to apply to the other\r
-parts of the aggregate.\r
-\r
-  6. Conveying Non-Source Forms.\r
-\r
-  You may convey a covered work in object code form under the terms\r
-of sections 4 and 5, provided that you also convey the\r
-machine-readable Corresponding Source under the terms of this License,\r
-in one of these ways:\r
-\r
-    a) Convey the object code in, or embodied in, a physical product\r
-    (including a physical distribution medium), accompanied by the\r
-    Corresponding Source fixed on a durable physical medium\r
-    customarily used for software interchange.\r
-\r
-    b) Convey the object code in, or embodied in, a physical product\r
-    (including a physical distribution medium), accompanied by a\r
-    written offer, valid for at least three years and valid for as\r
-    long as you offer spare parts or customer support for that product\r
-    model, to give anyone who possesses the object code either (1) a\r
-    copy of the Corresponding Source for all the software in the\r
-    product that is covered by this License, on a durable physical\r
-    medium customarily used for software interchange, for a price no\r
-    more than your reasonable cost of physically performing this\r
-    conveying of source, or (2) access to copy the\r
-    Corresponding Source from a network server at no charge.\r
-\r
-    c) Convey individual copies of the object code with a copy of the\r
-    written offer to provide the Corresponding Source.  This\r
-    alternative is allowed only occasionally and noncommercially, and\r
-    only if you received the object code with such an offer, in accord\r
-    with subsection 6b.\r
-\r
-    d) Convey the object code by offering access from a designated\r
-    place (gratis or for a charge), and offer equivalent access to the\r
-    Corresponding Source in the same way through the same place at no\r
-    further charge.  You need not require recipients to copy the\r
-    Corresponding Source along with the object code.  If the place to\r
-    copy the object code is a network server, the Corresponding Source\r
-    may be on a different server (operated by you or a third party)\r
-    that supports equivalent copying facilities, provided you maintain\r
-    clear directions next to the object code saying where to find the\r
-    Corresponding Source.  Regardless of what server hosts the\r
-    Corresponding Source, you remain obligated to ensure that it is\r
-    available for as long as needed to satisfy these requirements.\r
-\r
-    e) Convey the object code using peer-to-peer transmission, provided\r
-    you inform other peers where the object code and Corresponding\r
-    Source of the work are being offered to the general public at no\r
-    charge under subsection 6d.\r
-\r
-  A separable portion of the object code, whose source code is excluded\r
-from the Corresponding Source as a System Library, need not be\r
-included in conveying the object code work.\r
-\r
-  A "User Product" is either (1) a "consumer product", which means any\r
-tangible personal property which is normally used for personal, family,\r
-or household purposes, or (2) anything designed or sold for incorporation\r
-into a dwelling.  In determining whether a product is a consumer product,\r
-doubtful cases shall be resolved in favor of coverage.  For a particular\r
-product received by a particular user, "normally used" refers to a\r
-typical or common use of that class of product, regardless of the status\r
-of the particular user or of the way in which the particular user\r
-actually uses, or expects or is expected to use, the product.  A product\r
-is a consumer product regardless of whether the product has substantial\r
-commercial, industrial or non-consumer uses, unless such uses represent\r
-the only significant mode of use of the product.\r
-\r
-  "Installation Information" for a User Product means any methods,\r
-procedures, authorization keys, or other information required to install\r
-and execute modified versions of a covered work in that User Product from\r
-a modified version of its Corresponding Source.  The information must\r
-suffice to ensure that the continued functioning of the modified object\r
-code is in no case prevented or interfered with solely because\r
-modification has been made.\r
-\r
-  If you convey an object code work under this section in, or with, or\r
-specifically for use in, a User Product, and the conveying occurs as\r
-part of a transaction in which the right of possession and use of the\r
-User Product is transferred to the recipient in perpetuity or for a\r
-fixed term (regardless of how the transaction is characterized), the\r
-Corresponding Source conveyed under this section must be accompanied\r
-by the Installation Information.  But this requirement does not apply\r
-if neither you nor any third party retains the ability to install\r
-modified object code on the User Product (for example, the work has\r
-been installed in ROM).\r
-\r
-  The requirement to provide Installation Information does not include a\r
-requirement to continue to provide support service, warranty, or updates\r
-for a work that has been modified or installed by the recipient, or for\r
-the User Product in which it has been modified or installed.  Access to a\r
-network may be denied when the modification itself materially and\r
-adversely affects the operation of the network or violates the rules and\r
-protocols for communication across the network.\r
-\r
-  Corresponding Source conveyed, and Installation Information provided,\r
-in accord with this section must be in a format that is publicly\r
-documented (and with an implementation available to the public in\r
-source code form), and must require no special password or key for\r
-unpacking, reading or copying.\r
-\r
-  7. Additional Terms.\r
-\r
-  "Additional permissions" are terms that supplement the terms of this\r
-License by making exceptions from one or more of its conditions.\r
-Additional permissions that are applicable to the entire Program shall\r
-be treated as though they were included in this License, to the extent\r
-that they are valid under applicable law.  If additional permissions\r
-apply only to part of the Program, that part may be used separately\r
-under those permissions, but the entire Program remains governed by\r
-this License without regard to the additional permissions.\r
-\r
-  When you convey a copy of a covered work, you may at your option\r
-remove any additional permissions from that copy, or from any part of\r
-it.  (Additional permissions may be written to require their own\r
-removal in certain cases when you modify the work.)  You may place\r
-additional permissions on material, added by you to a covered work,\r
-for which you have or can give appropriate copyright permission.\r
-\r
-  Notwithstanding any other provision of this License, for material you\r
-add to a covered work, you may (if authorized by the copyright holders of\r
-that material) supplement the terms of this License with terms:\r
-\r
-    a) Disclaiming warranty or limiting liability differently from the\r
-    terms of sections 15 and 16 of this License; or\r
-\r
-    b) Requiring preservation of specified reasonable legal notices or\r
-    author attributions in that material or in the Appropriate Legal\r
-    Notices displayed by works containing it; or\r
-\r
-    c) Prohibiting misrepresentation of the origin of that material, or\r
-    requiring that modified versions of such material be marked in\r
-    reasonable ways as different from the original version; or\r
-\r
-    d) Limiting the use for publicity purposes of names of licensors or\r
-    authors of the material; or\r
-\r
-    e) Declining to grant rights under trademark law for use of some\r
-    trade names, trademarks, or service marks; or\r
-\r
-    f) Requiring indemnification of licensors and authors of that\r
-    material by anyone who conveys the material (or modified versions of\r
-    it) with contractual assumptions of liability to the recipient, for\r
-    any liability that these contractual assumptions directly impose on\r
-    those licensors and authors.\r
-\r
-  All other non-permissive additional terms are considered "further\r
-restrictions" within the meaning of section 10.  If the Program as you\r
-received it, or any part of it, contains a notice stating that it is\r
-governed by this License along with a term that is a further\r
-restriction, you may remove that term.  If a license document contains\r
-a further restriction but permits relicensing or conveying under this\r
-License, you may add to a covered work material governed by the terms\r
-of that license document, provided that the further restriction does\r
-not survive such relicensing or conveying.\r
-\r
-  If you add terms to a covered work in accord with this section, you\r
-must place, in the relevant source files, a statement of the\r
-additional terms that apply to those files, or a notice indicating\r
-where to find the applicable terms.\r
-\r
-  Additional terms, permissive or non-permissive, may be stated in the\r
-form of a separately written license, or stated as exceptions;\r
-the above requirements apply either way.\r
-\r
-  8. Termination.\r
-\r
-  You may not propagate or modify a covered work except as expressly\r
-provided under this License.  Any attempt otherwise to propagate or\r
-modify it is void, and will automatically terminate your rights under\r
-this License (including any patent licenses granted under the third\r
-paragraph of section 11).\r
-\r
-  However, if you cease all violation of this License, then your\r
-license from a particular copyright holder is reinstated (a)\r
-provisionally, unless and until the copyright holder explicitly and\r
-finally terminates your license, and (b) permanently, if the copyright\r
-holder fails to notify you of the violation by some reasonable means\r
-prior to 60 days after the cessation.\r
-\r
-  Moreover, your license from a particular copyright holder is\r
-reinstated permanently if the copyright holder notifies you of the\r
-violation by some reasonable means, this is the first time you have\r
-received notice of violation of this License (for any work) from that\r
-copyright holder, and you cure the violation prior to 30 days after\r
-your receipt of the notice.\r
-\r
-  Termination of your rights under this section does not terminate the\r
-licenses of parties who have received copies or rights from you under\r
-this License.  If your rights have been terminated and not permanently\r
-reinstated, you do not qualify to receive new licenses for the same\r
-material under section 10.\r
-\r
-  9. Acceptance Not Required for Having Copies.\r
-\r
-  You are not required to accept this License in order to receive or\r
-run a copy of the Program.  Ancillary propagation of a covered work\r
-occurring solely as a consequence of using peer-to-peer transmission\r
-to receive a copy likewise does not require acceptance.  However,\r
-nothing other than this License grants you permission to propagate or\r
-modify any covered work.  These actions infringe copyright if you do\r
-not accept this License.  Therefore, by modifying or propagating a\r
-covered work, you indicate your acceptance of this License to do so.\r
-\r
-  10. Automatic Licensing of Downstream Recipients.\r
-\r
-  Each time you convey a covered work, the recipient automatically\r
-receives a license from the original licensors, to run, modify and\r
-propagate that work, subject to this License.  You are not responsible\r
-for enforcing compliance by third parties with this License.\r
-\r
-  An "entity transaction" is a transaction transferring control of an\r
-organization, or substantially all assets of one, or subdividing an\r
-organization, or merging organizations.  If propagation of a covered\r
-work results from an entity transaction, each party to that\r
-transaction who receives a copy of the work also receives whatever\r
-licenses to the work the party's predecessor in interest had or could\r
-give under the previous paragraph, plus a right to possession of the\r
-Corresponding Source of the work from the predecessor in interest, if\r
-the predecessor has it or can get it with reasonable efforts.\r
-\r
-  You may not impose any further restrictions on the exercise of the\r
-rights granted or affirmed under this License.  For example, you may\r
-not impose a license fee, royalty, or other charge for exercise of\r
-rights granted under this License, and you may not initiate litigation\r
-(including a cross-claim or counterclaim in a lawsuit) alleging that\r
-any patent claim is infringed by making, using, selling, offering for\r
-sale, or importing the Program or any portion of it.\r
-\r
-  11. Patents.\r
-\r
-  A "contributor" is a copyright holder who authorizes use under this\r
-License of the Program or a work on which the Program is based.  The\r
-work thus licensed is called the contributor's "contributor version".\r
-\r
-  A contributor's "essential patent claims" are all patent claims\r
-owned or controlled by the contributor, whether already acquired or\r
-hereafter acquired, that would be infringed by some manner, permitted\r
-by this License, of making, using, or selling its contributor version,\r
-but do not include claims that would be infringed only as a\r
-consequence of further modification of the contributor version.  For\r
-purposes of this definition, "control" includes the right to grant\r
-patent sublicenses in a manner consistent with the requirements of\r
-this License.\r
-\r
-  Each contributor grants you a non-exclusive, worldwide, royalty-free\r
-patent license under the contributor's essential patent claims, to\r
-make, use, sell, offer for sale, import and otherwise run, modify and\r
-propagate the contents of its contributor version.\r
-\r
-  In the following three paragraphs, a "patent license" is any express\r
-agreement or commitment, however denominated, not to enforce a patent\r
-(such as an express permission to practice a patent or covenant not to\r
-sue for patent infringement).  To "grant" such a patent license to a\r
-party means to make such an agreement or commitment not to enforce a\r
-patent against the party.\r
-\r
-  If you convey a covered work, knowingly relying on a patent license,\r
-and the Corresponding Source of the work is not available for anyone\r
-to copy, free of charge and under the terms of this License, through a\r
-publicly available network server or other readily accessible means,\r
-then you must either (1) cause the Corresponding Source to be so\r
-available, or (2) arrange to deprive yourself of the benefit of the\r
-patent license for this particular work, or (3) arrange, in a manner\r
-consistent with the requirements of this License, to extend the patent\r
-license to downstream recipients.  "Knowingly relying" means you have\r
-actual knowledge that, but for the patent license, your conveying the\r
-covered work in a country, or your recipient's use of the covered work\r
-in a country, would infringe one or more identifiable patents in that\r
-country that you have reason to believe are valid.\r
-\r
-  If, pursuant to or in connection with a single transaction or\r
-arrangement, you convey, or propagate by procuring conveyance of, a\r
-covered work, and grant a patent license to some of the parties\r
-receiving the covered work authorizing them to use, propagate, modify\r
-or convey a specific copy of the covered work, then the patent license\r
-you grant is automatically extended to all recipients of the covered\r
-work and works based on it.\r
-\r
-  A patent license is "discriminatory" if it does not include within\r
-the scope of its coverage, prohibits the exercise of, or is\r
-conditioned on the non-exercise of one or more of the rights that are\r
-specifically granted under this License.  You may not convey a covered\r
-work if you are a party to an arrangement with a third party that is\r
-in the business of distributing software, under which you make payment\r
-to the third party based on the extent of your activity of conveying\r
-the work, and under which the third party grants, to any of the\r
-parties who would receive the covered work from you, a discriminatory\r
-patent license (a) in connection with copies of the covered work\r
-conveyed by you (or copies made from those copies), or (b) primarily\r
-for and in connection with specific products or compilations that\r
-contain the covered work, unless you entered into that arrangement,\r
-or that patent license was granted, prior to 28 March 2007.\r
-\r
-  Nothing in this License shall be construed as excluding or limiting\r
-any implied license or other defenses to infringement that may\r
-otherwise be available to you under applicable patent law.\r
-\r
-  12. No Surrender of Others' Freedom.\r
-\r
-  If conditions are imposed on you (whether by court order, agreement or\r
-otherwise) that contradict the conditions of this License, they do not\r
-excuse you from the conditions of this License.  If you cannot convey a\r
-covered work so as to satisfy simultaneously your obligations under this\r
-License and any other pertinent obligations, then as a consequence you may\r
-not convey it at all.  For example, if you agree to terms that obligate you\r
-to collect a royalty for further conveying from those to whom you convey\r
-the Program, the only way you could satisfy both those terms and this\r
-License would be to refrain entirely from conveying the Program.\r
-\r
-  13. Use with the GNU Affero General Public License.\r
-\r
-  Notwithstanding any other provision of this License, you have\r
-permission to link or combine any covered work with a work licensed\r
-under version 3 of the GNU Affero General Public License into a single\r
-combined work, and to convey the resulting work.  The terms of this\r
-License will continue to apply to the part which is the covered work,\r
-but the special requirements of the GNU Affero General Public License,\r
-section 13, concerning interaction through a network will apply to the\r
-combination as such.\r
-\r
-  14. Revised Versions of this License.\r
-\r
-  The Free Software Foundation may publish revised and/or new versions of\r
-the GNU General Public License from time to time.  Such new versions will\r
-be similar in spirit to the present version, but may differ in detail to\r
-address new problems or concerns.\r
-\r
-  Each version is given a distinguishing version number.  If the\r
-Program specifies that a certain numbered version of the GNU General\r
-Public License "or any later version" applies to it, you have the\r
-option of following the terms and conditions either of that numbered\r
-version or of any later version published by the Free Software\r
-Foundation.  If the Program does not specify a version number of the\r
-GNU General Public License, you may choose any version ever published\r
-by the Free Software Foundation.\r
-\r
-  If the Program specifies that a proxy can decide which future\r
-versions of the GNU General Public License can be used, that proxy's\r
-public statement of acceptance of a version permanently authorizes you\r
-to choose that version for the Program.\r
-\r
-  Later license versions may give you additional or different\r
-permissions.  However, no additional obligations are imposed on any\r
-author or copyright holder as a result of your choosing to follow a\r
-later version.\r
-\r
-  15. Disclaimer of Warranty.\r
-\r
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\r
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\r
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY\r
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\r
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\r
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\r
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\r
-\r
-  16. Limitation of Liability.\r
-\r
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\r
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\r
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\r
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\r
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\r
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\r
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\r
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\r
-SUCH DAMAGES.\r
-\r
-  17. Interpretation of Sections 15 and 16.\r
-\r
-  If the disclaimer of warranty and limitation of liability provided\r
-above cannot be given local legal effect according to their terms,\r
-reviewing courts shall apply local law that most closely approximates\r
-an absolute waiver of all civil liability in connection with the\r
-Program, unless a warranty or assumption of liability accompanies a\r
-copy of the Program in return for a fee.\r
-\r
-                     END OF TERMS AND CONDITIONS\r
-\r
-            How to Apply These Terms to Your New Programs\r
-\r
-  If you develop a new program, and you want it to be of the greatest\r
-possible use to the public, the best way to achieve this is to make it\r
-free software which everyone can redistribute and change under these terms.\r
-\r
-  To do so, attach the following notices to the program.  It is safest\r
-to attach them to the start of each source file to most effectively\r
-state the exclusion of warranty; and each file should have at least\r
-the "copyright" line and a pointer to where the full notice is found.\r
-\r
-    <one line to give the program's name and a brief idea of what it does.>\r
-    Copyright (C) <year>  <name of author>\r
-\r
-    This program is free software: you can redistribute it and/or modify\r
-    it under the terms of the GNU General Public License as published by\r
-    the Free Software Foundation, either version 3 of the License, or\r
-    (at your option) any later version.\r
-\r
-    This program is distributed in the hope that it will be useful,\r
-    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-    GNU General Public License for more details.\r
-\r
-    You should have received a copy of the GNU General Public License\r
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
-\r
-Also add information on how to contact you by electronic and paper mail.\r
-\r
-  If the program does terminal interaction, make it output a short\r
-notice like this when it starts in an interactive mode:\r
-\r
-    <program>  Copyright (C) <year>  <name of author>\r
-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\r
-    This is free software, and you are welcome to redistribute it\r
-    under certain conditions; type `show c' for details.\r
-\r
-The hypothetical commands `show w' and `show c' should show the appropriate\r
-parts of the General Public License.  Of course, your program's commands\r
-might be different; for a GUI interface, you would use an "about box".\r
-\r
-  You should also get your employer (if you work as a programmer) or school,\r
-if any, to sign a "copyright disclaimer" for the program, if necessary.\r
-For more information on this, and how to apply and follow the GNU GPL, see\r
-<http://www.gnu.org/licenses/>.\r
-\r
-  The GNU General Public License does not permit incorporating your program\r
-into proprietary programs.  If your program is a subroutine library, you\r
-may consider it more useful to permit linking proprietary applications with\r
-the library.  If this is what you want to do, use the GNU Lesser General\r
-Public License instead of this License.  But first, please read\r
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.\r
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 <https://www.gnu.org/licenses/>.
+
+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:
+
+    <program>  Copyright (C) <year>  <name of author>
+    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
+<https://www.gnu.org/licenses/>.
+
+  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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..52b123c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,154 @@
+<div align="center">
+
+  [![Stockfish][stockfish128-logo]][website-link]
+
+  <h3>Stockfish</h3>
+
+  A free and strong UCI chess engine.
+  <br>
+  <strong>[Explore Stockfish docs »][wiki-link]</strong>
+  <br>
+  <br>
+  [Report bug][issue-link]
+  ·
+  [Open a discussion][discussions-link]
+  ·
+  [Discord][discord-link]
+  ·
+  [Blog][website-blog-link]
+
+  [![Build][build-badge]][build-link]
+  [![License][license-badge]][license-link]
+  <br>
+  [![Release][release-badge]][release-link]
+  [![Commits][commits-badge]][commits-link]
+  <br>
+  [![Website][website-badge]][website-link]
+  [![Fishtest][fishtest-badge]][fishtest-link]
+  [![Discord][discord-badge]][discord-link]
+
+</div>
+
+## Overview
+
+[Stockfish][website-link] is a **free and strong UCI chess engine** derived from
+Glaurung 2.1 that analyzes chess positions and computes the optimal moves.
+
+Stockfish **does not include a graphical user interface** (GUI) that is required
+to display a chessboard and to make it easy to input moves. These GUIs are
+developed independently from Stockfish and are available online. **Read the
+documentation for your GUI** of choice for information about how to use
+Stockfish with it.
+
+See also the Stockfish [documentation][wiki-usage-link] for further usage help.
+
+## Files
+
+This distribution of Stockfish consists of the following files:
+
+  * [README.md][readme-link], the file you are currently reading.
+
+  * [Copying.txt][license-link], a text file containing the GNU General Public
+    License version 3.
+
+  * [AUTHORS][authors-link], a text file with the list of authors for the project.
+
+  * [src][src-link], a subdirectory containing the full source code, including a
+    Makefile that can be used to compile Stockfish on Unix-like systems.
+
+  * a file with the .nnue extension, storing the neural network for the NNUE
+    evaluation. Binary distributions will have this file embedded.
+
+## Contributing
+
+__See [Contributing Guide](CONTRIBUTING.md).__
+
+### Donating hardware
+
+Improving Stockfish requires a massive amount of testing. You can donate your
+hardware resources by installing the [Fishtest Worker][worker-link] and viewing
+the current tests on [Fishtest][fishtest-link].
+
+### Improving the code
+
+In the [chessprogramming wiki][programming-link], many techniques used in
+Stockfish are explained with a lot of background information.
+The [section on Stockfish][programmingsf-link] describes many features
+and techniques used by Stockfish. However, it is generic rather than
+focused on Stockfish's precise implementation.
+
+The engine testing is done on [Fishtest][fishtest-link].
+If you want to help improve Stockfish, please read this [guideline][guideline-link]
+first, where the basics of Stockfish development are explained.
+
+Discussions about Stockfish take place these days mainly in the Stockfish
+[Discord server][discord-link]. This is also the best place to ask questions
+about the codebase and how to improve it.
+
+## 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
+[**GNU General Public License version 3**][license-link] (GPL v3). Essentially,
+this means you are free to do almost exactly what you want with the program,
+including distributing it among your friends, making it available for download
+from your website, selling it (either by itself or as part of some bigger
+software package), or using it as the starting point for a software project of
+your own.
+
+The only real limitation is that whenever you distribute Stockfish in some way,
+you MUST always include the license and the full source code (or a pointer to
+where the source code can be found) to generate the exact binary you are
+distributing. If you make any changes to the source code, these changes must
+also be made available under GPL v3.
+
+
+[authors-link]:       https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS
+[build-link]:         https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml
+[commits-link]:       https://github.com/official-stockfish/Stockfish/commits/master
+[discord-link]:       https://discord.gg/GWDRS3kU6R
+[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/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
+[readme-link]:        https://github.com/official-stockfish/Stockfish/blob/master/README.md
+[release-link]:       https://github.com/official-stockfish/Stockfish/releases/latest
+[src-link]:           https://github.com/official-stockfish/Stockfish/tree/master/src
+[stockfish128-logo]:  https://stockfishchess.org/images/logo/icon_128x128.png
+[uci-link]:           https://backscattering.de/chess/uci/
+[website-link]:       https://stockfishchess.org
+[website-blog-link]:  https://stockfishchess.org/blog/
+[wiki-link]:          https://github.com/official-stockfish/Stockfish/wiki
+[wiki-compile-link]:  https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source
+[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
+[commits-badge]:      https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge
+[discord-badge]:      https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord
+[fishtest-badge]:     https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests%2Ffinished
+[license-badge]:      https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success
+[release-badge]:      https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release
+[website-badge]:      https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org
diff --git a/Readme.md b/Readme.md
deleted file mode 100644 (file)
index a759eff..0000000
--- a/Readme.md
+++ /dev/null
@@ -1,210 +0,0 @@
-## Overview
-
-[![Build Status](https://travis-ci.org/official-stockfish/Stockfish.svg?branch=master)](https://travis-ci.org/official-stockfish/Stockfish)
-[![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
-
-[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
-derived from Glaurung 2.1. It is not a complete chess program and requires a
-UCI-compatible GUI (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, Arena,
-Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably.
-Read the documentation for your GUI of choice for information about how to use
-Stockfish with it.
-
-
-## Files
-
-This distribution of Stockfish consists of the following files:
-
-  * Readme.md, the file you are currently reading.
-
-  * Copying.txt, a text file containing the GNU General Public License version 3.
-
-  * src, a subdirectory containing the full source code, including a Makefile
-    that can be used to compile Stockfish on Unix-like systems.
-
-
-## UCI parameters
-
-Currently, Stockfish has the following UCI options:
-
-  * #### Debug Log File
-    Write all communication to and from the engine into a text file.
-
-  * #### Contempt
-    A positive value for contempt favors middle game positions and avoids draws.
-
-  * #### Analysis Contempt
-    By default, contempt is set to prefer the side to move. Set this option to "White"
-    or "Black" to analyse with contempt for that side, or "Off" to disable contempt.
-
-  * #### Threads
-    The number of CPU threads used for searching a position. For best performance, set
-    this equal to the number of CPU cores available.
-
-  * #### Hash
-    The size of the hash table in MB.
-
-  * #### Clear Hash
-    Clear the hash table.
-
-  * #### Ponder
-    Let Stockfish ponder its next move while the opponent is thinking.
-
-  * #### MultiPV
-    Output the N best lines (principal variations, PVs) when searching.
-    Leave at 1 for best performance.
-
-  * #### Skill Level
-    Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength).
-    Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a
-    weaker move will be played.
-
-  * #### UCI_LimitStrength
-    Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level.
-
-  * #### UCI_Elo
-    If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo.
-    This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4.
-
-  * #### Move Overhead
-    Assume a time delay of x ms due to network and GUI overheads. This is useful to
-    avoid losses on time in those cases.
-
-  * #### Minimum Thinking Time
-    Search for at least x ms per move.
-
-  * #### Slow Mover
-    Lower values will make Stockfish take less time in games, higher values will
-    make it think longer.
-
-  * #### nodestime
-    Tells the engine to use nodes searched instead of wall time to account for
-    elapsed time. Useful for engine testing.
-
-  * #### UCI_Chess960
-    An option handled by your GUI. If true, Stockfish will play Chess960.
-
-  * #### UCI_AnalyseMode
-    An option handled by your GUI.
-
-  * #### SyzygyPath
-    Path to the folders/directories storing the Syzygy tablebase files. Multiple
-    directories are to be separated by ";" on Windows and by ":" on Unix-based
-    operating systems. Do not use spaces around the ";" or ":".
-
-    Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6`
-
-    It is recommended to store .rtbw files on an SSD. There is no loss in storing
-    the .rtbz files on a regular HD. It is recommended to verify all md5 checksums
-    of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will
-    lead to engine crashes.
-
-  * #### SyzygyProbeDepth
-    Minimum remaining search depth for which a position is probed. Set this option
-    to a higher value to probe less agressively if you experience too much slowdown
-    (in terms of nps) due to TB probing.
-
-  * #### Syzygy50MoveRule
-    Disable to let fifty-move rule draws detected by Syzygy tablebase probes count
-    as wins or losses. This is useful for ICCF correspondence games.
-
-  * #### SyzygyProbeLimit
-    Limit Syzygy tablebase probing to positions with at most this many pieces left
-    (including kings and pawns).
-
-
-## What to expect from Syzygybases?
-
-If the engine is searching a position that is not in the tablebases (e.g.
-a position with 8 pieces), it will access the tablebases during the search.
-If the engine reports a very large score (typically 153.xx), this means
-that it has found a winning line into a tablebase position.
-
-If the engine is given a position to search that is in the tablebases, it
-will use the tablebases at the beginning of the search to preselect all
-good moves, i.e. all moves that preserve the win or preserve the draw while
-taking into account the 50-move rule.
-It will then perform a search only on those moves. **The engine will not move
-immediately**, unless there is only a single good move. **The engine likely
-will not report a mate score even if the position is known to be won.**
-
-It is therefore clear that this behaviour is not identical to what one might
-be used to with Nalimov tablebases. There are technical reasons for this
-difference, the main technical reason being that Nalimov tablebases use the
-DTM metric (distance-to-mate), while Syzygybases use a variation of the
-DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move
-counter). This special metric is one of the reasons that Syzygybases are
-more compact than Nalimov tablebases, while still storing all information
-needed for optimal play and in addition being able to take into account
-the 50-move rule.
-
-
-## Compiling Stockfish yourself from the sources
-
-On Unix-like systems, it should be possible to compile Stockfish
-directly from the source code with the included Makefile.
-
-Stockfish has support for 32 or 64-bit CPUs, the hardware POPCNT
-instruction, big-endian machines such as Power PC, and other platforms.
-
-In general it is recommended to run `make help` to see a list of make
-targets with corresponding descriptions. When not using the Makefile to
-compile (for instance with Microsoft MSVC) you need to manually
-set/unset some switches in the compiler command line; see file *types.h*
-for a quick reference.
-
-When reporting an issue or a bug, please tell us which version and
-compiler you used to create your executable. These informations can
-be found by typing the following commands in a console:
-
-```
-    ./stockfish
-    compiler
-```
-
-## Understanding the code base and participating in the project
-
-Stockfish's improvement over the last couple of years has been a great
-community effort. There are a few ways to help contribute to its growth.
-
-### Donating hardware
-
-Improving Stockfish requires a massive amount of testing. You can donate
-your hardware resources by installing the [Fishtest Worker](https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview)
-and view the current tests on [Fishtest](https://tests.stockfishchess.org/tests).
-
-### Improving the code
-
-If you want to help improve the code, there are several valuable resources:
-
-* [In this wiki,](https://www.chessprogramming.org) many techniques used in
-Stockfish are explained with a lot of background information.
-
-* [The section on Stockfish](https://www.chessprogramming.org/Stockfish)
-describes many features and techniques used by Stockfish. However, it is
-generic rather than being focused on Stockfish's precise implementation.
-Nevertheless, a helpful resource.
-
-* The latest source can always be found on [GitHub](https://github.com/official-stockfish/Stockfish).
-Discussions about Stockfish take place in the [FishCooking](https://groups.google.com/forum/#!forum/fishcooking)
-group and engine testing is done on [Fishtest](https://tests.stockfishchess.org/tests).
-If you want to help improve Stockfish, please read this [guideline](https://github.com/glinscott/fishtest/wiki/Creating-my-first-test)
-first, where the basics of Stockfish development are explained.
-
-
-## Terms of use
-
-Stockfish is free, and distributed under the **GNU General Public License version 3**
-(GPL v3). Essentially, this means that you are free to do almost exactly
-what you want with the program, including distributing it among your
-friends, making it available for download from your web site, selling
-it (either by itself or as part of some bigger software package), or
-using it as the starting point for a software project of your own.
-
-The only real limitation is that whenever you distribute Stockfish in
-some way, you must always include the full source code, or a pointer
-to where the source code can be found. If you make any changes to the
-source code, these changes must also be made available under the GPL.
-
-For full details, read the copy of the GPL v3 found in the file named
-*Copying.txt*.
index 0ea5ac72e20961bf1d06afcf9aa46c168c0e1638..74c471b74048e393dedd6f1b9c61de5fee7004b4 100644 (file)
-Contributors with >10,000 CPU hours as of January 7, 2020
+Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20.
 Thank you!
 
-Username                  CPU Hours   Games played
---------------------------------------------------
-noobpwnftw                  9305707      695548021
-mlang                        780050       61648867
-dew                          621626       43921547
-mibere                       524702       42238645
-crunchy                      354587       27344275
-cw                           354495       27274181
-fastgm                       332801       22804359
-JojoM                        295750       20437451
-CSU_Dynasty                  262015       21828122
-Fisherman                    232181       18939229
-ctoks                        218866       17622052
-glinscott                    201989       13780820
-tvijlbrief                   201204       15337115
-velislav                     188630       14348485
-gvreuls                      187164       15149976
-bking_US                     180289       11876016
-nordlandia                   172076       13467830
-leszek                       157152       11443978
-Thanar                       148021       12365359
-spams                        141975       10319326
-drabel                       138073       11121749
-vdv                          137850        9394330
-mgrabiak                     133578       10454324
-TueRens                      132485       10878471
-bcross                       129683       11557084
-marrco                       126078        9356740
-sqrt2                        125830        9724586
-robal                        122873        9593418
-vdbergh                      120766        8926915
-malala                       115926        8002293
-CoffeeOne                    114241        5004100
-dsmith                       113189        7570238
-BrunoBanani                  104644        7436849
-Data                          92328        8220352
-mhoram                        89333        6695109
-davar                         87924        7009424
-xoto                          81094        6869316
-ElbertoOne                    80899        7023771
-grandphish2                   78067        6160199
-brabos                        77212        6186135
-psk                           75733        5984901
-BRAVONE                       73875        5054681
-sunu                          70771        5597972
-sterni1971                    70605        5590573
-MaZePallas                    66886        5188978
-Vizvezdenec                   63708        4967313
-nssy                          63462        5259388
-jromang                       61634        4940891
-teddybaer                     61231        5407666
-Pking_cda                     60099        5293873
-solarlight                    57469        5028306
-dv8silencer                   56913        3883992
-tinker                        54936        4086118
-renouve                       49732        3501516
-Freja                         49543        3733019
-robnjr                        46972        4053117
-rap                           46563        3219146
-Bobo1239                      46036        3817196
-ttruscott                     45304        3649765
-racerschmacer                 44881        3975413
-finfish                       44764        3370515
-eva42                         41783        3599691
-biffhero                      40263        3111352
-bigpen0r                      39817        3291647
-mhunt                         38871        2691355
-ronaldjerum                   38820        3240695
-Antihistamine                 38785        2761312
-pb00067                       38038        3086320
-speedycpu                     37591        3003273
-rkl                           37207        3289580
-VoyagerOne                    37050        3441673
-jbwiebe                       35320        2805433
-cuistot                       34191        2146279
-homyur                        33927        2850481
-manap                         32873        2327384
-gri                           32538        2515779
-oryx                          31267        2899051
-EthanOConnor                  30959        2090311
-SC                            30832        2730764
-csnodgrass                    29505        2688994
-jmdana                        29458        2205261
-strelock                      28219        2067805
-jkiiski                       27832        1904470
-Pyafue                        27533        1902349
-Garf                          27515        2747562
-eastorwest                    27421        2317535
-slakovv                       26903        2021889
-Prcuvu                        24835        2170122
-anst                          24714        2190091
-hyperbolic.tom                24319        2017394
-Patrick_G                     23687        1801617
-Sharaf_DG                     22896        1786697
-nabildanial                   22195        1519409
-chriswk                       21931        1868317
-achambord                     21665        1767323
-Zirie                         20887        1472937
-team-oh                       20217        1636708
-Isidor                        20096        1680691
-ncfish1                       19931        1520927
-nesoneg                       19875        1463031
-Spprtr                        19853        1548165
-JanErik                       19849        1703875
-agg177                        19478        1395014
-SFTUser                       19231        1567999
-xor12                         19017        1680165
-sg4032                        18431        1641865
-rstoesser                     18118        1293588
-MazeOfGalious                 17917        1629593
-j3corre                       17743         941444
-cisco2015                     17725        1690126
-ianh2105                      17706        1632562
-dex                           17678        1467203
-jundery                       17194        1115855
-iisiraider                    17019        1101015
-horst.prack                   17012        1465656
-Adrian.Schmidt123             16563        1281436
-purplefishies                 16342        1092533
-wei                           16274        1745989
-ville                         16144        1384026
-eudhan                        15712        1283717
-OuaisBla                      15581         972000
-DragonLord                    15559        1162790
-dju                           14716         875569
-chris                         14479        1487385
-0xB00B1ES                     14079        1001120
-OssumOpossum                  13776        1007129
-enedene                       13460         905279
-bpfliegel                     13346         884523
-Ente                          13198        1156722
-IgorLeMasson                  13087        1147232
-jpulman                       13000         870599
-ako027ako                     12775        1173203
-Nikolay.IT                    12352        1068349
-Andrew Grant                  12327         895539
-joster                        12008         950160
-AdrianSA                      11996         804972
-Nesa92                        11455        1111993
-fatmurphy                     11345         853210
-Dark_wizzie                   11108        1007152
-modolief                      10869         896470
-mschmidt                      10757         803401
-infinity                      10594         727027
-mabichito                     10524         749391
-Thomas A. Anderson            10474         732094
-thijsk                        10431         719357
-Flopzee                       10339         894821
-crocogoat                     10104        1013854
-SapphireBrand                 10104         969604
-stocky                        10017         699440
+Username                                CPU Hours     Games played
+------------------------------------------------------------------
+noobpwnftw                               37457426       2850540907
+technologov                              14135647        742892808
+linrock                                   4423514        303254809
+mlang                                     3026000        200065824
+dew                                       1689162        100033738
+okrout                                    1578136        148855886
+pemo                                      1508508         48814305
+grandphish2                               1461406         91540343
+TueRens                                   1194790         70400852
+JojoM                                      947612         61773190
+tvijlbrief                                 796125         51897690
+sebastronomy                               742434         38218524
+mibere                                     703840         46867607
+gvreuls                                    651026         42988582
+oz                                         543438         39314736
+cw                                         517858         34869755
+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
+Fisherman                                  327231         21829379
+mgrabiak                                   300612         20608380
+Dantist                                    296386         18031762
+nordlandia                                 246201         16189678
+robal                                      241300         15656382
+marrco                                     234581         17714473
+ncfish1                                    227517         15233777
+glinscott                                  208125         13277240
+drabel                                     204167         13930674
+mhoram                                     202894         12601997
+bking_US                                   198894         11876016
+Thanar                                     179852         12365359
+vdv                                        175544          9904472
+spams                                      157128         10319326
+sqrt2                                      147963          9724586
+DesolatedDodo                              146350          9536172
+Calis007                                   143165          9478764
+vdbergh                                    138650          9064413
+CoffeeOne                                  137100          5024116
+armo9494                                   136191          9460264
+malala                                     136182          8002293
+xoto                                       133759          9159372
+davar                                      129023          8376525
+DMBK                                       122960          8980062
+dsmith                                     122059          7570238
+amicic                                     119661          7938029
+Data                                       113305          8220352
+BrunoBanani                                112960          7436849
+CypressChess                               108331          7759788
+skiminki                                   107583          7218170
+jcAEie                                     105675          8238962
+MaZePallas                                 102823          6633619
+sterni1971                                 100532          5880772
+sunu                                       100167          7040199
+zeryl                                       99331          6221261
+thirdlife                                   99124          2242380
+ElbertoOne                                  99028          7023771
+cuistot                                     98853          6069816
+bigpen0r                                    94809          6529203
+brabos                                      92118          6186135
+Wolfgang                                    91939          6105872
+psk                                         89957          5984901
+sschnee                                     88235          5268000
+racerschmacer                               85805          6122790
+Fifis                                       85722          5709729
+Dubslow                                     84986          6042456
+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
+Bobo1239                                    70579          4794999
+solarlight                                  70517          5028306
+dv8silencer                                 70287          3883992
+manap                                       66273          4121774
+tinker                                      64333          4268790
+qurashee                                    61208          3429862
+Mineta                                      59357          4418202
+Spprtr                                      58723          3911011
+AGI                                         58147          4325994
+robnjr                                      57262          4053117
+Freja                                       56938          3733019
+MaxKlaxxMiner                               56879          3423958
+MarcusTullius                               56746          3762951
+ttruscott                                   56010          3680085
+rkl                                         55132          4164467
+renouve                                     53811          3501516
+javran                                      53785          4627608
+finfish                                     51360          3370515
+eva42                                       51272          3599691
+eastorwest                                  51117          3454811
+rap                                         49985          3219146
+pb00067                                     49733          3298934
+OuaisBla                                    48626          3445134
+ronaldjerum                                 47654          3240695
+biffhero                                    46564          3111352
+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
+csnodgrass                                  36207          2688994
+strelock                                    34716          2074055
+szupaw                                      34102          2880346
+EthanOConnor                                33370          2090311
+slakovv                                     32915          2021889
+Gelma                                       31771          1551204
+gopeto                                      31671          2060990
+kdave                                       31157          2198362
+manapbk                                     30987          1810399
+Prcuvu                                      30377          2170122
+anst                                        30301          2190091
+jkiiski                                     30136          1904470
+spcc                                        29925          1901692
+hyperbolic.tom                              29840          2017394
+chuckstablers                               29659          2093438
+Pyafue                                      29650          1902349
+belzedar94                                  28846          1811530
+chriswk                                     26902          1868317
+xwziegtm                                    26897          2124586
+achambord                                   26582          1767323
+Patrick_G                                   26276          1801617
+yorkman                                     26193          1992080
+Ulysses                                     25288          1689730
+SFTUser                                     25182          1675689
+nabildanial                                 24942          1519409
+Sharaf_DG                                   24765          1786697
+Maxim                                       24705          1502062
+rodneyc                                     24376          1416402
+agg177                                      23890          1395014
+Goatminola                                  23763          1956036
+Ente                                        23639          1671638
+Jopo12321                                   23467          1483172
+JanErik                                     23408          1703875
+Isidor                                      23388          1680691
+Norabor                                     23371          1603244
+cisco2015                                   22920          1763301
+jsys14                                      22824          1591906
+Zirie                                       22542          1472937
+team-oh                                     22272          1636708
+Roady                                       22220          1465606
+MazeOfGalious                               21978          1629593
+sg4032                                      21947          1643353
+ianh2105                                    21725          1632562
+xor12                                       21628          1680365
+dex                                         21612          1467203
+nesoneg                                     21494          1463031
+user213718                                  21454          1404128
+sphinx                                      21211          1384728
+AndreasKrug                                 21097          1634811
+jjoshua2                                    21001          1423089
+Zake9298                                    20938          1565848
+horst.prack                                 20878          1465656
+0xB00B1ES                                   20590          1208666
+j3corre                                     20405           941444
+Adrian.Schmidt123                           20316          1281436
+wei                                         19973          1745989
+notchris                                    19958          1800128
+Serpensin                                   19840          1697528
+Gaster319                                   19712          1677310
+fishtester                                  19617          1257388
+rstoesser                                   19569          1293588
+eudhan                                      19274          1283717
+votoanthuan                                 19108          1609992
+vulcan                                      18871          1729392
+Karpovbot                                   18766          1053178
+qoo_charly_cai                              18543          1284937
+jundery                                     18445          1115855
+ville                                       17883          1384026
+chris                                       17698          1487385
+purplefishies                               17595          1092533
+dju                                         17414           981289
+iisiraider                                  17275          1049015
+DragonLord                                  17014          1162790
+redstone59                                  16842          1461780
+Alb11747                                    16787          1213926
+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
+enedene                                     14476           905279
+bpfliegel                                   14233           882523
+mpx86                                       14019           759568
+jpulman                                     13982           870599
+Skiff84                                     13826           721996
+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
+korposzczur                                 12606           838168
+fatmurphy                                   12547           853210
+SapphireBrand                               12416           969604
+Oakwen                                      12399           844109
+deflectooor                                 12386           579392
+modolief                                    12386           896470
+Farseer                                     12249           694108
+Jackfish                                    12213           805008
+pgontarz                                    12151           848794
+dbernier                                    12103           860824
+getraideBFF                                 12072          1024966
+stocky                                      11954           699440
+mschmidt                                    11941           803401
+MooTheCow                                   11870           773598
+FormazChar                                  11766           885707
+whelanh                                     11557           245188
+3cho                                        11494          1031076
+infinity                                    11470           727027
+aga                                         11412           695127
+torbjo                                      11395           729145
+Thomas A. Anderson                          11372           732094
+savage84                                    11358           670860
+d64                                         11263           789184
+ali-al-zhrani                               11245           779246
+snicolet                                    11106           869170
+dapper                                      11032           771402
+ols                                         10947           624903
+Karmatron                                   10828           677458
+basepi                                      10637           744851
+Cubox                                       10621           826448
+michaelrpg                                  10509           739239
+OIVAS7572                                   10420           995586
+jojo2357                                    10419           929708
+WoodMan777                                  10380           873720
+Garruk                                      10365           706465
+dzjp                                        10343           732529
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644 (file)
index 21f3bbe..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-version: 1.0.{build}
-clone_depth: 50
-
-branches:
-  only:
-    - master
-    - appveyor
-
-# Operating system (build VM template)
-os: Visual Studio 2017
-
-# Build platform, i.e. x86, x64, AnyCPU. This setting is optional.
-platform:
-  - x86
-  - x64
-
-# build Configuration, i.e. Debug, Release, etc.
-configuration:
-  - Debug
-  - Release
-
-matrix:
-  # The build fail immediately once one of the job fails
-  fast_finish: true
-
-# Scripts that are called at very beginning, before repo cloning
-init:
-  - cmake --version
-  - msbuild /version
-
-before_build:
-  - ps: |
-      # Get sources
-      $src = get-childitem -Path *.cpp -Recurse | select -ExpandProperty FullName
-      $src = $src -join ' '
-      $src = $src.Replace("\", "/")
-
-      # Build CMakeLists.txt
-      $t = 'cmake_minimum_required(VERSION 3.8)',
-           'project(Stockfish)',
-           'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)',
-           'set(source_files', $src, ')',
-           'add_executable(stockfish ${source_files})'
-
-      # Write CMakeLists.txt withouth BOM
-      $MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt'
-      $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
-      [System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding)
-
-      # Obtain bench reference from git log
-      $b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1
-      $bench = $b -match '\D+(\d+)' | % { $matches[1] }
-      Write-Host "Reference bench:" $bench
-      $g = "Visual Studio 15 2017"
-      If (${env:PLATFORM} -eq 'x64') { $g = $g + ' Win64' }
-      cmake -G "${g}" .
-      Write-Host "Generated files for: " $g
-
-build_script:
-  - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal
-
-before_test:
-  - cd src/%CONFIGURATION%
-  - stockfish bench 2> out.txt >NUL
-  - ps: |
-      # Verify bench number
-      $s = (gc "./out.txt" | out-string)
-      $r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] })
-      Write-Host "Engine bench:" $r
-      Write-Host "Reference bench:" $bench
-      If ($r -ne $bench) { exit 1 }
diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh
new file mode 100755 (executable)
index 0000000..ae23c3b
--- /dev/null
@@ -0,0 +1,120 @@
+#!/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
+# 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]*:|^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
+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:]' | tr -d '_.')
+        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"
index 679eb8d90fcb740c183660ed9eae3682e9a2c6f7..7a65345ee56121a91f7791c2103d39e2a6145cce 100644 (file)
@@ -1,7 +1,5 @@
 # Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-# Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-# Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+# 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
 ### Section 1. General Configuration
 ### ==========================================================================
 
+### Establish the operating system name
+KERNEL := $(shell uname -s)
+ifeq ($(KERNEL),Linux)
+       OS := $(shell uname -o)
+endif
+
+### Target Windows OS
+ifeq ($(OS),Windows_NT)
+       ifneq ($(COMP),ndk)
+               target_windows = yes
+       endif
+else ifeq ($(COMP),mingw)
+       target_windows = yes
+       ifeq ($(WINE_PATH),)
+               WINE_PATH := $(shell which wine)
+       endif
+endif
+
 ### Executable name
-ifeq ($(COMP),mingw)
-EXE = stockfish.exe
+ifeq ($(target_windows),yes)
+       EXE = stockfish.exe
 else
-EXE = stockfish
+       EXE = stockfish
 endif
 
 ### Installation dir definitions
@@ -33,123 +49,350 @@ PREFIX = /usr/local
 BINDIR = $(PREFIX)/bin
 
 ### Built-in benchmark for pgo-builds
-PGOBENCH = ./$(EXE) bench
+PGOBENCH = $(WINE_PATH) ./$(EXE) bench
 
-### Object files
-OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \
-       material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \
-       search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o
+### Source and object files
+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 \
+       hashprobe.grpc.pb.cc hashprobe.pb.cc
+CLISRCS = client.cpp hashprobe.grpc.pb.cc hashprobe.pb.cc uci.cpp
 
-### Establish the operating system name
-KERNEL = $(shell uname -s)
-ifeq ($(KERNEL),Linux)
-       OS = $(shell uname -o)
-endif
+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))
+CLIOBJS = $(notdir $(CLISRCS:.cpp=.o))
+
+VPATH = syzygy:nnue:nnue/features
 
 ### ==========================================================================
 ### Section 2. High-level Configuration
 ### ==========================================================================
 #
-# flag                --- Comp switch --- Description
+# flag                --- Comp switch        --- Description
 # ----------------------------------------------------------------------------
 #
-# debug = yes/no      --- -DNDEBUG         --- Enable/Disable debug mode
-# sanitize = undefined/thread/no (-fsanitize )
-#                     --- ( undefined )    --- enable undefined behavior checks
-#                     --- ( thread    )    --- enable threading error  checks
-# optimize = yes/no   --- (-O3/-fast etc.) --- Enable/Disable optimizations
-# arch = (name)       --- (-arch)          --- Target architecture
-# bits = 64/32        --- -DIS_64BIT       --- 64-/32-bit operating system
-# prefetch = yes/no   --- -DUSE_PREFETCH   --- Use prefetch asm-instruction
-# popcnt = yes/no     --- -DUSE_POPCNT     --- Use popcnt asm-instruction
-# sse = yes/no        --- -msse            --- Use Intel Streaming SIMD Extensions
-# pext = yes/no       --- -DUSE_PEXT       --- Use pext x86_64 asm-instruction
+# debug = yes/no      --- -DNDEBUG           --- Enable/Disable debug mode
+# sanitize = none/<sanitizer> ... (-fsanitize )
+#                     --- ( undefined )      --- enable undefined behavior checks
+#                     --- ( thread    )      --- enable threading error checks
+#                     --- ( address   )      --- enable memory access checks
+#                     --- ...etc...          --- see compiler documentation for supported sanitizers
+# optimize = yes/no   --- (-O3/-fast etc.)   --- Enable/Disable optimizations
+# arch = (name)       --- (-arch)            --- Target architecture
+# bits = 64/32        --- -DIS_64BIT         --- 64-/32-bit operating system
+# prefetch = yes/no   --- -DUSE_PREFETCH     --- Use prefetch asm-instruction
+# popcnt = yes/no     --- -DUSE_POPCNT       --- Use popcnt asm-instruction
+# pext = yes/no       --- -DUSE_PEXT         --- Use pext x86_64 asm-instruction
+# sse = yes/no        --- -msse              --- Use Intel Streaming SIMD Extensions
+# mmx = yes/no        --- -mmmx              --- Use Intel MMX instructions
+# sse2 = yes/no       --- -msse2             --- Use Intel Streaming SIMD Extensions 2
+# ssse3 = yes/no      --- -mssse3            --- Use Intel Supplemental Streaming SIMD Extensions 3
+# sse41 = yes/no      --- -msse4.1           --- Use Intel Streaming SIMD Extensions 4.1
+# avx2 = yes/no       --- -mavx2             --- Use Intel Advanced Vector Extensions 2
+# avxvnni = yes/no    --- -mavxvnni          --- Use Intel Vector Neural Network Instructions AVX
+# avx512 = yes/no     --- -mavx512bw         --- Use Intel Advanced Vector Extensions 512
+# vnni256 = yes/no    --- -mavx256vnni       --- Use Intel Vector Neural Network Instructions 512 with 256bit operands
+# vnni512 = yes/no    --- -mavx512vnni       --- Use Intel Vector Neural Network Instructions 512
+# neon = yes/no       --- -DUSE_NEON         --- Use ARM SIMD architecture
+# dotprod = yes/no    --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
 #
 # Note that Makefile is space sensitive, so when adding new architectures
 # or modifying existing flags, you have to make sure there are no extra spaces
 # at the end of the line for flag values.
+#
+# Example of use for these flags:
+# make build ARCH=x86-64-avx512 debug=yes sanitize="address undefined"
+
 
 ### 2.1. General and architecture defaults
+
+ifeq ($(ARCH),)
+   ARCH = native
+endif
+
+ifeq ($(ARCH), native)
+   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),
+# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
+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 loongarch64))
+   SUPPORTED_ARCH=true
+else
+   SUPPORTED_ARCH=false
+endif
+
 optimize = yes
 debug = no
-sanitize = no
-bits = 32
+sanitize = none
+bits = 64
 prefetch = no
 popcnt = no
-sse = no
 pext = no
+sse = no
+mmx = no
+sse2 = no
+ssse3 = no
+sse41 = no
+avx2 = no
+avxvnni = no
+avx512 = no
+vnni256 = no
+vnni512 = no
+neon = no
+dotprod = no
+arm_version = 0
+STRIP = strip
+
+ifneq ($(shell which clang-format-17 2> /dev/null),)
+       CLANG-FORMAT = clang-format-17
+else
+       CLANG-FORMAT = clang-format
+endif
 
 ### 2.2 Architecture specific
 
-ifeq ($(ARCH),general-32)
-       arch = any
-endif
+ifeq ($(findstring x86,$(ARCH)),x86)
 
-ifeq ($(ARCH),x86-32-old)
+# x86-32/64
+
+ifeq ($(findstring x86-32,$(ARCH)),x86-32)
        arch = i386
+       bits = 32
+       sse = no
+       mmx = yes
+else
+       arch = x86_64
+       sse = yes
+       sse2 = yes
 endif
 
-ifeq ($(ARCH),x86-32)
-       arch = i386
-       prefetch = yes
+ifeq ($(findstring -sse,$(ARCH)),-sse)
        sse = yes
 endif
 
-ifeq ($(ARCH),general-64)
-       arch = any
-       bits = 64
+ifeq ($(findstring -popcnt,$(ARCH)),-popcnt)
+       popcnt = yes
 endif
 
-ifeq ($(ARCH),x86-64)
-       arch = x86_64
-       bits = 64
-       prefetch = yes
+ifeq ($(findstring -mmx,$(ARCH)),-mmx)
+       mmx = yes
+endif
+
+ifeq ($(findstring -sse2,$(ARCH)),-sse2)
        sse = yes
+       sse2 = yes
 endif
 
-ifeq ($(ARCH),x86-64-modern)
-       arch = x86_64
-       bits = 64
-       prefetch = yes
+ifeq ($(findstring -ssse3,$(ARCH)),-ssse3)
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+endif
+
+ifeq ($(findstring -sse41,$(ARCH)),-sse41)
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+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
+       ssse3 = yes
+       sse41 = yes
 endif
 
-ifeq ($(ARCH),x86-64-bmi2)
-       arch = x86_64
-       bits = 64
-       prefetch = yes
+ifeq ($(findstring -avx2,$(ARCH)),-avx2)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+endif
+
+ifeq ($(findstring -avxvnni,$(ARCH)),-avxvnni)
        popcnt = yes
        sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+       avxvnni = yes
        pext = yes
 endif
 
+ifeq ($(findstring -bmi2,$(ARCH)),-bmi2)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+       pext = yes
+endif
+
+ifeq ($(findstring -avx512,$(ARCH)),-avx512)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+       pext = yes
+       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
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+       pext = yes
+       avx512 = yes
+       vnni512 = yes
+endif
+
+ifeq ($(sse),yes)
+       prefetch = yes
+endif
+
+# 64-bit pext is not available on x86-32
+ifeq ($(bits),32)
+       pext = no
+endif
+
+else
+
+# all other architectures
+
+ifeq ($(ARCH),general-32)
+       arch = any
+       bits = 32
+endif
+
+ifeq ($(ARCH),general-64)
+       arch = any
+endif
+
 ifeq ($(ARCH),armv7)
        arch = armv7
        prefetch = yes
+       bits = 32
+       arm_version = 7
+endif
+
+ifeq ($(ARCH),armv7-neon)
+       arch = armv7
+       prefetch = yes
+       popcnt = yes
+       neon = yes
+       bits = 32
+       arm_version = 7
+endif
+
+ifeq ($(ARCH),armv8)
+       arch = armv8
+       prefetch = yes
+       popcnt = yes
+       neon = yes
+       arm_version = 8
+endif
+
+ifeq ($(ARCH),armv8-dotprod)
+       arch = armv8
+       prefetch = yes
+       popcnt = yes
+       neon = yes
+       dotprod = yes
+       arm_version = 8
+endif
+
+ifeq ($(ARCH),apple-silicon)
+       arch = arm64
+       prefetch = yes
+       popcnt = yes
+       neon = yes
+       dotprod = yes
+       arm_version = 8
 endif
 
 ifeq ($(ARCH),ppc-32)
        arch = ppc
+       bits = 32
 endif
 
 ifeq ($(ARCH),ppc-64)
        arch = ppc64
-       bits = 64
        popcnt = yes
        prefetch = yes
 endif
 
+ifeq ($(findstring e2k,$(ARCH)),e2k)
+       arch = e2k
+       mmx = yes
+       bits = 64
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       popcnt = yes
+endif
+
+ifeq ($(ARCH),riscv64)
+       arch = riscv64
+endif
+
+ifeq ($(ARCH),loongarch64)
+       arch = loongarch64
+endif
+endif
+
 
 ### ==========================================================================
-### Section 3. Low-level configuration
+### Section 3. Low-level Configuration
 ### ==========================================================================
 
 ### 3.1 Selecting compiler (default = gcc)
+ifeq ($(MAKELEVEL),0)
+       export ENV_CXXFLAGS := $(CXXFLAGS)
+       export ENV_DEPENDFLAGS := $(DEPENDFLAGS)
+       export ENV_LDFLAGS := $(LDFLAGS)
+endif
 
-CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS)
-DEPENDFLAGS += -std=c++11
-LDFLAGS += $(EXTRALDFLAGS)
+CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
+DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17
+LDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS)
 
 ifeq ($(COMP),)
        COMP=gcc
@@ -158,92 +401,144 @@ endif
 ifeq ($(COMP),gcc)
        comp=gcc
        CXX=g++
-       CXXFLAGS += -pedantic -Wextra -Wshadow
+       CXXFLAGS += -pedantic -Wextra -Wmissing-declarations
 
-       ifeq ($(ARCH),armv7)
+       ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
                ifeq ($(OS),Android)
                        CXXFLAGS += -m$(bits)
                        LDFLAGS += -m$(bits)
                endif
+               ifeq ($(ARCH),riscv64)
+                       CXXFLAGS += -latomic
+               endif
+       else ifeq ($(ARCH),loongarch64)
+               CXXFLAGS += -latomic
        else
                CXXFLAGS += -m$(bits)
                LDFLAGS += -m$(bits)
        endif
 
+       ifeq ($(arch),$(filter $(arch),armv7))
+               LDFLAGS += -latomic
+       endif
+
        ifneq ($(KERNEL),Darwin)
           LDFLAGS += -Wl,--no-as-needed
        endif
 endif
 
+ifeq ($(target_windows),yes)
+       LDFLAGS += -static
+endif
+
 ifeq ($(COMP),mingw)
        comp=mingw
 
-       ifeq ($(KERNEL),Linux)
-               ifeq ($(bits),64)
-                       ifeq ($(shell which x86_64-w64-mingw32-c++-posix),)
-                               CXX=x86_64-w64-mingw32-c++
-                       else
-                               CXX=x86_64-w64-mingw32-c++-posix
-                       endif
+       ifeq ($(bits),64)
+               ifeq ($(shell which x86_64-w64-mingw32-c++-posix 2> /dev/null),)
+                       CXX=x86_64-w64-mingw32-c++
                else
-                       ifeq ($(shell which i686-w64-mingw32-c++-posix),)
-                               CXX=i686-w64-mingw32-c++
-                       else
-                               CXX=i686-w64-mingw32-c++-posix
-                       endif
+                       CXX=x86_64-w64-mingw32-c++-posix
                endif
        else
-               CXX=g++
+               ifeq ($(shell which i686-w64-mingw32-c++-posix 2> /dev/null),)
+                       CXX=i686-w64-mingw32-c++
+               else
+                       CXX=i686-w64-mingw32-c++-posix
+               endif
        endif
-
-       CXXFLAGS += -Wextra -Wshadow
-       LDFLAGS += -static
+       CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations
 endif
 
-ifeq ($(COMP),icc)
-       comp=icc
-       CXX=icpc
-       CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi
+ifeq ($(COMP),icx)
+       comp=icx
+       CXX=icpx
+       CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \
+               -Wconditional-uninitialized -Wabi -Wdeprecated
 endif
 
 ifeq ($(COMP),clang)
        comp=clang
        CXX=clang++
-       CXXFLAGS += -pedantic -Wextra -Wshadow
+       ifeq ($(target_windows),yes)
+               CXX=x86_64-w64-mingw32-clang++
+       endif
 
-       ifneq ($(KERNEL),Darwin)
-       ifneq ($(KERNEL),OpenBSD)
+       CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \
+                   -Wconditional-uninitialized
+
+       ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)
+       ifeq ($(target_windows),)
+       ifneq ($(RTLIB),compiler-rt)
                LDFLAGS += -latomic
        endif
        endif
+       endif
 
-       ifeq ($(ARCH),armv7)
+       ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
                ifeq ($(OS),Android)
                        CXXFLAGS += -m$(bits)
                        LDFLAGS += -m$(bits)
                endif
+               ifeq ($(ARCH),riscv64)
+                       CXXFLAGS += -latomic
+               endif
+       else ifeq ($(ARCH),loongarch64)
+               CXXFLAGS += -latomic
        else
                CXXFLAGS += -m$(bits)
                LDFLAGS += -m$(bits)
        endif
 endif
 
-ifeq ($(comp),icc)
-       profile_make = icc-profile-make
-       profile_use = icc-profile-use
-else
-ifeq ($(comp),clang)
+ifeq ($(KERNEL),Darwin)
+       CXXFLAGS += -mmacosx-version-min=10.14
+       LDFLAGS += -mmacosx-version-min=10.14
+       ifneq ($(arch),any)
+               CXXFLAGS += -arch $(arch)
+               LDFLAGS += -arch $(arch)
+       endif
+       XCRUN = xcrun
+endif
+
+# To cross-compile for Android, NDK version r21 or later is recommended.
+# 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
+       comp=clang
+       ifeq ($(arch),armv7)
+               CXX=armv7a-linux-androideabi16-clang++
+               CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon
+               ifneq ($(shell which arm-linux-androideabi-strip 2>/dev/null),)
+                       STRIP=arm-linux-androideabi-strip
+               else
+                       STRIP=llvm-strip
+               endif
+       endif
+       ifeq ($(arch),armv8)
+               CXX=aarch64-linux-android21-clang++
+               ifneq ($(shell which aarch64-linux-android-strip 2>/dev/null),)
+                       STRIP=aarch64-linux-android-strip
+               else
+                       STRIP=llvm-strip
+               endif
+       endif
+       LDFLAGS += -static-libstdc++ -pie -lm -latomic
+endif
+
+ifeq ($(comp),icx)
+       profile_make = icx-profile-make
+       profile_use = icx-profile-use
+else ifeq ($(comp),clang)
        profile_make = clang-profile-make
        profile_use = clang-profile-use
 else
        profile_make = gcc-profile-make
        profile_use = gcc-profile-use
-endif
-endif
-
-ifeq ($(KERNEL),Darwin)
-       CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.9
-       LDFLAGS += -arch $(arch) -mmacosx-version-min=10.9
+       ifeq ($(KERNEL),Darwin)
+               EXTRAPROFILEFLAGS = -fvisibility=hidden
+       endif
 endif
 
 ### Travis CI script uses COMPILER to overwrite CXX
@@ -256,13 +551,26 @@ ifdef COMPCXX
        CXX=$(COMPCXX)
 endif
 
+### Sometimes gcc is really clang
+ifeq ($(COMP),gcc)
+       gccversion := $(shell $(CXX) --version 2>/dev/null)
+       gccisclang := $(findstring clang,$(gccversion))
+       ifneq ($(gccisclang),)
+               profile_make = clang-profile-make
+               profile_use = clang-profile-use
+       endif
+endif
+
 ### On mingw use Windows threads, otherwise POSIX
 ifneq ($(comp),mingw)
+       CXXFLAGS += -DUSE_PTHREADS
        # On Android Bionic's C library comes with its own pthread implementation bundled in
        ifneq ($(OS),Android)
                # Haiku has pthreads in its libroot, so only link it in on other platforms
                ifneq ($(KERNEL),Haiku)
-                       LDFLAGS += -lpthread
+                       ifneq ($(COMP),ndk)
+                               LDFLAGS += -lpthread
+                       endif
                endif
        endif
 endif
@@ -275,15 +583,15 @@ else
 endif
 
 ### 3.2.2 Debugging with undefined behavior sanitizers
-ifneq ($(sanitize),no)
-        CXXFLAGS += -g3 -fsanitize=$(sanitize) -fuse-ld=gold
-        LDFLAGS += -fsanitize=$(sanitize) -fuse-ld=gold
+ifneq ($(sanitize),none)
+        CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize))
+        LDFLAGS += $(addprefix -fsanitize=,$(sanitize))
 endif
 
 ### 3.3 Optimization
 ifeq ($(optimize),yes)
 
-       CXXFLAGS += -O3
+       CXXFLAGS += -O3 -g -funroll-loops
 
        ifeq ($(comp),gcc)
                ifeq ($(OS), Android)
@@ -291,10 +599,23 @@ ifeq ($(optimize),yes)
                endif
        endif
 
-       ifeq ($(comp),$(filter $(comp),gcc clang icc))
-               ifeq ($(KERNEL),Darwin)
+       ifeq ($(KERNEL),Darwin)
+               ifeq ($(comp),$(filter $(comp),clang icx))
                        CXXFLAGS += -mdynamic-no-pic
                endif
+
+               ifeq ($(comp),gcc)
+                       ifneq ($(arch),arm64)
+                               CXXFLAGS += -mdynamic-no-pic
+                       endif
+               endif
+       endif
+
+       ifeq ($(comp),clang)
+               clangmajorversion := $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.)
+               ifeq ($(shell expr $(clangmajorversion) \< 16),1)
+                       CXXFLAGS += -fexperimental-new-pass-manager
+               endif
        endif
 endif
 
@@ -303,126 +624,265 @@ ifeq ($(bits),64)
        CXXFLAGS += -DIS_64BIT
 endif
 
-### 3.5 prefetch
+### 3.5 prefetch and popcount
 ifeq ($(prefetch),yes)
        ifeq ($(sse),yes)
                CXXFLAGS += -msse
-               DEPENDFLAGS += -msse
        endif
 else
        CXXFLAGS += -DNO_PREFETCH
 endif
 
-### 3.6 popcnt
 ifeq ($(popcnt),yes)
-       ifeq ($(arch),ppc64)
+       ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64))
                CXXFLAGS += -DUSE_POPCNT
-       else ifeq ($(comp),icc)
-               CXXFLAGS += -msse3 -DUSE_POPCNT
        else
                CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT
        endif
 endif
 
+### 3.6 SIMD architectures
+ifeq ($(avx2),yes)
+       CXXFLAGS += -DUSE_AVX2
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+               CXXFLAGS += -mavx2 -mbmi
+       endif
+endif
+
+ifeq ($(avxvnni),yes)
+       CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+               CXXFLAGS += -mavxvnni
+       endif
+endif
+
+ifeq ($(avx512),yes)
+       CXXFLAGS += -DUSE_AVX512
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+               CXXFLAGS += -mavx512f -mavx512bw
+       endif
+endif
+
+ifeq ($(vnni256),yes)
+       CXXFLAGS += -DUSE_VNNI
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw 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
+       endif
+endif
+
+ifeq ($(sse41),yes)
+       CXXFLAGS += -DUSE_SSE41
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+               CXXFLAGS += -msse4.1
+       endif
+endif
+
+ifeq ($(ssse3),yes)
+       CXXFLAGS += -DUSE_SSSE3
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+               CXXFLAGS += -mssse3
+       endif
+endif
+
+ifeq ($(sse2),yes)
+       CXXFLAGS += -DUSE_SSE2
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+               CXXFLAGS += -msse2
+       endif
+endif
+
+ifeq ($(mmx),yes)
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+               CXXFLAGS += -mmmx
+       endif
+endif
+
+ifeq ($(neon),yes)
+       CXXFLAGS += -DUSE_NEON=$(arm_version)
+       ifeq ($(KERNEL),Linux)
+       ifneq ($(COMP),ndk)
+       ifneq ($(arch),armv8)
+               CXXFLAGS += -mfpu=neon
+       endif
+       endif
+       endif
+endif
+
+ifeq ($(dotprod),yes)
+       CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD
+endif
+
 ### 3.7 pext
 ifeq ($(pext),yes)
        CXXFLAGS += -DUSE_PEXT
-       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
-               CXXFLAGS += -msse4 -mbmi2
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+               CXXFLAGS += -mbmi2
        endif
 endif
 
-### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows.
+### 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.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.8.3 Try to include architecture
+ifneq ($(ARCH), )
+       CXXFLAGS += -DARCH=$(ARCH)
+endif
+
+### 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)
 ifeq ($(debug), no)
-       ifeq ($(comp),$(filter $(comp),gcc clang))
-               CXXFLAGS += -flto
+       ifeq ($(comp),$(filter $(comp),clang icx))
+               CXXFLAGS += -flto=full
+               ifeq ($(comp),icx)
+                       CXXFLAGS += -fwhole-program-vtables
+                endif
+               ifeq ($(target_windows),yes)
+                       CXXFLAGS += -fuse-ld=lld
+               endif
                LDFLAGS += $(CXXFLAGS)
-       endif
 
-       ifeq ($(comp),mingw)
-       ifeq ($(KERNEL),Linux)
-               CXXFLAGS += -flto
+# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be
+# GCC on some systems.
+       else ifeq ($(comp),gcc)
+       ifeq ($(gccisclang),)
+               CXXFLAGS += -flto -flto-partition=one
+               LDFLAGS += $(CXXFLAGS) -flto=jobserver
+       else
+               CXXFLAGS += -flto=full
                LDFLAGS += $(CXXFLAGS)
        endif
+
+# To use LTO and static linking on Windows,
+# the tool chain requires gcc version 10.1 or later.
+       else ifeq ($(comp),mingw)
+               CXXFLAGS += -flto -flto-partition=one
+               LDFLAGS += $(CXXFLAGS) -save-temps
        endif
 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
        LDFLAGS += -fPIE -pie
 endif
 
-
 ### ==========================================================================
-### Section 4. Public targets
+### 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 ""
-       @echo "build                   > Standard build"
-       @echo "profile-build           > PGO build"
+       @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 "strip                   > Strip executable"
        @echo "install                 > Install executable"
        @echo "clean                   > Clean up"
        @echo ""
        @echo "Supported archs:"
        @echo ""
-       @echo "x86-64-bmi2             > x86 64-bit with pext support (also enables SSE4)"
-       @echo "x86-64-modern           > x86 64-bit with popcnt support (also enables SSE3)"
-       @echo "x86-64                  > x86 64-bit generic"
-       @echo "x86-32                  > x86 32-bit (also enables SSE)"
-       @echo "x86-32-old              > x86 32-bit fall back for old hardware"
+       @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"
+       @echo "x86-64-avxvnni          > x86 64-bit with vnni 256bit support"
+       @echo "x86-64-bmi2             > x86 64-bit with bmi2 support"
+       @echo "x86-64-avx2             > x86 64-bit with avx2 support"
+       @echo "x86-64-sse41-popcnt     > x86 64-bit with sse41 and popcnt support"
+       @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 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 compile support)"
        @echo "ppc-64                  > PPC 64-bit"
        @echo "ppc-32                  > PPC 32-bit"
        @echo "armv7                   > ARMv7 32-bit"
+       @echo "armv7-neon              > ARMv7 32-bit with popcnt and neon"
+       @echo "armv8                   > ARMv8 64-bit with popcnt and neon"
+       @echo "armv8-dotprod           > ARMv8 64-bit with popcnt, neon and dot product support"
+       @echo "e2k                     > Elbrus 2000"
+       @echo "apple-silicon           > Apple silicon ARM64"
        @echo "general-64              > unspecified 64-bit"
        @echo "general-32              > unspecified 32-bit"
+       @echo "riscv64                 > RISC-V 64-bit"
+       @echo "loongarch64             > LoongArch 64-bit"
        @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 "icc                     > Intel compiler"
+       @echo "icx                     > Intel oneAPI DPC++/C++ Compiler"
+       @echo "ndk                     > Google NDK to cross-compile for Android"
        @echo ""
-       @echo "Simple examples. If you don't know what to do, you likely want to run: "
+       @echo "Simple examples. If you don't know what to do, you likely want to run one of: "
        @echo ""
-       @echo "make build ARCH=x86-64    (This is for 64-bit systems)"
-       @echo "make build ARCH=x86-32    (This is for 32-bit systems)"
+       @echo "make -j profile-build ARCH=x86-64-avx2    # typically a fast compile for common systems "
+       @echo "make -j profile-build ARCH=x86-64-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 build ARCH=x86-64 COMP=clang"
-       @echo "make profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-4.8"
+       @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 ""
+ifneq ($(SUPPORTED_ARCH), true)
+       @echo "Specify a supported architecture with the ARCH option for more details"
+       @echo ""
+endif
 
 
-.PHONY: help build profile-build strip install clean objclean profileclean help \
-        config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
-        clang-profile-use clang-profile-make
+.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 \
+       format analyze
 
-build: config-sanity
+analyze: net config-sanity objclean
+       $(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS)
+
+build: net config-sanity
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
 
-profile-build: config-sanity objclean profileclean
+profile-build: net config-sanity objclean profileclean
        @echo ""
        @echo "Step 1/4. Building instrumented executable ..."
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
        @echo ""
        @echo "Step 2/4. Running benchmark for pgo-build ..."
-       $(PGOBENCH) > /dev/null
+       $(PGOBENCH) > PGOBENCH.out 2>&1
+       tail -n 4 PGOBENCH.out
        @echo ""
        @echo "Step 3/4. Building optimized executable ..."
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
@@ -432,37 +892,91 @@ profile-build: config-sanity objclean profileclean
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean
 
 strip:
-       strip $(EXE)
+       $(STRIP) $(EXE)
 
 install:
        -mkdir -p -m 755 $(BINDIR)
        -cp $(EXE) $(BINDIR)
-       -strip $(BINDIR)/$(EXE)
+       $(STRIP) $(BINDIR)/$(EXE)
 
-#clean all
+# clean all
 clean: objclean profileclean
        @rm -f .depend *~ core
 
 # clean binaries and objects
 objclean:
-       @rm -f $(EXE) *.o ./syzygy/*.o
+       @rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
 
 # clean auxiliary profiling files
 profileclean:
        @rm -rf profdir
-       @rm -f bench.txt *.gcda ./syzygy/*.gcda *.gcno ./syzygy/*.gcno
+       @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*
+       @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; \
+
+format:
+       $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file
+
+# default target
 default:
        help
 
 ### ==========================================================================
-### Section 5. Private targets
+### Section 5. Private Targets
 ### ==========================================================================
 
-all: $(EXE) .depend
+all: $(EXE) client .depend
 
-config-sanity:
+config-sanity: net
        @echo ""
        @echo "Config:"
        @echo "debug: '$(debug)'"
@@ -474,8 +988,21 @@ config-sanity:
        @echo "os: '$(OS)'"
        @echo "prefetch: '$(prefetch)'"
        @echo "popcnt: '$(popcnt)'"
-       @echo "sse: '$(sse)'"
        @echo "pext: '$(pext)'"
+       @echo "sse: '$(sse)'"
+       @echo "mmx: '$(mmx)'"
+       @echo "sse2: '$(sse2)'"
+       @echo "ssse3: '$(ssse3)'"
+       @echo "sse41: '$(sse41)'"
+       @echo "avx2: '$(avx2)'"
+       @echo "avxvnni: '$(avxvnni)'"
+       @echo "avx512: '$(avx512)'"
+       @echo "vnni256: '$(vnni256)'"
+       @echo "vnni512: '$(vnni512)'"
+       @echo "neon: '$(neon)'"
+       @echo "dotprod: '$(dotprod)'"
+       @echo "arm_version: '$(arm_version)'"
+       @echo "target_windows: '$(target_windows)'"
        @echo ""
        @echo "Flags:"
        @echo "CXX: $(CXX)"
@@ -485,19 +1012,34 @@ config-sanity:
        @echo "Testing config sanity. If this fails, try 'make help' ..."
        @echo ""
        @test "$(debug)" = "yes" || test "$(debug)" = "no"
-       @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no"
        @test "$(optimize)" = "yes" || test "$(optimize)" = "no"
+       @test "$(SUPPORTED_ARCH)" = "true"
        @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
-        test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7"
+        test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
+        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"
-       @test "$(sse)" = "yes" || test "$(sse)" = "no"
        @test "$(pext)" = "yes" || test "$(pext)" = "no"
-       @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang"
+       @test "$(sse)" = "yes" || test "$(sse)" = "no"
+       @test "$(mmx)" = "yes" || test "$(mmx)" = "no"
+       @test "$(sse2)" = "yes" || test "$(sse2)" = "no"
+       @test "$(ssse3)" = "yes" || test "$(ssse3)" = "no"
+       @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 "$(neon)" = "yes" || test "$(neon)" = "no"
+       @test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \
+       || test "$(comp)" = "armv7a-linux-androideabi16-clang"  || test "$(comp)" = "aarch64-linux-android21-clang"
 
 $(EXE): $(OBJS)
-       $(CXX) -o $@ $(OBJS) $(LDFLAGS)
+       +$(CXX) -o $@ $(OBJS) $(LDFLAGS)
+
+# Force recompilation to ensure version info is up-to-date
+misc.o: FORCE
+FORCE:
 
 clang-profile-make:
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
@@ -506,37 +1048,69 @@ clang-profile-make:
        all
 
 clang-profile-use:
-       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 ' \
        all
 
 gcc-profile-make:
+       @mkdir -p profdir
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
-       EXTRACXXFLAGS='-fprofile-generate' \
+       EXTRACXXFLAGS='-fprofile-generate=profdir' \
+       EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \
        EXTRALDFLAGS='-lgcov' \
        all
 
 gcc-profile-use:
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
-       EXTRACXXFLAGS='-fprofile-use -fno-peel-loops -fno-tracer' \
+       EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \
+       EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \
        EXTRALDFLAGS='-lgcov' \
        all
 
-icc-profile-make:
-       @mkdir -p profdir
+icx-profile-make:
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
-       EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \
+       EXTRACXXFLAGS='-fprofile-instr-generate ' \
+       EXTRALDFLAGS=' -fprofile-instr-generate' \
        all
 
-icc-profile-use:
+icx-profile-use:
+       $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw
        $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
-       EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
+       EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \
+       EXTRALDFLAGS='-fprofile-use ' \
        all
 
-.depend:
-       -@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null
+### GRPC
 
--include .depend
+PROTOS_PATH = .
+PROTOC = protoc
+GRPC_CPP_PLUGIN = grpc_cpp_plugin
+GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)`
+
+%.grpc.pb.h %.grpc.pb.cc: %.proto
+       $(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<
+
+# oh my
+%.cpp: %.cc
+       cp $< $@
 
+%.pb.h %.pb.cc: %.proto
+       $(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $<
+
+#LDFLAGS += -Wl,-Bstatic -Wl,-\( -lprotobuf -lgrpc++_unsecure -lgrpc_unsecure -lgrpc -lz -Wl,-\) -Wl,-Bdynamic -ldl
+LDFLAGS += -Wl,--start-group /usr/lib/x86_64-linux-gnu/libprotobuf.a /usr/lib/x86_64-linux-gnu/libgrpc++_unsecure.a /usr/lib/x86_64-linux-gnu/libgrpc_unsecure.a /usr/lib/x86_64-linux-gnu/libgrpc.a /usr/lib/x86_64-linux-gnu/libaddress_sorting.a /usr/lib/x86_64-linux-gnu/libupb.a /usr/lib/x86_64-linux-gnu/libcares.a /usr/lib/x86_64-linux-gnu/libgpr.a /usr/lib/x86_64-linux-gnu/libabsl_*.a -Wl,--end-group -ldl -lz
+#LDFLAGS += /usr/lib/x86_64-linux-gnu/libprotobuf.a /usr/lib/libgrpc++_unsecure.a /usr/lib/libgrpc_unsecure.a /usr/lib/libgrpc.a /usr/lib/x86_64-linux-gnu/libcares.a -ldl -lz
+
+client: $(CLIOBJS)
+       $(CXX) -o $@ $(CLIOBJS) $(LDFLAGS)
+
+# Other stuff
+
+.depend: $(SRCS)
+       -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
+
+ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity))
+-include .depend
+endif
index f906e731b408a3f369f138bac1983bf3e7a8746b..2270dcc3c83db01d6cf9485dd3eced0d628facbe 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "benchmark.h"
+
+#include <cstdlib>
 #include <fstream>
 #include <iostream>
-#include <istream>
 #include <vector>
 
 #include "position.h"
 
-using namespace std;
-
 namespace {
 
-const vector<string> Defaults = {
+// clang-format off
+const std::vector<std::string> 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",
@@ -65,6 +64,7 @@ const vector<string> Defaults = {
   "4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21",
   "r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
   "3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
+  "4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
 
   // 5-man positions
   "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1",     // Kc2 - mate
@@ -87,74 +87,79 @@ const vector<string> Defaults = {
 
   // Chess 960
   "setoption name UCI_Chess960 value true",
-  "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w KQkq - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
+  "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
+  "nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
   "setoption name UCI_Chess960 value false"
 };
-
-} // namespace
-
-/// 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 millisecs).
-///
-/// 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
-
-vector<string> setup_bench(const Position& current, istream& is) {
-
-  vector<string> fens, list;
-  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";
-
-  go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
-
-  if (fenFile == "default")
-      fens = Defaults;
-
-  else if (fenFile == "current")
-      fens.push_back(current.fen());
-
-  else
-  {
-      string fen;
-      ifstream file(fenFile);
-
-      if (!file.is_open())
-      {
-          cerr << "Unable to open file " << fenFile << endl;
-          exit(EXIT_FAILURE);
-      }
-
-      while (getline(file, fen))
-          if (!fen.empty())
-              fens.push_back(fen);
-
-      file.close();
-  }
-
-  list.emplace_back("setoption name Threads value " + threads);
-  list.emplace_back("setoption name Hash value " + ttSize);
-  list.emplace_back("ucinewgame");
-
-  for (const string& fen : fens)
-      if (fen.find("setoption") != string::npos)
-          list.emplace_back(fen);
-      else
-      {
-          list.emplace_back("position fen " + fen);
-          list.emplace_back(go);
-      }
-
-  return list;
+// clang-format on
+
+}  // namespace
+
+namespace Stockfish {
+
+// 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<std::string> setup_bench(const Position& current, std::istream& is) {
+
+    std::vector<std::string> 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";
+
+    go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
+
+    if (fenFile == "default")
+        fens = Defaults;
+
+    else if (fenFile == "current")
+        fens.push_back(current.fen());
+
+    else
+    {
+        std::string   fen;
+        std::ifstream file(fenFile);
+
+        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);
+
+        file.close();
+    }
+
+    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);
+        }
+
+    return list;
 }
+
+}  // namespace Stockfish
\ No newline at end of file
diff --git a/src/benchmark.h b/src/benchmark.h
new file mode 100644 (file)
index 0000000..e6206d1
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BENCHMARK_H_INCLUDED
+#define BENCHMARK_H_INCLUDED
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+namespace Stockfish {
+
+class Position;
+
+std::vector<std::string> setup_bench(const Position&, std::istream&);
+
+}  // namespace Stockfish
+
+#endif  // #ifndef BENCHMARK_H_INCLUDED
diff --git a/src/bitbase.cpp b/src/bitbase.cpp
deleted file mode 100644 (file)
index 78614fa..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
-  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
-  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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <cassert>
-#include <numeric>
-#include <vector>
-
-#include "bitboard.h"
-#include "types.h"
-
-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
-
-  // Each uint32_t stores results of 32 positions, one per bit
-  uint32_t KPKBitbase[MAX_INDEX / 32];
-
-  // 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 us, Square bksq, Square wksq, Square psq) {
-    return int(wksq) | (bksq << 6) | (us << 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<KPKPosition>& db)
-    { return us == WHITE ? classify<WHITE>(db) : classify<BLACK>(db); }
-
-    template<Color Us> Result classify(const std::vector<KPKPosition>& db);
-
-    Color us;
-    Square ksq[COLOR_NB], psq;
-    Result result;
-  };
-
-} // namespace
-
-
-bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color us) {
-
-  assert(file_of(wpsq) <= FILE_D);
-
-  unsigned idx = index(us, bksq, wksq, wpsq);
-  return KPKBitbase[idx / 32] & (1 << (idx & 0x1F));
-}
-
-
-void Bitbases::init() {
-
-  std::vector<KPKPosition> 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);
-
-  // Map 32 results into one KPKBitbase[] entry
-  for (idx = 0; idx < MAX_INDEX; ++idx)
-      if (db[idx] == WIN)
-          KPKBitbase[idx / 32] |= 1 << (idx & 0x1F);
-}
-
-
-namespace {
-
-  KPKPosition::KPKPosition(unsigned idx) {
-
-    ksq[WHITE] = Square((idx >>  0) & 0x3F);
-    ksq[BLACK] = Square((idx >>  6) & 0x3F);
-    us         = Color ((idx >> 12) & 0x01);
-    psq        = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
-
-    // Check 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
-        || (us == WHITE && (PawnAttacks[WHITE][psq] & ksq[BLACK])))
-        result = INVALID;
-
-    // Immediate win if a pawn can be promoted without getting captured
-    else if (   us == WHITE
-             && rank_of(psq) == RANK_7
-             && ksq[us] != psq + NORTH
-             && (    distance(ksq[~us], psq + NORTH) > 1
-                 || (PseudoAttacks[KING][ksq[us]] & (psq + NORTH))))
-        result = WIN;
-
-    // Immediate draw if it is a stalemate or a king captures undefended pawn
-    else if (   us == BLACK
-             && (  !(PseudoAttacks[KING][ksq[us]] & ~(PseudoAttacks[KING][ksq[~us]] | PawnAttacks[~us][psq]))
-                 || (PseudoAttacks[KING][ksq[us]] & psq & ~PseudoAttacks[KING][ksq[~us]])))
-        result = DRAW;
-
-    // Position will be classified later
-    else
-        result = UNKNOWN;
-  }
-
-  template<Color Us>
-  Result KPKPosition::classify(const std::vector<KPKPosition>& 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.
-
-    constexpr Color  Them = (Us == WHITE ? BLACK : WHITE);
-    constexpr Result Good = (Us == WHITE ? WIN   : DRAW);
-    constexpr Result Bad  = (Us == WHITE ? DRAW  : WIN);
-
-    Result r = INVALID;
-    Bitboard b = PseudoAttacks[KING][ksq[Us]];
-
-    while (b)
-        r |= Us == WHITE ? db[index(Them, ksq[Them]  , pop_lsb(&b), psq)]
-                         : db[index(Them, pop_lsb(&b), ksq[Them]  , psq)];
-
-    if (Us == WHITE)
-    {
-        if (rank_of(psq) < RANK_7)      // Single push
-            r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH)];
-
-        if (   rank_of(psq) == RANK_2   // Double push
-            && psq + NORTH != ksq[Us]
-            && psq + NORTH != ksq[Them])
-            r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH + NORTH)];
-    }
-
-    return result = r & Good  ? Good  : r & UNKNOWN ? UNKNOWN : Bad;
-  }
-
-} // namespace
index 70114f20fadd3485e2c30d93f2760038ea50c61d..a8a10cbb874f103e1c55480ede6e359659db3fcd 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "bitboard.h"
+
 #include <algorithm>
 #include <bitset>
+#include <initializer_list>
 
-#include "bitboard.h"
 #include "misc.h"
 
+namespace Stockfish {
+
 uint8_t PopCnt16[1 << 16];
 uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
 
-Bitboard SquareBB[SQUARE_NB];
 Bitboard LineBB[SQUARE_NB][SQUARE_NB];
+Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
 Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
 Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
 
@@ -37,122 +39,113 @@ 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(Bitboard table[], Magic magics[], Direction directions[]);
 }
 
+// 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
-/// to be printed to standard output. Useful for debugging.
 
-const std::string Bitboards::pretty(Bitboard b) {
+// 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";
+    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 += "|\n+---+---+---+---+---+---+---+---+\n";
-  }
+        s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
+    }
+    s += "  a   b   c   d   e   f   g   h\n";
 
-  return s;
+    return s;
 }
 
 
-/// Bitboards::init() initializes various bitboard tables. It is called at
-/// startup and relies on global objects to be already zero-initialized.
-
+// 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)
-      PopCnt16[i] = std::bitset<16>(i).count();
-
-  for (Square s = SQ_A1; s <= SQ_H8; ++s)
-      SquareBB[s] = (1ULL << s);
-
-  for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
-      for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
-          SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
-
-  for (Square s = SQ_A1; s <= SQ_H8; ++s)
-  {
-      PawnAttacks[WHITE][s] = pawn_attacks_bb<WHITE>(square_bb(s));
-      PawnAttacks[BLACK][s] = pawn_attacks_bb<BLACK>(square_bb(s));
-  }
-
-  // Helper returning the target bitboard of a step from a square
-  auto landing_square_bb = [&](Square s, int step)
-  {
-      Square to = Square(s + step);
-      return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
-  };
-
-  for (Square s = SQ_A1; s <= SQ_H8; ++s)
-  {
-      for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} )
-         PseudoAttacks[KING][s] |= landing_square_bb(s, step);
-
-      for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} )
-         PseudoAttacks[KNIGHT][s] |= landing_square_bb(s, step);
-  }
-
-  Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST };
-  Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST };
-
-  init_magics(RookTable, RookMagics, RookDirections);
-  init_magics(BishopTable, BishopMagics, BishopDirections);
-
-  for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
-  {
-      PseudoAttacks[QUEEN][s1]  = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
-      PseudoAttacks[QUEEN][s1] |= PseudoAttacks[  ROOK][s1] = attacks_bb<  ROOK>(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;
-  }
-}
+    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<File>(s1, s2), distance<Rank>(s1, s2));
 
-namespace {
+    init_magics(ROOK, RookTable, RookMagics);
+    init_magics(BISHOP, BishopTable, BishopMagics);
 
-  Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) {
+    for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
+    {
+        PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
+        PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
 
-    Bitboard attack = 0;
+        for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})
+            PseudoAttacks[KING][s1] |= safe_destination(s1, step);
 
-    for (int i = 0; i < 4; ++i)
-        for (Square s = sq + directions[i];
-             is_ok(s) && distance(s, s - directions[i]) == 1;
-             s += directions[i])
-        {
-            attack |= s;
+        for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})
+            PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
 
-            if (occupied & s)
-                break;
-        }
+        PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
+        PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1]  = attacks_bb<ROOK>(s1, 0);
 
-    return attack;
-  }
+        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) {
 
-  // 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.
+    Bitboard  attacks             = 0;
+    Direction RookDirections[4]   = {NORTH, SOUTH, EAST, WEST};
+    Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
 
-  void init_magics(Bitboard table[], Magic magics[], Direction directions[]) {
+    for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
+    {
+        Square s = sq;
+        while (safe_destination(s, d) && !(occupied & s))
+            attacks |= (s += d);
+    }
+
+    return attacks;
+}
+
+
+// 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
-    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)
     {
@@ -165,8 +158,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(directions, 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".
@@ -175,9 +168,10 @@ 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(directions, s, b);
+            reference[size] = sliding_attack(pt, s, b);
 
             if (HasPext)
                 m.attacks[pext(b, m.mask)] = reference[size];
@@ -193,9 +187,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<Bitboard>();
 
             // A good magic must map every possible occupancy to an index that
@@ -210,7 +204,7 @@ namespace {
 
                 if (epoch[idx] < cnt)
                 {
-                    epoch[idx] = cnt;
+                    epoch[idx]     = cnt;
                     m.attacks[idx] = reference[i];
                 }
                 else if (m.attacks[idx] != reference[i])
@@ -218,5 +212,7 @@ namespace {
             }
         }
     }
-  }
 }
+}
+
+}  // namespace Stockfish
index 440de1ea1ee0be74befddbac703a15d91b900bbf..7dbd5329be82abe8caaf5f07b163f8e4234b3a40 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 #ifndef BITBOARD_H_INCLUDED
 #define BITBOARD_H_INCLUDED
 
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
 #include <string>
 
 #include "types.h"
 
-namespace Bitbases {
-
-void init();
-bool probe(Square wksq, Square wpsq, Square bksq, Color us);
-
-}
+namespace Stockfish {
 
 namespace Bitboards {
 
-void init();
-const std::string pretty(Bitboard b);
+void        init();
+std::string pretty(Bitboard b);
 
-}
-
-constexpr Bitboard AllSquares = ~Bitboard(0);
-constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL;
+}  // namespace Stockfish::Bitboards
 
 constexpr Bitboard FileABB = 0x0101010101010101ULL;
 constexpr Bitboard FileBBB = FileABB << 1;
@@ -60,331 +55,320 @@ 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];
 
-extern Bitboard SquareBB[SQUARE_NB];
+extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
 extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
 extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
 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;
-  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(s >= SQ_A1 && s <= SQ_H8);
-  return SquareBB[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); }
+// 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&(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 s, Square s2) { return square_bb(s) | square_bb(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); }
 
-inline bool opposite_colors(Square s1, Square s2) {
-  return bool(DarkSquares & s1) != bool(DarkSquares & s2);
-}
 
+// 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); }
 
-inline Bitboard rank_bb(Rank r) {
-  return Rank1BB << (8 * r);
-}
+constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); }
 
-inline Bitboard rank_bb(Square s) {
-  return rank_bb(rank_of(s));
-}
+constexpr Bitboard file_bb(File f) { return FileABB << f; }
 
-inline Bitboard file_bb(File f) {
-  return FileABB << f;
-}
-
-inline 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 step along direction D
-
+// Moves a bitboard one or two steps as specified by the direction D
 template<Direction D>
 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;
 }
 
 
-/// pawn_attacks_bb() returns the squares attacked by pawns of the given color
-/// from the squares in the given bitboard.
-
+// Returns the squares attacked by pawns of the given color
+// from the squares in the given bitboard.
 template<Color C>
 constexpr Bitboard pawn_attacks_bb(Bitboard b) {
-  return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
-                    : shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
+    return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
+                      : shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
 }
 
+inline Bitboard pawn_attacks_bb(Color c, Square s) {
 
-/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the
-/// given color from the squares in the given bitboard.
-
-template<Color C>
-constexpr Bitboard pawn_double_attacks_bb(Bitboard b) {
-  return C == WHITE ? shift<NORTH_WEST>(b) & shift<NORTH_EAST>(b)
-                    : shift<SOUTH_WEST>(b) & shift<SOUTH_EAST>(b);
+    assert(is_ok(s));
+    return PawnAttacks[c][s];
 }
 
+// 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) {
 
-/// adjacent_files_bb() returns a bitboard representing all the squares on the
-/// adjacent files of the given one.
+    assert(is_ok(s1) && is_ok(s2));
 
-inline Bitboard adjacent_files_bb(Square s) {
-  return shift<EAST>(file_bb(s)) | shift<WEST>(file_bb(s));
+    return LineBB[s1][s2];
 }
 
 
-/// between_bb() returns squares that are linearly between the given squares
-/// If the given squares are not on a same file/rank/diagonal, return 0.
-
+// 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) {
-  return LineBB[s1][s2] & ( (AllSquares << (s1 +  (s1 < s2)))
-                           ^(AllSquares << (s2 + !(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.
+    assert(is_ok(s1) && is_ok(s2));
 
-inline Bitboard forward_ranks_bb(Color c, Square s) {
-  return c == WHITE ? ~Rank1BB << 8 * (rank_of(s) - RANK_1)
-                    : ~Rank8BB >> 8 * (RANK_8 - rank_of(s));
+    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; }
 
-/// 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.
-
-inline Bitboard forward_file_bb(Color c, Square s) {
-  return forward_ranks_bb(c, s) & file_bb(s);
-}
 
+// distance() functions return the distance between x and y, defined as the
+// number of steps for a king in x to reach y.
 
-/// 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.
+template<typename T1 = Square>
+inline int distance(Square x, Square y);
 
-inline Bitboard pawn_attack_span(Color c, Square s) {
-  return forward_ranks_bb(c, s) & adjacent_files_bb(s);
+template<>
+inline int distance<File>(Square x, Square y) {
+    return std::abs(file_of(x) - file_of(y));
 }
 
-
-/// 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.
-
-inline Bitboard passed_pawn_span(Color c, Square s) {
-  return forward_ranks_bb(c, s) & (adjacent_files_bb(s) | file_bb(s));
+template<>
+inline int distance<Rank>(Square x, Square y) {
+    return std::abs(rank_of(x) - rank_of(y));
 }
 
-
-/// 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 LineBB[s1][s2] & s3;
+template<>
+inline int distance<Square>(Square x, Square y) {
+    return SquareDistance[x][y];
 }
 
+inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
 
-/// distance() functions return the distance between x and y, defined as the
-/// number of steps for a king in x to reach y.
+// Returns the pseudo attacks of the given piece type
+// assuming an empty board.
+template<PieceType Pt>
+inline Bitboard attacks_bb(Square s) {
 
-template<typename T1 = Square> inline int distance(Square x, Square y);
-template<> inline int distance<File>(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); }
-template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
-template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
+    assert((Pt != PAWN) && (is_ok(s)));
 
-template<class T> constexpr const T& clamp(const T& v, const T& lo, const T&  hi) {
-  return v < lo ? lo : v > hi ? hi : v;
+    return PseudoAttacks[Pt][s];
 }
 
-/// attacks_bb() returns a bitboard representing all the squares attacked by a
-/// piece of type Pt (bishop or rook) placed on 's'.
 
+// 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<PieceType Pt>
 inline Bitboard attacks_bb(Square s, Bitboard occupied) {
 
-  const Magic& m = Pt == ROOK ? RookMagics[s] : BishopMagics[s];
-  return m.attacks[m.index(occupied)];
+    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<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
+    default :
+        return PseudoAttacks[Pt][s];
+    }
 }
 
+// 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);
-
-  switch (pt)
-  {
-  case BISHOP: return attacks_bb<BISHOP>(s, occupied);
-  case ROOK  : return attacks_bb<  ROOK>(s, occupied);
-  case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
-  default    : return PseudoAttacks[pt][s];
-  }
+    assert((pt != PAWN) && (is_ok(s)));
+
+    switch (pt)
+    {
+    case BISHOP :
+        return attacks_bb<BISHOP>(s, occupied);
+    case ROOK :
+        return attacks_bb<ROOK>(s, occupied);
+    case QUEEN :
+        return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
+    default :
+        return PseudoAttacks[pt][s];
+    }
 }
 
 
-/// 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
 
-  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) || defined(__INTEL_COMPILER)
+#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
 }
 
-
-/// lsb() and msb() return the least/most significant bit in a non-zero bitboard
-
-#if defined(__GNUC__)  // GCC, Clang, ICC
-
+// Returns the least significant bit in a non-zero bitboard.
 inline Square lsb(Bitboard b) {
-  assert(b);
-  return Square(__builtin_ctzll(b));
-}
+    assert(b);
 
-inline Square msb(Bitboard b) {
-  assert(b);
-  return Square(63 ^ __builtin_clzll(b));
-}
+#if defined(__GNUC__)  // GCC, Clang, ICX
 
-#elif defined(_MSC_VER)  // MSVC
+    return Square(__builtin_ctzll(b));
 
-#ifdef _WIN64  // MSVC, WIN64
+#elif defined(_MSC_VER)
+    #ifdef _WIN64  // MSVC, WIN64
 
-inline Square lsb(Bitboard b) {
-  assert(b);
-  unsigned long idx;
-  _BitScanForward64(&idx, b);
-  return (Square) idx;
-}
+    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
+    #else  // MSVC, WIN32
+    unsigned long idx;
 
-inline Square lsb(Bitboard b) {
-  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);
+    }
+    #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);
-  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);
-  }
-}
+    assert(b);
 
-#endif
+#if defined(__GNUC__)  // GCC, Clang, ICX
 
-#else  // Compiler is neither GCC nor MSVC compatible
+    return Square(63 ^ __builtin_clzll(b));
 
-#error "Compiler not supported."
+#elif defined(_MSC_VER)
+    #ifdef _WIN64  // MSVC, WIN64
 
-#endif
+    unsigned long idx;
+    _BitScanReverse64(&idx, b);
+    return Square(idx);
 
+    #else  // MSVC, WIN32
 
-/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
+    unsigned long idx;
 
-inline Square pop_lsb(Bitboard* b) {
-  const Square s = lsb(*b);
-  *b &= *b - 1;
-  return s;
+    if (b >> 32)
+    {
+        _BitScanReverse(&idx, int32_t(b >> 32));
+        return Square(idx + 32);
+    }
+    else
+    {
+        _BitScanReverse(&idx, int32_t(b));
+        return Square(idx);
+    }
+    #endif
+#else  // Compiler is neither GCC nor MSVC compatible
+    #error "Compiler not supported."
+#endif
 }
 
+// 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;
+}
 
-/// frontmost_sq() returns the most advanced square for the given color
-inline Square frontmost_sq(Color c, Bitboard b) {
-  return c == WHITE ? msb(b) : lsb(b);
+// 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;
 }
 
-#endif // #ifndef BITBOARD_H_INCLUDED
+}  // namespace Stockfish
+
+#endif  // #ifndef BITBOARD_H_INCLUDED
diff --git a/src/client.cpp b/src/client.cpp
new file mode 100644 (file)
index 0000000..0400f93
--- /dev/null
@@ -0,0 +1,81 @@
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include <grpc++/grpc++.h>
+
+#include "hashprobe.grpc.pb.h"
+#include "types.h"
+#include "uci.h"
+
+using grpc::Channel;
+using grpc::ClientContext;
+using grpc::Status;
+using namespace hashprobe;
+
+std::string FormatMove(const HashProbeMove &move) {
+  if (move.pretty().empty()) return "MOVE_NONE";
+  return move.pretty();
+}
+
+int main(int argc, char** argv) {
+  std::shared_ptr<Channel> channel(grpc::CreateChannel(
+      "localhost:50051", grpc::InsecureChannelCredentials()));
+  std::unique_ptr<HashProbe::Stub> stub(HashProbe::NewStub(channel));
+
+  for ( ;; ) {
+    char buf[256];
+    if (fgets(buf, sizeof(buf), stdin) == nullptr || buf[0] == '\n') {
+      exit(0);
+    }
+
+    char *ptr = strchr(buf, '\n');
+    if (ptr != nullptr) *ptr = 0;
+
+    HashProbeRequest request;
+    request.set_fen(buf);
+
+    HashProbeResponse response;
+    ClientContext context;
+    Status status = stub->Probe(&context, request, &response);
+
+    if (status.ok()) {
+      for (const HashProbeLine &line : response.line()) {
+        std::cout << FormatMove(line.move()) << " ";
+        std::cout << line.found() << " ";
+        for (const HashProbeMove &move : line.pv()) {
+          std::cout << FormatMove(move) << ",";
+        }
+        std::cout << " ";
+        switch (line.bound()) {
+        case HashProbeLine::BOUND_NONE:
+          std::cout << "?";
+          break;
+        case HashProbeLine::BOUND_EXACT:
+          std::cout << "==";
+          break;
+        case HashProbeLine::BOUND_UPPER:
+          std::cout << "<=";
+          break;
+        case HashProbeLine::BOUND_LOWER:
+          std::cout << ">=";
+          break;
+        }
+        switch (line.value().score_type()) {
+        case HashProbeScore::SCORE_CP:
+          std::cout << " cp " << line.value().score_cp() << " ";
+          break;
+        case HashProbeScore::SCORE_MATE:
+          std::cout << " mate " << line.value().score_mate() << " ";
+          break;
+        }
+        std::cout << line.depth() << std::endl;
+      }
+      std::cout << "END" << std::endl;
+    } else {
+      std::cout << "ERROR" << std::endl;
+    }
+  }
+
+  return 0;
+}
diff --git a/src/endgame.cpp b/src/endgame.cpp
deleted file mode 100644 (file)
index 2ed6ebc..0000000
+++ /dev/null
@@ -1,806 +0,0 @@
-/*
-  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
-  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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <cassert>
-
-#include "bitboard.h"
-#include "endgame.h"
-#include "movegen.h"
-
-using std::string;
-
-namespace {
-
-  // Table used to drive the king towards the edge of the board
-  // in KX vs K and KQ vs KR endgames.
-  constexpr int PushToEdges[SQUARE_NB] = {
-    100, 90, 80, 70, 70, 80, 90, 100,
-     90, 70, 60, 50, 50, 60, 70,  90,
-     80, 60, 40, 30, 30, 40, 60,  80,
-     70, 50, 30, 20, 20, 30, 50,  70,
-     70, 50, 30, 20, 20, 30, 50,  70,
-     80, 60, 40, 30, 30, 40, 60,  80,
-     90, 70, 60, 50, 50, 60, 70,  90,
-    100, 90, 80, 70, 70, 80, 90, 100
-  };
-
-  // Table used to drive the king towards a corner square of the
-  // right color in KBN vs K endgames.
-  constexpr int PushToCorners[SQUARE_NB] = {
-     6400, 6080, 5760, 5440, 5120, 4800, 4480, 4160,
-     6080, 5760, 5440, 5120, 4800, 4480, 4160, 4480,
-     5760, 5440, 4960, 4480, 4480, 4000, 4480, 4800,
-     5440, 5120, 4480, 3840, 3520, 4480, 4800, 5120,
-     5120, 4800, 4480, 3520, 3840, 4480, 5120, 5440,
-     4800, 4480, 4000, 4480, 4480, 4960, 5440, 5760,
-     4480, 4160, 4480, 4800, 5120, 5440, 5760, 6080,
-     4160, 4480, 4800, 5120, 5440, 5760, 6080, 6400
-  };
-
-  // Tables used to drive a piece towards or away from another piece
-  constexpr int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 };
-  constexpr int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 };
-
-  // Pawn Rank based scaling factors used in KRPPKRP endgame
-  constexpr int KRPPKRPScaleFactors[RANK_NB] = { 0, 9, 10, 14, 21, 44, 0, 0 };
-
-#ifndef NDEBUG
-  bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
-    return pos.non_pawn_material(c) == npm && pos.count<PAWN>(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<PAWN>(strongSide) == 1);
-
-    if (file_of(pos.square<PAWN>(strongSide)) >= FILE_E)
-        sq = Square(int(sq) ^ 7); // Mirror SQ_H1 -> SQ_A1
-
-    return strongSide == WHITE ? sq : ~sq;
-  }
-
-} // namespace
-
-
-namespace Endgames {
-
-  std::pair<Map<Value>, Map<ScaleFactor>> maps;
-
-  void init() {
-
-    add<KPK>("KPK");
-    add<KNNK>("KNNK");
-    add<KBNK>("KBNK");
-    add<KRKP>("KRKP");
-    add<KRKB>("KRKB");
-    add<KRKN>("KRKN");
-    add<KQKP>("KQKP");
-    add<KQKR>("KQKR");
-    add<KNNKP>("KNNKP");
-
-    add<KNPK>("KNPK");
-    add<KNPKB>("KNPKB");
-    add<KRPKR>("KRPKR");
-    add<KRPKB>("KRPKB");
-    add<KBPKB>("KBPKB");
-    add<KBPKN>("KBPKN");
-    add<KBPPKB>("KBPPKB");
-    add<KRPPKRP>("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<KXK>::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<LEGAL>(pos).size())
-      return VALUE_DRAW;
-
-  Square winnerKSq = pos.square<KING>(strongSide);
-  Square loserKSq = pos.square<KING>(weakSide);
-
-  Value result =  pos.non_pawn_material(strongSide)
-                + pos.count<PAWN>(strongSide) * PawnValueEg
-                + PushToEdges[loserKSq]
-                + PushClose[distance(winnerKSq, loserKSq)];
-
-  if (   pos.count<QUEEN>(strongSide)
-      || pos.count<ROOK>(strongSide)
-      ||(pos.count<BISHOP>(strongSide) && pos.count<KNIGHT>(strongSide))
-      || (   (pos.pieces(strongSide, BISHOP) & ~DarkSquares)
-          && (pos.pieces(strongSide, BISHOP) &  DarkSquares)))
-      result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_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<KBNK>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
-  assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
-
-  Square winnerKSq = pos.square<KING>(strongSide);
-  Square loserKSq = pos.square<KING>(weakSide);
-  Square bishopSq = pos.square<BISHOP>(strongSide);
-
-  // 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
-                + PushClose[distance(winnerKSq, loserKSq)]
-                + PushToCorners[opposite_colors(bishopSq, SQ_A1) ? ~loserKSq : loserKSq];
-
-  assert(abs(result) < VALUE_MATE_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<KPK>::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 wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
-  Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
-  Square psq  = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
-
-  Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
-
-  if (!Bitbases::probe(wksq, psq, bksq, us))
-      return VALUE_DRAW;
-
-  Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq));
-
-  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<KRKP>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, RookValueMg, 0));
-  assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
-  Square wksq = relative_square(strongSide, pos.square<KING>(strongSide));
-  Square bksq = relative_square(strongSide, pos.square<KING>(weakSide));
-  Square rsq  = relative_square(strongSide, pos.square<ROOK>(strongSide));
-  Square psq  = relative_square(strongSide, pos.square<PAWN>(weakSide));
-
-  Square queeningSq = make_square(file_of(psq), RANK_1);
-  Value result;
-
-  // If the stronger side's king is in front of the pawn, it's a win
-  if (forward_file_bb(WHITE, wksq) & psq)
-      result = RookValueEg - distance(wksq, psq);
-
-  // If the weaker side's king is too far from the pawn and the rook,
-  // it's a win.
-  else if (   distance(bksq, psq) >= 3 + (pos.side_to_move() == weakSide)
-           && distance(bksq, rsq) >= 3)
-      result = RookValueEg - distance(wksq, psq);
-
-  // If the pawn is far advanced and supported by the defending king,
-  // the position is drawish
-  else if (   rank_of(bksq) <= RANK_3
-           && distance(bksq, psq) == 1
-           && rank_of(wksq) >= RANK_4
-           && distance(wksq, psq) > 2 + (pos.side_to_move() == strongSide))
-      result = Value(80) - 8 * distance(wksq, psq);
-
-  else
-      result =  Value(200) - 8 * (  distance(wksq, psq + SOUTH)
-                                  - distance(bksq, psq + SOUTH)
-                                  - distance(psq, queeningSq));
-
-  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<KRKB>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, RookValueMg, 0));
-  assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
-  Value result = Value(PushToEdges[pos.square<KING>(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<KRKN>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, RookValueMg, 0));
-  assert(verify_material(pos, weakSide, KnightValueMg, 0));
-
-  Square bksq = pos.square<KING>(weakSide);
-  Square bnsq = pos.square<KNIGHT>(weakSide);
-  Value result = Value(PushToEdges[bksq] + PushAway[distance(bksq, bnsq)]);
-  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<KQKP>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, QueenValueMg, 0));
-  assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
-  Square winnerKSq = pos.square<KING>(strongSide);
-  Square loserKSq = pos.square<KING>(weakSide);
-  Square pawnSq = pos.square<PAWN>(weakSide);
-
-  Value result = Value(PushClose[distance(winnerKSq, loserKSq)]);
-
-  if (   relative_rank(weakSide, pawnSq) != RANK_7
-      || distance(loserKSq, pawnSq) != 1
-      || !((FileABB | FileCBB | FileFBB | FileHBB) & pawnSq))
-      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<KQKR>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, QueenValueMg, 0));
-  assert(verify_material(pos, weakSide, RookValueMg, 0));
-
-  Square winnerKSq = pos.square<KING>(strongSide);
-  Square loserKSq = pos.square<KING>(weakSide);
-
-  Value result =  QueenValueEg
-                - RookValueEg
-                + PushToEdges[loserKSq]
-                + PushClose[distance(winnerKSq, loserKSq)];
-
-  return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// KNN vs KP. Simply push the opposing king to the corner
-template<>
-Value Endgame<KNNKP>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
-  assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
-  Value result =  2 * KnightValueEg
-                - PawnValueEg
-                + PushToEdges[pos.square<KING>(weakSide)];
-
-  return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// Some cases of trivial draws
-template<> Value Endgame<KNNK>::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<KBPsK>::operator()(const Position& pos) const {
-
-  assert(pos.non_pawn_material(strongSide) == BishopValueMg);
-  assert(pos.count<PAWN>(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 pawns = pos.pieces(strongSide, PAWN);
-  File pawnsFile = file_of(lsb(pawns));
-
-  // All pawns are on a single rook file?
-  if (    (pawnsFile == FILE_A || pawnsFile == FILE_H)
-      && !(pawns & ~file_bb(pawnsFile)))
-  {
-      Square bishopSq = pos.square<BISHOP>(strongSide);
-      Square queeningSq = relative_square(strongSide, make_square(pawnsFile, RANK_8));
-      Square kingSq = pos.square<KING>(weakSide);
-
-      if (   opposite_colors(queeningSq, bishopSq)
-          && distance(queeningSq, kingSq) <= 1)
-          return SCALE_FACTOR_DRAW;
-  }
-
-  // If all the pawns are on the same B or G file, then it's potentially a draw
-  if (    (pawnsFile == FILE_B || pawnsFile == FILE_G)
-      && !(pos.pieces(PAWN) & ~file_bb(pawnsFile))
-      && pos.non_pawn_material(weakSide) == 0
-      && pos.count<PAWN>(weakSide) >= 1)
-  {
-      // Get weakSide pawn that is closest to the home rank
-      Square weakPawnSq = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
-
-      Square strongKingSq = pos.square<KING>(strongSide);
-      Square weakKingSq = pos.square<KING>(weakSide);
-      Square bishopSq = pos.square<BISHOP>(strongSide);
-
-      // 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, weakPawnSq) == RANK_7
-          && (pos.pieces(strongSide, PAWN) & (weakPawnSq + pawn_push(weakSide)))
-          && (opposite_colors(bishopSq, weakPawnSq) || pos.count<PAWN>(strongSide) == 1))
-      {
-          int strongKingDist = distance(weakPawnSq, strongKingSq);
-          int weakKingDist = distance(weakPawnSq, weakKingSq);
-
-          // 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, weakKingSq) >= 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<KQKRPs>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, QueenValueMg, 0));
-  assert(pos.count<ROOK>(weakSide) == 1);
-  assert(pos.count<PAWN>(weakSide) >= 1);
-
-  Square kingSq = pos.square<KING>(weakSide);
-  Square rsq = pos.square<ROOK>(weakSide);
-
-  if (    relative_rank(weakSide, kingSq) <= RANK_2
-      &&  relative_rank(weakSide, pos.square<KING>(strongSide)) >= RANK_4
-      &&  relative_rank(weakSide, rsq) == RANK_3
-      && (  pos.pieces(weakSide, PAWN)
-          & pos.attacks_from<KING>(kingSq)
-          & pos.attacks_from<PAWN>(rsq, strongSide)))
-          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<KRPKR>::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 wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
-  Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
-  Square wrsq = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
-  Square wpsq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
-  Square brsq = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
-
-  File f = file_of(wpsq);
-  Rank r = rank_of(wpsq);
-  Square queeningSq = make_square(f, 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 (   r <= RANK_5
-      && distance(bksq, queeningSq) <= 1
-      && wksq <= SQ_H5
-      && (rank_of(brsq) == RANK_6 || (r <= RANK_3 && rank_of(wrsq) != 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 (   r == RANK_6
-      && distance(bksq, queeningSq) <= 1
-      && rank_of(wksq) + tempo <= RANK_6
-      && (rank_of(brsq) == RANK_1 || (!tempo && distance<File>(brsq, wpsq) >= 3)))
-      return SCALE_FACTOR_DRAW;
-
-  if (   r >= RANK_6
-      && bksq == queeningSq
-      && rank_of(brsq) == RANK_1
-      && (!tempo || distance(wksq, wpsq) >= 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 (   wpsq == SQ_A7
-      && wrsq == SQ_A8
-      && (bksq == SQ_H7 || bksq == SQ_G7)
-      && file_of(brsq) == FILE_A
-      && (rank_of(brsq) <= RANK_3 || file_of(wksq) >= FILE_D || rank_of(wksq) <= 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 (   r <= RANK_5
-      && bksq == wpsq + NORTH
-      && distance(wksq, wpsq) - tempo >= 2
-      && distance(wksq, brsq) - 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 (   r == RANK_7
-      && f != FILE_A
-      && file_of(wrsq) == f
-      && wrsq != queeningSq
-      && (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo)
-      && (distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo))
-      return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(wksq, queeningSq));
-
-  // Similar to the above, but with the pawn further back
-  if (   f != FILE_A
-      && file_of(wrsq) == f
-      && wrsq < wpsq
-      && (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo)
-      && (distance(wksq, wpsq + NORTH) < distance(bksq, wpsq + NORTH) - 2 + tempo)
-      && (  distance(bksq, wrsq) + tempo >= 3
-          || (    distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo
-              && (distance(wksq, wpsq + NORTH) < distance(bksq, wrsq) + tempo))))
-      return ScaleFactor(  SCALE_FACTOR_MAX
-                         - 8 * distance(wpsq, queeningSq)
-                         - 2 * distance(wksq, queeningSq));
-
-  // If the pawn is not far advanced and the defending king is somewhere in
-  // the pawn's path, it's probably a draw.
-  if (r <= RANK_4 && bksq > wpsq)
-  {
-      if (file_of(bksq) == file_of(wpsq))
-          return ScaleFactor(10);
-      if (   distance<File>(bksq, wpsq) == 1
-          && distance(wksq, bksq) > 2)
-          return ScaleFactor(24 - 2 * distance(wksq, bksq));
-  }
-  return SCALE_FACTOR_NONE;
-}
-
-template<>
-ScaleFactor Endgame<KRPKB>::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 ksq = pos.square<KING>(weakSide);
-      Square bsq = pos.square<BISHOP>(weakSide);
-      Square psq = pos.square<PAWN>(strongSide);
-      Rank rk = relative_rank(strongSide, psq);
-      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 (rk == RANK_5 && !opposite_colors(bsq, psq))
-      {
-          int d = distance(psq + 3 * push, ksq);
-
-          if (d <= 2 && !(d == 0 && ksq == pos.square<KING>(strongSide) + 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 (   rk == RANK_6
-          && distance(psq + 2 * push, ksq) <= 1
-          && (PseudoAttacks[BISHOP][bsq] & (psq + push))
-          && distance<File>(bsq, psq) >= 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<KRPPKRP>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, RookValueMg, 2));
-  assert(verify_material(pos, weakSide,   RookValueMg, 1));
-
-  Square wpsq1 = pos.squares<PAWN>(strongSide)[0];
-  Square wpsq2 = pos.squares<PAWN>(strongSide)[1];
-  Square bksq = pos.square<KING>(weakSide);
-
-  // Does the stronger side have a passed pawn?
-  if (pos.pawn_passed(strongSide, wpsq1) || pos.pawn_passed(strongSide, wpsq2))
-      return SCALE_FACTOR_NONE;
-
-  Rank r = std::max(relative_rank(strongSide, wpsq1), relative_rank(strongSide, wpsq2));
-
-  if (   distance<File>(bksq, wpsq1) <= 1
-      && distance<File>(bksq, wpsq2) <= 1
-      && relative_rank(strongSide, bksq) > r)
-  {
-      assert(r > RANK_1 && r < RANK_7);
-      return ScaleFactor(KRPPKRPScaleFactors[r]);
-  }
-  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<KPsK>::operator()(const Position& pos) const {
-
-  assert(pos.non_pawn_material(strongSide) == VALUE_ZERO);
-  assert(pos.count<PAWN>(strongSide) >= 2);
-  assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
-
-  Square ksq = pos.square<KING>(weakSide);
-  Bitboard pawns = pos.pieces(strongSide, PAWN);
-
-  // If all pawns are ahead of the king, on a single rook file and
-  // the king is within one file of the pawns, it's a draw.
-  if (   !(pawns & ~forward_ranks_bb(weakSide, ksq))
-      && !((pawns & ~FileABB) && (pawns & ~FileHBB))
-      &&  distance<File>(ksq, lsb(pawns)) <= 1)
-      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<KBPKB>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, BishopValueMg, 1));
-  assert(verify_material(pos, weakSide,   BishopValueMg, 0));
-
-  Square pawnSq = pos.square<PAWN>(strongSide);
-  Square strongBishopSq = pos.square<BISHOP>(strongSide);
-  Square weakBishopSq = pos.square<BISHOP>(weakSide);
-  Square weakKingSq = pos.square<KING>(weakSide);
-
-  // Case 1: Defending king blocks the pawn, and cannot be driven away
-  if (   file_of(weakKingSq) == file_of(pawnSq)
-      && relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq)
-      && (   opposite_colors(weakKingSq, strongBishopSq)
-          || relative_rank(strongSide, weakKingSq) <= RANK_6))
-      return SCALE_FACTOR_DRAW;
-
-  // Case 2: Opposite colored bishops
-  if (opposite_colors(strongBishopSq, weakBishopSq))
-      return SCALE_FACTOR_DRAW;
-
-  return SCALE_FACTOR_NONE;
-}
-
-
-/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops
-template<>
-ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, BishopValueMg, 2));
-  assert(verify_material(pos, weakSide,   BishopValueMg, 0));
-
-  Square wbsq = pos.square<BISHOP>(strongSide);
-  Square bbsq = pos.square<BISHOP>(weakSide);
-
-  if (!opposite_colors(wbsq, bbsq))
-      return SCALE_FACTOR_NONE;
-
-  Square ksq = pos.square<KING>(weakSide);
-  Square psq1 = pos.squares<PAWN>(strongSide)[0];
-  Square psq2 = pos.squares<PAWN>(strongSide)[1];
-  Square blockSq1, blockSq2;
-
-  if (relative_rank(strongSide, psq1) > relative_rank(strongSide, psq2))
-  {
-      blockSq1 = psq1 + pawn_push(strongSide);
-      blockSq2 = make_square(file_of(psq2), rank_of(psq1));
-  }
-  else
-  {
-      blockSq1 = psq2 + pawn_push(strongSide);
-      blockSq2 = make_square(file_of(psq1), rank_of(psq2));
-  }
-
-  switch (distance<File>(psq1, psq2))
-  {
-  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(ksq) == file_of(blockSq1)
-        && relative_rank(strongSide, ksq) >= relative_rank(strongSide, blockSq1)
-        && opposite_colors(ksq, wbsq))
-        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 (   ksq == blockSq1
-        && opposite_colors(ksq, wbsq)
-        && (   bbsq == blockSq2
-            || (pos.attacks_from<BISHOP>(blockSq2) & pos.pieces(weakSide, BISHOP))
-            || distance<Rank>(psq1, psq2) >= 2))
-        return SCALE_FACTOR_DRAW;
-
-    else if (   ksq == blockSq2
-             && opposite_colors(ksq, wbsq)
-             && (   bbsq == blockSq1
-                 || (pos.attacks_from<BISHOP>(blockSq1) & 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<KBPKN>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, BishopValueMg, 1));
-  assert(verify_material(pos, weakSide, KnightValueMg, 0));
-
-  Square pawnSq = pos.square<PAWN>(strongSide);
-  Square strongBishopSq = pos.square<BISHOP>(strongSide);
-  Square weakKingSq = pos.square<KING>(weakSide);
-
-  if (   file_of(weakKingSq) == file_of(pawnSq)
-      && relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq)
-      && (   opposite_colors(weakKingSq, strongBishopSq)
-          || relative_rank(strongSide, weakKingSq) <= RANK_6))
-      return SCALE_FACTOR_DRAW;
-
-  return SCALE_FACTOR_NONE;
-}
-
-
-/// KNP vs K. There is a single rule: if the pawn is a rook pawn on the 7th rank
-/// and the defending king prevents the pawn from advancing, the position is drawn.
-template<>
-ScaleFactor Endgame<KNPK>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, KnightValueMg, 1));
-  assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
-
-  // Assume strongSide is white and the pawn is on files A-D
-  Square pawnSq     = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
-  Square weakKingSq = normalize(pos, strongSide, pos.square<KING>(weakSide));
-
-  if (pawnSq == SQ_A7 && distance(SQ_A8, weakKingSq) <= 1)
-      return SCALE_FACTOR_DRAW;
-
-  return SCALE_FACTOR_NONE;
-}
-
-
-/// KNP vs KB. If knight can block bishop from taking pawn, it's a win.
-/// Otherwise the position is drawn.
-template<>
-ScaleFactor Endgame<KNPKB>::operator()(const Position& pos) const {
-
-  assert(verify_material(pos, strongSide, KnightValueMg, 1));
-  assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
-  Square pawnSq = pos.square<PAWN>(strongSide);
-  Square bishopSq = pos.square<BISHOP>(weakSide);
-  Square weakKingSq = pos.square<KING>(weakSide);
-
-  // King needs to get close to promoting pawn to prevent knight from blocking.
-  // Rules for this are very tricky, so just approximate.
-  if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from<BISHOP>(bishopSq))
-      return ScaleFactor(distance(weakKingSq, pawnSq));
-
-  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<KPKP>::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 wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
-  Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
-  Square psq  = normalize(pos, strongSide, pos.square<PAWN>(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(psq) >= RANK_5 && file_of(psq) != 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(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
-}
diff --git a/src/endgame.h b/src/endgame.h
deleted file mode 100644 (file)
index 4642e44..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
-  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
-  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 <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef ENDGAME_H_INCLUDED
-#define ENDGAME_H_INCLUDED
-
-#include <unordered_map>
-#include <memory>
-#include <string>
-#include <type_traits>
-#include <utility>
-
-#include "position.h"
-#include "types.h"
-
-
-/// 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
-  KNPK,    // KNP vs K
-  KNPKB,   // KNP vs KB
-  KPKP     // KP vs KP
-};
-
-
-/// Endgame functions can be of two types depending on whether they return a
-/// Value or a ScaleFactor.
-
-template<EndgameCode E> using
-eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type;
-
-
-/// Base and derived functors for endgame evaluation and scaling functions
-
-template<typename T>
-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<EndgameCode E, typename T = eg_type<E>>
-struct Endgame : public EndgameBase<T> {
-
-  explicit Endgame(Color c) : EndgameBase<T>(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<typename T> using Ptr = std::unique_ptr<EndgameBase<T>>;
-  template<typename T> using Map = std::unordered_map<Key, Ptr<T>>;
-
-  extern std::pair<Map<Value>, Map<ScaleFactor>> maps;
-
-  void init();
-
-  template<typename T>
-  Map<T>& map() {
-    return std::get<std::is_same<T, ScaleFactor>::value>(maps);
-  }
-
-  template<EndgameCode E, typename T = eg_type<E>>
-  void add(const std::string& code) {
-
-    StateInfo st;
-    map<T>()[Position().set(code, WHITE, &st).material_key()] = Ptr<T>(new Endgame<E>(WHITE));
-    map<T>()[Position().set(code, BLACK, &st).material_key()] = Ptr<T>(new Endgame<E>(BLACK));
-  }
-
-  template<typename T>
-  const EndgameBase<T>* probe(Key key) {
-    auto it = map<T>().find(key);
-    return it != map<T>().end() ? it->second.get() : nullptr;
-  }
-}
-
-#endif // #ifndef ENDGAME_H_INCLUDED
index 7c7ce95cebcf27d72960c0d34d45857e3d10f052..586cadc0ec50c6cb5fe211aaecd7ad1efa755afc 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "evaluate.h"
+
 #include <algorithm>
 #include <cassert>
-#include <cstring>   // For std::memset
+#include <cmath>
+#include <cstdlib>
+#include <fstream>
 #include <iomanip>
+#include <iostream>
 #include <sstream>
+#include <vector>
 
-#include "bitboard.h"
-#include "evaluate.h"
-#include "material.h"
-#include "pawns.h"
+#include "incbin/incbin.h"
+#include "misc.h"
+#include "nnue/evaluate_nnue.h"
+#include "position.h"
 #include "thread.h"
-
-namespace Trace {
-
-  enum Tracing { NO_TRACE, TRACE };
-
-  enum Term { // The first 8 entries are reserved for PieceType
-    MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, INITIATIVE, TOTAL, TERM_NB
-  };
-
-  Score scores[TERM_NB][COLOR_NB];
-
-  double to_cp(Value v) { return double(v) / PawnValueEg; }
-
-  void add(int idx, Color c, Score s) {
-    scores[idx][c] = s;
-  }
-
-  void add(int idx, Score w, Score b = SCORE_ZERO) {
-    scores[idx][WHITE] = w;
-    scores[idx][BLACK] = b;
-  }
-
-  std::ostream& operator<<(std::ostream& os, Score s) {
-    os << std::setw(5) << to_cp(mg_value(s)) << " "
-       << std::setw(5) << to_cp(eg_value(s));
-    return os;
-  }
-
-  std::ostream& operator<<(std::ostream& os, Term t) {
-
-    if (t == MATERIAL || t == IMBALANCE || t == INITIATIVE || 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 LazyThreshold  = Value(1400);
-  constexpr Value SpaceThreshold = Value(12222);
-
-  // KingAttackWeights[PieceType] contains king attack weights by piece type
-  constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
-
-  // Penalties for enemy's safe checks
-  constexpr int QueenSafeCheck  = 780;
-  constexpr int RookSafeCheck   = 1080;
-  constexpr int BishopSafeCheck = 635;
-  constexpr int KnightSafeCheck = 790;
-
-#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,-81), S(-53,-56), S(-12,-30), S( -4,-14), S(  3,  8), S( 13, 15), // Knights
-      S( 22, 23), S( 28, 27), S( 33, 33) },
-    { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishops
-      S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86),
-      S( 91, 88), S( 98, 97) },
-    { S(-58,-76), S(-27,-18), S(-15, 28), S(-10, 55), S( -5, 69), S( -2, 82), // Rooks
-      S(  9,112), S( 16,118), S( 30,132), S( 29,142), S( 32,155), S( 38,165),
-      S( 46,166), S( 48,169), S( 58,171) },
-    { S(-39,-36), S(-21,-15), S(  3,  8), S(  3, 18), S( 14, 34), S( 22, 54), // Queens
-      S( 28, 61), S( 41, 73), S( 43, 79), S( 48, 92), S( 56, 94), S( 60,104),
-      S( 60,113), S( 66,120), S( 67,123), S( 70,126), S( 71,133), S( 73,136),
-      S( 79,140), S( 88,143), S( 88,148), S( 99,166), S(102,170), S(102,175),
-      S(106,184), S(109,191), S(113,206), S(116,212) }
-  };
-
-  // RookOnFile[semiopen/open] contains bonuses for each rook when there is
-  // no (friendly) pawn on the rook file.
-  constexpr Score RookOnFile[] = { S(21, 4), S(47, 25) };
-
-  // 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, 32), S(59, 41), S(79, 56), S(90, 119), S(79, 161)
-  };
-
-  constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
-    S(0, 0), S(3, 44), S(38, 71), S(38, 61), S(0, 38), S(51, 38)
-  };
-
-  // PassedRank[Rank] contains a bonus according to the rank of a passed pawn
-  constexpr Score PassedRank[RANK_NB] = {
-    S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260)
-  };
-
-  // Assorted bonuses and penalties
-  constexpr Score BishopPawns        = S(  3,  7);
-  constexpr Score CorneredBishop     = S( 50, 50);
-  constexpr Score FlankAttacks       = S(  8,  0);
-  constexpr Score Hanging            = S( 69, 36);
-  constexpr Score KingProtector      = S(  7,  8);
-  constexpr Score KnightOnQueen      = S( 16, 12);
-  constexpr Score LongDiagonalBishop = S( 45,  0);
-  constexpr Score MinorBehindPawn    = S( 18,  3);
-  constexpr Score Outpost            = S( 30, 21);
-  constexpr Score PassedFile         = S( 11,  8);
-  constexpr Score PawnlessFlank      = S( 17, 95);
-  constexpr Score RestrictedPiece    = S(  7,  7);
-  constexpr Score ReachableOutpost   = S( 32, 10);
-  constexpr Score RookOnQueenFile    = S(  7,  6);
-  constexpr Score SliderOnQueen      = S( 59, 18);
-  constexpr Score ThreatByKing       = S( 24, 89);
-  constexpr Score ThreatByPawnPush   = S( 48, 39);
-  constexpr Score ThreatBySafePawn   = S(173, 94);
-  constexpr Score TrappedRook        = S( 52, 10);
-  constexpr Score WeakQueen          = S( 49, 15);
-
-#undef S
-
-  // Evaluation class computes and stores attacks tables and other working data
-  template<Tracing T>
-  class Evaluation {
-
-  public:
-    Evaluation() = delete;
-    explicit Evaluation(const Position& p) : pos(p) {}
-    Evaluation& operator=(const Evaluation&) = delete;
-    Value value();
-
-  private:
-    template<Color Us> void initialize();
-    template<Color Us, PieceType Pt> Score pieces();
-    template<Color Us> Score king() const;
-    template<Color Us> Score threats() const;
-    template<Color Us> Score passed() const;
-    template<Color Us> Score space() const;
-    ScaleFactor scale_factor(Value eg) const;
-    Score initiative(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<Tracing T> template<Color Us>
-  void Evaluation<T>::initialize() {
-
-    constexpr Color     Them = (Us == WHITE ? BLACK : WHITE);
-    constexpr Direction Up   = pawn_push(Us);
-    constexpr Direction Down = -Up;
-    constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB);
-
-    const Square ksq = pos.square<KING>(Us);
-
-    Bitboard dblAttackByPawn = pawn_double_attacks_bb<Us>(pos.pieces(Us, PAWN));
-
-    // Find our pawns that are blocked or on the first two ranks
-    Bitboard b = pos.pieces(Us, PAWN) & (shift<Down>(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] = pos.attacks_from<KING>(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(clamp(file_of(ksq), FILE_B, FILE_G),
-                           clamp(rank_of(ksq), RANK_2, RANK_7));
-    kingRing[Us] = PseudoAttacks[KING][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<Tracing T> template<Color Us, PieceType Pt>
-  Score Evaluation<T>::pieces() {
-
-    constexpr Color     Them = (Us == WHITE ? BLACK : WHITE);
-    constexpr Direction Down = -pawn_push(Us);
-    constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
-                                                   : Rank5BB | Rank4BB | Rank3BB);
-    const Square* pl = pos.squares<Pt>(Us);
-
-    Bitboard b, bb;
-    Score score = SCORE_ZERO;
-
-    attackedBy[Us][Pt] = 0;
-
-    for (Square s = *pl; s != SQ_NONE; s = *++pl)
-    {
-        // Find attacked squares, including x-ray attacks for bishops and rooks
-        b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
-          : Pt ==   ROOK ? attacks_bb<  ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
-                         : pos.attacks_from<Pt>(s);
-
-        if (pos.blockers_for_king(Us) & s)
-            b &= LineBB[pos.square<KING>(Us)][s];
-
-        attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b;
-        attackedBy[Us][Pt] |= b;
-        attackedBy[Us][ALL_PIECES] |= b;
-
-        if (b & kingRing[Them])
+#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).
+// 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(EmbeddedNNUE, EvalFileDefaultName);
+#else
+const unsigned char        gEmbeddedNNUEData[1] = {0x0};
+const unsigned char* const gEmbeddedNNUEEnd     = &gEmbeddedNNUEData[1];
+const unsigned int         gEmbeddedNNUESize    = 1;
+#endif
+
+
+namespace Stockfish {
+
+namespace Eval {
+
+std::string currentEvalFileName = "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"
+// 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"]);
+    if (eval_file.empty())
+        eval_file = EvalFileDefaultName;
+
+#if defined(DEFAULT_NNUE_DIRECTORY)
+    std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory,
+                                     stringify(DEFAULT_NNUE_DIRECTORY)};
+#else
+    std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory};
+#endif
+
+    for (const std::string& directory : dirs)
+        if (currentEvalFileName != eval_file)
         {
-            kingAttackersCount[Us]++;
-            kingAttackersWeight[Us] += KingAttackWeights[Pt];
-            kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]);
-        }
-
-        int mob = popcount(b & mobilityArea[Us]);
-
-        mobility[Us] += MobilityBonus[Pt - 2][mob];
-
-        if (Pt == BISHOP || Pt == KNIGHT)
-        {
-            // Bonus if piece is on an outpost square or can reach one
-            bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them);
-            if (bb & s)
-                score += Outpost * (Pt == KNIGHT ? 2 : 1);
-
-            else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
-                score += ReachableOutpost;
-
-            // Knight and Bishop bonus for being right behind a pawn
-            if (shift<Down>(pos.pieces(PAWN)) & s)
-                score += MinorBehindPawn;
-
-            // Penalty if the piece is far from the king
-            score -= KingProtector * distance(s, pos.square<KING>(Us));
-
-            if (Pt == BISHOP)
+            if (directory != "<internal>")
             {
-                // Penalty according to number of pawns on the same color square as the
-                // bishop, bigger when the center files are blocked with pawns.
-                Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
-
-                score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s)
-                                     * (1 + popcount(blocked & CenterFiles));
-
-                // Bonus for bishop on a long diagonal which can "see" both center squares
-                if (more_than_one(attacks_bb<BISHOP>(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))                ? CorneredBishop * 4
-                                : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2
-                                                                                  : CorneredBishop;
-                }
+                std::ifstream stream(directory + eval_file, std::ios::binary);
+                if (NNUE::load_eval(eval_file, stream))
+                    currentEvalFileName = eval_file;
             }
-        }
 
-        if (Pt == ROOK)
-        {
-            // Bonus for rook on the same file as a queen
-            if (file_bb(s) & pos.pieces(QUEEN))
-                score += RookOnQueenFile;
-
-            // Bonus for rook on an open or semi-open file
-            if (pos.is_on_semiopen_file(Us, s))
-                score += RookOnFile[pos.is_on_semiopen_file(Them, s)];
-
-            // Penalty when trapped by the king, even more if the king cannot castle
-            else if (mob <= 3)
+            if (directory == "<internal>" && eval_file == EvalFileDefaultName)
             {
-                File kf = file_of(pos.square<KING>(Us));
-                if ((kf < FILE_E) == (file_of(s) < kf))
-                    score -= TrappedRook * (1 + !pos.castling_rights(Us));
+                // C++ way to prepare a buffer for a memory stream
+                class MemoryBuffer: public std::basic_streambuf<char> {
+                   public:
+                    MemoryBuffer(char* p, size_t n) {
+                        setg(p, p, p + n);
+                        setp(p, p + n);
+                    }
+                };
+
+                MemoryBuffer buffer(
+                  const_cast<char*>(reinterpret_cast<const char*>(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;
             }
         }
+}
 
-        if (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 (T)
-        Trace::add(Pt, Us, score);
-
-    return score;
-  }
-
-
-  // Evaluation::king() assigns bonuses and penalties to a king of a given color
-  template<Tracing T> template<Color Us>
-  Score Evaluation<T>::king() const {
-
-    constexpr Color    Them = (Us == WHITE ? BLACK : WHITE);
-    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<KING>(Us);
-
-    // Init the score with king shelter and enemy pawns storm
-    Score score = pe->king_safety<Us>(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<ROOK  >(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
-    b2 = attacks_bb<BISHOP>(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
-
-    // Enemy rooks checks
-    rookChecks = b1 & safe & attackedBy[Them][ROOK];
-
-    if (rookChecks)
-        kingDanger += RookSafeCheck;
-    else
-        unsafeChecks |= b1 & attackedBy[Them][ROOK];
-
-    // Enemy queen safe checks: we count them only if they are from squares from
-    // which we can't give a rook check, because rook checks are more valuable.
-    queenChecks =  (b1 | b2)
-                 & attackedBy[Them][QUEEN]
-                 & safe
-                 & ~attackedBy[Us][QUEEN]
-                 & ~rookChecks;
-
-    if (queenChecks)
-        kingDanger += QueenSafeCheck;
-
-    // Enemy bishops checks: we count them only if they are from squares from
-    // which we can't give a queen check, because queen checks are more valuable.
-    bishopChecks =  b2
-                  & attackedBy[Them][BISHOP]
-                  & safe
-                  & ~queenChecks;
-
-    if (bishopChecks)
-        kingDanger += BishopSafeCheck;
-    else
-        unsafeChecks |= b2 & attackedBy[Them][BISHOP];
-
-    // Enemy knights checks
-    knightChecks = pos.attacks_from<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
-
-    if (knightChecks & safe)
-        kingDanger += KnightSafeCheck;
-    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]
-                 + 185 * popcount(kingRing[Us] & weak)
-                 + 148 * popcount(unsafeChecks)
-                 +  98 * popcount(pos.blockers_for_king(Us))
-                 +  69 * kingAttacksCount[Them]
-                 +   3 * kingFlankAttack * kingFlankAttack / 8
-                 +       mg_value(mobility[Them] - mobility[Us])
-                 - 873 * !pos.count<QUEEN>(Them)
-                 - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING])
-                 -   6 * mg_value(score) / 8
-                 -   4 * kingFlankDefense
-                 +  37;
-
-    // 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 (T)
-        Trace::add(KING, Us, score);
-
-    return score;
-  }
-
-
-  // Evaluation::threats() assigns bonuses according to the types of the
-  // attacking and the attacked pieces.
-  template<Tracing T> template<Color Us>
-  Score Evaluation<T>::threats() const {
-
-    constexpr Color     Them     = (Us == WHITE ? BLACK   : WHITE);
-    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);
-    }
-
-    // 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<Us>(b) & nonPawnEnemies;
-    score += ThreatBySafePawn * popcount(b);
-
-    // Find squares where our pawns can push on the next move
-    b  = shift<Up>(pos.pieces(Us, PAWN)) & ~pos.pieces();
-    b |= shift<Up>(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<Us>(b) & nonPawnEnemies;
-    score += ThreatByPawnPush * popcount(b);
-
-    // Bonus for threats on the next moves against enemy queen
-    if (pos.count<QUEEN>(Them) == 1)
-    {
-        Square s = pos.square<QUEEN>(Them);
-        safe = mobilityArea[Us] & ~stronglyProtected;
-
-        b = attackedBy[Us][KNIGHT] & pos.attacks_from<KNIGHT>(s);
-
-        score += KnightOnQueen * popcount(b & safe);
-
-        b =  (attackedBy[Us][BISHOP] & pos.attacks_from<BISHOP>(s))
-           | (attackedBy[Us][ROOK  ] & pos.attacks_from<ROOK  >(s));
-
-        score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]);
-    }
-
-    if (T)
-        Trace::add(THREAT, Us, score);
-
-    return score;
-  }
-
-  // Evaluation::passed() evaluates the passed pawns and candidate passed
-  // pawns of the given color.
-
-  template<Tracing T> template<Color Us>
-  Score Evaluation<T>::passed() const {
-
-    constexpr Color     Them = (Us == WHITE ? BLACK : WHITE);
-    constexpr Direction Up   = pawn_push(Us);
-
-    auto king_proximity = [&](Color c, Square s) {
-      return std::min(distance(pos.square<KING>(c), s), 5);
-    };
-
-    Bitboard b, bb, squaresToQueen, unsafeSquares;
-    Score score = SCORE_ZERO;
+// Verifies that the last net used was loaded successfully
+void NNUE::verify() {
 
-    b = pe->passed_pawns(Us);
+    std::string eval_file = std::string(Options["EvalFile"]);
+    if (eval_file.empty())
+        eval_file = EvalFileDefaultName;
 
-    while (b)
+    if (currentEvalFileName != eval_file)
     {
-        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];
-
-                // If there are no enemy attacks on passed pawn span, assign a big 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                    ? 35 :
-                        !(unsafeSquares & squaresToQueen) ? 20 :
-                        !(unsafeSquares & blockSq)        ?  9 :
-                                                             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
-
-        // Scale down bonus for candidate passers which need more than one
-        // pawn push to become passed, or have a pawn in front of them.
-        if (   !pos.pawn_passed(Us, s + Up)
-            || (pos.pieces(PAWN) & (s + Up)))
-            bonus = bonus / 2;
-
-        score += bonus - PassedFile * map_to_queenside(file_of(s));
+        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;
+        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);
     }
 
-    if (T)
-        Trace::add(PASSED, Us, score);
-
-    return score;
-  }
-
-
-  // Evaluation::space() computes the space evaluation for a given side. The
-  // space evaluation is a simple bonus based on the number of safe squares
-  // available for minor pieces on the central four files on ranks 2--4. Safe
-  // squares one, two or three squares behind a friendly pawn are counted
-  // twice. Finally, the space bonus is multiplied by a weight. The aim is to
-  // improve play on game opening.
-
-  template<Tracing T> template<Color Us>
-  Score Evaluation<T>::space() const {
-
-    if (pos.non_pawn_material() < SpaceThreshold)
-        return SCORE_ZERO;
-
-    constexpr Color Them     = (Us == WHITE ? BLACK : WHITE);
-    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<Down>(behind);
-    behind |= shift<Down+Down>(behind);
-
-    int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
-    int weight = pos.count<ALL_PIECES>(Us) - 1;
-    Score score = make_score(bonus * weight * weight / 16, 0);
-
-    if (T)
-        Trace::add(SPACE, Us, score);
-
-    return score;
-  }
-
-
-  // Evaluation::initiative() computes the initiative correction value
-  // for the position. It is a second order bonus/malus based on the
-  // known attacking/defending status of the players.
-
-  template<Tracing T>
-  Score Evaluation<T>::initiative(Score score) const {
-
-    Value mg = mg_value(score);
-    Value eg = eg_value(score);
-
-    int outflanking =  distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
-                     - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
-
-    bool infiltration =   rank_of(pos.square<KING>(WHITE)) > RANK_4
-                       || rank_of(pos.square<KING>(BLACK)) < RANK_5;
-
-    bool pawnsOnBothFlanks =   (pos.pieces(PAWN) & QueenSide)
-                            && (pos.pieces(PAWN) & KingSide);
-
-    bool almostUnwinnable =   !pe->passed_count()
-                           &&  outflanking < 0
-                           && !pawnsOnBothFlanks;
-
-    // Compute the initiative bonus for the attacking side
-    int complexity =   9 * pe->passed_count()
-                    + 11 * pos.count<PAWN>()
-                    +  9 * outflanking
-                    + 12 * infiltration
-                    + 21 * pawnsOnBothFlanks
-                    + 51 * !pos.non_pawn_material()
-                    - 43 * almostUnwinnable
-                    - 100 ;
-
-    // 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::max(std::min(complexity + 50, 0), -abs(mg));
-    int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
-
-    if (T)
-        Trace::add(INITIATIVE, make_score(u, v));
-
-    return make_score(u, v);
-  }
-
-
-  // Evaluation::scale_factor() computes the scale factor for the winning side
-
-  template<Tracing T>
-  ScaleFactor Evaluation<T>::scale_factor(Value eg) const {
-
-    Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
-    int sf = me->scale_factor(pos, strongSide);
-
-    // If scale is not already specific, scale down the endgame via general heuristics
-    if (sf == SCALE_FACTOR_NORMAL)
-    {
-        if (   pos.opposite_bishops()
-            && pos.non_pawn_material() == 2 * BishopValueMg)
-            sf = 22 ;
-        else
-            sf = std::min(sf, 36 + (pos.opposite_bishops() ? 2 : 7) * pos.count<PAWN>(strongSide));
-
-        sf = std::max(0, sf - (pos.rule50_count() - 12) / 4);
-    }
+    sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl;
+}
+}
 
-    return ScaleFactor(sf);
-  }
 
+// 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<PAWN>(c) - pos.count<PAWN>(~c))
+         + (pos.non_pawn_material(c) - pos.non_pawn_material(~c));
+}
 
-  // 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<Tracing T>
-  Value Evaluation<T>::value() {
+// 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());
 
-    // 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() + pos.this_thread()->contempt;
-
-    // Probe the pawn hash table
-    pe = Pawns::probe(pos);
-    score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK);
-
-    // Early exit if score is high
-    Value v = (mg_value(score) + eg_value(score)) / 2;
-    if (abs(v) > LazyThreshold + pos.non_pawn_material() / 64)
-       return pos.side_to_move() == WHITE ? v : -v;
-
-    // Main evaluation begins here
-
-    initialize<WHITE>();
-    initialize<BLACK>();
-
-    // Pieces should be evaluated first (populate attack tables)
-    score +=  pieces<WHITE, KNIGHT>() - pieces<BLACK, KNIGHT>()
-            + pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>()
-            + pieces<WHITE, ROOK  >() - pieces<BLACK, ROOK  >()
-            + pieces<WHITE, QUEEN >() - pieces<BLACK, QUEEN >();
-
-    score += mobility[WHITE] - mobility[BLACK];
+    Value v;
+    Color stm        = pos.side_to_move();
+    int   shuffling  = pos.rule50_count();
+    int   simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3);
 
-    score +=  king<   WHITE>() - king<   BLACK>()
-            + threats<WHITE>() - threats<BLACK>()
-            + passed< WHITE>() - passed< BLACK>()
-            + space<  WHITE>() - space<  BLACK>();
+    bool lazy = std::abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling
+                                          + std::abs(pos.this_thread()->bestValue)
+                                          + std::abs(pos.this_thread()->rootSimpleEval);
 
-    score += initiative(score);
+    if (lazy)
+        v = Value(simpleEval);
+    else
+    {
+        int   nnueComplexity;
+        Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
 
-    // Interpolate between a middlegame and a (scaled by 'sf') endgame score
-    ScaleFactor sf = scale_factor(eg_value(score));
-    v =  mg_value(score) * int(me->game_phase())
-       + eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
+        Value optimism = pos.this_thread()->optimism[stm];
 
-    v /= PHASE_MIDGAME;
+        // 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;
 
-    // In case of tracing add all remaining individual evaluation terms
-    if (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]);
-        Trace::add(TOTAL, score);
+        int npm = pos.non_pawn_material() / 64;
+        v       = (nnue * (915 + npm + 9 * pos.count<PAWN>()) + optimism * (154 + npm)) / 1024;
     }
 
-    return  (pos.side_to_move() == WHITE ? v : -v) // Side to move point of view
-           + Eval::Tempo;
-  }
-
-} // namespace
-
+    // Damp down the evaluation linearly when shuffling
+    v = v * (200 - shuffling) / 214;
 
-/// 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.
+    // 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);
 
-Value Eval::evaluate(const Position& pos) {
-  return Evaluation<NO_TRACE>(pos).value();
+    return v;
 }
 
+// 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) {
 
-/// 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.
-
-std::string Eval::trace(const Position& pos) {
-
-  if (pos.checkers())
-      return "Total evaluation: none (in check)";
+    if (pos.checkers())
+        return "Final evaluation: none (in check)";
 
-  std::memset(scores, 0, sizeof(scores));
+    // 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;
 
-  pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt
+    std::stringstream ss;
+    ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
+    ss << '\n' << NNUE::trace(pos) << '\n';
 
-  Value v = Evaluation<TRACE>(pos).value();
+    ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
 
-  v = pos.side_to_move() == WHITE ? v : -v; // Trace scores are from white's point of view
+    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";
 
-  std::stringstream ss;
-  ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
-     << "     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)
-     << "  Initiative | " << Term(INITIATIVE)
-     << " ------------+-------------+-------------+------------\n"
-     << "       Total | " << Term(TOTAL);
+    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";
 
-  ss << "\nTotal evaluation: " << to_cp(v) << " (white side)\n";
-
-  return ss.str();
+    return ss.str();
 }
+
+}  // namespace Stockfish
index 077de70c97427b9ec13bf8f4637e7f143c579e75..2ab477eced24d62460ac14531449c56fd9b8439a 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 
 #include "types.h"
 
+namespace Stockfish {
+
 class Position;
 
 namespace Eval {
 
-constexpr Value Tempo = Value(28); // Must be visible to search
-
-std::string trace(const Position& pos);
+std::string trace(Position& pos);
 
+Value simple_eval(const Position& pos, Color c);
 Value evaluate(const Position& pos);
-}
 
-#endif // #ifndef EVALUATE_H_INCLUDED
+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"
+
+namespace NNUE {
+
+void init();
+void verify();
+
+}  // namespace NNUE
+
+}  // namespace Eval
+
+}  // namespace Stockfish
+
+#endif  // #ifndef EVALUATE_H_INCLUDED
diff --git a/src/hashprobe.h b/src/hashprobe.h
new file mode 100644 (file)
index 0000000..42c13ee
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef HASHPROBE_H_INCLUDED
+#define HASHPROBE_H_INCLUDED
+
+#include "position.h"
+#include "types.h"
+
+#include <deque>
+#include <string>
+
+#include <grpc/grpc.h>
+#include <grpc++/server.h>
+#include <grpc++/server_builder.h>
+#include "hashprobe.grpc.pb.h"
+
+class HashProbeImpl final : public hashprobe::HashProbe::Service {
+public:
+       grpc::Status Probe(grpc::ServerContext* context,
+                          const hashprobe::HashProbeRequest* request,
+                          hashprobe::HashProbeResponse *response);
+
+private:
+       void FillMove(Stockfish::Position* pos, Stockfish::Move move, hashprobe::HashProbeMove* decoded);
+       void ProbeMove(Stockfish::Position* pos, std::deque<Stockfish::StateInfo>* setup_states, bool invert, hashprobe::HashProbeLine* response);
+       void FillValue(Stockfish::Value value, hashprobe::HashProbeScore* score);
+};
+
+class HashProbeThread {
+public:
+       HashProbeThread(const std::string &server_address);
+       void Shutdown();
+
+private:
+       HashProbeImpl service;
+       grpc::ServerBuilder builder;
+       std::unique_ptr<grpc::Server> server;
+};
+
+#endif
diff --git a/src/hashprobe.proto b/src/hashprobe.proto
new file mode 100644 (file)
index 0000000..175cd77
--- /dev/null
@@ -0,0 +1,49 @@
+syntax = "proto3";
+package hashprobe;
+
+message HashProbeRequest {
+       string fen = 1;
+}
+message HashProbeResponse {
+       HashProbeLine root = 2;
+       repeated HashProbeLine line = 1;
+}
+message HashProbeLine {
+       HashProbeMove move = 1;
+       bool found = 2;
+
+       repeated HashProbeMove pv = 3;
+       HashProbeScore value = 4;  // Dynamic eval (may be inexact, see the "bound" field)
+       HashProbeScore eval = 5;  // Static eval
+       int32 depth = 6;
+
+       enum ValueBound {
+               BOUND_NONE = 0;
+               BOUND_UPPER = 1;
+               BOUND_LOWER = 2;
+               BOUND_EXACT = 3;
+       };
+       ValueBound bound = 7;
+}
+
+message HashProbeMove {
+       string from_sq = 1;  // a1, a2, etc.
+       string to_sq = 2;
+       string promotion = 3;  // Q, R, etc.
+
+       string pretty = 4;  // e.g. Rxf6+
+}
+message HashProbeScore {
+       enum ScoreType {
+               SCORE_NONE = 0;
+               SCORE_CP = 1;
+               SCORE_MATE = 2;
+       }
+       ScoreType score_type = 1;
+       int32 score_cp = 2;
+       int32 score_mate = 3;
+}
+
+service HashProbe {
+       rpc Probe(HashProbeRequest) returns (HashProbeResponse) {}
+}
diff --git a/src/incbin/UNLICENCE b/src/incbin/UNLICENCE
new file mode 100644 (file)
index 0000000..32484ab
--- /dev/null
@@ -0,0 +1,26 @@
+The file "incbin.h" is free and unencumbered software released into
+the public domain by Dale Weiler, see:
+   <https://github.com/graphitemaster/incbin>
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/src/incbin/incbin.h b/src/incbin/incbin.h
new file mode 100644 (file)
index 0000000..c19684d
--- /dev/null
@@ -0,0 +1,368 @@
+/**
+ * @file incbin.h
+ * @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.
+ */
+#ifndef INCBIN_HDR
+#define INCBIN_HDR
+#include <limits.h>
+#if   defined(__AVX512BW__) || \
+      defined(__AVX512CD__) || \
+      defined(__AVX512DQ__) || \
+      defined(__AVX512ER__) || \
+      defined(__AVX512PF__) || \
+      defined(__AVX512VL__) || \
+      defined(__AVX512F__)
+# define INCBIN_ALIGNMENT_INDEX 6
+#elif defined(__AVX__)      || \
+      defined(__AVX2__)
+# define INCBIN_ALIGNMENT_INDEX 5
+#elif defined(__SSE__)      || \
+      defined(__SSE2__)     || \
+      defined(__SSE3__)     || \
+      defined(__SSSE3__)    || \
+      defined(__SSE4_1__)   || \
+      defined(__SSE4_2__)   || \
+      defined(__neon__)
+# define INCBIN_ALIGNMENT_INDEX 4
+#elif ULONG_MAX != 0xffffffffu
+# define INCBIN_ALIGNMENT_INDEX 3
+# else
+# define INCBIN_ALIGNMENT_INDEX 2
+#endif
+
+/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */
+#define INCBIN_ALIGN_SHIFT_0 1
+#define INCBIN_ALIGN_SHIFT_1 2
+#define INCBIN_ALIGN_SHIFT_2 4
+#define INCBIN_ALIGN_SHIFT_3 8
+#define INCBIN_ALIGN_SHIFT_4 16
+#define INCBIN_ALIGN_SHIFT_5 32
+#define INCBIN_ALIGN_SHIFT_6 64
+
+/* Actual alignment value */
+#define INCBIN_ALIGNMENT \
+    INCBIN_CONCATENATE( \
+        INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \
+        INCBIN_ALIGNMENT_INDEX)
+
+/* Stringize */
+#define INCBIN_STR(X) \
+    #X
+#define INCBIN_STRINGIZE(X) \
+    INCBIN_STR(X)
+/* Concatenate */
+#define INCBIN_CAT(X, Y) \
+    X ## Y
+#define INCBIN_CONCATENATE(X, Y) \
+    INCBIN_CAT(X, Y)
+/* Deferred macro expansion */
+#define INCBIN_EVAL(X) \
+    X
+#define INCBIN_INVOKE(N, ...) \
+    INCBIN_EVAL(N(__VA_ARGS__))
+
+/* Green Hills uses a different directive for including binary data */
+#if defined(__ghs__)
+#  if (__ghs_asm == 2)
+#    define INCBIN_MACRO ".file"
+/* Or consider the ".myrawdata" entry in the ld file */
+#  else
+#    define INCBIN_MACRO "\tINCBIN"
+#  endif
+#else
+#  define INCBIN_MACRO ".incbin"
+#endif
+
+#ifndef _MSC_VER
+#  define INCBIN_ALIGN \
+    __attribute__((aligned(INCBIN_ALIGNMENT)))
+#else
+#  define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT))
+#endif
+
+#if defined(__arm__) || /* GNU C and RealView */ \
+    defined(__arm) || /* Diab */ \
+    defined(_ARM) /* ImageCraft */
+#  define INCBIN_ARM
+#endif
+
+#ifdef __GNUC__
+/* Utilize .balign where supported */
+#  define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
+#  define INCBIN_ALIGN_BYTE ".balign 1\n"
+#elif defined(INCBIN_ARM)
+/*
+ * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is
+ * the shift count. This is the value passed to `.align'
+ */
+#  define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n"
+#  define INCBIN_ALIGN_BYTE ".align 0\n"
+#else
+/* We assume other inline assembler's treat `.align' as `.balign' */
+#  define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
+#  define INCBIN_ALIGN_BYTE ".align 1\n"
+#endif
+
+/* INCBIN_CONST is used by incbin.c generated files */
+#if defined(__cplusplus)
+#  define INCBIN_EXTERNAL extern "C"
+#  define INCBIN_CONST    extern const
+#else
+#  define INCBIN_EXTERNAL extern
+#  define INCBIN_CONST    const
+#endif
+
+/**
+ * @brief Optionally override the linker section into which data is emitted.
+ *
+ * @warning If you use this facility, you'll have to deal with platform-specific linker output
+ * section naming on your own
+ *
+ * Overriding the default linker output section, e.g for esp8266/Arduino:
+ * @code
+ * #define INCBIN_OUTPUT_SECTION ".irom.text"
+ * #include "incbin.h"
+ * INCBIN(Foo, "foo.txt");
+ * // Data is emitted into program memory that never gets copied to RAM
+ * @endcode
+ */
+#if !defined(INCBIN_OUTPUT_SECTION)
+#  if defined(__APPLE__)
+#    define INCBIN_OUTPUT_SECTION         ".const_data"
+#  else
+#    define INCBIN_OUTPUT_SECTION         ".rodata"
+#  endif
+#endif
+
+#if defined(__APPLE__)
+/* 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 "
+#  define INCBIN_MANGLE          "_"
+#  define INCBIN_BYTE            ".byte "
+#  define INCBIN_TYPE(...)
+#else
+#  define INCBIN_SECTION         ".section " INCBIN_OUTPUT_SECTION "\n"
+#  define INCBIN_GLOBAL(NAME)    ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
+#  if defined(__ghs__)
+#    define INCBIN_INT           ".word "
+#  else
+#    define INCBIN_INT           ".int "
+#  endif
+#  if defined(__USER_LABEL_PREFIX__)
+#    define INCBIN_MANGLE        INCBIN_STRINGIZE(__USER_LABEL_PREFIX__)
+#  else
+#    define INCBIN_MANGLE        ""
+#  endif
+#  if defined(INCBIN_ARM)
+/* On arm assemblers, `@' is used as a line comment token */
+#    define INCBIN_TYPE(NAME)    ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n"
+#  elif defined(__MINGW32__) || defined(__MINGW64__)
+/* Mingw doesn't support this directive either */
+#    define INCBIN_TYPE(NAME)
+#  else
+/* It's safe to use `@' on other architectures */
+#    define INCBIN_TYPE(NAME)    ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n"
+#  endif
+#  define INCBIN_BYTE            ".byte "
+#endif
+
+/* List of style types used for symbol names */
+#define INCBIN_STYLE_CAMEL 0
+#define INCBIN_STYLE_SNAKE 1
+
+/**
+ * @brief Specify the prefix to use for symbol names.
+ *
+ * By default this is `g', producing symbols of the form:
+ * @code
+ * #include "incbin.h"
+ * INCBIN(Foo, "foo.txt");
+ *
+ * // Now you have the following symbols:
+ * // const unsigned char gFooData[];
+ * // const unsigned char *const gFooEnd;
+ * // const unsigned int gFooSize;
+ * @endcode
+ *
+ * If however you specify a prefix before including: e.g:
+ * @code
+ * #define INCBIN_PREFIX incbin
+ * #include "incbin.h"
+ * INCBIN(Foo, "foo.txt");
+ *
+ * // Now you have the following symbols instead:
+ * // const unsigned char incbinFooData[];
+ * // const unsigned char *const incbinFooEnd;
+ * // const unsigned int incbinFooSize;
+ * @endcode
+ */
+#if !defined(INCBIN_PREFIX)
+#  define INCBIN_PREFIX g
+#endif
+
+/**
+ * @brief Specify the style used for symbol names.
+ *
+ * Possible options are
+ * - INCBIN_STYLE_CAMEL "CamelCase"
+ * - INCBIN_STYLE_SNAKE "snake_case"
+ *
+ * Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form:
+ * @code
+ * #include "incbin.h"
+ * INCBIN(Foo, "foo.txt");
+ *
+ * // Now you have the following symbols:
+ * // const unsigned char <prefix>FooData[];
+ * // const unsigned char *const <prefix>FooEnd;
+ * // const unsigned int <prefix>FooSize;
+ * @endcode
+ *
+ * If however you specify a style before including: e.g:
+ * @code
+ * #define INCBIN_STYLE INCBIN_STYLE_SNAKE
+ * #include "incbin.h"
+ * INCBIN(foo, "foo.txt");
+ *
+ * // Now you have the following symbols:
+ * // const unsigned char <prefix>foo_data[];
+ * // const unsigned char *const <prefix>foo_end;
+ * // const unsigned int <prefix>foo_size;
+ * @endcode
+ */
+#if !defined(INCBIN_STYLE)
+#  define INCBIN_STYLE INCBIN_STYLE_CAMEL
+#endif
+
+/* Style lookup tables */
+#define INCBIN_STYLE_0_DATA Data
+#define INCBIN_STYLE_0_END End
+#define INCBIN_STYLE_0_SIZE Size
+#define INCBIN_STYLE_1_DATA _data
+#define INCBIN_STYLE_1_END _end
+#define INCBIN_STYLE_1_SIZE _size
+
+/* Style lookup: returning identifier */
+#define INCBIN_STYLE_IDENT(TYPE) \
+    INCBIN_CONCATENATE( \
+        INCBIN_STYLE_, \
+        INCBIN_CONCATENATE( \
+            INCBIN_EVAL(INCBIN_STYLE), \
+            INCBIN_CONCATENATE(_, TYPE)))
+
+/* Style lookup: returning string literal */
+#define INCBIN_STYLE_STRING(TYPE) \
+    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. */
+#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
+    INCBIN_INVOKE( \
+        INCBIN_GLOBAL, \
+        INCBIN_CONCATENATE( \
+            NAME, \
+            INCBIN_INVOKE( \
+                INCBIN_STYLE_IDENT, \
+                TYPE))) \
+    INCBIN_INVOKE( \
+        INCBIN_TYPE, \
+        INCBIN_CONCATENATE( \
+            NAME, \
+            INCBIN_INVOKE( \
+                INCBIN_STYLE_IDENT, \
+                TYPE)))
+
+/**
+ * @brief Externally reference binary data included in another translation unit.
+ *
+ * Produces three external symbols that reference the binary data included in
+ * another translation unit.
+ *
+ * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
+ * "Data", as well as "End" and "Size" after. An example is provided below.
+ *
+ * @param NAME The name given for the binary data
+ *
+ * @code
+ * INCBIN_EXTERN(Foo);
+ *
+ * // Now you have the following symbols:
+ * // extern const unsigned char <prefix>FooData[];
+ * // extern const unsigned char *const <prefix>FooEnd;
+ * // extern const unsigned int <prefix>FooSize;
+ * @endcode
+ */
+#define INCBIN_EXTERN(NAME) \
+    INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \
+        INCBIN_CONCATENATE( \
+            INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
+            INCBIN_STYLE_IDENT(DATA))[]; \
+    INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \
+    INCBIN_CONCATENATE( \
+        INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
+        INCBIN_STYLE_IDENT(END)); \
+    INCBIN_EXTERNAL const unsigned int \
+        INCBIN_CONCATENATE( \
+            INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
+            INCBIN_STYLE_IDENT(SIZE))
+
+/**
+ * @brief Include a binary file into the current translation unit.
+ *
+ * Includes a binary file into the current translation unit, producing three symbols
+ * for objects that encode the data and size respectively.
+ *
+ * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
+ * "Data", as well as "End" and "Size" after. An example is provided below.
+ *
+ * @param NAME The name to associate with this binary data (as an identifier.)
+ * @param FILENAME The file to include (as a string literal.)
+ *
+ * @code
+ * INCBIN(Icon, "icon.png");
+ *
+ * // Now you have the following symbols:
+ * // const unsigned char <prefix>IconData[];
+ * // const unsigned char *const <prefix>IconEnd;
+ * // const unsigned int <prefix>IconSize;
+ * @endcode
+ *
+ * @warning This must be used in global scope
+ * @warning The identifiers may be different if INCBIN_STYLE is not default
+ *
+ * To externally reference the data included by this in another translation unit
+ * please @see INCBIN_EXTERN.
+ */
+#ifdef _MSC_VER
+#define INCBIN(NAME, FILENAME) \
+    INCBIN_EXTERN(NAME)
+#else
+#define INCBIN(NAME, FILENAME) \
+    __asm__(INCBIN_SECTION \
+            INCBIN_GLOBAL_LABELS(NAME, DATA) \
+            INCBIN_ALIGN_HOST \
+            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \
+            INCBIN_MACRO " \"" FILENAME "\"\n" \
+            INCBIN_GLOBAL_LABELS(NAME, END) \
+            INCBIN_ALIGN_BYTE \
+            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \
+                INCBIN_BYTE "1\n" \
+            INCBIN_GLOBAL_LABELS(NAME, SIZE) \
+            INCBIN_ALIGN_HOST \
+            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \
+                INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \
+                           INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \
+            INCBIN_ALIGN_HOST \
+            ".text\n" \
+    ); \
+    INCBIN_EXTERN(NAME)
+
+#endif
+#endif
index 148bf248f5337aec842666bdeb42df8d163837b1..fbd6a7c9cbbb98181415e5c697281558d454c142 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <deque>
+#include <cstddef>
 #include <iostream>
+#include <stack>
+#include <thread>
 
 #include "bitboard.h"
+#include "evaluate.h"
+#include "misc.h"
 #include "position.h"
 #include "search.h"
 #include "thread.h"
-#include "tt.h"
+#include "tune.h"
+#include "types.h"
 #include "uci.h"
-#include "endgame.h"
-#include "syzygy/tbprobe.h"
 
-namespace PSQT {
-  void init();
+#include <grpc/grpc.h>
+#include <grpc++/server.h>
+#include <grpc++/server_builder.h>
+#include "hashprobe.h"
+#include "hashprobe.grpc.pb.h"
+#include "tt.h"
+
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::ServerContext;
+using grpc::Status;
+using grpc::StatusCode;
+using namespace hashprobe;
+using namespace Stockfish;
+
+Status HashProbeImpl::Probe(ServerContext* context,
+                            const HashProbeRequest* request,
+                           HashProbeResponse *response) {
+       Position pos;
+       StateInfo st;
+       pos.set(request->fen(), /*isChess960=*/false, &st, Threads.main());
+       if (!pos.pos_is_ok()) {
+               return Status(StatusCode::INVALID_ARGUMENT, "Invalid FEN");
+       }
+
+       bool invert = (pos.side_to_move() == BLACK);
+       StateListPtr setup_states = StateListPtr(new std::deque<StateInfo>(1));
+
+       ProbeMove(&pos, setup_states.get(), invert, response->mutable_root());
+
+       MoveList<LEGAL> moves(pos);
+       for (const ExtMove* em = moves.begin(); em != moves.end(); ++em) {
+               HashProbeLine *line = response->add_line();
+               FillMove(&pos, em->move, line->mutable_move());
+               setup_states->push_back(StateInfo());
+               pos.do_move(em->move, setup_states->back());
+               ProbeMove(&pos, setup_states.get(), !invert, line);
+               pos.undo_move(em->move);
+       }
+
+       return Status::OK;
+}
+
+void HashProbeImpl::FillMove(Position *pos, Move move, HashProbeMove* decoded) {
+       if (!is_ok(move)) return;
+
+       Square from = from_sq(move);
+       Square to = to_sq(move);
+
+       if (type_of(move) == CASTLING) {
+               to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
+       }
+
+       Piece moved_piece = pos->moved_piece(move);
+       std::string pretty;
+       if (type_of(move) == CASTLING) {
+               if (to > from) {
+                       pretty = "O-O";
+               } else {
+                       pretty = "O-O-O";
+               }
+       } else if (type_of(moved_piece) == PAWN) {
+               if (type_of(move) == EN_PASSANT || pos->piece_on(to) != NO_PIECE) {
+                       // Capture.
+                       pretty = char('a' + file_of(from));
+                       pretty += "x";
+               }
+               pretty += UCI::square(to);
+               if (type_of(move) == PROMOTION) {
+                       pretty += "=";
+                       pretty += " PNBRQK"[promotion_type(move)];
+               }
+       } else {
+               pretty = " PNBRQK"[type_of(moved_piece)];
+               Bitboard attackers = pos->attackers_to(to) & pos->pieces(color_of(moved_piece), type_of(moved_piece));
+               if (more_than_one(attackers)) {
+                       // Remove all illegal moves to disambiguate.
+                       Bitboard att_copy = attackers;
+                       while (att_copy) {
+                               Square s = pop_lsb(att_copy);
+                               Move m = make_move(s, to);
+                               if (!pos->pseudo_legal(m) || !pos->legal(m)) {
+                                       attackers &= ~square_bb(s);
+                               }
+                       }
+               }
+               if (more_than_one(attackers)) {
+                       // Disambiguate by file if possible.
+                       Bitboard attackers_this_file = attackers & file_bb(file_of(from));
+                       if (attackers != attackers_this_file) {
+                               pretty += char('a' + file_of(from));
+                               attackers = attackers_this_file;
+                       }
+                       if (more_than_one(attackers)) {
+                               // Still ambiguous, so need to disambiguate by rank.
+                               pretty += char('1' + rank_of(from));
+                       }
+               }
+
+               if (type_of(move) == EN_PASSANT || pos->piece_on(to) != NO_PIECE) {
+                       pretty += "x";
+               }
+
+               pretty += UCI::square(to);
+       }
+
+       if (pos->gives_check(move)) {
+               // Check if mate.
+               StateInfo si;
+               pos->do_move(move, si, true);
+               if (MoveList<LEGAL>(*pos).size() > 0) {
+                       pretty += "+";
+               } else {
+                       pretty += "#";
+               }
+               pos->undo_move(move);
+       }
+
+       decoded->set_pretty(pretty);
+}
+
+void HashProbeImpl::ProbeMove(Position* pos, std::deque<StateInfo>* setup_states, bool invert, HashProbeLine* response) {
+       bool found;
+       TTEntry *entry = TT.probe(pos->key(), found);
+       response->set_found(found);
+       if (found) {
+               TTEntry entry_copy = *entry;
+               Value value = entry_copy.value();
+               Value eval = entry_copy.eval();
+               Bound bound = entry_copy.bound();
+
+               if (invert) {
+                       value = -value;
+                       eval = -eval;
+                       if (bound == BOUND_UPPER) {
+                               bound = BOUND_LOWER;
+                       } else if (bound == BOUND_LOWER) {
+                               bound = BOUND_UPPER;
+                       }
+               }
+
+               response->set_depth(entry_copy.depth());
+               FillValue(eval, response->mutable_eval());
+               if (entry_copy.depth() > DEPTH_NONE) {
+                       FillValue(value, response->mutable_value());
+               }
+               response->set_bound(HashProbeLine::ValueBound(bound));
+
+               // Follow the PV until we hit an illegal move.
+               std::stack<Move> pv;
+               std::set<Key> seen;
+               while (is_ok(entry_copy.move()) &&
+                      pos->pseudo_legal(entry_copy.move()) &&
+                      pos->legal(entry_copy.move())) {
+                       FillMove(pos, entry_copy.move(), response->add_pv());
+                       if (seen.count(pos->key())) break;
+                       pv.push(entry_copy.move());
+                       seen.insert(pos->key());
+                       setup_states->push_back(StateInfo());
+                       pos->do_move(entry_copy.move(), setup_states->back());
+                       entry = TT.probe(pos->key(), found);
+                       if (!found) {
+                               break;
+                       }
+                       entry_copy = *entry;
+               }
+
+               // Unroll the PV back again, so the Position object remains unchanged.
+               while (!pv.empty()) {
+                       pos->undo_move(pv.top());
+                       pv.pop();
+               }
+       }
+}
+
+void HashProbeImpl::FillValue(Value value, HashProbeScore* score) {
+       if (abs(value) < VALUE_MATE - MAX_PLY) {
+               score->set_score_type(HashProbeScore::SCORE_CP);
+               score->set_score_cp(value * 100 / UCI::NormalizeToPawnValue);
+       } else {
+               score->set_score_type(HashProbeScore::SCORE_MATE);
+               score->set_score_mate((value > 0 ? VALUE_MATE - value + 1 : -VALUE_MATE - value) / 2);
+       }
+}
+
+HashProbeThread::HashProbeThread(const std::string &server_address) {
+       builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+       builder.RegisterService(&service);
+       server = std::move(builder.BuildAndStart());
+       std::cout << "Server listening on " << server_address << std::endl;
+       std::thread([this]{ server->Wait(); }).detach();
+}
+
+void HashProbeThread::Shutdown() {
+       server->Shutdown();
 }
 
 int main(int argc, char* argv[]) {
 
-  std::cout << engine_info() << std::endl;
+    std::cout << engine_info() << std::endl;
 
-  UCI::init(Options);
-  PSQT::init();
-  Bitboards::init();
-  Position::init();
-  Bitbases::init();
-  Endgames::init();
-  Threads.set(Options["Threads"]);
-  Search::clear(); // After threads are up
+    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/material.cpp b/src/material.cpp
deleted file mode 100644 (file)
index 0e13087..0000000
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
-  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
-  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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <cassert>
-#include <cstring>   // For std::memset
-
-#include "material.h"
-#include "thread.h"
-
-using namespace std;
-
-namespace {
-
-  // Polynomial material imbalance parameters
-
-  constexpr int QuadraticOurs[][PIECE_TYPE_NB] = {
-    //            OUR PIECES
-    // pair pawn knight bishop rook queen
-    {1438                               }, // Bishop pair
-    {  40,   38                         }, // Pawn
-    {  32,  255, -62                    }, // Knight      OUR PIECES
-    {   0,  104,   4,    0              }, // Bishop
-    { -26,   -2,  47,   105,  -208      }, // Rook
-    {-189,   24, 117,   133,  -134, -6  }  // Queen
-  };
-
-  constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = {
-    //           THEIR PIECES
-    // pair pawn knight bishop rook queen
-    {   0                               }, // Bishop pair
-    {  36,    0                         }, // Pawn
-    {   9,   63,   0                    }, // Knight      OUR PIECES
-    {  59,   65,  42,     0             }, // Bishop
-    {  46,   39,  24,   -24,    0       }, // Rook
-    {  97,  100, -42,   137,  268,    0 }  // Queen
-  };
-
-  // 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<KXK>    EvaluateKXK[] = { Endgame<KXK>(WHITE),    Endgame<KXK>(BLACK) };
-
-  Endgame<KBPsK>  ScaleKBPsK[]  = { Endgame<KBPsK>(WHITE),  Endgame<KBPsK>(BLACK) };
-  Endgame<KQKRPs> ScaleKQKRPs[] = { Endgame<KQKRPs>(WHITE), Endgame<KQKRPs>(BLACK) };
-  Endgame<KPsK>   ScaleKPsK[]   = { Endgame<KPsK>(WHITE),   Endgame<KPsK>(BLACK) };
-  Endgame<KPKP>   ScaleKPKP[]   = { Endgame<KPKP>(WHITE),   Endgame<KPKP>(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<PAWN  >(us) >= 1;
-  }
-
-  bool is_KQKRPs(const Position& pos, Color us) {
-    return  !pos.count<PAWN>(us)
-          && pos.non_pawn_material(us) == QueenValueMg
-          && pos.count<ROOK>(~us) == 1
-          && pos.count<PAWN>(~us) >= 1;
-  }
-
-  /// imbalance() calculates the imbalance by comparing the piece count of each
-  /// piece type for both colors.
-  template<Color Us>
-  int imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
-
-    constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
-
-    int bonus = 0;
-
-    // Second-degree polynomial material imbalance, by Tord Romstad
-    for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
-    {
-        if (!pieceCount[Us][pt1])
-            continue;
-
-        int v = 0;
-
-        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   = 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<Value>(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<ScaleFactor>(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<PAWN>(BLACK))
-      {
-          assert(pos.count<PAWN>(WHITE) >= 2);
-
-          e->scalingFunction[WHITE] = &ScaleKPsK[WHITE];
-      }
-      else if (!pos.count<PAWN>(WHITE))
-      {
-          assert(pos.count<PAWN>(BLACK) >= 2);
-
-          e->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
-      }
-      else if (pos.count<PAWN>(WHITE) == 1 && pos.count<PAWN>(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<PAWN>(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<PAWN>(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<BISHOP>(WHITE) > 1, pos.count<PAWN>(WHITE), pos.count<KNIGHT>(WHITE),
-    pos.count<BISHOP>(WHITE)    , pos.count<ROOK>(WHITE), pos.count<QUEEN >(WHITE) },
-  { pos.count<BISHOP>(BLACK) > 1, pos.count<PAWN>(BLACK), pos.count<KNIGHT>(BLACK),
-    pos.count<BISHOP>(BLACK)    , pos.count<ROOK>(BLACK), pos.count<QUEEN >(BLACK) } };
-
-  e->value = int16_t((imbalance<WHITE>(pieceCount) - imbalance<BLACK>(pieceCount)) / 16);
-  return e;
-}
-
-} // namespace Material
diff --git a/src/material.h b/src/material.h
deleted file mode 100644 (file)
index 9ab1d81..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
-  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
-  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 <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef MATERIAL_H_INCLUDED
-#define MATERIAL_H_INCLUDED
-
-#include "endgame.h"
-#include "misc.h"
-#include "position.h"
-#include "types.h"
-
-namespace Material {
-
-/// Material::Entry contains various information about a material configuration.
-/// It contains a material imbalance evaluation, a function pointer to a special
-/// endgame evaluation function (which in most cases is NULL, meaning that the
-/// 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 make_score(value, value); }
-  Phase game_phase() const { return 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<Value>* evaluationFunction;
-  const EndgameBase<ScaleFactor>* scalingFunction[COLOR_NB]; // Could be one for each
-                                                             // side (e.g. KPKP, KBPsK)
-  int16_t value;
-  uint8_t factor[COLOR_NB];
-  Phase gamePhase;
-};
-
-typedef HashTable<Entry, 8192> Table;
-
-Entry* probe(const Position& pos);
-
-} // namespace Material
-
-#endif // #ifndef MATERIAL_H_INCLUDED
index 95862ebbd36cd0c329e421b95d4c6a6b91e123b0..59c5e406030e25b6b5937957a0540920ba7ec099 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#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 <windows.h>
+    #include <windows.h>
 // 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" {
-typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP,
-                      PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
-typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY);
-typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
+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
 
+#include <atomic>
+#include <cmath>
+#include <cstdlib>
 #include <fstream>
 #include <iomanip>
 #include <iostream>
+#include <mutex>
 #include <sstream>
-#include <vector>
+#include <string_view>
 
-#include "misc.h"
-#include "thread.h"
+#include "types.h"
 
-using namespace std;
+#if defined(__linux__) && !defined(__ANDROID__)
+    #include <sys/mman.h>
+#endif
+
+#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \
+  || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \
+  || defined(__e2k__)
+    #define POSIXALIGNEDALLOC
+    #include <stdlib.h>
+#endif
+
+namespace Stockfish {
 
 namespace {
 
-/// Version number. If Version is left empty, then compile date in the format
-/// DD-MM-YY and show in engine_info.
-const string Version = "11";
+// 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 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(), ">> "); }
+    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) {
+    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(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {}
- ~Logger() { start(""); }
+    Logger() :
+        in(std::cin.rdbuf(), file.rdbuf()),
+        out(std::cout.rdbuf(), file.rdbuf()) {}
+    ~Logger() { start(""); }
 
-  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 (!fname.empty() && !l.file.is_open())
-    {
-        l.file.open(fname, ifstream::out);
-
-        if (!l.file.is_open())
+        if (l.file.is_open())
         {
-            cerr << "Unable to open debug log file " << fname << endl;
-            exit(EXIT_FAILURE);
+            std::cout.rdbuf(l.out.buf);
+            std::cin.rdbuf(l.in.buf);
+            l.file.close();
         }
 
-        cin.rdbuf(&l.in);
-        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);
+        }
     }
-    else if (fname.empty() && l.file.is_open())
+};
+
+}  // namespace
+
+
+// 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;
+    ss << "Stockfish " << version << std::setfill('0');
+
+    if constexpr (version == "dev")
     {
-        cout.rdbuf(l.out.buf);
-        cin.rdbuf(l.in.buf);
-        l.file.close();
+        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
+
+        ss << "-";
+
+#ifdef GIT_SHA
+        ss << stringify(GIT_SHA);
+#else
+        ss << "nogit";
+#endif
+       ss << "-asn";
     }
-  }
-};
 
-} // namespace
+    ss << (to_uci ? "\nid author " : " by ") << "the Stockfish developers (see AUTHORS file)";
 
-/// engine_info() returns the full name of the current Stockfish version. This
-/// will be either "Stockfish <Tag> DD-MM-YY" (where DD-MM-YY is the date when
-/// the program was compiled) or "Stockfish <Version>", depending on whether
-/// Version is empty.
+    return ss.str();
+}
 
-const string engine_info(bool to_uci) {
 
-  const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
-  string month, day, year;
-  stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008"
+// 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
+
+    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__)
+    #define dot_ver2(n) \
+        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
 
-  ss << "Stockfish " << Version << setfill('0');
+#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 (Version.empty())
-  {
-      date >> month >> day >> year;
-      ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2);
-  }
+    compiler += "\nCompilation architecture   : ";
+#if defined(ARCH)
+    compiler += stringify(ARCH);
+#else
+    compiler += "(undefined architecture)";
+#endif
 
-  ss << (Is64Bit ? " 64" : "")
-     << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : ""))
-     << (to_uci  ? "\nid author ": " by ")
-     << "T. Romstad, M. Costalba, J. Kiiski, G. Linscott";
+    compiler += "\nCompilation settings       : ";
+    compiler += (Is64Bit ? "64bit" : "32bit");
+#if defined(USE_VNNI)
+    compiler += " VNNI";
+#endif
+#if defined(USE_AVX512)
+    compiler += " AVX512";
+#endif
+    compiler += (HasPext ? " BMI2" : "");
+#if defined(USE_AVX2)
+    compiler += " AVX2";
+#endif
+#if defined(USE_SSE41)
+    compiler += " SSE41";
+#endif
+#if defined(USE_SSSE3)
+    compiler += " SSSE3";
+#endif
+#if defined(USE_SSE2)
+    compiler += " SSE2";
+#endif
+    compiler += (HasPopCnt ? " POPCNT" : "");
+#if defined(USE_NEON_DOTPROD)
+    compiler += " NEON_DOTPROD";
+#elif defined(USE_NEON)
+    compiler += " NEON";
+#endif
+
+#if !defined(NDEBUG)
+    compiler += " DEBUG";
+#endif
+
+    compiler += "\nCompiler __VERSION__ macro : ";
+#ifdef __VERSION__
+    compiler += __VERSION__;
+#else
+    compiler += "(undefined macro)";
+#endif
 
-  return ss.str();
+    compiler += "\n";
+
+    return compiler;
 }
 
 
-/// compiler_info() returns a string trying to describe the compiler we use
-
-const std::string compiler_info() {
-
-  #define STRINGIFY2(x) #x
-  #define STRINGIFY(x) STRINGIFY2(x)
-  #define VER_STRING(major, minor, patch) STRINGIFY(major) "." STRINGIFY(minor) "." STRINGIFY(patch)
-
-/// 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
-
-  std::string compiler = "\nCompiled by ";
-
-  #ifdef __clang__
-     compiler += "clang++ ";
-     compiler += VER_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 ";
-     compiler += STRINGIFY(_MSC_FULL_VER) "." STRINGIFY(_MSC_BUILD);
-     compiler += ")";
-  #elif __GNUC__
-     compiler += "g++ (GNUC) ";
-     compiler += VER_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
-
-  compiler += "\n __VERSION__ macro expands to: ";
-  #ifdef __VERSION__
-     compiler += __VERSION__;
-  #else
-     compiler += "(undefined macro)";
-  #endif
-  compiler += "\n";
-
-  return compiler;
+// Debug functions used mainly to collect run-time statistics
+constexpr int MaxDebugSlots = 32;
+
+namespace {
+
+template<size_t N>
+struct DebugInfo {
+    std::atomic<int64_t> data[N] = {0};
+
+    constexpr inline std::atomic<int64_t>& operator[](int index) { return data[index]; }
+};
+
+DebugInfo<2> hit[MaxDebugSlots];
+DebugInfo<2> mean[MaxDebugSlots];
+DebugInfo<3> stdev[MaxDebugSlots];
+DebugInfo<6> correl[MaxDebugSlots];
+
+}  // namespace
+
+void dbg_hit_on(bool cond, int slot) {
+
+    ++hit[slot][0];
+    if (cond)
+        ++hit[slot][1];
 }
 
+void dbg_mean_of(int64_t value, int slot) {
 
-/// Debug functions used mainly to collect run-time statistics
-static std::atomic<int64_t> hits[2], means[2];
+    ++mean[slot][0];
+    mean[slot][1] += value;
+}
 
-void dbg_hit_on(bool b) { ++hits[0]; if (b) ++hits[1]; }
-void dbg_hit_on(bool c, bool b) { if (c) dbg_hit_on(b); }
-void dbg_mean_of(int v) { ++means[0]; means[1] += v; }
+void dbg_stdev_of(int64_t value, int slot) {
 
-void dbg_print() {
+    ++stdev[slot][0];
+    stdev[slot][1] += value;
+    stdev[slot][2] += value * value;
+}
 
-  if (hits[0])
-      cerr << "Total " << hits[0] << " Hits " << hits[1]
-           << " hit rate (%) " << 100 * hits[1] / hits[0] << endl;
+void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
 
-  if (means[0])
-      cerr << "Total " << means[0] << " Mean "
-           << (double)means[1] / means[0] << endl;
+    ++correl[slot][0];
+    correl[slot][1] += value1;
+    correl[slot][2] += value1 * value1;
+    correl[slot][3] += value2;
+    correl[slot][4] += value2 * value2;
+    correl[slot][5] += value1 * value2;
 }
 
+void dbg_print() {
+
+    int64_t n;
+    auto    E   = [&n](int64_t x) { return double(x) / n; };
+    auto    sqr = [](double x) { return x * x; };
 
-/// Used to serialize access to std::cout to avoid multiple threads writing at
-/// the same time.
+    for (int i = 0; i < MaxDebugSlots; ++i)
+        if ((n = hit[i][0]))
+            std::cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1]
+                      << " Hit Rate (%) " << 100.0 * E(hit[i][1]) << std::endl;
 
+    for (int i = 0; i < MaxDebugSlots; ++i)
+        if ((n = mean[i][0]))
+        {
+            std::cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) << std::endl;
+        }
+
+    for (int i = 0; i < MaxDebugSlots; ++i)
+        if ((n = stdev[i][0]))
+        {
+            double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1])));
+            std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl;
+        }
+
+    for (int i = 0; i < MaxDebugSlots; ++i)
+        if ((n = correl[i][0]))
+        {
+            double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3]))
+                     / (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;
+        }
+}
+
+
+// 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;
+    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;
 }
 
 
-/// 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.
 #ifdef NO_PREFETCH
 
 void prefetch(void*) {}
@@ -273,21 +422,177 @@ 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)
-  _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
+
+
+// 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)
+    void* mem;
+    return posix_memalign(&mem, alignment, size) ? nullptr : mem;
+#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)
+    return _mm_malloc(size, alignment);
+#elif defined(_WIN32)
+    return _aligned_malloc(size, alignment);
+#else
+    return std::aligned_alloc(alignment, size);
+#endif
+}
+
+void std_aligned_free(void* ptr) {
+
+#if defined(POSIXALIGNEDALLOC)
+    free(ptr);
+#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)
+    _mm_free(ptr);
+#elif defined(_WIN32)
+    _aligned_free(ptr);
+#else
+    free(ptr);
+#endif
+}
+
+// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages.
+
+#if defined(_WIN32)
+
+static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) {
+
+    #if !defined(_WIN64)
+    return nullptr;
+    #else
+
+    HANDLE hProcessToken{};
+    LUID   luid{};
+    void*  mem = nullptr;
+
+    const size_t largePageSize = GetLargePageMinimum();
+    if (!largePageSize)
+        return nullptr;
+
+    // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges
+
+    HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll"));
+
+    if (!hAdvapi32)
+        hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
+
+    auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken"));
+    if (!fun6)
+        return nullptr;
+    auto fun7 = fun7_t((void (*)()) GetProcAddress(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;
+
+    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;
+
+        // 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 ()
+              hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
+        }
+    }
+
+    CloseHandle(hProcessToken);
+
+    return mem;
+
+    #endif
+}
+
+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
+    if (!mem)
+        mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+
+    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
+
+    // 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
 
+
+// aligned_large_pages_free() will free the previously allocated ttmem
+
+#if defined(_WIN32)
+
+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);
+    }
+}
+
+#else
+
+void aligned_large_pages_free(void* mem) { std_aligned_free(mem); }
+
+#endif
+
+
 namespace WinProcGroup {
 
 #ifndef _WIN32
@@ -296,100 +601,173 @@ void bindThisThread(size_t) {}
 
 #else
 
-/// best_group() retrieves logical processor information using Windows specific
-/// API and returns the best group id for the thread with index idx. Original
-/// code from Texel by Peter Österlund.
-
-int best_group(size_t idx) {
-
-  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("Kernel32.dll");
-  auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx");
-  if (!fun1)
-      return -1;
-
-  // First call to get returnLength. We expect it 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);
-
-  // Second call, now we expect to succeed
-  if (!fun1(RelationAll, buffer, &returnLength))
-  {
-      free(buffer);
-      return -1;
-  }
-
-  while (byteOffset < returnLength)
-  {
-      if (ptr->Relationship == RelationNumaNode)
-          nodes++;
-
-      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);
-  }
-
-  free(buffer);
-
-  std::vector<int> groups;
-
-  // Run as many threads as possible on the same node until core limit is
-  // reached, then move on 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);
-
-  // 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;
-}
+// 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;
+    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;
+
+    // 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);
+
+    // 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++;
 
-/// bindThisThread() set the group affinity of the current thread
+        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);
+    }
+
+    free(buffer);
+
+    std::vector<int> 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);
+
+    // In case a core has more than one logical processor (we assume 2) and we
+    // still have threads to allocate, 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;
+}
+
+
+// Sets the group affinity of the current thread
 void bindThisThread(size_t idx) {
 
-  // Use only local variables to be thread-safe
-  int group = best_group(idx);
+    // Use only local variables to be thread-safe
+    int node = best_node(idx);
 
-  if (group == -1)
-      return;
+    if (node == -1)
+        return;
 
-  // Early exit if the needed API are not available at runtime
-  HMODULE k32 = GetModuleHandle("Kernel32.dll");
-  auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
-  auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
+    // 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;
 
-  GROUP_AFFINITY affinity;
-  if (fun2(group, &affinity))
-      fun3(GetCurrentThread(), &affinity, nullptr);
+    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 <direct.h>
+    #define GETCWD _getcwd
+#else
+    #include <unistd.h>
+    #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[]) {
+    std::string pathSeparator;
+
+    // Extract the path+name of the executable binary
+    argv0 = argv[0];
+
+#ifdef _WIN32
+    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.
+    char* pgmptr = nullptr;
+    if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
+        argv0 = pgmptr;
+    #endif
+#else
+    pathSeparator = "/";
+#endif
+
+    // Extract the working directory
+    workingDirectory = "";
+    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("\\/");
+    if (pos == std::string::npos)
+        binaryDirectory = "." + pathSeparator;
+    else
+        binaryDirectory.resize(pos + 1);
+
+    // Pattern replacement: "./" at the start of path is replaced by the working directory
+    if (binaryDirectory.find("." + pathSeparator) == 0)
+        binaryDirectory.replace(0, 1, workingDirectory);
+}
+
+
+}  // namespace CommandLine
+
+}  // namespace Stockfish
index b11c5aa843c5519a68699f115860419ca85f9eb7..91fdb72f1b1a308b389106293705897bd5016728 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 
 #include <cassert>
 #include <chrono>
-#include <ostream>
+#include <cstddef>
+#include <cstdint>
+#include <iosfwd>
 #include <string>
-#include <vector>
 
-#include "types.h"
+#define stringify2(x) #x
+#define stringify(x) stringify2(x)
 
-const std::string engine_info(bool to_uci = false);
-const std::string compiler_info();
+namespace Stockfish {
+
+std::string engine_info(bool to_uci = false);
+std::string compiler_info();
+
+// 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 dbg_hit_on(bool b);
-void dbg_hit_on(bool c, bool b);
-void dbg_mean_of(int v);
+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);
+void dbg_stdev_of(int64_t value, int slot = 0);
+void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0);
 void dbg_print();
 
-typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
-
+using TimePoint = std::chrono::milliseconds::rep;  // A value in milliseconds
 static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
-
 inline TimePoint now() {
-  return std::chrono::duration_cast<std::chrono::milliseconds>
-        (std::chrono::steady_clock::now().time_since_epoch()).count();
+    return std::chrono::duration_cast<std::chrono::milliseconds>(
+             std::chrono::steady_clock::now().time_since_epoch())
+      .count();
 }
 
-template<class Entry, int Size>
-struct HashTable {
-  Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; }
 
-private:
-  std::vector<Entry> table = std::vector<Entry>(Size); // Allocate on the heap
+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
 #define sync_endl std::endl << IO_UNLOCK
 
 
-/// 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
-///   <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
+// 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<uintptr_t Alignment, typename T>
+T* align_ptr_up(T* ptr) {
+    static_assert(alignof(T) < Alignment);
 
-class PRNG {
+    const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
+    return reinterpret_cast<T*>(
+      reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
+}
 
-  uint64_t s;
 
-  uint64_t rand64() {
+// 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);
 
-    s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
-    return s * 2685821657736338717LL;
-  }
 
-public:
-  PRNG(uint64_t seed) : s(seed) { assert(seed); }
+template<typename T, std::size_t MaxSize>
+class ValueList {
 
-  template<typename T> T rand() { return T(rand64()); }
+   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]; }
 
-  /// Special generator used to fast init magic numbers.
-  /// Output values only have 1/8th of their bits set on average.
-  template<typename T> T sparse_rand()
-  { return T(rand64() & rand64() & rand64()); }
+   private:
+    T           values_[MaxSize];
+    std::size_t size_ = 0;
 };
 
 
-/// 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.
+// 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
+//   <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
+
+class PRNG {
+
+    uint64_t s;
+
+    uint64_t rand64() {
 
+        s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
+        return s * 2685821657736338717LL;
+    }
+
+   public:
+    PRNG(uint64_t seed) :
+        s(seed) {
+        assert(seed);
+    }
+
+    template<typename T>
+    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<typename T>
+    T sparse_rand() {
+        return T(rand64() & rand64() & rand64());
+    }
+};
+
+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;
+#else
+    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);
+    return aH * bH + (c2 >> 32) + (c3 >> 32);
+#endif
+}
+
+// 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);
+void bindThisThread(size_t idx);
 }
 
-#endif // #ifndef MISC_H_INCLUDED
+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
+}
+
+}  // namespace Stockfish
+
+#endif  // #ifndef MISC_H_INCLUDED
index 8f6edffbfc33da9034530038912dadbbc1eb1dc5..7d6856bb03682f393dc2f34141e23ac2b2248c4a 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "movegen.h"
+
 #include <cassert>
+#include <initializer_list>
 
-#include "movegen.h"
+#include "bitboard.h"
 #include "position.h"
 
+namespace Stockfish {
+
 namespace {
 
-  template<GenType Type, Direction D>
-  ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) {
+template<GenType Type, Direction D, bool Enemy>
+ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
 
-    if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+    constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS;
+
+    if constexpr (Type == CAPTURES || all)
         *moveList++ = make<PROMOTION>(to - D, to, QUEEN);
 
-    if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
+    if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all)
     {
         *moveList++ = make<PROMOTION>(to - D, to, ROOK);
         *moveList++ = make<PROMOTION>(to - D, to, BISHOP);
         *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
     }
 
-    // Knight promotion is the only promotion that can give a direct check
-    // that's not already included in the queen promotion.
-    if (Type == QUIET_CHECKS && (PseudoAttacks[KNIGHT][to] & ksq))
-        *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
-    else
-        (void)ksq; // Silence a warning under MSVC
-
     return moveList;
-  }
+}
 
 
-  template<Color Us, GenType Type>
-  ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
+template<Color Us, GenType Type>
+ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
 
-    // Compute some compile time parameters relative to the white side
-    constexpr Color     Them     = (Us == WHITE ? BLACK      : WHITE);
-    constexpr Bitboard  TRank7BB = (Us == WHITE ? Rank7BB    : Rank2BB);
-    constexpr Bitboard  TRank3BB = (Us == WHITE ? Rank3BB    : Rank6BB);
+    constexpr Color     Them     = ~Us;
+    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 Square ksq = pos.square<KING>(Them);
-    Bitboard emptySquares;
+    const Bitboard emptySquares = ~pos.pieces();
+    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;
 
-    Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target:
-                        Type == CAPTURES ? target : pos.pieces(Them));
-
     // Single and double pawn pushes, no promotions
-    if (Type != CAPTURES)
+    if constexpr (Type != CAPTURES)
     {
-        emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces());
-
-        Bitboard b1 = shift<Up>(pawnsNotOn7)   & emptySquares;
+        Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
         Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
 
-        if (Type == EVASIONS) // Consider only blocking squares
+        if constexpr (Type == EVASIONS)  // Consider only blocking squares
         {
             b1 &= target;
             b2 &= target;
         }
 
-        if (Type == QUIET_CHECKS)
+        if constexpr (Type == QUIET_CHECKS)
         {
-            b1 &= pos.attacks_from<PAWN>(ksq, Them);
-            b2 &= pos.attacks_from<PAWN>(ksq, Them);
-
-            // Add pawn pushes which give discovered check. This is possible only
-            // if the pawn is not on the same file as the enemy king, because we
-            // don't generate captures. Note that a possible discovery check
-            // promotion has been already generated amongst the captures.
-            Bitboard dcCandidateQuiets = pos.blockers_for_king(Them) & pawnsNotOn7;
-            if (dcCandidateQuiets)
-            {
-                Bitboard dc1 = shift<Up>(dcCandidateQuiets) & emptySquares & ~file_bb(ksq);
-                Bitboard dc2 = shift<Up>(dc1 & TRank3BB) & emptySquares;
-
-                b1 |= dc1;
-                b2 |= dc2;
-            }
+            // 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<KING>(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<Up + Up>(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);
         }
     }
@@ -119,41 +102,38 @@ namespace {
     // Promotions and underpromotions
     if (pawnsOn7)
     {
-        if (Type == CAPTURES)
-            emptySquares = ~pos.pieces();
-
-        if (Type == EVASIONS)
-            emptySquares &= target;
-
         Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
-        Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
-        Bitboard b3 = shift<Up     >(pawnsOn7) & emptySquares;
+        Bitboard b2 = shift<UpLeft>(pawnsOn7) & enemies;
+        Bitboard b3 = shift<Up>(pawnsOn7) & emptySquares;
+
+        if constexpr (Type == EVASIONS)
+            b3 &= target;
 
         while (b1)
-            moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(&b1), ksq);
+            moveList = make_promotions<Type, UpRight, true>(moveList, pop_lsb(b1));
 
         while (b2)
-            moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(&b2), ksq);
+            moveList = make_promotions<Type, UpLeft, true>(moveList, pop_lsb(b2));
 
         while (b3)
-            moveList = make_promotions<Type, Up     >(moveList, pop_lsb(&b3), ksq);
+            moveList = make_promotions<Type, Up, false>(moveList, pop_lsb(b3));
     }
 
-    // Standard and en-passant captures
-    if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+    // Standard and en passant captures
+    if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
     {
         Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
-        Bitboard b2 = shift<UpLeft >(pawnsNotOn7) & enemies;
+        Bitboard b2 = shift<UpLeft>(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);
         }
 
@@ -161,210 +141,140 @@ namespace {
         {
             assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6));
 
-            // An en passant capture can be an evasion only if the checking piece
-            // is the double pushed pawn and so is in the target. Otherwise this
-            // is a discovery check and we are forced to do otherwise.
-            if (Type == EVASIONS && !(target & (pos.ep_square() - Up)))
+            // An en passant capture cannot resolve a discovered check
+            if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
                 return moveList;
 
-            b1 = pawnsNotOn7 & pos.attacks_from<PAWN>(pos.ep_square(), Them);
+            b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
 
             assert(b1);
 
             while (b1)
-                *moveList++ = make<ENPASSANT>(pop_lsb(&b1), pos.ep_square());
+                *moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
         }
     }
 
     return moveList;
-  }
+}
 
 
-  template<PieceType Pt, bool Checks>
-  ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Color us,
-                          Bitboard target) {
+template<Color Us, PieceType Pt, bool Checks>
+ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
 
     static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
 
-    const Square* pl = pos.squares<Pt>(us);
+    Bitboard bb = pos.pieces(Us, Pt);
 
-    for (Square from = *pl; from != SQ_NONE; from = *++pl)
+    while (bb)
     {
-        if (Checks)
-        {
-            if (    (Pt == BISHOP || Pt == ROOK || Pt == QUEEN)
-                && !(PseudoAttacks[Pt][from] & target & pos.check_squares(Pt)))
-                continue;
-
-            if (pos.blockers_for_king(~us) & from)
-                continue;
-        }
-
-        Bitboard b = pos.attacks_from<Pt>(from) & target;
+        Square   from = pop_lsb(bb);
+        Bitboard b    = attacks_bb<Pt>(from, pos.pieces()) & target;
 
-        if (Checks)
+        // To check, you either move freely a blocker or make a direct check.
+        if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from)))
             b &= pos.check_squares(Pt);
 
         while (b)
-            *moveList++ = make_move(from, pop_lsb(&b));
+            *moveList++ = make_move(from, pop_lsb(b));
     }
 
     return moveList;
-  }
+}
 
 
-  template<Color Us, GenType Type>
-  ExtMove* generate_all(const Position& pos, ExtMove* moveList, Bitboard target) {
+template<Color Us, GenType Type>
+ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
 
-    constexpr CastlingRights OO  = Us & KING_SIDE;
-    constexpr CastlingRights OOO = Us & QUEEN_SIDE;
-    constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations
+    static_assert(Type != LEGAL, "Unsupported type in generate_all()");
 
-    moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
-    moveList = generate_moves<KNIGHT, Checks>(pos, moveList, Us, target);
-    moveList = generate_moves<BISHOP, Checks>(pos, moveList, Us, target);
-    moveList = generate_moves<  ROOK, Checks>(pos, moveList, Us, target);
-    moveList = generate_moves< QUEEN, Checks>(pos, moveList, Us, target);
+    constexpr bool Checks = Type == QUIET_CHECKS;  // Reduce template instantiations
+    const Square   ksq    = pos.square<KING>(Us);
+    Bitboard       target;
 
-    if (Type != QUIET_CHECKS && Type != EVASIONS)
+    // Skip generating non-king moves when in double check
+    if (Type != EVASIONS || !more_than_one(pos.checkers()))
     {
-        Square ksq = pos.square<KING>(Us);
-        Bitboard b = pos.attacks_from<KING>(ksq) & target;
-        while (b)
-            *moveList++ = make_move(ksq, pop_lsb(&b));
+        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<Us, Type>(pos, moveList, target);
+        moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
+        moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
+        moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
+        moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
+    }
 
-        if (Type != CAPTURES && pos.can_castle(CastlingRights(OO | OOO)))
-        {
-            if (!pos.castling_impeded(OO) && pos.can_castle(OO))
-                *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OO));
+    if (!Checks || pos.blockers_for_king(~Us) & ksq)
+    {
+        Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
+        if (Checks)
+            b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
 
-            if (!pos.castling_impeded(OOO) && pos.can_castle(OOO))
-                *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OOO));
-        }
+        while (b)
+            *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})
+                if (!pos.castling_impeded(cr) && pos.can_castle(cr))
+                    *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
     }
 
     return moveList;
-  }
-
-} // namespace
+}
 
+}  // namespace
 
-/// <CAPTURES>     Generates all pseudo-legal captures and queen promotions
-/// <QUIETS>       Generates all pseudo-legal non-captures and underpromotions
-/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
-///
-/// Returns a pointer to the end of the move list.
 
+// <CAPTURES>     Generates all pseudo-legal captures plus queen promotions
+// <QUIETS>       Generates all pseudo-legal non-captures and underpromotions
+// <EVASIONS>     Generates all pseudo-legal check evasions
+// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
+// <QUIET_CHECKS> Generates all pseudo-legal non-captures giving check,
+//                except castling and promotions
+//
+// Returns a pointer to the end of the move list.
 template<GenType Type>
 ExtMove* generate(const Position& pos, ExtMove* moveList) {
 
-  static_assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS, "Unsupported type in generate()");
-  assert(!pos.checkers());
-
-  Color us = pos.side_to_move();
+    static_assert(Type != LEGAL, "Unsupported type in generate()");
+    assert((Type == EVASIONS) == bool(pos.checkers()));
 
-  Bitboard target =  Type == CAPTURES     ?  pos.pieces(~us)
-                   : Type == QUIETS       ? ~pos.pieces()
-                   : Type == NON_EVASIONS ? ~pos.pieces(us) : 0;
+    Color us = pos.side_to_move();
 
-  return us == WHITE ? generate_all<WHITE, Type>(pos, moveList, target)
-                     : generate_all<BLACK, Type>(pos, moveList, target);
+    return us == WHITE ? generate_all<WHITE, Type>(pos, moveList)
+                       : generate_all<BLACK, Type>(pos, moveList);
 }
 
 // Explicit template instantiations
 template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
 template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
+template ExtMove* generate<EVASIONS>(const Position&, ExtMove*);
+template ExtMove* generate<QUIET_CHECKS>(const Position&, ExtMove*);
 template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
 
 
-/// generate<QUIET_CHECKS> generates all pseudo-legal non-captures and knight
-/// underpromotions that give check. Returns a pointer to the end of the move list.
-template<>
-ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
-
-  assert(!pos.checkers());
-
-  Color us = pos.side_to_move();
-  Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us);
-
-  while (dc)
-  {
-     Square from = pop_lsb(&dc);
-     PieceType pt = type_of(pos.piece_on(from));
-
-     if (pt == PAWN)
-         continue; // Will be generated together with direct checks
-
-     Bitboard b = pos.attacks_from(pt, from) & ~pos.pieces();
+// generate<LEGAL> generates all the legal moves in the given position
 
-     if (pt == KING)
-         b &= ~PseudoAttacks[QUEEN][pos.square<KING>(~us)];
-
-     while (b)
-         *moveList++ = make_move(from, pop_lsb(&b));
-  }
-
-  return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList, ~pos.pieces())
-                     : generate_all<BLACK, QUIET_CHECKS>(pos, moveList, ~pos.pieces());
-}
-
-
-/// generate<EVASIONS> generates all pseudo-legal check evasions when the side
-/// to move is in check. Returns a pointer to the end of the move list.
 template<>
-ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
-
-  assert(pos.checkers());
-
-  Color us = pos.side_to_move();
-  Square ksq = pos.square<KING>(us);
-  Bitboard sliderAttacks = 0;
-  Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN);
-
-  // Find all the squares attacked by slider checkers. We will remove them from
-  // the king evasions in order to skip known illegal moves, which avoids any
-  // useless legality checks later on.
-  while (sliders)
-  {
-      Square checksq = pop_lsb(&sliders);
-      sliderAttacks |= LineBB[checksq][ksq] ^ checksq;
-  }
-
-  // Generate evasions for king, capture and non capture moves
-  Bitboard b = pos.attacks_from<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
-  while (b)
-      *moveList++ = make_move(ksq, pop_lsb(&b));
-
-  if (more_than_one(pos.checkers()))
-      return moveList; // Double check, only a king move can save the day
-
-  // Generate blocking evasions or captures of the checking piece
-  Square checksq = lsb(pos.checkers());
-  Bitboard target = between_bb(checksq, ksq) | checksq;
-
-  return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList, target)
-                     : generate_all<BLACK, EVASIONS>(pos, moveList, target);
-}
-
+ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
 
-/// generate<LEGAL> generates all the legal moves in the given position
+    Color    us     = pos.side_to_move();
+    Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
+    Square   ksq    = pos.square<KING>(us);
+    ExtMove* cur    = moveList;
 
-template<>
-ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
+    moveList =
+      pos.checkers() ? generate<EVASIONS>(pos, moveList) : generate<NON_EVASIONS>(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;
 
-  Color us = pos.side_to_move();
-  Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
-  Square ksq = pos.square<KING>(us);
-  ExtMove* cur = moveList;
-
-  moveList = pos.checkers() ? generate<EVASIONS    >(pos, moveList)
-                            : generate<NON_EVASIONS>(pos, moveList);
-  while (cur != moveList)
-      if (   (pinned || from_sq(*cur) == ksq || type_of(*cur) == ENPASSANT)
-          && !pos.legal(*cur))
-          *cur = (--moveList)->move;
-      else
-          ++cur;
-
-  return moveList;
+    return moveList;
 }
+
+}  // namespace Stockfish
index c2e7c3f1101bad9f5ec2d9ffcf85e956b70abb9c..9a39d1c50ea299ce53c44d1de7e38b78ee9556ae 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 #ifndef MOVEGEN_H_INCLUDED
 #define MOVEGEN_H_INCLUDED
 
-#include <algorithm>
+#include <algorithm>  // IWYU pragma: keep
+#include <cstddef>
 
 #include "types.h"
 
+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<GenType>
 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<GenType T>
 struct MoveList {
 
-  explicit MoveList(const Position& pos) : last(generate<T>(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<T>(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;
 };
 
-#endif // #ifndef MOVEGEN_H_INCLUDED
+}  // namespace Stockfish
+
+#endif  // #ifndef MOVEGEN_H_INCLUDED
index 025f5b82cdeb2ae808d23c0e75a955862b48524b..0267a8e2e6f0418074bfbc172bf72335651dfb50 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "movepick.h"
+
+#include <algorithm>
 #include <cassert>
+#include <iterator>
+#include <utility>
 
-#include "movepick.h"
+#include "bitboard.h"
+#include "position.h"
 
-namespace {
+namespace Stockfish {
 
-  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
-  };
+namespace {
 
-  // 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) {
+enum Stages {
+    // generate main search moves
+    MAIN_TT,
+    CAPTURE_INIT,
+    GOOD_CAPTURE,
+    REFUTATION,
+    QUIET_INIT,
+    QUIET,
+    BAD_CAPTURE,
+
+    // 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
+};
+
+// 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) {
 
     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
-
-
-/// Constructors of the MovePicker class. As arguments we pass information
-/// to help it to 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.
-
-/// 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, Move* killers)
-           : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
-             refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) {
-
-  assert(d > 0);
-
-  stage = pos.checkers() ? EVASION_TT : MAIN_TT;
-  ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
-  stage += (ttMove == MOVE_NONE);
 }
 
-/// 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), recaptureSquare(rs), depth(d) {
-
-  assert(d <= 0);
-
-  stage = pos.checkers() ? EVASION_TT : QSEARCH_TT;
-  ttMove =   ttm
-          && (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
-          && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
-  stage += (ttMove == MOVE_NONE);
+}  // 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.
+
+// MovePicker constructor for the main search
+MovePicker::MovePicker(const Position&              p,
+                       Move                         ttm,
+                       Depth                        d,
+                       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) {
+    assert(d > 0);
+
+    stage = (pos.checkers() ? EVASION_TT : MAIN_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), threshold(th) {
-
-  assert(!pos.checkers());
+// Constructor for quiescence search
+MovePicker::MovePicker(const Position&              p,
+                       Move                         ttm,
+                       Depth                        d,
+                       const ButterflyHistory*      mh,
+                       const CapturePieceToHistory* cph,
+                       const PieceToHistory**       ch,
+                       const PawnHistory*           ph) :
+    pos(p),
+    mainHistory(mh),
+    captureHistory(cph),
+    continuationHistory(ch),
+    pawnHistory(ph),
+    ttMove(ttm),
+    depth(d) {
+    assert(d <= 0);
+
+    stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm));
+}
 
-  stage = PROBCUT_TT;
-  ttMove =   ttm
-          && pos.capture(ttm)
-          && pos.pseudo_legal(ttm)
-          && pos.see_ge(ttm, threshold) ? ttm : MOVE_NONE;
-  stage += (ttMove == MOVE_NONE);
+// 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());
+
+    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
-/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring
-/// captures with a good history. Quiets moves are ordered using the histories.
+// 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<GenType Type>
 void MovePicker::score() {
 
-  static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
-
-  for (auto& m : *this)
-      if (Type == CAPTURES)
-          m.value =  int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6
-                   + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
-
-      else if (Type == QUIETS)
-          m.value =      (*mainHistory)[pos.side_to_move()][from_to(m)]
-                   + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
-                   + 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
-                   + 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
-                   +     (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)];
-
-      else // Type == EVASIONS
-      {
-          if (pos.capture(m))
-              m.value =  PieceValue[MG][pos.piece_on(to_sq(m))]
-                       - Value(type_of(pos.moved_piece(m)));
-          else
-              m.value =  (*mainHistory)[pos.side_to_move()][from_to(m)]
-                       + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
-                       - (1 << 28);
-      }
+    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();
+
+        threatenedByPawn = pos.attacks_by<PAWN>(~us);
+        threatenedByMinor =
+          pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
+        threatenedByRook = pos.attacks_by<ROOK>(~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);
+    }
+
+    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);
+
+            // 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];
+
+            // 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))
+                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)]
+                        + (*pawnHistory)[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)];
+        }
 }
 
-/// MovePicker::select() returns the next move satisfying a predicate function.
-/// It never returns the TT move.
+// Returns the next move satisfying a predicate function.
+// It never returns the TT move.
 template<MovePicker::PickType T, typename Pred>
 Move MovePicker::select(Pred filter) {
 
-  while (cur < endMoves)
-  {
-      if (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
-/// 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.
+// 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:
-  switch (stage) {
-
-  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<CAPTURES>(pos, cur);
-
-      score<CAPTURES>();
-      ++stage;
-      goto top;
-
-  case GOOD_CAPTURE:
-      if (select<Best>([&](){
-                       return pos.see_ge(*cur, Value(-55 * cur->value / 1024)) ?
-                              // 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);
-
-      // 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 */
-
-  case REFUTATION:
-      if (select<Next>([&](){ return    *cur != MOVE_NONE
-                                    && !pos.capture(*cur)
-                                    &&  pos.pseudo_legal(*cur); }))
-          return *(cur - 1);
-      ++stage;
-      /* fallthrough */
-
-  case QUIET_INIT:
-      if (!skipQuiets)
-      {
-          cur = endBadCaptures;
-          endMoves = generate<QUIETS>(pos, cur);
-
-          score<QUIETS>();
-          partial_insertion_sort(cur, endMoves, -3000 * depth);
-      }
-
-      ++stage;
-      /* fallthrough */
-
-  case QUIET:
-      if (   !skipQuiets
-          && select<Next>([&](){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;
-
-      ++stage;
-      /* fallthrough */
-
-  case BAD_CAPTURE:
-      return select<Next>([](){ return true; });
-
-  case EVASION_INIT:
-      cur = moves;
-      endMoves = generate<EVASIONS>(pos, cur);
-
-      score<EVASIONS>();
-      ++stage;
-      /* fallthrough */
-
-  case EVASION:
-      return select<Best>([](){ return true; });
-
-  case PROBCUT:
-      return select<Best>([&](){ return pos.see_ge(*cur, threshold); });
-
-  case QCAPTURE:
-      if (select<Best>([&](){ 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;
-
-      ++stage;
-      /* fallthrough */
-
-  case QCHECK_INIT:
-      cur = moves;
-      endMoves = generate<QUIET_CHECKS>(pos, cur);
-
-      ++stage;
-      /* fallthrough */
-
-  case QCHECK:
-      return select<Next>([](){ return true; });
-  }
-
-  assert(false);
-  return MOVE_NONE; // Silence warning
+    switch (stage)
+    {
+
+    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<CAPTURES>(pos, cur);
+
+        score<CAPTURES>();
+        partial_insertion_sort(cur, endMoves, std::numeric_limits<int>::min());
+        ++stage;
+        goto top;
+
+    case GOOD_CAPTURE :
+        if (select<Next>([&]() {
+                // Move losing capture to endBadCaptures to be tried later
+                return pos.see_ge(*cur, Value(-cur->value)) ? true
+                                                            : (*endBadCaptures++ = *cur, false);
+            }))
+            return *(cur - 1);
+
+        // 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;
+
+        ++stage;
+        [[fallthrough]];
+
+    case REFUTATION :
+        if (select<Next>([&]() {
+                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<QUIETS>(pos, cur);
+
+            score<QUIETS>();
+            partial_insertion_sort(cur, endMoves, -1960 - 3130 * depth);
+        }
+
+        ++stage;
+        [[fallthrough]];
+
+    case QUIET :
+        if (!skipQuiets && select<Next>([&]() {
+                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;
+
+        ++stage;
+        [[fallthrough]];
+
+    case BAD_CAPTURE :
+        return select<Next>([]() { return true; });
+
+    case EVASION_INIT :
+        cur      = moves;
+        endMoves = generate<EVASIONS>(pos, cur);
+
+        score<EVASIONS>();
+        ++stage;
+        [[fallthrough]];
+
+    case EVASION :
+        return select<Best>([]() { return true; });
+
+    case PROBCUT :
+        return select<Next>([&]() { return pos.see_ge(*cur, threshold); });
+
+    case QCAPTURE :
+        if (select<Next>([]() { return true; }))
+            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;
+
+        ++stage;
+        [[fallthrough]];
+
+    case QCHECK_INIT :
+        cur      = moves;
+        endMoves = generate<QUIET_CHECKS>(pos, cur);
+
+        ++stage;
+        [[fallthrough]];
+
+    case QCHECK :
+        return select<Next>([]() { return true; });
+    }
+
+    assert(false);
+    return MOVE_NONE;  // Silence warning
 }
+
+}  // namespace Stockfish
index cdedc9b616f9f78c6a652626c0c0a5ba096ed71b..5077f4e3b729b9aa1315d578e62dacad6a2db4e5 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 #define MOVEPICK_H_INCLUDED
 
 #include <array>
+#include <cassert>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
 #include <limits>
-#include <type_traits>
+#include <type_traits>  // IWYU pragma: keep
 
 #include "movegen.h"
-#include "position.h"
 #include "types.h"
+#include "position.h"
 
-/// 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.
-template<typename T, int D>
-class StatsEntry {
+namespace Stockfish {
 
-  T entry;
+constexpr int PAWN_HISTORY_SIZE = 512;  // has to be a power of 2
 
-public:
-  void operator=(const T& v) { entry = v; }
-  T* operator&() { return &entry; }
-  T* operator->() { return &entry; }
-  operator const T&() const { return entry; }
+static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0,
+              "PAWN_HISTORY_SIZE has to be a power of 2");
 
-  void operator<<(int bonus) {
-    assert(abs(bonus) <= D); // Ensure range is [-D, D]
-    static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
+inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); }
 
-    entry += bonus - entry * abs(bonus) / D;
+// 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<typename T, int D>
+class StatsEntry {
 
-    assert(abs(entry) <= D);
-  }
-};
+    T entry;
 
-/// 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.
-template <typename T, int D, int Size, int... Sizes>
-struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
-{
-  typedef Stats<T, D, Size, Sizes...> stats;
-
-  void fill(const T& v) {
-
-    // For standard-layout 'this' points to first struct member
-    assert(std::is_standard_layout<stats>::value);
-
-    typedef StatsEntry<T, D> entry;
-    entry* p = reinterpret_cast<entry*>(this);
-    std::fill(p, p + sizeof(*this) / sizeof(entry), v);
-  }
-};
+   public:
+    void operator=(const T& v) { entry = v; }
+    T*   operator&() { return &entry; }
+    T*   operator->() { return &entry; }
+    operator const T&() const { return entry; }
 
-template <typename T, int D, int Size>
-struct Stats<T, D, Size> : public std::array<StatsEntry<T, D>, Size> {};
+    void operator<<(int bonus) {
+        assert(std::abs(bonus) <= D);  // Ensure range is [-D, D]
+        static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
 
-/// In stats table, D=0 means that the template parameter is not used
-enum StatsParams { NOT_USED = 0 };
-enum StatsType { NoCaptures, Captures };
+        entry += bonus - entry * std::abs(bonus) / D;
 
-/// 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
-typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
+        assert(std::abs(entry) <= D);
+    }
+};
 
-/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
-/// move, see www.chessprogramming.org/Countermove_Heuristic
-typedef Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
+// 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<typename T, int D, int Size, int... Sizes>
+struct Stats: public std::array<Stats<T, D, Sizes...>, Size> {
+    using stats = Stats<T, D, Size, Sizes...>;
 
-/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
-typedef Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB> CapturePieceToHistory;
+    void fill(const T& v) {
 
-/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
-typedef Stats<int16_t, 29952, PIECE_NB, SQUARE_NB> PieceToHistory;
+        // For standard-layout 'this' points to the first struct member
+        assert(std::is_standard_layout_v<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.
-typedef Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHistory;
+        using entry = StatsEntry<T, D>;
+        entry* p    = reinterpret_cast<entry*>(this);
+        std::fill(p, p + sizeof(*this) / sizeof(entry), v);
+    }
+};
 
+template<typename T, int D, int Size>
+struct Stats<T, D, Size>: public std::array<StatsEntry<T, D>, Size> {};
 
-/// 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.
+// 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)
+using ButterflyHistory = Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)>;
+
+// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
+// move, see www.chessprogramming.org/Countermove_Heuristic
+using CounterMoveHistory = Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB>;
+
+// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
+using CapturePieceToHistory = Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
+
+// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
+using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>;
+
+// 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<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>;
+
+// PawnHistory is addressed by the pawn structure and a move's [piece][to]
+using PawnHistory = Stats<int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>;
+
+// 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 };
-
-public:
-  MovePicker(const MovePicker&) = delete;
-  MovePicker& operator=(const MovePicker&) = delete;
-  MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
-  MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
-                                           const CapturePieceToHistory*,
-                                           const PieceToHistory**,
-                                           Square);
-  MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
-                                           const CapturePieceToHistory*,
-                                           const PieceToHistory**,
-                                           Move,
-                                           Move*);
-  Move next_move(bool skipQuiets = false);
-
-private:
-  template<PickType T, typename Pred> Move select(Pred);
-  template<GenType> 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];
+    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**,
+               const PawnHistory*,
+               Move,
+               const Move*);
+    MovePicker(const Position&,
+               Move,
+               Depth,
+               const ButterflyHistory*,
+               const CapturePieceToHistory*,
+               const PieceToHistory**,
+               const PawnHistory*);
+    MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
+    Move next_move(bool skipQuiets = false);
+
+   private:
+    template<PickType T, typename Pred>
+    Move select(Pred);
+    template<GenType>
+    void     score();
+    ExtMove* begin() { return cur; }
+    ExtMove* end() { return endMoves; }
+
+    const Position&              pos;
+    const ButterflyHistory*      mainHistory;
+    const CapturePieceToHistory* captureHistory;
+    const PieceToHistory**       continuationHistory;
+    const PawnHistory*           pawnHistory;
+    Move                         ttMove;
+    ExtMove                      refutations[3], *cur, *endMoves, *endBadCaptures;
+    int                          stage;
+    Value                        threshold;
+    Depth                        depth;
+    ExtMove                      moves[MAX_MOVES];
 };
 
-#endif // #ifndef MOVEPICK_H_INCLUDED
+}  // namespace Stockfish
+
+#endif  // #ifndef MOVEPICK_H_INCLUDED
diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp
new file mode 100644 (file)
index 0000000..e7339c1
--- /dev/null
@@ -0,0 +1,429 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// Code for calculating NNUE evaluation function
+
+#include "evaluate_nnue.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <string_view>
+
+#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<FeatureTransformer> featureTransformer;
+
+// Evaluation function
+AlignedPtr<Network> network[LayerStacks];
+
+// Evaluation function file name
+std::string fileName;
+std::string netDescription;
+
+namespace Detail {
+
+// Initialize the evaluation function parameters
+template<typename T>
+void initialize(AlignedPtr<T>& pointer) {
+
+    pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
+    std::memset(pointer.get(), 0, sizeof(T));
+}
+
+template<typename T>
+void initialize(LargePagePtr<T>& pointer) {
+
+    static_assert(alignof(T) <= 4096,
+                  "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
+    pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
+    std::memset(pointer.get(), 0, sizeof(T));
+}
+
+// Read evaluation function parameters
+template<typename T>
+bool read_parameters(std::istream& stream, T& reference) {
+
+    std::uint32_t header;
+    header = read_little_endian<std::uint32_t>(stream);
+    if (!stream || header != T::get_hash_value())
+        return false;
+    return reference.read_parameters(stream);
+}
+
+// Write evaluation function parameters
+template<typename T>
+bool write_parameters(std::ostream& stream, const T& reference) {
+
+    write_little_endian<std::uint32_t>(stream, T::get_hash_value());
+    return reference.write_parameters(stream);
+}
+
+}  // namespace Detail
+
+
+// Initialize the evaluation function parameters
+static void initialize() {
+
+    Detail::initialize(featureTransformer);
+    for (std::size_t i = 0; i < LayerStacks; ++i)
+        Detail::initialize(network[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<std::uint32_t>(stream);
+    *hashValue = read_little_endian<std::uint32_t>(stream);
+    size       = read_little_endian<std::uint32_t>(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<std::uint32_t>(stream, Version);
+    write_little_endian<std::uint32_t>(stream, hashValue);
+    write_little_endian<std::uint32_t>(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) {
+
+    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;
+    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();
+}
+
+// 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;
+    for (std::size_t i = 0; i < LayerStacks; ++i)
+        if (!Detail::write_parameters(stream, *(network[i])))
+            return false;
+    return bool(stream);
+}
+
+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) {
+
+    // 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<alignment>(&transformedFeaturesUnaligned[0]);
+#else
+    alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
+#endif
+
+    ASSERT_ALIGNED(transformedFeatures, alignment);
+
+    const int  bucket     = (pos.count<ALL_PIECES>() - 1) / 4;
+    const auto psqt       = featureTransformer->transform(pos, transformedFeatures, bucket);
+    const auto positional = network[bucket]->propagate(transformedFeatures);
+
+    if (complexity)
+        *complexity = std::abs(psqt - positional) / OutputScale;
+
+    // Give more value to positional evaluation when adjusted flag is set
+    if (adjusted)
+        return static_cast<Value>(((1024 - delta) * psqt + (1024 + delta) * positional)
+                                  / (1024 * OutputScale));
+    else
+        return static_cast<Value>((psqt + positional) / OutputScale);
+}
+
+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<alignment>(&transformedFeaturesUnaligned[0]);
+#else
+    alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
+#endif
+
+    ASSERT_ALIGNED(transformedFeatures, alignment);
+
+    NnueEvalTrace t{};
+    t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
+    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<Value>(materialist / OutputScale);
+        t.positional[bucket] = static_cast<Value>(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->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);
+        }
+
+    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
+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) {
+
+    if (fileName.empty())
+        return false;
+
+    return write_parameters(stream);
+}
+
+// Save eval, to a file given by its name
+bool save_eval(const std::optional<std::string>& filename) {
+
+    std::string actualFilename;
+    std::string msg;
+
+    if (filename.has_value())
+        actualFilename = filename.value();
+    else
+    {
+        if (currentEvalFileName != EvalFileDefaultName)
+        {
+            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 = EvalFileDefaultName;
+    }
+
+    std::ofstream stream(actualFilename, std::ios_base::binary);
+    bool          saved = save_eval(stream);
+
+    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
new file mode 100644 (file)
index 0000000..6edc212
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// header used in NNUE evaluation function
+
+#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
+#define NNUE_EVALUATE_NNUE_H_INCLUDED
+
+#include <cstdint>
+#include <iosfwd>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "../misc.h"
+#include "nnue_architecture.h"
+#include "nnue_feature_transformer.h"
+
+namespace Stockfish {
+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();
+
+
+// Deleter for automating release of memory area
+template<typename T>
+struct AlignedDeleter {
+    void operator()(T* ptr) const {
+        ptr->~T();
+        std_aligned_free(ptr);
+    }
+};
+
+template<typename T>
+struct LargePageDeleter {
+    void operator()(T* ptr) const {
+        ptr->~T();
+        aligned_large_pages_free(ptr);
+    }
+};
+
+template<typename T>
+using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
+
+template<typename T>
+using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
+
+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<std::string>& filename);
+
+}  // namespace Stockfish::Eval::NNUE
+
+#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
new file mode 100644 (file)
index 0000000..6d1b60c
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKAv2_hm of NNUE evaluation function
+
+#include "half_ka_v2_hm.h"
+
+#include "../../bitboard.h"
+#include "../../position.h"
+#include "../../types.h"
+#include "../nnue_common.h"
+
+namespace Stockfish::Eval::NNUE::Features {
+
+// Index of a feature for a given king position and another piece on some square
+template<Color Perspective>
+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<Color Perspective>
+void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) {
+    Square   ksq = pos.square<KING>(Perspective);
+    Bitboard bb  = pos.pieces();
+    while (bb)
+    {
+        Square s = pop_lsb(bb);
+        active.push_back(make_index<Perspective>(s, pos.piece_on(s), ksq));
+    }
+}
+
+// Explicit template instantiations
+template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
+template void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
+
+// Get a list of indices for recently changed features
+template<Color Perspective>
+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<Perspective>(dp.from[i], dp.piece[i], ksq));
+        if (dp.to[i] != SQ_NONE)
+            added.push_back(make_index<Perspective>(dp.to[i], dp.piece[i], ksq));
+    }
+}
+
+// Explicit template instantiations
+template void HalfKAv2_hm::append_changed_indices<WHITE>(Square            ksq,
+                                                         const DirtyPiece& dp,
+                                                         IndexList&        removed,
+                                                         IndexList&        added);
+template void HalfKAv2_hm::append_changed_indices<BLACK>(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::refresh_cost(const Position& pos) { return pos.count<ALL_PIECES>(); }
+
+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
new file mode 100644 (file)
index 0000000..540ff89
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKP of NNUE evaluation function
+
+#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
+#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
+
+#include <cstdint>
+
+#include "../../misc.h"
+#include "../../types.h"
+#include "../nnue_common.h"
+
+namespace Stockfish {
+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 {
+
+    // 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
+    };
+
+    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}};
+
+    // Index of a feature for a given king position and another piece on some square
+    template<Color Perspective>
+    static IndexType make_index(Square s, Piece pc, Square ksq);
+
+   public:
+    // Feature name
+    static constexpr const char* Name = "HalfKAv2_hm(Friend)";
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t HashValue = 0x7f234cb8u;
+
+    // Number of feature dimensions
+    static constexpr IndexType Dimensions =
+      static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(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),
+        B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
+        B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
+        B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
+        B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
+        B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
+        B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) },
+      { B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0),
+        B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
+        B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
+        B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
+        B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
+        B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
+        B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
+        B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) }
+    };
+    // 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,
+        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+        SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 },
+      { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+        SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+        SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+        SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+        SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+        SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+        SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+        SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 }
+    };
+    // clang-format on
+
+    // Maximum number of simultaneously active features.
+    static constexpr IndexType MaxActiveDimensions = 32;
+    using IndexList                                = ValueList<IndexType, MaxActiveDimensions>;
+
+    // Get a list of indices for active features
+    template<Color Perspective>
+    static void append_active_indices(const Position& pos, IndexList& active);
+
+    // Get a list of indices for recently changed features
+    template<Color Perspective>
+    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.
+    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.
+    static bool requires_refresh(const StateInfo* st, Color perspective);
+};
+
+}  // namespace Stockfish::Eval::NNUE::Features
+
+#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
new file mode 100644 (file)
index 0000000..44fa5d0
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer AffineTransform of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
+#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
+
+#include <cstdint>
+#include <iostream>
+
+#include "../nnue_common.h"
+#include "simd.h"
+
+/*
+  This file contains the definition for a fully connected layer (aka affine transform).
+
+    - 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
+    - inputs are processed in chunks of 4, weights are respectively transposed
+    - accumulation happens directly to int32s
+*/
+
+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<IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
+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<IndexType>(InputDimensions, 16) / 16;
+    const __m128i       Zeros       = _mm_setzero_si128();
+    const auto          inputVector = reinterpret_cast<const __m128i*>(input);
+
+        #elif defined(USE_NEON_DOTPROD)
+    constexpr IndexType NumChunks   = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
+    const auto          inputVector = reinterpret_cast<const int8x16_t*>(input);
+
+        #elif defined(USE_NEON)
+    constexpr IndexType NumChunks   = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
+    const auto          inputVector = reinterpret_cast<const int8x8_t*>(input);
+        #endif
+
+    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<const __m128i*>(&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<const int8x16_t*>(&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<const int8x8_t*>(&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
+    }
+    #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
+
+template<IndexType InDims, IndexType OutDims>
+class AffineTransform {
+   public:
+    // 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<IndexType>(InputDimensions, MaxSimdWidth);
+    static constexpr IndexType PaddedOutputDimensions =
+      ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
+
+    using OutputBuffer = OutputType[PaddedOutputDimensions];
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
+        std::uint32_t hashValue = 0xCC03DAE4u;
+        hashValue += OutputDimensions;
+        hashValue ^= prevHash >> 1;
+        hashValue ^= prevHash << 31;
+        return hashValue;
+    }
+
+    static 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);
+#else
+        return i;
+#endif
+    }
+
+    // Read network parameters
+    bool read_parameters(std::istream& stream) {
+        read_little_endian<BiasType>(stream, biases, OutputDimensions);
+        for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
+            weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
+
+        return !stream.fail();
+    }
+
+    // Write network parameters
+    bool write_parameters(std::ostream& stream) const {
+        write_little_endian<BiasType>(stream, biases, OutputDimensions);
+
+        for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
+            write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
+
+        return !stream.fail();
+    }
+    // Forward propagation
+    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
+        #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<IndexType>(InputDimensions, 8) / 4;
+            constexpr IndexType NumRegs   = OutputDimensions / OutputSimdWidth;
+
+            const auto   input32 = reinterpret_cast<const std::int32_t*>(input);
+            const vec_t* biasvec = reinterpret_cast<const vec_t*>(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<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
+                const auto col1 =
+                  reinterpret_cast<const vec_t*>(&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<vec_t*>(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 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
+        #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<const vec_t*>(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<const vec_t*>(&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
+        }
+#else
+        // Use old implementation for the other architectures.
+        affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
+          output, weights, biases, input);
+#endif
+    }
+
+   private:
+    using BiasType   = OutputType;
+    using WeightType = std::int8_t;
+
+    alignas(CacheLineSize) BiasType biases[OutputDimensions];
+    alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
+};
+
+}  // namespace Stockfish::Eval::NNUE::Layers
+
+#endif  // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h
new file mode 100644 (file)
index 0000000..6cb4d1a
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer AffineTransformSparseInput of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
+#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <iostream>
+
+#include "../../bitboard.h"
+#include "../nnue_common.h"
+#include "affine_transform.h"
+#include "simd.h"
+
+/*
+  This file contains the definition for a fully connected layer (aka affine transform) with block sparse input.
+*/
+
+namespace Stockfish::Eval::NNUE::Layers {
+
+#if (USE_SSSE3 | (USE_NEON >= 8))
+alignas(CacheLineSize) static inline const
+  std::array<std::array<std::uint16_t, 8>, 256> lookup_indices = []() {
+      std::array<std::array<std::uint16_t, 8>, 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<const IndexType InputDimensions>
+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
+    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;
+    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<const std::uint16_t*>(a))
+        #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast<std::uint16_t*>(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<IndexType>(InputSimdWidth, 8);
+    constexpr IndexType NumChunks       = InputDimensions / ChunkSize;
+    constexpr IndexType InputsPerChunk  = ChunkSize / InputSimdWidth;
+    constexpr IndexType OutputsPerChunk = ChunkSize / 8;
+
+    const auto     inputVector = reinterpret_cast<const vec_t*>(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<const vec128_t*>(&lookup_indices[lookup]));
+            vec128_storeu(reinterpret_cast<vec128_t*>(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
+#endif
+
+// Sparse input implementation
+template<IndexType InDims, IndexType OutDims>
+class AffineTransformSparseInput {
+   public:
+    // Input/output type
+    using InputType  = std::uint8_t;
+    using OutputType = std::int32_t;
+
+    // Number of input/output dimensions
+    static constexpr IndexType InputDimensions  = InDims;
+    static constexpr IndexType OutputDimensions = OutDims;
+
+    static_assert(OutputDimensions % 16 == 0,
+                  "Only implemented for OutputDimensions divisible by 16.");
+
+    static constexpr IndexType PaddedInputDimensions =
+      ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
+    static constexpr IndexType PaddedOutputDimensions =
+      ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
+
+#if (USE_SSSE3 | (USE_NEON >= 8))
+    static constexpr IndexType ChunkSize = 4;
+#else
+    static constexpr IndexType ChunkSize = 1;
+#endif
+
+    using OutputBuffer = OutputType[PaddedOutputDimensions];
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
+        std::uint32_t hashValue = 0xCC03DAE4u;
+        hashValue += OutputDimensions;
+        hashValue ^= prevHash >> 1;
+        hashValue ^= prevHash << 31;
+        return hashValue;
+    }
+
+    static 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) {
+#if (USE_SSSE3 | (USE_NEON >= 8))
+        return get_weight_index_scrambled(i);
+#else
+        return i;
+#endif
+    }
+
+    // Read network parameters
+    bool read_parameters(std::istream& stream) {
+        read_little_endian<BiasType>(stream, biases, OutputDimensions);
+        for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
+            weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
+
+        return !stream.fail();
+    }
+
+    // Write network parameters
+    bool write_parameters(std::ostream& stream) const {
+        write_little_endian<BiasType>(stream, biases, OutputDimensions);
+
+        for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
+            write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
+
+        return !stream.fail();
+    }
+    // Forward propagation
+    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);
+
+        constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
+        constexpr IndexType NumRegs   = OutputDimensions / OutputSimdWidth;
+        std::uint16_t       nnz[NumChunks];
+        IndexType           count;
+
+        const auto input32 = reinterpret_cast<const std::int32_t*>(input);
+
+        // Find indices of nonzero 32bit blocks
+        find_nnz<NumChunks>(input32, nnz, count);
+
+        const outvec_t* biasvec = reinterpret_cast<const outvec_t*>(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<const invec_t*>(&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<outvec_t*>(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);
+#endif
+    }
+
+   private:
+    using BiasType   = OutputType;
+    using WeightType = std::int8_t;
+
+    alignas(CacheLineSize) BiasType biases[OutputDimensions];
+    alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
+};
+
+}  // namespace Stockfish::Eval::NNUE::Layers
+
+#endif  // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h
new file mode 100644 (file)
index 0000000..a3a0c1e
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer ClippedReLU of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
+#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
+
+#include <algorithm>
+#include <cstdint>
+#include <iosfwd>
+
+#include "../nnue_common.h"
+
+namespace Stockfish::Eval::NNUE::Layers {
+
+// Clipped ReLU
+template<IndexType InDims>
+class ClippedReLU {
+   public:
+    // Input/output type
+    using InputType  = std::int32_t;
+    using OutputType = std::uint8_t;
+
+    // Number of input/output dimensions
+    static constexpr IndexType InputDimensions  = InDims;
+    static constexpr IndexType OutputDimensions = InputDimensions;
+    static constexpr IndexType PaddedOutputDimensions =
+      ceil_to_multiple<IndexType>(OutputDimensions, 32);
+
+    using OutputBuffer = OutputType[PaddedOutputDimensions];
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
+        std::uint32_t hashValue = 0x538D24C7u;
+        hashValue += prevHash;
+        return hashValue;
+    }
+
+    // Read network parameters
+    bool read_parameters(std::istream&) { return true; }
+
+    // Write network parameters
+    bool write_parameters(std::ostream&) const { return true; }
+
+    // Forward propagation
+    void propagate(const InputType* input, OutputType* output) const {
+
+#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<const __m256i*>(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<const __m128i*>(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;
+
+    #ifdef USE_SSE41
+        const __m128i Zero = _mm_setzero_si128();
+    #else
+        const __m128i k0x80s = _mm_set1_epi8(-128);
+    #endif
+
+        const auto in  = reinterpret_cast<const __m128i*>(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<const int32x4_t*>(input);
+        const auto          out       = reinterpret_cast<int8x8_t*>(output);
+        for (IndexType i = 0; i < NumChunks; ++i)
+        {
+            int16x8_t  shifted;
+            const auto pack = reinterpret_cast<int16x4_t*>(&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<OutputType>(std::clamp(input[i] >> WeightScaleBits, 0, 127));
+        }
+    }
+};
+
+}  // namespace Stockfish::Eval::NNUE::Layers
+
+#endif  // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h
new file mode 100644 (file)
index 0000000..5425ca1
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef STOCKFISH_SIMD_H_INCLUDED
+#define STOCKFISH_SIMD_H_INCLUDED
+
+#if defined(USE_AVX2)
+    #include <immintrin.h>
+
+#elif defined(USE_SSE41)
+    #include <smmintrin.h>
+
+#elif defined(USE_SSSE3)
+    #include <tmmintrin.h>
+
+#elif defined(USE_SSE2)
+    #include <emmintrin.h>
+
+#elif defined(USE_NEON)
+    #include <arm_neon.h>
+#endif
+
+namespace Stockfish::Simd {
+
+#if defined(USE_AVX512)
+
+[[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]]
+        sum2 = [zmm2.i128[0], zmm2.i128[1], zmm2.i128[2], zmm2.i128[3]]
+        sum3 = [zmm3.i128[0], zmm3.i128[1], zmm3.i128[2], zmm3.i128[3]]
+
+      Returns:
+        ret = [
+          reduce_add_epi32(zmm0.i128[0]), reduce_add_epi32(zmm1.i128[0]), reduce_add_epi32(zmm2.i128[0]), reduce_add_epi32(zmm3.i128[0]),
+          reduce_add_epi32(zmm0.i128[1]), reduce_add_epi32(zmm1.i128[1]), reduce_add_epi32(zmm2.i128[1]), reduce_add_epi32(zmm3.i128[1]),
+          reduce_add_epi32(zmm0.i128[2]), reduce_add_epi32(zmm1.i128[2]), reduce_add_epi32(zmm2.i128[2]), reduce_add_epi32(zmm3.i128[2]),
+          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) {
+
+    __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 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);
+
+    return _mm512_add_epi32(sum0123a, sum0123b);
+}
+
+[[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
+}
+
+[[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)
+
+[[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) {
+
+    #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) {
+
+    #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)
+
+[[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) {
+
+    __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) {
+
+    __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) {
+
+    acc = vdotq_s32(acc, a, b);
+}
+#endif
+
+#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_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) {
+
+    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) {
+
+    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
diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h
new file mode 100644 (file)
index 0000000..f8e2d49
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer ClippedReLU of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
+#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
+
+#include <algorithm>
+#include <cstdint>
+#include <iosfwd>
+
+#include "../nnue_common.h"
+
+namespace Stockfish::Eval::NNUE::Layers {
+
+// Clipped ReLU
+template<IndexType InDims>
+class SqrClippedReLU {
+   public:
+    // Input/output type
+    using InputType  = std::int32_t;
+    using OutputType = std::uint8_t;
+
+    // Number of input/output dimensions
+    static constexpr IndexType InputDimensions  = InDims;
+    static constexpr IndexType OutputDimensions = InputDimensions;
+    static constexpr IndexType PaddedOutputDimensions =
+      ceil_to_multiple<IndexType>(OutputDimensions, 32);
+
+    using OutputBuffer = OutputType[PaddedOutputDimensions];
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
+        std::uint32_t hashValue = 0x538D24C7u;
+        hashValue += prevHash;
+        return hashValue;
+    }
+
+    // Read network parameters
+    bool read_parameters(std::istream&) { return true; }
+
+    // Write network parameters
+    bool write_parameters(std::ostream&) const { return true; }
+
+    // Forward propagation
+    void propagate(const InputType* input, OutputType* output) const {
+
+#if defined(USE_SSE2)
+        constexpr IndexType NumChunks = InputDimensions / 16;
+
+        static_assert(WeightScaleBits == 6);
+        const auto in  = reinterpret_cast<const __m128i*>(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);
+
+            _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
+        }
+        constexpr IndexType Start = NumChunks * 16;
+
+#else
+        constexpr IndexType Start = 0;
+#endif
+
+        for (IndexType i = Start; i < InputDimensions; ++i)
+        {
+            output[i] = static_cast<OutputType>(
+              // 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
diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h
new file mode 100644 (file)
index 0000000..2f1b1d3
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// Class for difference calculation of NNUE evaluation function
+
+#ifndef NNUE_ACCUMULATOR_H_INCLUDED
+#define NNUE_ACCUMULATOR_H_INCLUDED
+
+#include <cstdint>
+
+#include "nnue_architecture.h"
+#include "nnue_common.h"
+
+namespace Stockfish::Eval::NNUE {
+
+// 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];
+};
+
+}  // namespace Stockfish::Eval::NNUE
+
+#endif  // NNUE_ACCUMULATOR_H_INCLUDED
diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h
new file mode 100644 (file)
index 0000000..e4c308c
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// Input features and network structure used in NNUE evaluation function
+
+#ifndef NNUE_ARCHITECTURE_H_INCLUDED
+#define NNUE_ARCHITECTURE_H_INCLUDED
+
+#include <cstdint>
+#include <cstring>
+#include <iosfwd>
+
+#include "features/half_ka_v2_hm.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 "nnue_common.h"
+
+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;
+
+struct Network {
+    static constexpr int FC_0_OUTPUTS = 15;
+    static constexpr int FC_1_OUTPUTS = 32;
+
+    Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
+    Layers::SqrClippedReLU<FC_0_OUTPUTS + 1>                                           ac_sqr_0;
+    Layers::ClippedReLU<FC_0_OUTPUTS + 1>                                              ac_0;
+    Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS>                            fc_1;
+    Layers::ClippedReLU<FC_1_OUTPUTS>                                                  ac_1;
+    Layers::AffineTransform<FC_1_OUTPUTS, 1>                                           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;
+
+        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;
+    }
+
+    // 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);
+    }
+
+    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<IndexType>(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)); }
+        };
+
+#if defined(__clang__) && (__APPLE__)
+        // workaround for a bug reported with xcode 12
+        static thread_local auto tlsBuffer = std::make_unique<Buffer>();
+        // Access TLS only once, cache result.
+        Buffer& buffer = *tlsBuffer;
+#else
+        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);
+
+        // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in
+        // quantized form, but we want 1.0 to be equal to 600*OutputScale
+        std::int32_t fwdOut =
+          int(buffer.fc_0_out[FC_0_OUTPUTS]) * (600 * OutputScale) / (127 * (1 << WeightScaleBits));
+        std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
+
+        return outputValue;
+    }
+};
+
+}  // namespace Stockfish::Eval::NNUE
+
+#endif  // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h
new file mode 100644 (file)
index 0000000..f9cd7fb
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// Constants used in NNUE evaluation function
+
+#ifndef NNUE_COMMON_H_INCLUDED
+#define NNUE_COMMON_H_INCLUDED
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <iostream>
+#include <type_traits>
+
+#include "../misc.h"
+
+#if defined(USE_AVX2)
+    #include <immintrin.h>
+
+#elif defined(USE_SSE41)
+    #include <smmintrin.h>
+
+#elif defined(USE_SSSE3)
+    #include <tmmintrin.h>
+
+#elif defined(USE_SSE2)
+    #include <emmintrin.h>
+
+#elif defined(USE_NEON)
+    #include <arm_neon.h>
+#endif
+
+namespace Stockfish::Eval::NNUE {
+
+// 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;
+
+// Size of cache line (in bytes)
+constexpr std::size_t CacheLineSize = 64;
+
+constexpr const char        Leb128MagicString[]   = "COMPRESSED_LEB128";
+constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
+
+// SIMD width (in bytes)
+#if defined(USE_AVX2)
+constexpr std::size_t SimdWidth = 32;
+
+#elif defined(USE_SSE2)
+constexpr std::size_t SimdWidth = 16;
+
+#elif defined(USE_NEON)
+constexpr std::size_t SimdWidth = 16;
+#endif
+
+constexpr std::size_t MaxSimdWidth = 32;
+
+// 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<typename IntType>
+constexpr IntType ceil_to_multiple(IntType n, IntType base) {
+    return (n + base - 1) / base * base;
+}
+
+
+// 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<typename IntType>
+inline IntType read_little_endian(std::istream& stream) {
+    IntType result;
+
+    if (IsLittleEndian)
+        stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
+    else
+    {
+        std::uint8_t                  u[sizeof(IntType)];
+        std::make_unsigned_t<IntType> v = 0;
+
+        stream.read(reinterpret_cast<char*>(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));
+    }
+
+    return result;
+}
+
+
+// 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<typename IntType>
+inline void write_little_endian(std::ostream& stream, IntType value) {
+
+    if (IsLittleEndian)
+        stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
+    else
+    {
+        std::uint8_t                  u[sizeof(IntType)];
+        std::make_unsigned_t<IntType> v = value;
+
+        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);
+                v >>= 8;
+            }
+        }
+        u[i] = std::uint8_t(v);
+
+        stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
+    }
+}
+
+
+// Read integers in bulk from a little indian stream.
+// This reads N integers from stream s and put them in array out.
+template<typename IntType>
+inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
+    if (IsLittleEndian)
+        stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
+    else
+        for (std::size_t i = 0; i < count; ++i)
+            out[i] = read_little_endian<IntType>(stream);
+}
+
+
+// Write integers in bulk to a little indian stream.
+// This takes N integers from array values and writes them on stream s.
+template<typename IntType>
+inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
+    if (IsLittleEndian)
+        stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
+    else
+        for (std::size_t i = 0; i < count; ++i)
+            write_little_endian<IntType>(stream, values[i]);
+}
+
+
+// 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<typename IntType>
+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);
+
+    static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
+
+    const std::uint32_t BUF_SIZE = 4096;
+    std::uint8_t        buf[BUF_SIZE];
+
+    auto bytes_left = read_little_endian<std::uint32_t>(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<char*>(buf), std::min(bytes_left, BUF_SIZE));
+                buf_pos = 0;
+            }
+
+            std::uint8_t byte = buf[buf_pos++];
+            --bytes_left;
+            result |= (byte & 0x7f) << shift;
+            shift += 7;
+
+            if ((byte & 0x80) == 0)
+            {
+                out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0)
+                         ? result
+                         : result | ~((1 << shift) - 1);
+                break;
+            }
+        } while (shift < sizeof(IntType) * 8);
+    }
+
+    assert(bytes_left == 0);
+}
+
+
+// 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<typename IntType>
+inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
+
+    // Write our LEB128 magic string
+    stream.write(Leb128MagicString, Leb128MagicStringSize);
+
+    static_assert(std::is_signed_v<IntType>, "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);
+    }
+
+    write_little_endian(stream, byte_count);
+
+    const std::uint32_t BUF_SIZE = 4096;
+    std::uint8_t        buf[BUF_SIZE];
+    std::uint32_t       buf_pos = 0;
+
+    auto flush = [&]() {
+        if (buf_pos > 0)
+        {
+            stream.write(reinterpret_cast<char*>(buf), buf_pos);
+            buf_pos = 0;
+        }
+    };
+
+    auto write = [&](std::uint8_t byte) {
+        buf[buf_pos++] = byte;
+        if (buf_pos == BUF_SIZE)
+            flush();
+    };
+
+    for (std::size_t i = 0; i < count; ++i)
+    {
+        IntType value = values[i];
+        while (true)
+        {
+            std::uint8_t byte = value & 0x7f;
+            value >>= 7;
+            if ((byte & 0x40) == 0 ? value == 0 : value == -1)
+            {
+                write(byte);
+                break;
+            }
+            write(byte | 0x80);
+        }
+    }
+
+    flush();
+}
+
+}  // namespace Stockfish::Eval::NNUE
+
+#endif  // #ifndef NNUE_COMMON_H_INCLUDED
diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h
new file mode 100644 (file)
index 0000000..2af80f0
--- /dev/null
@@ -0,0 +1,716 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+// A class that converts the input features of the NNUE evaluation function
+
+#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
+#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <iosfwd>
+#include <utility>
+
+#include "../position.h"
+#include "../types.h"
+#include "nnue_accumulator.h"
+#include "nnue_architecture.h"
+#include "nnue_common.h"
+
+namespace Stockfish::Eval::NNUE {
+
+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
+
+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));
+    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
+
+#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
+
+#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<const vec_t*>(&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
+
+#endif
+
+
+#ifdef VECTOR
+
+    // 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
+
+template<typename SIMDRegisterType, typename LaneType, int NumLanes, int MaxRegisters>
+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);
+
+    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;
+
+    return 1;
+}
+
+static constexpr int NumRegs =
+  BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
+static constexpr int NumPsqtRegs =
+  BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
+    #if defined(__GNUC__)
+        #pragma GCC diagnostic pop
+    #endif
+#endif
+
+
+// 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;
+    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
+
+   public:
+    // Output type
+    using OutputType = TransformedFeatureType;
+
+    // Number of input/output 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);
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t get_hash_value() {
+        return FeatureSet::HashValue ^ (OutputDimensions * 2);
+    }
+
+    // Read network parameters
+    bool read_parameters(std::istream& stream) {
+
+        read_leb_128<BiasType>(stream, biases, HalfDimensions);
+        read_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
+        read_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
+
+        return !stream.fail();
+    }
+
+    // Write network parameters
+    bool write_parameters(std::ostream& stream) const {
+
+        write_leb_128<BiasType>(stream, biases, HalfDimensions);
+        write_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
+        write_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
+
+        return !stream.fail();
+    }
+
+    // Convert input features
+    std::int32_t transform(const Position& pos, OutputType* output, int bucket) const {
+        update_accumulator<WHITE>(pos);
+        update_accumulator<BLACK>(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 psqt =
+          (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket])
+          / 2;
+
+
+        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;
+
+            vec_t Zero = vec_zero();
+            vec_t One  = vec_set_16(127);
+
+            const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
+            const vec_t* in1 =
+              reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));
+            vec_t* out = reinterpret_cast<vec_t*>(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);
+
+                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);
+            }
+
+#else
+
+            for (IndexType j = 0; j < HalfDimensions / 2; ++j)
+            {
+                BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
+                BiasType sum1 =
+                  accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
+                sum0               = std::clamp<BiasType>(sum0, 0, 127);
+                sum1               = std::clamp<BiasType>(sum1, 0, 127);
+                output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 128);
+            }
+
+#endif
+        }
+
+        return psqt;
+    }  // end of function transform()
+
+    void hint_common_access(const Position& pos) const {
+        hint_common_access_for_perspective<WHITE>(pos);
+        hint_common_access_for_perspective<BLACK>(pos);
+    }
+
+   private:
+    template<Color Perspective>
+    [[nodiscard]] std::pair<StateInfo*, StateInfo*>
+    try_find_computed_accumulator(const Position& pos) const {
+        // Look for a usable accumulator of an earlier position. We keep track
+        // of the estimated gain in terms of features to be added/subtracted.
+        StateInfo *st = pos.state(), *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.
+    //       All states must be sequential, that is states_to_update[i] must either be reachable
+    //       by repeatedly applying ->previous from states_to_update[i+1] or
+    //       states_to_update[i] == nullptr.
+    //       computed_st must be reachable by repeatedly applying ->previous on
+    //       states_to_update[0], if not nullptr.
+    template<Color Perspective, size_t N>
+    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
+
+        if (states_to_update[0] == nullptr)
+            return;
+
+        // Update incrementally going back through states_to_update.
+
+        // Gather all features to be updated.
+        const Square ksq = pos.square<KING>(Perspective);
+
+        // The size must be enough to contain the largest possible update.
+        // That might depend on the feature set and generally relies on the
+        // feature set's update cost calculation to be correct and never
+        // allow updates with more added/removed features than MaxActiveDimensions.
+        FeatureSet::IndexList removed[N - 1], added[N - 1];
+
+        {
+            int i =
+              N
+              - 2;  // last potential state to update. Skip last element because it must be nullptr.
+            while (states_to_update[i] == nullptr)
+                --i;
+
+            StateInfo* st2 = states_to_update[i];
+
+            for (; i >= 0; --i)
+            {
+                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<Perspective>(ksq, st2->dirtyPiece,
+                                                                    removed[i], added[i]);
+            }
+        }
+
+        StateInfo* st = computed_st;
+
+        // 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]);
+
+            auto accIn =
+              reinterpret_cast<const vec_t*>(&st->accumulator.accumulation[Perspective][0]);
+            auto accOut = reinterpret_cast<vec_t*>(
+              &states_to_update[0]->accumulator.accumulation[Perspective][0]);
+
+            const IndexType offsetR0 = HalfDimensions * removed[0][0];
+            auto            columnR0 = reinterpret_cast<const vec_t*>(&weights[offsetR0]);
+            const IndexType offsetA  = HalfDimensions * added[0][0];
+            auto            columnA  = reinterpret_cast<const vec_t*>(&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<const vec_t*>(&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 = reinterpret_cast<const psqt_vec_t*>(
+              &st->accumulator.psqtAccumulation[Perspective][0]);
+            auto accPsqtOut = reinterpret_cast<psqt_vec_t*>(
+              &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]);
+
+            const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0];
+            auto columnPsqtR0 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR0]);
+            const IndexType offsetPsqtA = PSQTBuckets * added[0][0];
+            auto columnPsqtA = reinterpret_cast<const psqt_vec_t*>(&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<const psqt_vec_t*>(&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]),
+                                      vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k]));
+            }
+        }
+        else
+        {
+            for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
+            {
+                // Load accumulator
+                auto accTileIn = reinterpret_cast<const vec_t*>(
+                  &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<const vec_t*>(&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<const vec_t*>(&weights[offset]);
+                        for (IndexType k = 0; k < NumRegs; ++k)
+                            acc[k] = vec_add_16(acc[k], column[k]);
+                    }
+
+                    // Store accumulator
+                    auto accTileOut = reinterpret_cast<vec_t*>(
+                      &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<const psqt_vec_t*>(
+                  &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<const psqt_vec_t*>(&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<const psqt_vec_t*>(&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<psqt_vec_t*>(
+                      &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])
+            {
+                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<Color Perspective>
+    void update_accumulator_refresh(const Position& pos) const {
+#ifdef VECTOR
+        // Gcc-10.2 unnecessarily spills AVX2 registers if this array
+        // is defined in the VECTOR code below, once in each branch
+        vec_t      acc[NumRegs];
+        psqt_vec_t psqt[NumPsqtRegs];
+#endif
+
+        // Refresh the accumulator
+        // Could be extracted to a separate function because it's done in 2 places,
+        // but it's unclear if compilers would correctly handle register allocation.
+        auto& accumulator                 = pos.state()->accumulator;
+        accumulator.computed[Perspective] = true;
+        FeatureSet::IndexList active;
+        FeatureSet::append_active_indices<Perspective>(pos, active);
+
+#ifdef VECTOR
+        for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
+        {
+            auto biasesTile = reinterpret_cast<const vec_t*>(&biases[j * TileHeight]);
+            for (IndexType k = 0; k < NumRegs; ++k)
+                acc[k] = biasesTile[k];
+
+            for (const auto index : active)
+            {
+                const IndexType offset = HalfDimensions * index + j * TileHeight;
+                auto            column = reinterpret_cast<const vec_t*>(&weights[offset]);
+
+                for (unsigned k = 0; k < NumRegs; ++k)
+                    acc[k] = vec_add_16(acc[k], column[k]);
+            }
+
+            auto accTile =
+              reinterpret_cast<vec_t*>(&accumulator.accumulation[Perspective][j * TileHeight]);
+            for (unsigned k = 0; k < NumRegs; k++)
+                vec_store(&accTile[k], acc[k]);
+        }
+
+        for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
+        {
+            for (std::size_t k = 0; k < NumPsqtRegs; ++k)
+                psqt[k] = vec_zero_psqt();
+
+            for (const auto index : active)
+            {
+                const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
+                auto columnPsqt        = reinterpret_cast<const psqt_vec_t*>(&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<psqt_vec_t*>(
+              &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
+            for (std::size_t k = 0; k < NumPsqtRegs; ++k)
+                vec_store_psqt(&accTilePsqt[k], psqt[k]);
+        }
+
+#else
+        std::memcpy(accumulator.accumulation[Perspective], biases,
+                    HalfDimensions * sizeof(BiasType));
+
+        for (std::size_t k = 0; k < PSQTBuckets; ++k)
+            accumulator.psqtAccumulation[Perspective][k] = 0;
+
+        for (const auto index : active)
+        {
+            const IndexType offset = HalfDimensions * index;
+
+            for (IndexType j = 0; j < HalfDimensions; ++j)
+                accumulator.accumulation[Perspective][j] += weights[offset + j];
+
+            for (std::size_t k = 0; k < PSQTBuckets; ++k)
+                accumulator.psqtAccumulation[Perspective][k] +=
+                  psqtWeights[index * PSQTBuckets + k];
+        }
+#endif
+    }
+
+    template<Color Perspective>
+    void hint_common_access_for_perspective(const Position& pos) const {
+
+        // Works like update_accumulator, but performs less work.
+        // Updates ONLY the accumulator for pos.
+
+        // Look for a usable accumulator of an earlier position. We keep track
+        // of the estimated gain in terms of features to be added/subtracted.
+        // Fast early exit.
+        if (pos.state()->accumulator.computed[Perspective])
+            return;
+
+        auto [oldest_st, _] = try_find_computed_accumulator<Perspective>(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<Perspective, 2>(pos, oldest_st, states_to_update);
+        }
+        else
+        {
+            update_accumulator_refresh<Perspective>(pos);
+        }
+    }
+
+    template<Color Perspective>
+    void update_accumulator(const Position& pos) const {
+
+        auto [oldest_st, next] = try_find_computed_accumulator<Perspective>(pos);
+
+        if (oldest_st->accumulator.computed[Perspective])
+        {
+            if (next == nullptr)
+                return;
+
+            // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
+            // Currently we update 2 accumulators.
+            //     1. for the current position
+            //     2. the next accumulator after the computed one
+            // The heuristic may change in the future.
+            StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(),
+                                              nullptr};
+
+            update_accumulator_incremental<Perspective, 3>(pos, oldest_st, states_to_update);
+        }
+        else
+        {
+            update_accumulator_refresh<Perspective>(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
diff --git a/src/pawns.cpp b/src/pawns.cpp
deleted file mode 100644 (file)
index c3f7872..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
-  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
-  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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <algorithm>
-#include <cassert>
-
-#include "bitboard.h"
-#include "pawns.h"
-#include "position.h"
-#include "thread.h"
-
-namespace {
-
-  #define V Value
-  #define S(mg, eg) make_score(mg, eg)
-
-  // Pawn penalties
-  constexpr Score Backward      = S( 9, 24);
-  constexpr Score BlockedStorm  = S(82, 82);
-  constexpr Score Doubled       = S(11, 56);
-  constexpr Score Isolated      = S( 5, 15);
-  constexpr Score WeakLever     = S( 0, 56);
-  constexpr Score WeakUnopposed = S(13, 27);
-
-  // Connected pawn bonus
-  constexpr int Connected[RANK_NB] = { 0, 7, 8, 12, 29, 48, 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( -6), V( 81), V( 93), V( 58), V( 39), V( 18), V(  25) },
-    { V(-43), V( 61), V( 35), V(-49), V(-29), V(-11), V( -63) },
-    { V(-10), V( 75), V( 23), V( -2), V( 32), V(  3), V( -45) },
-    { V(-39), V(-13), V(-29), V(-52), V(-48), V(-67), V(-166) }
-  };
-
-  // 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( 85), V(-289), V(-166), V(97), V(50), V( 45), V( 50) },
-    { V( 46), V( -25), V( 122), V(45), V(37), V(-10), V( 20) },
-    { V( -6), V(  51), V( 168), V(34), V(-2), V(-22), V(-14) },
-    { V(-15), V( -11), V( 101), V( 4), V(11), V(-15), V(-29) }
-  };
-
-  #undef S
-  #undef V
-
-  template<Color Us>
-  Score evaluate(const Position& pos, Pawns::Entry* e) {
-
-    constexpr Color     Them = (Us == WHITE ? BLACK : WHITE);
-    constexpr Direction Up   = pawn_push(Us);
-
-    Bitboard neighbours, stoppers, support, phalanx, opposed;
-    Bitboard lever, leverPush, blocked;
-    Square s;
-    bool backward, passed, doubled;
-    Score score = SCORE_ZERO;
-    const Square* pl = pos.squares<PAWN>(Us);
-
-    Bitboard ourPawns   = pos.pieces(  Us, PAWN);
-    Bitboard theirPawns = pos.pieces(Them, PAWN);
-
-    Bitboard doubleAttackThem = pawn_double_attacks_bb<Them>(theirPawns);
-
-    e->passedPawns[Us] = 0;
-    e->kingSquares[Us] = SQ_NONE;
-    e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
-
-    // Loop through all pawns of the current color and score each pawn
-    while ((s = *pl++) != SQ_NONE)
-    {
-        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 & PawnAttacks[Us][s];
-        leverPush  = theirPawns & PawnAttacks[Us][s + Up];
-        doubled    = ourPawns   & (s - Up);
-        neighbours = ourPawns   & adjacent_files_bb(s);
-        phalanx    = neighbours & rank_bb(s);
-        support    = neighbours & rank_bb(s - Up);
-
-        // 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.
-        passed =   !(stoppers ^ lever)
-                || (   !(stoppers ^ leverPush)
-                    && popcount(phalanx) >= popcount(leverPush))
-                || (   stoppers == blocked && r >= RANK_5
-                    && (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
-
-        // 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))
-                   + 21 * popcount(support);
-
-            score += make_score(v, v * (r - 2) / 4);
-        }
-
-        else if (!neighbours)
-            score -=   Isolated
-                     + WeakUnopposed * !opposed;
-
-        else if (backward)
-            score -=   Backward
-                     + WeakUnopposed * !opposed;
-
-        if (!support)
-            score -=   Doubled * doubled
-                     + WeakLever * more_than_one(lever);
-    }
-
-    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->scores[WHITE] = evaluate<WHITE>(pos, e);
-  e->scores[BLACK] = evaluate<BLACK>(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<Color Us>
-Score Entry::evaluate_shelter(const Position& pos, Square ksq) {
-
-  constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
-
-  Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
-  Bitboard ourPawns = b & pos.pieces(Us);
-  Bitboard theirPawns = b & pos.pieces(Them);
-
-  Score bonus = make_score(5, 5);
-
-  File center = 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;
-
-      File d = map_to_queenside(f);
-      bonus += make_score(ShelterStrength[d][ourRank], 0);
-
-      if (ourRank && (ourRank == theirRank - 1))
-          bonus -= BlockedStorm * int(theirRank == RANK_3);
-      else
-          bonus -= make_score(UnblockedStorm[d][theirRank], 0);
-  }
-
-  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<Color Us>
-Score Entry::do_king_safety(const Position& pos) {
-
-  Square ksq = pos.square<KING>(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<Us>(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<Us>(pos, relative_square(Us, SQ_G1)), compare);
-
-  if (pos.can_castle(Us & QUEEN_SIDE))
-      shelter = std::max(shelter, evaluate_shelter<Us>(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 = pawns ? 8 : 0;
-
-  if (pawns & PseudoAttacks[KING][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<WHITE>(const Position& pos);
-template Score Entry::do_king_safety<BLACK>(const Position& pos);
-
-} // namespace Pawns
diff --git a/src/pawns.h b/src/pawns.h
deleted file mode 100644 (file)
index bd17618..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
-  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 <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef PAWNS_H_INCLUDED
-#define PAWNS_H_INCLUDED
-
-#include "misc.h"
-#include "position.h"
-#include "types.h"
-
-namespace 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]); }
-
-  template<Color Us>
-  Score king_safety(const Position& pos) {
-    return  kingSquares[Us] == pos.square<KING>(Us) && castlingRights[Us] == pos.castling_rights(Us)
-          ? kingSafety[Us] : (kingSafety[Us] = do_king_safety<Us>(pos));
-  }
-
-  template<Color Us>
-  Score do_king_safety(const Position& pos);
-
-  template<Color Us>
-  Score evaluate_shelter(const Position& pos, Square ksq);
-
-  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];
-};
-
-typedef HashTable<Entry, 131072> Table;
-
-Entry* probe(const Position& pos);
-
-} // namespace Pawns
-
-#endif // #ifndef PAWNS_H_INCLUDED
index 53d9b64e9ff0be947b4525433d32f360807d09b5..c45dd7b2e22a0000a439513e6f9687dde5bb6ad7 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "position.h"
+
 #include <algorithm>
+#include <atomic>
 #include <cassert>
-#include <cstddef> // For offsetof()
-#include <cstring> // For std::memset, std::memcmp
+#include <cctype>
+#include <cstddef>
+#include <cstring>
+#include <initializer_list>
 #include <iomanip>
+#include <iostream>
 #include <sstream>
+#include <string_view>
+#include <utility>
 
 #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;
 
+namespace Stockfish {
+
 namespace Zobrist {
 
-  Key psq[PIECE_NB][SQUARE_NB];
-  Key enpassant[FILE_NB];
-  Key castling[CASTLING_RIGHT_NB];
-  Key side, noPawns;
+Key psq[PIECE_NB][SQUARE_NB];
+Key enpassant[FILE_NB];
+Key castling[CASTLING_RIGHT_NB];
+Key side, noPawns;
 }
 
 namespace {
 
-const string PieceToChar(" PNBRQK  pnbrqk");
+constexpr std::string_view PieceToChar(" PNBRQK  pnbrqk");
 
-constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
-                             B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING };
-} // 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
-
+// Returns an ASCII representation of the position
 std::ostream& operator<<(std::ostream& os, const Position& pos) {
 
-  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))];
-
-      os << " |\n +---+---+---+---+---+---+---+---+\n";
-  }
-
-  os << "\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)) << " ";
-
-  if (    int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
-      && !pos.can_castle(ANY_CASTLING))
-  {
-      StateInfo st;
-      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;
+    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))];
+
+        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: ";
+
+    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);
+
+        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;
 }
 
 
-// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition"
-// situations. Description of the algorithm in the following paper:
-// https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
+// 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
 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];
 
 
-/// 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);
-
-  for (Piece pc : Pieces)
-      for (Square s = SQ_A1; s <= SQ_H8; ++s)
-          Zobrist::psq[pc][s] = rng.rand<Key>();
-
-  for (File f = FILE_A; f <= FILE_H; ++f)
-      Zobrist::enpassant[f] = rng.rand<Key>();
-
-  for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
-  {
-      Zobrist::castling[cr] = 0;
-      Bitboard b = cr;
-      while (b)
-      {
-          Key k = Zobrist::castling[1ULL << pop_lsb(&b)];
-          Zobrist::castling[cr] ^= k ? k : rng.rand<Key>();
-      }
-  }
-
-  Zobrist::side = rng.rand<Key>();
-  Zobrist::noPawns = rng.rand<Key>();
-
-  // Prepare the cuckoo tables
-  std::memset(cuckoo, 0, sizeof(cuckoo));
-  std::memset(cuckooMove, 0, sizeof(cuckooMove));
-  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 (PseudoAttacks[type_of(pc)][s1] & 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);
+    PRNG rng(1070372);
+
+    for (Piece pc : Pieces)
+        for (Square s = SQ_A1; s <= SQ_H8; ++s)
+            Zobrist::psq[pc][s] = rng.rand<Key>();
+
+    for (File f = FILE_A; f <= FILE_H; ++f)
+        Zobrist::enpassant[f] = rng.rand<Key>();
+
+    for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
+        Zobrist::castling[cr] = rng.rand<Key>();
+
+    Zobrist::side    = rng.rand<Key>();
+    Zobrist::noPawns = rng.rand<Key>();
+
+    // 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);
 }
 
 
-/// 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.
-
+// 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.
 
    A FEN string contains six fields separated by a space. The fields are:
@@ -186,9 +184,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. 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
@@ -198,940 +196,959 @@ 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);
-
-  std::memset(this, 0, sizeof(Position));
-  std::memset(si, 0, sizeof(StateInfo));
-  std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
-  st = si;
-
-  ss >> std::noskipws;
-
-  // 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 ((idx = PieceToChar.find(token)) != string::npos)
-      {
-          put_piece(Piece(idx), sq);
-          ++sq;
-      }
-  }
-
-  // 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);
-
-      token = char(toupper(token));
-
-      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 >= 'A' && token <= 'H')
-          rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
-
-      else
-          continue;
-
-      set_castling_right(c, rsq);
-  }
-
-  // 4. En passant square. Ignore if no pawn capture is possible
-  if (   ((ss >> col) && (col >= 'a' && col <= 'h'))
-      && ((ss >> row) && (row == '3' || row == '6')))
-  {
-      st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
-
-      if (   !(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))
-          || !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))))
-          st->epSquare = SQ_NONE;
-  }
-  else
-      st->epSquare = SQ_NONE;
-
-  // 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);
-
-  chess960 = isChess960;
-  thisThread = th;
-  set_state(st);
-
-  assert(pos_is_ok());
-
-  return *this;
-}
+    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;
 
-/// Position::set_castling_right() is a helper function used to set castling
-/// rights given the corresponding color and the rook starting square.
+    ss >> std::noskipws;
 
-void Position::set_castling_right(Color c, Square rfrom) {
+    // 1. Piece placement
+    while ((ss >> token) && !isspace(token))
+    {
+        if (isdigit(token))
+            sq += (token - '0') * EAST;  // Advance the given number of files
 
-  Square kfrom = square<KING>(c);
-  CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE);
+        else if (token == '/')
+            sq += 2 * SOUTH;
 
-  st->castlingRights |= cr;
-  castlingRightsMask[kfrom] |= cr;
-  castlingRightsMask[rfrom] |= cr;
-  castlingRookSquare[cr] = rfrom;
+        else if ((idx = PieceToChar.find(token)) != string::npos)
+        {
+            put_piece(Piece(idx), sq);
+            ++sq;
+        }
+    }
 
-  Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
-  Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
+    // 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);
 
-  castlingPath[cr] =   (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto)
-                    & ~(square_bb(kfrom) | rfrom);
-}
+        token = char(toupper(token));
+
+        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)
+            {}
 
-/// Position::set_check_info() sets king attacks to detect if a move gives check
+        else if (token >= 'A' && token <= 'H')
+            rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
 
-void Position::set_check_info(StateInfo* si) const {
+        else
+            continue;
 
-  si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), si->pinners[BLACK]);
-  si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK), si->pinners[WHITE]);
+        set_castling_right(c, rsq);
+    }
 
-  Square ksq = square<KING>(~sideToMove);
+    // 4. En passant square.
+    // Ignore if square is invalid or not on side to move relative rank 6.
+    bool enpassant = false;
 
-  si->checkSquares[PAWN]   = attacks_from<PAWN>(ksq, ~sideToMove);
-  si->checkSquares[KNIGHT] = attacks_from<KNIGHT>(ksq);
-  si->checkSquares[BISHOP] = attacks_from<BISHOP>(ksq);
-  si->checkSquares[ROOK]   = attacks_from<ROOK>(ksq);
-  si->checkSquares[QUEEN]  = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
-  si->checkSquares[KING]   = 0;
-}
+    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))));
+    }
 
+    if (!enpassant)
+        st->epSquare = SQ_NONE;
 
-/// Position::set_state() computes the hash keys of the position, and other
-/// data that once computed is updated incrementally as moves are made.
-/// The function is only used when a new position is set up, and to verify
-/// the correctness of the StateInfo data when running in debug mode.
+    // 5-6. Halfmove clock and fullmove number
+    ss >> std::skipws >> st->rule50 >> gamePly;
 
-void Position::set_state(StateInfo* si) const {
+    // 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);
 
-  si->key = si->materialKey = 0;
-  si->pawnKey = Zobrist::noPawns;
-  si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO;
-  si->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
+    chess960   = isChess960;
+    thisThread = th;
+    set_state();
 
-  set_check_info(si);
+    assert(pos_is_ok());
 
-  for (Bitboard b = pieces(); b; )
-  {
-      Square s = pop_lsb(&b);
-      Piece pc = piece_on(s);
-      si->key ^= Zobrist::psq[pc][s];
+    return *this;
+}
 
-      if (type_of(pc) == PAWN)
-          si->pawnKey ^= Zobrist::psq[pc][s];
 
-      else if (type_of(pc) != KING)
-          si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc];
-  }
+// 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) {
 
-  if (si->epSquare != SQ_NONE)
-      si->key ^= Zobrist::enpassant[file_of(si->epSquare)];
+    Square         kfrom = square<KING>(c);
+    CastlingRights cr    = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE);
 
-  if (sideToMove == BLACK)
-      si->key ^= Zobrist::side;
+    st->castlingRights |= cr;
+    castlingRightsMask[kfrom] |= cr;
+    castlingRightsMask[rfrom] |= cr;
+    castlingRookSquare[cr] = rfrom;
 
-  si->key ^= Zobrist::castling[si->castlingRights];
+    Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
+    Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
 
-  for (Piece pc : Pieces)
-      for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
-          si->materialKey ^= Zobrist::psq[pc][cnt];
+    castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom);
 }
 
 
-/// 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.
+// Sets king attacks to detect if a move gives check
+void Position::set_check_info() const {
 
-Position& Position::set(const string& code, Color c, StateInfo* si) {
+    update_slider_blockers(WHITE);
+    update_slider_blockers(BLACK);
 
-  assert(code.length() > 0 && code.length() < 8);
-  assert(code[0] == 'K');
+    Square ksq = square<KING>(~sideToMove);
 
-  string sides[] = { code.substr(code.find('K', 1)),      // Weak
-                     code.substr(0, code.find('K', 1)) }; // Strong
+    st->checkSquares[PAWN]   = pawn_attacks_bb(~sideToMove, ksq);
+    st->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
+    st->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
+    st->checkSquares[ROOK]   = attacks_bb<ROOK>(ksq, pieces());
+    st->checkSquares[QUEEN]  = st->checkSquares[BISHOP] | st->checkSquares[ROOK];
+    st->checkSquares[KING]   = 0;
+}
 
-  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";
+// 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 {
 
-  return set(fenStr, false, si, nullptr);
-}
+    st->key = st->materialKey  = 0;
+    st->pawnKey                = Zobrist::noPawns;
+    st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO;
+    st->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
 
+    set_check_info();
 
-/// 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.
+    for (Bitboard b = pieces(); b;)
+    {
+        Square s  = pop_lsb(b);
+        Piece  pc = piece_on(s);
+        st->key ^= Zobrist::psq[pc][s];
 
-const string Position::fen() const {
+        if (type_of(pc) == PAWN)
+            st->pawnKey ^= Zobrist::psq[pc][s];
 
-  int emptyCnt;
-  std::ostringstream ss;
+        else if (type_of(pc) != KING)
+            st->nonPawnMaterial[color_of(pc)] += PieceValue[pc];
+    }
 
-  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 (st->epSquare != SQ_NONE)
+        st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
 
-          if (emptyCnt)
-              ss << emptyCnt;
+    if (sideToMove == BLACK)
+        st->key ^= Zobrist::side;
 
-          if (f <= FILE_H)
-              ss << PieceToChar[piece_on(make_square(f, r))];
-      }
+    st->key ^= Zobrist::castling[st->castlingRights];
 
-      if (r > RANK_1)
-          ss << '/';
-  }
+    for (Piece pc : Pieces)
+        for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
+            st->materialKey ^= Zobrist::psq[pc][cnt];
+}
 
-  ss << (sideToMove == WHITE ? " w " : " b ");
 
-  if (can_castle(WHITE_OO))
-      ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K');
+// 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) {
 
-  if (can_castle(WHITE_OOO))
-      ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
+    assert(code[0] == 'K');
 
-  if (can_castle(BLACK_OO))
-      ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k');
+    string sides[] = {code.substr(code.find('K', 1)),                                // Weak
+                      code.substr(0, std::min(code.find('v'), code.find('K', 1)))};  // Strong
 
-  if (can_castle(BLACK_OOO))
-      ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
+    assert(sides[0].length() > 0 && sides[0].length() < 8);
+    assert(sides[1].length() > 0 && sides[1].length() < 8);
 
-  if (!can_castle(ANY_CASTLING))
-      ss << '-';
+    std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
 
-  ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ")
-     << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
+    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 ss.str();
+    return set(fenStr, false, si, nullptr);
 }
 
 
-/// 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.
+// 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 {
 
-Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const {
+    int                emptyCnt;
+    std::ostringstream ss;
 
-  Bitboard blockers = 0;
-  pinners = 0;
+    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;
 
-  // Snipers are sliders that attack 's' when a piece and other snipers are removed
-  Bitboard snipers = (  (PseudoAttacks[  ROOK][s] & pieces(QUEEN, ROOK))
-                      | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders;
-  Bitboard occupancy = pieces() ^ snipers;
+            if (emptyCnt)
+                ss << emptyCnt;
 
-  while (snipers)
-  {
-    Square sniperSq = pop_lsb(&snipers);
-    Bitboard b = between_bb(s, sniperSq) & occupancy;
+            if (f <= FILE_H)
+                ss << PieceToChar[piece_on(make_square(f, r))];
+        }
 
-    if (b && !more_than_one(b))
-    {
-        blockers |= b;
-        if (b & pieces(color_of(piece_on(s))))
-            pinners |= sniperSq;
+        if (r > RANK_1)
+            ss << '/';
     }
-  }
-  return blockers;
+
+    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_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_OOO))
+        ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
+
+    if (!can_castle(ANY_CASTLING))
+        ss << '-';
+
+    ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50
+       << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
+
+    return ss.str();
 }
 
+// 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::attackers_to() computes a bitboard of all pieces which attack a
-/// given square. Slider attacks use the occupied bitboard to indicate occupancy.
+    Square ksq = square<KING>(c);
 
-Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
+    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<BISHOP>(ksq) & pieces(QUEEN, BISHOP)))
+                     & pieces(~c);
+    Bitboard occupancy = pieces() ^ snipers;
 
-  return  (attacks_from<PAWN>(s, BLACK)    & pieces(WHITE, PAWN))
-        | (attacks_from<PAWN>(s, WHITE)    & pieces(BLACK, PAWN))
-        | (attacks_from<KNIGHT>(s)         & pieces(KNIGHT))
-        | (attacks_bb<  ROOK>(s, occupied) & pieces(  ROOK, QUEEN))
-        | (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
-        | (attacks_from<KING>(s)           & pieces(KING));
+    while (snipers)
+    {
+        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;
+        }
+    }
 }
 
 
-/// Position::legal() tests whether a pseudo-legal move is legal
+// 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))
+         | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN))
+         | (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
+         | (attacks_bb<ROOK>(s, occupied) & pieces(ROOK, QUEEN))
+         | (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
+         | (attacks_bb<KING>(s) & pieces(KING));
+}
+
 
+// Tests whether a pseudo-legal move is legal
 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<KING>(us)) == make_piece(us, KING));
+    assert(color_of(moved_piece(m)) == us);
+    assert(piece_on(square<KING>(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) == ENPASSANT)
-  {
-      Square ksq = square<KING>(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<KING>(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<ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
             && !(attacks_bb<BISHOP>(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;
-
-      for (Square s = to; s != from; s += step)
-          if (attackers_to(s) & pieces(~us))
-              return false;
-
-      // In case of Chess960, verify that when moving the castling rook we do
-      // not discover some hidden checker.
-      // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
-      return   !chess960
-            || !(attacks_bb<ROOK>(to, pieces() ^ to_sq(m)) & pieces(~us, ROOK, QUEEN));
-  }
-
-  // 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(~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<KING>(us));
-}
+    }
+
+    // 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;
+
+        // 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));
 
+    // 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<KING>(us));
+}
 
-/// 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.
 
+// 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;
-  Square from = from_sq(m);
-  Square to = to_sq(m);
-  Piece pc = moved_piece(m);
-
-  // Use a slower but simpler function for uncommon cases
-  if (type_of(m) != NORMAL)
-      return MoveList<LEGAL>(*this).contains(m);
-
-  // Is not a promotion, so promotion piece must be empty
-  if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE)
-      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;
-
-  // 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 (   !(attacks_from<PAWN>(from, us) & 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
-               && (rank_of(from) == relative_rank(us, RANK_2))
-               && empty(to)
-               && empty(to - pawn_push(us))))
-          return false;
-  }
-  else if (!(attacks_from(type_of(pc), from) & 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;
-
-          // Our move must be a blocking evasion or a capture of the checking piece
-          if (!((between_bb(lsb(checkers()), square<KING>(us)) | checkers()) & to))
-              return false;
-      }
-      // In case of king moves under check we have to remove 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;
-}
+    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<LEGAL>().
+    if (type_of(m) != NORMAL)
+        return checkers() ? MoveList<EVASIONS>(*this).contains(m)
+                          : MoveList<NON_EVASIONS>(*this).contains(m);
 
-/// Position::gives_check() tests whether a pseudo-legal move gives a check
+    // Is not a promotion, so the promotion piece must be empty
+    assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE);
 
-bool Position::gives_check(Move m) const {
+    // 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;
 
-  assert(is_ok(m));
-  assert(color_of(moved_piece(m)) == sideToMove);
-
-  Square from = from_sq(m);
-  Square to = to_sq(m);
-
-  // Is there a direct check?
-  if (st->checkSquares[type_of(piece_on(from))] & to)
-      return true;
-
-  // Is there a discovered check?
-  if (   (st->blockersForKing[~sideToMove] & from)
-      && !aligned(from, to, square<KING>(~sideToMove)))
-      return true;
-
-  switch (type_of(m))
-  {
-  case NORMAL:
-      return false;
-
-  case PROMOTION:
-      return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~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 ENPASSANT:
-  {
-      Square capsq = make_square(file_of(to), rank_of(from));
-      Bitboard b = (pieces() ^ from ^ capsq) | to;
-
-      return  (attacks_bb<  ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
-            | (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
-  }
-  case CASTLING:
-  {
-      Square kfrom = from;
-      Square rfrom = to; // Castling is encoded as 'King captures the rook'
-      Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1);
-      Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1);
-
-      return   (PseudoAttacks[ROOK][rto] & square<KING>(~sideToMove))
-            && (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
-  }
-  default:
-      assert(false);
-      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;
+
+        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;
+
+            // Our move must be a blocking interposition or a capture of the checking piece
+            if (!(between_bb(square<KING>(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;
 }
 
 
-/// 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.
+// Tests whether a pseudo-legal move gives a check
+bool Position::gives_check(Move m) const {
 
-void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
+    assert(is_ok(m));
+    assert(color_of(moved_piece(m)) == sideToMove);
 
-  assert(is_ok(m));
-  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
-  // 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;
-
-  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) == ENPASSANT ? 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);
-
-  if (type_of(m) == CASTLING)
-  {
-      assert(pc == make_piece(us, KING));
-      assert(captured == make_piece(us, ROOK));
-
-      Square rfrom, rto;
-      do_castling<true>(us, from, to, rfrom, rto);
-
-      k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
-      captured = NO_PIECE;
-  }
-
-  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) == ENPASSANT)
-          {
-              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));
-
-              board[capsq] = NO_PIECE; // Not done by remove_piece()
-          }
-
-          st->pawnKey ^= Zobrist::psq[captured][capsq];
-      }
-      else
-          st->nonPawnMaterial[them] -= PieceValue[MG][captured];
-
-      // Update board and piece lists
-      remove_piece(captured, capsq);
-
-      // 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;
-  }
-
-  // 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;
-  }
-
-  // Update castling rights if needed
-  if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
-  {
-      int cr = castlingRightsMask[from] | castlingRightsMask[to];
-      k ^= Zobrist::castling[st->castlingRights & cr];
-      st->castlingRights &= ~cr;
-  }
-
-  // Move the piece. The tricky Chess960 castling is handled earlier
-  if (type_of(m) != CASTLING)
-      move_piece(pc, 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_from<PAWN>(to - pawn_push(us), 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));
-
-          assert(relative_rank(us, to) == RANK_8);
-          assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
-
-          remove_piece(pc, to);
-          put_piece(promotion, to);
-
-          // 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]];
-
-          // Update material
-          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;
-  }
-
-  // Set capture piece
-  st->capturedPiece = captured;
-
-  // Update the key with the final value
-  st->key = k;
-
-  // Calculate checkers bitboard (if move gives check)
-  st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
-
-  sideToMove = ~sideToMove;
-
-  // Update king attacks used for fast check detection
-  set_check_info(st);
-
-  // 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());
+    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 discovered check?
+    if (blockers_for_king(~sideToMove) & from)
+        return !aligned(from, to, square<KING>(~sideToMove)) || type_of(m) == CASTLING;
+
+    switch (type_of(m))
+    {
+    case NORMAL :
+        return false;
+
+    case PROMOTION :
+        return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~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;
+
+        return (attacks_bb<ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
+             | (attacks_bb<BISHOP>(square<KING>(~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;
+    }
+    }
 }
 
 
-/// 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.
+// 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));
+    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
+    // 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;
+
+    // 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);
+
+    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));
 
+        Square rfrom, rto;
+        do_castling<true>(us, from, to, rfrom, rto);
+
+        k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
+        captured = NO_PIECE;
+    }
+
+    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);
+
+                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));
+            }
+
+            st->pawnKey ^= Zobrist::psq[captured][capsq];
+        }
+        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;
+
+        // 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]];
+
+        // Reset rule 50 counter
+        st->rule50 = 0;
+    }
+
+    // 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;
+    }
+
+    // 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_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)];
+        }
+
+        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);
+
+            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++;
+
+            // 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]];
+
+            // Update material
+            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;
+    }
+
+    // Set capture piece
+    st->capturedPiece = captured;
+
+    // Update the key with the final value
+    st->key = k;
+
+    // Calculate checkers bitboard (if move gives check)
+    st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
+
+    sideToMove = ~sideToMove;
+
+    // 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;
+            }
+        }
+    }
+
+    assert(pos_is_ok());
+}
+
+
+// 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));
-
-  sideToMove = ~sideToMove;
-
-  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);
-
-  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(pc, to);
-      pc = make_piece(us, PAWN);
-      put_piece(pc, to);
-  }
-
-  if (type_of(m) == CASTLING)
-  {
-      Square rfrom, rto;
-      do_castling<false>(us, from, to, rfrom, rto);
-  }
-  else
-  {
-      move_piece(pc, to, from); // Put the piece back at the source square
-
-      if (st->capturedPiece)
-      {
-          Square capsq = to;
-
-          if (type_of(m) == ENPASSANT)
-          {
-              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));
-          }
-
-          put_piece(st->capturedPiece, capsq); // Restore the captured piece
-      }
-  }
-
-  // Finally point our state pointer back to the previous state
-  st = st->previous;
-  --gamePly;
-
-  assert(pos_is_ok());
+    assert(is_ok(m));
+
+    sideToMove = ~sideToMove;
+
+    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);
+
+    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);
+    }
+
+    if (type_of(m) == CASTLING)
+    {
+        Square rfrom, rto;
+        do_castling<false>(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 (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));
+            }
+
+            put_piece(st->capturedPiece, capsq);  // Restore the captured piece
+        }
+    }
+
+    // Finally point our state pointer back to the previous state
+    st = st->previous;
+    --gamePly;
+
+    assert(pos_is_ok());
 }
 
 
-/// 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.
+// Helper used to do/undo a castling move. This is a bit
+// tricky in Chess960 where from/to squares can overlap.
 template<bool Do>
 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);
-
-  // Remove both pieces first since squares could overlap in Chess960
-  remove_piece(make_piece(us, KING), Do ? from : to);
-  remove_piece(make_piece(us, ROOK), Do ? rfrom : rto);
-  board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us
-  put_piece(make_piece(us, KING), Do ? to : from);
-  put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
-}
+    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;
+    }
+
+    // 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);
+}
 
-/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips
-/// the side to move without executing any move on the board.
 
+// 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());
-  assert(&newSt != st);
+    assert(!checkers());
+    assert(&newSt != st);
+
+    std::memcpy(&newSt, st, offsetof(StateInfo, accumulator));
 
-  std::memcpy(&newSt, st, sizeof(StateInfo));
-  newSt.previous = st;
-  st = &newSt;
+    newSt.previous = st;
+    st             = &newSt;
 
-  if (st->epSquare != SQ_NONE)
-  {
-      st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
-      st->epSquare = SQ_NONE;
-  }
+    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;
+    }
 
-  st->key ^= Zobrist::side;
-  prefetch(TT.first_entry(st->key));
+    st->key ^= Zobrist::side;
+    ++st->rule50;
+    prefetch(TT.first_entry(key()));
 
-  ++st->rule50;
-  st->pliesFromNull = 0;
+    st->pliesFromNull = 0;
 
-  sideToMove = ~sideToMove;
+    sideToMove = ~sideToMove;
 
-  set_check_info(st);
+    set_check_info();
 
-  st->repetition = 0;
+    st->repetition = 0;
 
-  assert(pos_is_ok());
+    assert(pos_is_ok());
 }
 
+
+// Must be used to undo a "null move"
 void Position::undo_null_move() {
 
-  assert(!checkers());
+    assert(!checkers());
 
-  st = st->previous;
-  sideToMove = ~sideToMove;
+    st         = st->previous;
+    sideToMove = ~sideToMove;
 }
 
 
-/// 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.
-
+// 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);
-  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];
 
-  return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
-}
+    k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
 
+    return (captured || type_of(pc) == PAWN) ? k : adjust_key50<true>(k);
+}
 
-/// 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.
 
+// 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));
-
-  // 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);
-
-  int swap = PieceValue[MG][piece_on(to)] - threshold;
-  if (swap < 0)
-      return false;
-
-  swap = PieceValue[MG][piece_on(from)] - swap;
-  if (swap <= 0)
-      return true;
-
-  Bitboard occupied = pieces() ^ from ^ to;
-  Color stm = color_of(piece_on(from));
-  Bitboard attackers = attackers_to(to, occupied);
-  Bitboard stmAttackers, bb;
-  int res = 1;
-
-  while (true)
-  {
-      stm = ~stm;
-      attackers &= occupied;
-
-      // If stm has no more attackers then give up: stm loses
-      if (!(stmAttackers = attackers & pieces(stm)))
-          break;
-
-      // Don't allow pinned pieces to attack (except the king) as long as
-      // there are pinners on their original square.
-      if (st->pinners[~stm] & occupied)
-          stmAttackers &= ~st->blockersForKing[stm];
-
-      if (!stmAttackers)
-          break;
-
-      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 = PawnValueMg - swap) < res)
-              break;
-
-          occupied ^= lsb(bb);
-          attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
-      }
-
-      else if ((bb = stmAttackers & pieces(KNIGHT)))
-      {
-          if ((swap = KnightValueMg - swap) < res)
-              break;
-
-          occupied ^= lsb(bb);
-      }
-
-      else if ((bb = stmAttackers & pieces(BISHOP)))
-      {
-          if ((swap = BishopValueMg - swap) < res)
-              break;
-
-          occupied ^= lsb(bb);
-          attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
-      }
-
-      else if ((bb = stmAttackers & pieces(ROOK)))
-      {
-          if ((swap = RookValueMg - swap) < res)
-              break;
-
-          occupied ^= lsb(bb);
-          attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
-      }
-
-      else if ((bb = stmAttackers & pieces(QUEEN)))
-      {
-          if ((swap = QueenValueMg - swap) < res)
-              break;
-
-          occupied ^= lsb(bb);
-          attackers |=  (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
-                      | (attacks_bb<ROOK  >(to, occupied) & pieces(ROOK  , QUEEN));
-      }
-
-      else // KING
-           // If we "capture" with the king but opponent still has attackers,
-           // reverse the result.
-          return (attackers & ~pieces(stm)) ? res ^ 1 : res;
-  }
-
-  return bool(res);
-}
+    assert(is_ok(m));
 
-/// Position::is_draw() tests whether the position is drawn by 50-move rule
-/// or by repetition. It does not detect stalemates.
+    // Only deal with normal moves, assume others pass a simple SEE
+    if (type_of(m) != NORMAL)
+        return VALUE_ZERO >= threshold;
 
-bool Position::is_draw(int ply) const {
+    Square from = from_sq(m), to = to_sq(m);
 
-  if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
-      return true;
+    int swap = PieceValue[piece_on(to)] - threshold;
+    if (swap < 0)
+        return false;
 
-  // Return a draw score if a position repeats once earlier but strictly
-  // after the root, or repeats twice before or at the root.
-  if (st->repetition && st->repetition < ply)
-      return true;
+    swap = PieceValue[piece_on(from)] - swap;
+    if (swap <= 0)
+        return true;
 
-  return false;
+    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;
+
+        // 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);
+
+            if (!stmAttackers)
+                break;
+        }
+
+        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);
+
+            attackers |= attacks_bb<BISHOP>(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(BISHOP)))
+        {
+            if ((swap = BishopValue - swap) < res)
+                break;
+            occupied ^= least_significant_square_bb(bb);
+
+            attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
+        }
+
+        else if ((bb = stmAttackers & pieces(ROOK)))
+        {
+            if ((swap = RookValue - swap) < res)
+                break;
+            occupied ^= least_significant_square_bb(bb);
+
+            attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
+        }
+
+        else if ((bb = stmAttackers & pieces(QUEEN)))
+        {
+            if ((swap = QueenValue - swap) < res)
+                break;
+            occupied ^= least_significant_square_bb(bb);
+
+            attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
+                       | (attacks_bb<ROOK>(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;
+    }
+
+    return bool(res);
 }
 
+// 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 {
 
-// Position::has_repeated() tests whether there has been at least one repetition
-// of positions since the last capture or pawn move.
+    if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
+        return true;
 
+    // Return a draw score if a position repeats once earlier but strictly
+    // after the root, or repeats twice before or at the root.
+    return st->repetition && st->repetition < ply;
+}
+
+
+// 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;
-    int end = std::min(st->rule50, st->pliesFromNull);
+    int        end = std::min(st->rule50, st->pliesFromNull);
     while (end-- >= 4)
     {
         if (stc->repetition)
@@ -1143,156 +1160,137 @@ 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.
-
+// 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;
+    int j;
 
-  int end = std::min(st->rule50, st->pliesFromNull);
+    int end = std::min(st->rule50, st->pliesFromNull);
 
-  if (end < 3)
-    return false;
+    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) & 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;
+}
 
-/// Position::flip() flips position with the white and black sides reversed. This
-/// is only useful for debugging e.g. for finding evaluation symmetry bugs.
 
+// 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;
-  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());
 }
 
 
-/// Position::pos_is_ok() performs some consistency checks for the
-/// position object and raises an asserts if something wrong is detected.
-/// This is meant to be helpful when debugging.
-
+// 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?
-
-  if (   (sideToMove != WHITE && sideToMove != BLACK)
-      || piece_on(square<KING>(WHITE)) != W_KING
-      || piece_on(square<KING>(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 (   pieceCount[W_KING] != 1
-      || pieceCount[B_KING] != 1
-      || attackers_to(square<KING>(~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(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");
-
-  StateInfo si = *st;
-  set_state(&si);
-  if (std::memcmp(&si, st, sizeof(StateInfo)))
-      assert(0 && "pos_is_ok: State");
-
-  for (Piece pc : Pieces)
-  {
-      if (   pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
-          || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
-          assert(0 && "pos_is_ok: Pieces");
-
-      for (int i = 0; i < pieceCount[pc]; ++i)
-          if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i)
-              assert(0 && "pos_is_ok: Index");
-  }
-
-  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<KING>(c)] & cr) != cr)
-              assert(0 && "pos_is_ok: Castling");
-      }
-
-  return true;
+    constexpr bool Fast = true;  // Quick (default) or full check?
+
+    if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square<KING>(WHITE)) != W_KING
+        || piece_on(square<KING>(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 (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1
+        || attackers_to(square<KING>(~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(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 (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;
+
+            if (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
+                || castlingRightsMask[castlingRookSquare[cr]] != cr
+                || (castlingRightsMask[square<KING>(c)] & cr) != cr)
+                assert(0 && "pos_is_ok: Castling");
+        }
+
+    return true;
 }
+
+}  // namespace Stockfish
index 6791455fd00bc1f83f7fc42752ac0f044179bfed..ce03c34f3325de63102b910ff570b3456f0e8b31 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 
 #include <cassert>
 #include <deque>
-#include <memory> // For std::unique_ptr
+#include <iosfwd>
+#include <memory>
 #include <string>
 
 #include "bitboard.h"
+#include "nnue/nnue_accumulator.h"
 #include "types.h"
 
+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 {
 
-  // Copied when making a move
-  Key    pawnKey;
-  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;
-  Piece      capturedPiece;
-  StateInfo* previous;
-  Bitboard   blockersForKing[COLOR_NB];
-  Bitboard   pinners[COLOR_NB];
-  Bitboard   checkSquares[PIECE_TYPE_NB];
-  int        repetition;
+    // Copied when making a move
+    Key    materialKey;
+    Key    pawnKey;
+    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;
+
+    // Used by NNUE
+    Eval::NNUE::Accumulator accumulator;
+    DirtyPiece              dirtyPiece;
 };
 
-/// 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.
-typedef std::unique_ptr<std::deque<StateInfo>> StateListPtr;
 
+// 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<std::deque<StateInfo>>;
 
-/// 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 {
-public:
-  static void init();
-
-  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);
-  const std::string fen() const;
-
-  // Position representation
-  Bitboard pieces() const;
-  Bitboard pieces(PieceType pt) const;
-  Bitboard pieces(PieceType pt1, PieceType pt2) const;
-  Bitboard pieces(Color c) const;
-  Bitboard pieces(Color c, PieceType pt) const;
-  Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const;
-  Piece piece_on(Square s) const;
-  Square ep_square() const;
-  bool empty(Square s) const;
-  template<PieceType Pt> int count(Color c) const;
-  template<PieceType Pt> int count() const;
-  template<PieceType Pt> const Square* squares(Color c) const;
-  template<PieceType Pt> Square square(Color c) const;
-  bool is_on_semiopen_file(Color c, Square s) const;
-
-  // Castling
-  int 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;
-  bool is_discovery_check_on_king(Color c, Move m) const;
-
-  // Attacks to/from a given square
-  Bitboard attackers_to(Square s) const;
-  Bitboard attackers_to(Square s, Bitboard occupied) const;
-  Bitboard attacks_from(PieceType pt, Square s) const;
-  template<PieceType> Bitboard attacks_from(Square s) const;
-  template<PieceType> Bitboard attacks_from(Square s, Color c) const;
-  Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
-
-  // Properties of moves
-  bool legal(Move m) const;
-  bool pseudo_legal(const Move m) const;
-  bool capture(Move m) const;
-  bool capture_or_promotion(Move m) const;
-  bool gives_check(Move m) const;
-  bool advanced_pawn_push(Move m) const;
-  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);
-  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;
-
-  // Accessing hash keys
-  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;
-  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;
-  Score psq_score() 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();
-
-private:
-  // Initialization helpers (used while setting up a position)
-  void set_castling_right(Color c, Square rfrom);
-  void set_state(StateInfo* si) const;
-  void set_check_info(StateInfo* si) const;
-
-  // Other helpers
-  void put_piece(Piece pc, Square s);
-  void remove_piece(Piece pc, Square s);
-  void move_piece(Piece pc, Square from, Square to);
-  template<bool Do>
-  void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
-
-  // Data members
-  Piece board[SQUARE_NB];
-  Bitboard byTypeBB[PIECE_TYPE_NB];
-  Bitboard byColorBB[COLOR_NB];
-  int pieceCount[PIECE_NB];
-  Square pieceList[PIECE_NB][16];
-  int index[SQUARE_NB];
-  int castlingRightsMask[SQUARE_NB];
-  Square castlingRookSquare[CASTLING_RIGHT_NB];
-  Bitboard castlingPath[CASTLING_RIGHT_NB];
-  int gamePly;
-  Color sideToMove;
-  Score psq;
-  Thread* thisThread;
-  StateInfo* st;
-  bool chess960;
+   public:
+    static void init();
+
+    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;
+
+    // Position representation
+    Bitboard pieces(PieceType pt = ALL_PIECES) const;
+    template<typename... PieceTypes>
+    Bitboard pieces(PieceType pt, PieceTypes... pts) const;
+    Bitboard pieces(Color c) const;
+    template<typename... PieceTypes>
+    Bitboard pieces(Color c, PieceTypes... pts) const;
+    Piece    piece_on(Square s) const;
+    Square   ep_square() const;
+    bool     empty(Square s) const;
+    template<PieceType Pt>
+    int count(Color c) const;
+    template<PieceType Pt>
+    int count() const;
+    template<PieceType Pt>
+    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;
+
+    // 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<PieceType Pt>
+    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;
+
+    // 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;
+
+    // Accessing hash keys
+    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;
+    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();
+
+    // Used by NNUE
+    StateInfo* state() const;
+
+    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;
+
+    // Other helpers
+    void move_piece(Square from, Square to);
+    template<bool Do>
+    void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
+    template<bool AfterMove>
+    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;
 };
 
-namespace PSQT {
-  extern Score psq[PIECE_NB][SQUARE_NB];
-}
-
-extern std::ostream& operator<<(std::ostream& os, const Position& pos);
+std::ostream& operator<<(std::ostream& os, const Position& pos);
 
-inline Color Position::side_to_move() const {
-  return sideToMove;
-}
-
-inline bool Position::empty(Square s) const {
-  return board[s] == NO_PIECE;
-}
+inline Color Position::side_to_move() const { return sideToMove; }
 
 inline Piece Position::piece_on(Square s) const {
-  return board[s];
+    assert(is_ok(s));
+    return board[s];
 }
 
-inline Piece Position::moved_piece(Move m) const {
-  return board[from_sq(m)];
-}
-
-inline Bitboard Position::pieces() const {
-  return byTypeBB[ALL_PIECES];
-}
-
-inline Bitboard Position::pieces(PieceType pt) const {
-  return byTypeBB[pt];
-}
-
-inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const {
-  return byTypeBB[pt1] | byTypeBB[pt2];
-}
+inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }
 
-inline Bitboard Position::pieces(Color c) const {
-  return byColorBB[c];
-}
+inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); }
 
-inline Bitboard Position::pieces(Color c, PieceType pt) const {
-  return byColorBB[c] & byTypeBB[pt];
-}
+inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; }
 
-inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const {
-  return byColorBB[c] & (byTypeBB[pt1] | byTypeBB[pt2]);
+template<typename... PieceTypes>
+inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const {
+    return pieces(pt) | pieces(pts...);
 }
 
-template<PieceType Pt> inline int Position::count(Color c) const {
-  return pieceCount[make_piece(c, Pt)];
-}
+inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; }
 
-template<PieceType Pt> inline int Position::count() const {
-  return pieceCount[make_piece(WHITE, Pt)] + pieceCount[make_piece(BLACK, Pt)];
+template<typename... PieceTypes>
+inline Bitboard Position::pieces(Color c, PieceTypes... pts) const {
+    return pieces(c) & pieces(pts...);
 }
 
-template<PieceType Pt> inline const Square* Position::squares(Color c) const {
-  return pieceList[make_piece(c, Pt)];
+template<PieceType Pt>
+inline int Position::count(Color c) const {
+    return pieceCount[make_piece(c, Pt)];
 }
 
-template<PieceType Pt> inline Square Position::square(Color c) const {
-  assert(pieceCount[make_piece(c, Pt)] == 1);
-  return pieceList[make_piece(c, Pt)][0];
+template<PieceType Pt>
+inline int Position::count() const {
+    return count<Pt>(WHITE) + count<Pt>(BLACK);
 }
 
-inline Square Position::ep_square() const {
-  return st->epSquare;
+template<PieceType Pt>
+inline Square Position::square(Color c) const {
+    assert(count<Pt>(c) == 1);
+    return lsb(pieces(c, Pt));
 }
 
-inline bool Position::is_on_semiopen_file(Color c, Square s) const {
-  return !(pieces(c, PAWN) & file_bb(s));
-}
+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 int Position::castling_rights(Color c) const {
-  return st->castlingRights & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING);
+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);
+    assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
 
-  return byTypeBB[ALL_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);
-
-  return castlingRookSquare[cr];
-}
+    assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
 
-template<PieceType Pt>
-inline Bitboard Position::attacks_from(Square s) const {
-  static_assert(Pt != PAWN, "Pawn attacks need color");
-
-  return  Pt == BISHOP || Pt == ROOK ? attacks_bb<Pt>(s, byTypeBB[ALL_PIECES])
-        : Pt == QUEEN  ? attacks_from<ROOK>(s) | attacks_from<BISHOP>(s)
-        : PseudoAttacks[Pt][s];
+    return castlingRookSquare[cr];
 }
 
-template<>
-inline Bitboard Position::attacks_from<PAWN>(Square s, Color c) const {
-  return PawnAttacks[c][s];
-}
+inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); }
 
-inline Bitboard Position::attacks_from(PieceType pt, Square s) const {
-  return attacks_bb(pt, s, byTypeBB[ALL_PIECES]);
-}
+template<PieceType Pt>
+inline Bitboard Position::attacks_by(Color c) const {
 
-inline Bitboard Position::attackers_to(Square s) const {
-  return attackers_to(s, byTypeBB[ALL_PIECES]);
+    if constexpr (Pt == PAWN)
+        return c == WHITE ? pawn_attacks_bb<WHITE>(pieces(WHITE, PAWN))
+                          : pawn_attacks_bb<BLACK>(pieces(BLACK, PAWN));
+    else
+    {
+        Bitboard threats   = 0;
+        Bitboard attackers = pieces(c, Pt);
+        while (attackers)
+            threats |= attacks_bb<Pt>(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::check_squares(PieceType pt) const {
-  return st->checkSquares[pt];
-}
+inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; }
 
-inline bool Position::is_discovery_check_on_king(Color c, Move m) const {
-  return st->blockersForKing[c] & from_sq(m);
-}
+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 Key Position::key() const { return adjust_key50<false>(st->key); }
 
-inline bool Position::advanced_pawn_push(Move m) const {
-  return   type_of(moved_piece(m)) == PAWN
-        && relative_rank(sideToMove, to_sq(m)) > RANK_5;
+template<bool AfterMove>
+inline Key Position::adjust_key50(Key k) const {
+    return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
 }
 
-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::pawn_key() const { return st->pawnKey; }
 
-inline Key Position::key() const {
-  return st->key;
-}
+inline Key Position::material_key() const { return st->materialKey; }
 
-inline Key Position::pawn_key() const {
-  return st->pawnKey;
-}
+inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; }
 
-inline Key Position::material_key() const {
-  return st->materialKey;
+inline Value Position::non_pawn_material() const {
+    return non_pawn_material(WHITE) + non_pawn_material(BLACK);
 }
 
-inline Score Position::psq_score() const {
-  return psq;
-}
+inline int Position::game_ply() const { return gamePly; }
 
-inline Value Position::non_pawn_material(Color c) const {
-  return st->nonPawnMaterial[c];
-}
+inline int Position::rule50_count() const { return st->rule50; }
 
-inline Value Position::non_pawn_material() const {
-  return st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK];
-}
+inline bool Position::is_chess960() const { return chess960; }
 
-inline int Position::game_ply() const {
-  return gamePly;
+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;
 }
 
-inline int Position::rule50_count() const {
-  return st->rule50;
+// 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;
 }
 
-inline bool Position::opposite_bishops() const {
-  return   pieceCount[W_BISHOP] == 1
-        && pieceCount[B_BISHOP] == 1
-        && opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
-}
+inline Piece Position::captured_piece() const { return st->capturedPiece; }
 
-inline bool Position::is_chess960() const {
-  return chess960;
-}
+inline Thread* Position::this_thread() const { return thisThread; }
 
-inline bool Position::capture_or_promotion(Move m) const {
-  assert(is_ok(m));
-  return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m));
-}
+inline void Position::put_piece(Piece pc, Square s) {
 
-inline bool Position::capture(Move m) const {
-  assert(is_ok(m));
-  // Castling is encoded as "king captures rook"
-  return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == ENPASSANT;
+    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 Piece Position::captured_piece() const {
-  return st->capturedPiece;
-}
+inline void Position::remove_piece(Square s) {
 
-inline Thread* Position::this_thread() const {
-  return thisThread;
+    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::put_piece(Piece pc, Square s) {
+inline void Position::move_piece(Square from, Square to) {
 
-  board[s] = pc;
-  byTypeBB[ALL_PIECES] |= s;
-  byTypeBB[type_of(pc)] |= s;
-  byColorBB[color_of(pc)] |= s;
-  index[s] = pieceCount[pc]++;
-  pieceList[pc][index[s]] = s;
-  pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
-  psq += PSQT::psq[pc][s];
+    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::remove_piece(Piece pc, Square s) {
-
-  // WARNING: This is not a reversible operation. If we remove a piece in
-  // do_move() and then replace it in undo_move() we will put it at the end of
-  // the list and not in its original place, it means index[] and pieceList[]
-  // are not invariant to a do_move() + undo_move() sequence.
-  byTypeBB[ALL_PIECES] ^= s;
-  byTypeBB[type_of(pc)] ^= s;
-  byColorBB[color_of(pc)] ^= s;
-  /* board[s] = NO_PIECE;  Not needed, overwritten by the capturing one */
-  Square lastSquare = pieceList[pc][--pieceCount[pc]];
-  index[lastSquare] = index[s];
-  pieceList[pc][index[lastSquare]] = lastSquare;
-  pieceList[pc][pieceCount[pc]] = SQ_NONE;
-  pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
-  psq -= PSQT::psq[pc][s];
-}
+inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); }
 
-inline void Position::move_piece(Piece pc, Square from, Square to) {
-
-  // index[from] is not updated and becomes stale. This works as long as index[]
-  // is accessed just by known occupied squares.
-  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;
-  index[to] = index[from];
-  pieceList[pc][index[to]] = to;
-  psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
-}
+inline StateInfo* Position::state() const { return st; }
 
-inline void Position::do_move(Move m, StateInfo& newSt) {
-  do_move(m, newSt, gives_check(m));
-}
+}  // namespace Stockfish
 
-#endif // #ifndef POSITION_H_INCLUDED
+#endif  // #ifndef POSITION_H_INCLUDED
diff --git a/src/psqt.cpp b/src/psqt.cpp
deleted file mode 100644 (file)
index 647bd86..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
-  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
-  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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <algorithm>
-
-#include "types.h"
-
-Value PieceValue[PHASE_NB][PIECE_NB] = {
-  { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg },
-  { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg }
-};
-
-namespace PSQT {
-
-#define S(mg, eg) make_score(mg, eg)
-
-// Bonus[PieceType][Square / 2] contains Piece-Square scores. For each piece
-// type on a given square a (middlegame, endgame) score pair is assigned. Table
-// is defined for files A..D and white side: it is symmetric for black side and
-// second half of the files.
-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(-53,-57), S( -5,-30), S( -8,-37), S(-23,-12) },
-   { S(-15,-37), S(  8,-13), S( 19,-17), S(  4,  1) },
-   { S( -7,-16), S( 21, -1), S( -5, -2), S( 17, 10) },
-   { S( -5,-20), S( 11, -6), S( 25,  0), S( 39, 17) },
-   { S(-12,-17), S( 29, -1), S( 22,-14), S( 31, 15) },
-   { S(-16,-30), S(  6,  6), S(  1,  4), S( 11,  6) },
-   { S(-17,-31), S(-14,-20), S(  5, -1), S(  0,  1) },
-   { S(-48,-46), S(  1,-42), S(-14,-37), S(-23,-24) }
-  },
-  { // 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,-55), 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,-12), S( 8,  1) },
-   { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) },
-   { S(-2,-75), S(-2,-52), S( 1,-43), S(-2,-36) }
-  },
-  { // 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(  3,-10), S(  3, -6), S( 10, 10), S( 19,  0), S( 16, 14), S( 19,  7), S(  7, -5), S( -5,-19) },
-   { S( -9,-10), S(-15,-10), S( 11,-10), S( 15,  4), S( 32,  4), S( 22,  3), S(  5, -6), S(-22, -4) },
-   { S( -8,  6), S(-23, -2), S(  6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S(  4,-10), S(-12, -9) },
-   { S( 13,  9), S(  0,  4), S(-13,  3), S(  1,-12), S( 11,-12), S( -2, -6), S(-13, 13), S(  5,  8) },
-   { S( -5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5,  7), S(-15,  6), S(-18, 13) },
-   { S( -7,  0), S(  7,-11), S( -3, 12), S(-13, 21), S(  5, 25), S(-16, 19), S( 10,  4), S( -8,  7) }
-  };
-
-#undef S
-
-Score psq[PIECE_NB][SQUARE_NB];
-
-// init() initializes piece-square tables: the white halves of the tables are
-// copied from Bonus[] 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; pc <= W_KING; ++pc)
-  {
-      PieceValue[MG][~pc] = PieceValue[MG][pc];
-      PieceValue[EG][~pc] = PieceValue[EG][pc];
-
-      Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
-
-      for (Square s = SQ_A1; s <= SQ_H8; ++s)
-      {
-          File f = map_to_queenside(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][~s] = -psq[pc][s];
-      }
-  }
-}
-
-} // namespace PSQT
index 0eea4127f80a36955d652301d73882f64806eca2..3c61ea2f395cdbc441f58dcffd94b615436cbd5a 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "search.h"
+
 #include <algorithm>
+#include <array>
+#include <atomic>
 #include <cassert>
 #include <cmath>
-#include <cstring>   // For std::memset
+#include <cstdlib>
+#include <cstring>
+#include <initializer_list>
 #include <iostream>
 #include <sstream>
+#include <string>
+#include <utility>
 
+#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"
+
+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;
@@ -58,115 +68,98 @@ using namespace Search;
 
 namespace {
 
-  // Different node types, used as a template parameter
-  enum NodeType { NonPV, PV };
-
-  constexpr uint64_t ttHitAverageWindow     = 4096;
-  constexpr uint64_t ttHitAverageResolution = 1024;
-
-  // Razor and futility margins
-  constexpr int RazorMargin = 531;
-  Value futility_margin(Depth d, bool improving) {
-    return Value(217 * (d - improving));
-  }
-
-  // Reductions lookup table, initialized at startup
-  int Reductions[MAX_MOVES]; // [depth or moveNumber]
-
-  Depth reduction(bool i, Depth d, int mn) {
-    int r = Reductions[d] * Reductions[mn];
-    return (r + 511) / 1024 + (!i && r > 1007);
-  }
-
-  constexpr int futility_move_count(bool improving, Depth depth) {
-    return (5 + depth * depth) * (1 + improving) / 2 - 1;
-  }
-
-  // History and stats update bonus, based on depth
-  int stat_bonus(Depth d) {
-    return d > 15 ? -8 : 19 * d * d + 155 * d - 132;
-  }
-
-  // Add a small random component to draw evaluations to avoid 3fold-blindness
-  Value value_draw(Thread* thisThread) {
-    return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1);
-  }
-
-  // Skill structure is used to implement strength limit
-  struct Skill {
-    explicit Skill(int l) : level(l) {}
-    bool enabled() const { return level < 20; }
-    bool time_to_pick(Depth depth) const { return depth == 1 + level; }
-    Move pick_best(size_t multiPV);
+// Different node types, used as a template parameter
+enum NodeType {
+    NonPV,
+    PV,
+    Root
+};
 
-    int level;
-    Move best = MOVE_NONE;
-  };
-
-  // Breadcrumbs are used to mark nodes as being searched by a given thread
-  struct Breadcrumb {
-    std::atomic<Thread*> thread;
-    std::atomic<Key> key;
-  };
-  std::array<Breadcrumb, 1024> breadcrumbs;
-
-  // ThreadHolding structure keeps track of which thread left breadcrumbs at the given
-  // node for potential reductions. A free node will be marked upon entering the moves
-  // loop by the constructor, and unmarked upon leaving that loop by the destructor.
-  struct ThreadHolding {
-    explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) {
-       location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr;
-       otherThread = false;
-       owning = false;
-       if (location)
-       {
-          // See if another already marked this location, if not, mark it ourselves
-          Thread* tmp = (*location).thread.load(std::memory_order_relaxed);
-          if (tmp == nullptr)
-          {
-              (*location).thread.store(thisThread, std::memory_order_relaxed);
-              (*location).key.store(posKey, std::memory_order_relaxed);
-              owning = true;
-          }
-          else if (   tmp != thisThread
-                   && (*location).key.load(std::memory_order_relaxed) == posKey)
-              otherThread = true;
-       }
-    }
+// Futility margin
+Value futility_margin(Depth d, bool noTtCutNode, bool improving) {
+    return Value((116 - 44 * noTtCutNode) * (d - improving));
+}
 
-    ~ThreadHolding() {
-       if (owning) // Free the marked location
-           (*location).thread.store(nullptr, std::memory_order_relaxed);
-    }
+// Reductions lookup table initialized at startup
+int Reductions[MAX_MOVES];  // [depth or moveNumber]
 
-    bool marked() { return otherThread; }
+Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) {
+    int reductionScale = Reductions[d] * Reductions[mn];
+    return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024
+         + (!i && reductionScale > 880);
+}
 
-    private:
-    Breadcrumb* location;
-    bool otherThread, owning;
-  };
+constexpr int futility_move_count(bool improving, Depth depth) {
+    return improving ? (3 + depth * depth) : (3 + depth * depth) / 2;
+}
 
-  template <NodeType NT>
-  Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
+// History and stats update bonus, based on depth
+int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); }
 
-  template <NodeType NT>
-  Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
+// History and stats update malus, based on depth
+int stat_malus(Depth d) { return std::min(400 * d - 354, 1201); }
 
-  Value value_to_tt(Value v, int ply);
-  Value value_from_tt(Value v, int ply, int r50c);
-  void update_pv(Move* pv, Move move, Move* childPv);
-  void update_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);
+// 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);
+}
 
-  // 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<bool Root>
-  uint64_t perft(Position& pos, Depth depth) {
+// 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)
+        {
+            double e = double(uci_elo - 1320) / (3190 - 1320);
+            level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0);
+        }
+        else
+            level = double(skill_level);
+    }
+    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);
+
+    double level;
+    Move   best = MOVE_NONE;
+};
+
+template<NodeType nodeType>
+Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
+
+template<NodeType nodeType>
+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);
+
+// Utility to verify move generation. All the leaf nodes up
+// to the given depth are generated and counted, and the sum is returned.
+template<bool Root>
+uint64_t perft(Position& pos, Depth depth) {
 
     StateInfo st;
-    uint64_t cnt, nodes = 0;
+    ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
+
+    uint64_t   cnt, nodes = 0;
     const bool leaf = (depth == 2);
 
     for (const auto& m : MoveList<LEGAL>(pos))
@@ -184,460 +177,395 @@ 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
 
+// Called at startup to initialize various lookup tables
 void Search::init() {
 
-  for (int i = 1; i < MAX_MOVES; ++i)
-      Reductions[i] = int((24.8 + 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));
 }
 
 
-/// 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();
+    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
 }
 
 
-/// MainThread::search() is started 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 MainThread::search() {
 
-  if (Limits.perft)
-  {
-      nodes = perft<true>(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();
-
-  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
-  {
-      for (Thread* th : Threads)
-      {
-          th->bestMoveChanges = 0;
-          if (th != this)
-              th->start_searching();
-      }
-
-      Thread::search(); // Let's 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.
-
-  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;
-
-  // Wait until all threads have finished
-  for (Thread* th : Threads)
-      if (th != this)
-          th->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();
-
-  Thread* bestThread = this;
-
-  // Check if there are threads with a better score than main thread
-  if (    Options["MultiPV"] == 1
-      && !Limits.depth
-      && !(Skill(Options["Skill Level"]).enabled() || Options["UCI_LimitStrength"])
-      &&  rootMoves[0].pv[0] != MOVE_NONE)
-  {
-      std::map<Move, int64_t> votes;
-      Value minScore = this->rootMoves[0].score;
-
-      // Find out minimum score
-      for (Thread* th: Threads)
-          minScore = std::min(minScore, th->rootMoves[0].score);
-
-      // Vote according to score and depth, and select the best thread
-      for (Thread* th : Threads)
-      {
-          votes[th->rootMoves[0].pv[0]] +=
-              (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
-
-          if (bestThread->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY)
-          {
-              // Make sure we pick the shortest mate
-              if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
-                  bestThread = th;
-          }
-          else if (   th->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY
-                   || votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])
-              bestThread = th;
-      }
-  }
-
-  previousScore = bestThread->rootMoves[0].score;
-
-  // Send again PV info if we have a new best thread
-  if (bestThread != this)
-      sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl;
-
-  sync_cout << "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());
-
-  std::cout << sync_endl;
-}
+    if (Limits.perft)
+    {
+        nodes = perft<true>(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();
+
+    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.
 
-/// 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.
+    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;
+
+    // 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();
+
+    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();
+
+    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;
+
+    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());
+
+    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() {
 
-  // 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 statScores and killer initialization.
-  Stack stack[MAX_PLY+10], *ss = stack+7;
-  Move  pv[MAX_PLY+1];
-  Value bestValue, 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->pv = pv;
-
-  bestValue = delta = alpha = -VALUE_INFINITE;
-  beta = VALUE_INFINITE;
-
-  if (mainThread)
-  {
-      if (mainThread->previousScore == 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->previousScore;
-  }
-
-  size_t multiPV = Options["MultiPV"];
-
-  // Pick integer skill levels, but non-deterministically round up or down
-  // such that the average integer skill corresponds to the input floating point one.
-  // UCI_Elo is converted 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) results spanning a wide range of k values.
-  PRNG rng(now());
-  double floatLevel = Options["UCI_LimitStrength"] ?
-                        clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) :
-                        double(Options["Skill Level"]);
-  int intLevel = int(floatLevel) +
-                 ((floatLevel - int(floatLevel)) * 1024 > rng.rand<unsigned>() % 1024  ? 1 : 0);
-  Skill skill(intLevel);
-
-  // 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());
-  ttHitAverage = ttHitAverageWindow * ttHitAverageResolution / 2;
-
-  int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns
-
-  // In analysis mode, adjust contempt in accordance with user preference
-  if (Limits.infinite || Options["UCI_AnalyseMode"])
-      ct =  Options["Analysis Contempt"] == "Off"  ? 0
-          : Options["Analysis Contempt"] == "Both" ? ct
-          : Options["Analysis Contempt"] == "White" && us == BLACK ? -ct
-          : Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct
-          : ct;
-
-  // Evaluation score is from the white point of view
-  contempt = (us == WHITE ?  make_score(ct, ct / 2)
-                          : -make_score(ct, ct / 2));
-
-  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;
-
-      // Save the last iteration's scores before 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;
-
-      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;
-          }
-
-          // Reset UCI info selDepth for each depth and each PV line
-          selDepth = 0;
-
-          // Reset aspiration window starting size
-          if (rootDepth >= 4)
-          {
-              Value previousScore = rootMoves[pvIdx].previousScore;
-              delta = Value(21 + abs(previousScore) / 256);
-              alpha = std::max(previousScore - delta,-VALUE_INFINITE);
-              beta  = std::min(previousScore + delta, VALUE_INFINITE);
-
-              // Adjust contempt based on root move's previousScore (dynamic contempt)
-              int dct = ct + (102 - ct / 2) * previousScore / (abs(previousScore) + 157);
-
-              contempt = (us == WHITE ?  make_score(dct, dct / 2)
-                                      : -make_score(dct, dct / 2));
-          }
-
-          // 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)
-          {
-              Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter);
-              bestValue = ::search<PV>(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
-              // 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
-              // 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;
-
-              // 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, alpha, beta) << 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);
-
-                  failedHighCnt = 0;
-                  if (mainThread)
-                      mainThread->stopOnPonderhit = false;
-              }
-              else if (bestValue >= beta)
-              {
-                  beta = std::min(bestValue + delta, VALUE_INFINITE);
-                  ++failedHighCnt;
-              }
-              else
-              {
-                  ++rootMoves[pvIdx].bestMoveCount;
-                  break;
-              }
-
-              delta += delta / 4 + 5;
-
-              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);
-
-          if (    mainThread
-              && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000))
-              sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl;
-      }
-
-      if (!Threads.stop)
-          completedDepth = 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;
-
-      if (!mainThread)
-          continue;
-
-      // If 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);
-
-      // Do we have time for the next iteration? Can we stop searching now?
-      if (    Limits.use_time_management()
-          && !Threads.stop
-          && !mainThread->stopOnPonderhit)
-      {
-          double fallingEval = (332 +  6 * (mainThread->previousScore - bestValue)
-                                    +  6 * (mainThread->iterValue[iterIdx]  - bestValue)) / 704.0;
-          fallingEval = clamp(fallingEval, 0.5, 1.5);
-
-          // If the bestMove is stable over several iterations, reduce time accordingly
-          timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.94 : 0.91;
-          double reduction = (1.41 + mainThread->previousTimeReduction) / (2.27 * timeReduction);
-
-          // 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;
-          }
-          double bestMoveInstability = 1 + totBestMoveChanges / Threads.size();
-
-          // Stop the search if we have only one legal move, or if available time elapsed
-          if (   rootMoves.size() == 1
-              || Time.elapsed() > Time.optimum() * fallingEval * reduction * bestMoveInstability)
-          {
-              // 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 (   Threads.increaseDepth
-                   && !mainThread->ponder
-                   && Time.elapsed() > Time.optimum() * fallingEval * reduction * bestMoveInstability * 0.6)
-                   Threads.increaseDepth = false;
-          else
-                   Threads.increaseDepth = true;
-      }
-
-      mainThread->iterValue[iterIdx] = bestValue;
-      iterIdx = (iterIdx + 1) & 3;
-  }
-
-  if (!mainThread)
-      return;
-
-  mainThread->previousTimeReduction = timeReduction;
-
-  // If skill level is enabled, swap 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)));
+    // 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;
+    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;
+    }
+
+    for (int i = 0; i <= MAX_PLY + 2; ++i)
+        (ss + i)->ply = i;
+
+    ss->pv = pv;
+
+    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;
+    }
+
+    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));
+
+    multiPV = std::min(multiPV, rootMoves.size());
+
+    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;
+
+        // 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;
+
+        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;
+            }
+
+            // Reset UCI info selDepth for each depth and each PV line
+            selDepth = 0;
+
+            // Reset aspiration window starting size
+            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);
+
+            // Adjust optimism based on root move's averageScore (~4 Elo)
+            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
+            // 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<Root>(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);
+
+                // 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;
+
+                // 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;
+
+                delta += delta / 3;
+
+                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);
+
+            if (mainThread && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000))
+                sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl;
+        }
+
+        if (!Threads.stop)
+            completedDepth = 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;
+
+        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);
+
+        // 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 = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue)
+                                  + 6 * (mainThread->iterValue[iterIdx] - bestValue))
+                               / 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 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);
+
+            // 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;
+    }
+
+    if (!mainThread)
+        return;
+
+    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)));
 }
 
 
 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<NodeType nodeType>
+Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
 
-  template <NodeType NT>
-  Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
+    constexpr bool PvNode   = nodeType != NonPV;
+    constexpr bool rootNode = nodeType == Root;
 
-    constexpr bool PvNode = NT == PV;
-    const bool rootNode = PvNode && ss->ply == 0;
+    // Dive into quiescence search when the depth reaches zero
+    if (depth <= 0)
+        return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta);
 
-    // 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 (   pos.rule50_count() >= 3
-        && alpha < VALUE_DRAW
-        && !rootNode
-        && 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)
             return alpha;
     }
 
-    // Dive into quiescence search when the depth reaches zero
-    if (depth <= 0)
-        return qsearch<NT>(pos, ss, alpha, beta);
-
     assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
     assert(PvNode || (alpha == beta - 1));
     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);
+
     TTEntry* tte;
-    Key posKey;
-    Move ttMove, move, excludedMove, bestMove;
-    Depth extension, newDepth;
-    Value bestValue, value, ttValue, eval, maxValue;
-    bool ttHit, ttPv, inCheck, givesCheck, improving, didLMR, priorCapture;
-    bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture, singularLMR;
-    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();
-    inCheck = pos.checkers();
-    priorCapture = pos.captured_piece();
-    Color us = pos.side_to_move();
+    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;
+    bestValue                                             = -VALUE_INFINITE;
+    maxValue                                              = VALUE_INFINITE;
 
     // Check for the available remaining time
     if (thisThread == Threads.main())
@@ -650,100 +578,95 @@ namespace {
     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 && !inCheck) ? evaluate(pos)
-                                                    : value_draw(pos.this_thread());
+            return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos)
+                                                        : 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 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);
+        beta  = std::min(mate_in(ss->ply + 1), beta);
         if (alpha >= beta)
             return alpha;
     }
+    else
+        thisThread->rootDelta = beta - alpha;
 
     assert(0 <= ss->ply && ss->ply < MAX_PLY);
 
-    (ss+1)->ply = ss->ply + 1;
-    (ss+1)->excludedMove = bestMove = MOVE_NONE;
-    (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
-    Square prevSq = to_sq((ss-1)->currentMove);
-
-    // Initialize statScore to zero for the grandchildren of the current position.
-    // So statScore is shared between all grandchildren and only the first grandchild
-    // starts with statScore = 0. Later grandchildren start with the last calculated
-    // statScore of the previous grandchild. This influences the reduction rules in
-    // LMR which are based on the statScore of parent position.
-    if (rootNode)
-        (ss+4)->statScore = 0;
-    else
-        (ss+2)->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. We don't want the score of a partial
-    // search to overwrite a previous full search TT value, so we use a different
-    // position key in case of an excluded move.
+    // Step 4. Transposition table lookup.
     excludedMove = ss->excludedMove;
-    posKey = pos.key() ^ Key(excludedMove << 16); // Isn't a very good hash
-    tte = TT.probe(posKey, ttHit);
-    ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
-    ttMove =  rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
-            : ttHit    ? tte->move() : MOVE_NONE;
-    ttPv = PvNode || (ttHit && tte->is_pv());
-    // thisThread->ttHitAverage can be used to approximate the running average of ttHit
-    thisThread->ttHitAverage =   (ttHitAverageWindow - 1) * thisThread->ttHitAverage / ttHitAverageWindow
-                                + ttHitAverageResolution * 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    = 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,
+    // to save indentation, we list the condition in all code between here and there.
+    if (!excludedMove)
+        ss->ttPv = PvNode || (ss->ttHit && tte->is_pv());
 
     // At non-PV nodes we check for an early TT cutoff
-    if (  !PvNode
-        && ttHit
-        && tte->depth() >= depth
-        && ttValue != VALUE_NONE // Possible in case of TT access race
-        && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
-                            : (tte->bound() & BOUND_UPPER)))
+    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
+        // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo)
         if (ttMove)
         {
             if (ttValue >= beta)
             {
-                if (!pos.capture_or_promotion(ttMove))
+                // Bonus for a quiet ttMove that fails high (~2 Elo)
+                if (!ttCapture)
                     update_quiet_stats(pos, ss, ttMove, stat_bonus(depth));
 
-                // Extra penalty for early quiet moves of the previous ply
-                if ((ss-1)->moveCount <= 2 && !priorCapture)
-                    update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(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));
             }
-            // Penalty for a quiet ttMove that fails low
-            else if (!pos.capture_or_promotion(ttMove))
+            // 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);
             }
         }
 
+        // 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;
     }
 
     // Step 5. Tablebases probe
-    if (!rootNode && TB::Cardinality)
+    if (!rootNode && !excludedMove && TB::Cardinality)
     {
         int piecesCount = pos.count<ALL_PIECES>();
 
-        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())
@@ -755,19 +678,21 @@ namespace {
 
                 int drawScore = TB::UseRule50 ? 1 : 0;
 
-                value =  wdl < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply + 1
-                       : wdl >  drawScore ?  VALUE_MATE - MAX_PLY - ss->ply - 1
-                                          :  VALUE_DRAW + 2 * wdl * drawScore;
+                Value tbValue = VALUE_TB - ss->ply;
 
-                Bound b =  wdl < -drawScore ? BOUND_UPPER
-                         : wdl >  drawScore ? BOUND_LOWER : BOUND_EXACT;
+                // 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;
 
-                if (    b == BOUND_EXACT
-                    || (b == BOUND_LOWER ? value >= beta : value <= alpha))
+                Bound b = wdl < -drawScore ? BOUND_UPPER
+                        : wdl > drawScore  ? BOUND_LOWER
+                                           : BOUND_EXACT;
+
+                if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha))
                 {
-                    tte->save(posKey, value_to_tt(value, ss->ply), ttPv, b,
-                              std::min(MAX_PLY - 1, depth + 6),
-                              MOVE_NONE, VALUE_NONE);
+                    tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b,
+                              std::min(MAX_PLY - 1, depth + 6), MOVE_NONE, VALUE_NONE);
 
                     return value;
                 }
@@ -783,100 +708,115 @@ namespace {
         }
     }
 
+    CapturePieceToHistory& captureHistory = thisThread->captureHistory;
+
     // Step 6. Static evaluation of the position
-    if (inCheck)
+    if (ss->inCheck)
     {
+        // Skip early pruning when in check
         ss->staticEval = eval = VALUE_NONE;
-        improving = false;
-        goto moves_loop;  // Skip early pruning when in check
+        improving             = false;
+        goto moves_loop;
     }
-    else if (ttHit)
+    else if (excludedMove)
+    {
+        // Providing the hint that this node's accumulator will be used often
+        // brings significant Elo gain (~13 Elo).
+        Eval::NNUE::hint_common_parent_position(pos);
+        eval = ss->staticEval;
+    }
+    else if (ss->ttHit)
     {
         // Never assume anything about values stored in TT
         ss->staticEval = eval = tte->eval();
         if (eval == VALUE_NONE)
             ss->staticEval = eval = evaluate(pos);
+        else if (PvNode)
+            Eval::NNUE::hint_common_parent_position(pos);
 
-        if (eval == VALUE_DRAW)
-            eval = value_draw(thisThread);
-
-        // Can ttValue be used as a better position evaluation?
-        if (    ttValue != VALUE_NONE
-            && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)))
+        // 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
     {
-        if ((ss-1)->currentMove != MOVE_NULL)
-        {
-            int bonus = -(ss-1)->statScore / 512;
-
-            ss->staticEval = eval = evaluate(pos) + bonus;
-        }
-        else
-            ss->staticEval = eval = -(ss-1)->staticEval + 2 * Eval::Tempo;
+        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);
+    }
 
-        tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
+    // 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), -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;
     }
 
+    // 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 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;
+
     // Step 7. Razoring (~1 Elo)
-    if (   !rootNode // The required rootNode PV handling is not available in qsearch
-        &&  depth < 2
-        &&  eval <= alpha - RazorMargin)
-        return qsearch<NT>(pos, ss, alpha, beta);
-
-    improving =  (ss-2)->staticEval == VALUE_NONE ? (ss->staticEval >= (ss-4)->staticEval
-              || (ss-4)->staticEval == VALUE_NONE) : ss->staticEval >= (ss-2)->staticEval;
-
-    // Step 8. Futility pruning: child node (~50 Elo)
-    if (   !PvNode
-        &&  depth < 6
-        &&  eval - futility_margin(depth, improving) >= beta
-        &&  eval < VALUE_KNOWN_WIN) // Do not return unproven wins
-        return eval;
-
-    // Step 9. Null move search with verification search (~40 Elo)
-    if (   !PvNode
-        && (ss-1)->currentMove != MOVE_NULL
-        && (ss-1)->statScore < 23397
-        &&  eval >= beta
-        &&  eval >= ss->staticEval
-        &&  ss->staticEval >= beta - 32 * depth + 292 - improving * 30
-        && !excludedMove
-        &&  pos.non_pawn_material(us)
-        && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
+    // 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)
+    {
+        value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
+        if (value < alpha)
+            return value;
+    }
+
+    // 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 / 337
+             >= beta
+        && 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 < 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 value
-        Depth R = (854 + 68 * depth) / 258 + std::min(int(eval - beta) / 192, 3);
+        // 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);
 
-        Value nullValue = -search<NonPV>(pos, ss+1, -beta, -beta+1, depth-R, !cutNode);
+        Value nullValue = -search<NonPV>(pos, ss + 1, -beta, -beta + 1, depth - R, !cutNode);
 
         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 scores
-            if (nullValue >= VALUE_MATE_IN_MAX_PLY)
-                nullValue = beta;
-
-            if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 13))
+            if (thisThread->nmpMinPly || depth < 15)
                 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
-            // for us, until ply exceeds nmpMinPly.
-            thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4;
-            thisThread->nmpColor = us;
+            // until ply exceeds nmpMinPly.
+            thisThread->nmpMinPly = ss->ply + 3 * (depth - R) / 4;
 
-            Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false);
+            Value v = search<NonPV>(pos, ss, beta - 1, beta, depth - R, false);
 
             thisThread->nmpMinPly = 0;
 
@@ -885,810 +825,988 @@ namespace {
         }
     }
 
-    // Step 10. ProbCut (~10 Elo)
-    // If we have a good enough capture and a reduced search returns a value
+    // 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.
+    if (PvNode && !ttMove)
+        depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
+
+    if (depth <= 0)
+        return qsearch<PV>(pos, ss, alpha, beta);
+
+    // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough.
+    if (cutNode && depth >= 8 && !ttMove)
+        depth -= 2;
+
+    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
     // much above beta, we can (almost) safely prune the previous move.
-    if (   !PvNode
-        &&  depth >= 5
-        &&  abs(beta) < VALUE_MATE_IN_MAX_PLY)
+    if (
+      !PvNode && depth > 3
+      && 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
+      // So effective depth is equal to depth - 3
+      && !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta))
     {
-        Value raisedBeta = std::min(beta + 189 - 45 * improving, VALUE_INFINITE);
-        MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &thisThread->captureHistory);
-        int probCutCount = 0;
+        assert(probCutBeta < VALUE_INFINITE);
+
+        MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
 
-        while (  (move = mp.next_move()) != MOVE_NONE
-               && probCutCount < 2 + 2 * cutNode)
+        while ((move = mp.next_move()) != MOVE_NONE)
             if (move != excludedMove && pos.legal(move))
             {
-                assert(pos.capture_or_promotion(move));
-                assert(depth >= 5);
+                assert(pos.capture_stage(move));
 
-                captureOrPromotion = true;
-                probCutCount++;
+                // Prefetch the TT entry for the resulting position
+                prefetch(TT.first_entry(pos.key_after(move)));
 
                 ss->currentMove = move;
-                ss->continuationHistory = &thisThread->continuationHistory[inCheck]
-                                                                          [captureOrPromotion]
-                                                                          [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<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1);
+                value = -qsearch<NonPV>(pos, ss + 1, -probCutBeta, -probCutBeta + 1);
 
                 // If the qsearch held, perform the regular search
-                if (value >= raisedBeta)
-                    value = -search<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1, depth - 4, !cutNode);
+                if (value >= probCutBeta)
+                    value = -search<NonPV>(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4,
+                                           !cutNode);
 
                 pos.undo_move(move);
 
-                if (value >= raisedBeta)
-                    return value;
+                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);
+                    return value - (probCutBeta - beta);
+                }
             }
-    }
-
-    // Step 11. Internal iterative deepening (~1 Elo)
-    if (depth >= 7 && !ttMove)
-    {
-        search<NT>(pos, ss, alpha, beta, depth - 7, cutNode);
 
-        tte = TT.probe(posKey, ttHit);
-        ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
-        ttMove = ttHit ? tte->move() : MOVE_NONE;
+        Eval::NNUE::hint_common_parent_position(pos);
     }
 
-moves_loop: // When in check, search starts from here
+moves_loop:  // When in check, search starts here
 
-    const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
-                                          nullptr                   , (ss-4)->continuationHistory,
-                                          nullptr                   , (ss-6)->continuationHistory };
+    // Step 12. A small Probcut idea, when we are in check (~4 Elo)
+    probCutBeta = beta + 425;
+    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)
+        return probCutBeta;
 
-    Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
+    const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
+                                        (ss - 2)->continuationHistory,
+                                        (ss - 3)->continuationHistory,
+                                        (ss - 4)->continuationHistory,
+                                        nullptr,
+                                        (ss - 6)->continuationHistory};
 
-    MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
-                                      &thisThread->captureHistory,
-                                      contHist,
-                                      countermove,
-                                      ss->killers);
+    Move countermove =
+      prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE;
 
-    value = bestValue;
-    singularLMR = moveCountPruning = false;
-    ttCapture = ttMove && pos.capture_or_promotion(ttMove);
+    MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist,
+                  &thisThread->pawnHistory, countermove, ss->killers);
 
-    // Mark this node as being searched
-    ThreadHolding th(thisThread, posKey, ss->ply);
+    value            = bestValue;
+    moveCountPruning = singularQuietLMR = false;
 
-    // Step 12. Loop through all pseudo-legal moves until no moves remain
+    // 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)
     {
-      assert(is_ok(move));
-
-      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 which 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;
-
-      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;
-      captureOrPromotion = pos.capture_or_promotion(move);
-      movedPiece = pos.moved_piece(move);
-      givesCheck = pos.gives_check(move);
-
-      // Calculate new depth for this move
-      newDepth = depth - 1;
-
-      // Step 13. Pruning at shallow depth (~200 Elo)
-      if (  !rootNode
-          && pos.non_pawn_material(us)
-          && bestValue > VALUE_MATED_IN_MAX_PLY)
-      {
-          // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
-          moveCountPruning = moveCount >= futility_move_count(improving, depth);
-
-          if (   !captureOrPromotion
-              && !givesCheck)
-          {
-              // Reduced depth of the next LMR search
-              int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0);
-
-              // Countermoves based pruning (~20 Elo)
-              if (   lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
-                  && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
-                  && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
-                  continue;
-
-              // Futility pruning: parent node (~5 Elo)
-              if (   lmrDepth < 6
-                  && !inCheck
-                  && ss->staticEval + 235 + 172 * lmrDepth <= alpha
-                  &&  thisThread->mainHistory[us][from_to(move)]
-                    + (*contHist[0])[movedPiece][to_sq(move)]
-                    + (*contHist[1])[movedPiece][to_sq(move)]
-                    + (*contHist[3])[movedPiece][to_sq(move)] < 25000)
-                  continue;
-
-              // Prune moves with negative SEE (~20 Elo)
-              if (!pos.see_ge(move, Value(-(32 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
-                  continue;
-          }
-          else if (!pos.see_ge(move, Value(-194) * depth)) // (~25 Elo)
-                  continue;
-      }
-
-      // Step 14. Extensions (~75 Elo)
-
-      // Singular extension search (~70 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.
-      if (    depth >= 6
-          &&  move == ttMove
-          && !rootNode
-          && !excludedMove // Avoid recursive singular search
-       /* &&  ttValue != VALUE_NONE Already implicit in the next condition */
-          &&  abs(ttValue) < VALUE_KNOWN_WIN
-          && (tte->bound() & BOUND_LOWER)
-          &&  tte->depth() >= depth - 3
-          &&  pos.legal(move))
-      {
-          Value singularBeta = ttValue - 2 * depth;
-          Depth halfDepth = depth / 2;
-          ss->excludedMove = move;
-          value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, halfDepth, cutNode);
-          ss->excludedMove = MOVE_NONE;
-
-          if (value < singularBeta)
-          {
-              extension = 1;
-              singularLMR = true;
-          }
-
-          // 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 soft bound.
-          else if (singularBeta >= beta)
-              return singularBeta;
-      }
-
-      // Check extension (~2 Elo)
-      else if (    givesCheck
-               && (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move)))
-          extension = 1;
-
-      // Passed pawn extension
-      else if (   move == ss->killers[0]
-               && pos.advanced_pawn_push(move)
-               && pos.pawn_passed(us, to_sq(move)))
-          extension = 1;
-
-      // Last captures extension
-      else if (   PieceValue[EG][pos.captured_piece()] > PawnValueEg
-               && pos.non_pawn_material() <= 2 * RookValueMg)
-          extension = 1;
-
-      // Castling extension
-      if (type_of(move) == CASTLING)
-          extension = 1;
-
-      // Add extension to new depth
-      newDepth += extension;
-
-      // Speculative prefetch as early as possible
-      prefetch(TT.first_entry(pos.key_after(move)));
-
-      // Check for legality just before making the move
-      if (!rootNode && !pos.legal(move))
-      {
-          ss->moveCount = --moveCount;
-          continue;
-      }
-
-      // Update the current move (this must be done after singular extension search)
-      ss->currentMove = move;
-      ss->continuationHistory = &thisThread->continuationHistory[inCheck]
-                                                                [captureOrPromotion]
-                                                                [movedPiece]
-                                                                [to_sq(move)];
-
-      // Step 15. Make the move
-      pos.do_move(move, st, givesCheck);
-
-      // Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be
-      // re-searched at full depth.
-      if (    depth >= 3
-          &&  moveCount > 1 + rootNode + (rootNode && bestValue < alpha)
-          && (!rootNode || thisThread->best_move_count(move) == 0)
-          && (  !captureOrPromotion
-              || moveCountPruning
-              || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
-              || cutNode
-              || thisThread->ttHitAverage < 375 * ttHitAverageResolution * ttHitAverageWindow / 1024))
-      {
-          Depth r = reduction(improving, depth, moveCount);
-
-          // Decrease reduction if the ttHit running average is large
-          if (thisThread->ttHitAverage > 500 * ttHitAverageResolution * ttHitAverageWindow / 1024)
-              r--;
-
-          // Reduction if other threads are searching this position.
-          if (th.marked())
-              r++;
-
-          // Decrease reduction if position is or has been on the PV (~10 Elo)
-          if (ttPv)
-              r -= 2;
-
-          // Decrease reduction if opponent's move count is high (~5 Elo)
-          if ((ss-1)->moveCount > 14)
-              r--;
-
-          // Decrease reduction if ttMove has been singularly extended (~3 Elo)
-          if (singularLMR)
-              r -= 2;
-
-          if (!captureOrPromotion)
-          {
-              // Increase reduction if ttMove is a capture (~5 Elo)
-              if (ttCapture)
-                  r++;
-
-              // Increase reduction for cut nodes (~10 Elo)
-              if (cutNode)
-                  r += 2;
-
-              // Decrease reduction for moves that escape a capture. Filter out
-              // castling moves, because they are coded as "king captures rook" and
-              // hence break make_move(). (~2 Elo)
-              else if (    type_of(move) == NORMAL
-                       && !pos.see_ge(reverse_move(move)))
-                  r -= 2;
-
-              ss->statScore =  thisThread->mainHistory[us][from_to(move)]
-                             + (*contHist[0])[movedPiece][to_sq(move)]
-                             + (*contHist[1])[movedPiece][to_sq(move)]
-                             + (*contHist[3])[movedPiece][to_sq(move)]
-                             - 4926;
-
-              // Reset statScore to zero if negative and most stats shows >= 0
-              if (    ss->statScore < 0
-                  && (*contHist[0])[movedPiece][to_sq(move)] >= 0
-                  && (*contHist[1])[movedPiece][to_sq(move)] >= 0
-                  && thisThread->mainHistory[us][from_to(move)] >= 0)
-                  ss->statScore = 0;
-
-              // Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
-              if (ss->statScore >= -102 && (ss-1)->statScore < -114)
-                  r--;
-
-              else if ((ss-1)->statScore >= -116 && ss->statScore < -154)
-                  r++;
-
-              // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
-              r -= ss->statScore / 16384;
-          }
-
-          // Increase reduction for captures/promotions if late move and at low depth
-          else if (depth < 8 && moveCount > 2)
-              r++;
-
-          Depth d = clamp(newDepth - r, 1, newDepth);
-
-          value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
-
-          doFullDepthSearch = (value > alpha && d != newDepth), didLMR = true;
-      }
-      else
-          doFullDepthSearch = !PvNode || moveCount > 1, didLMR = false;
-
-      // Step 17. Full depth search when LMR is skipped or fails high
-      if (doFullDepthSearch)
-      {
-          value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
-
-          if (didLMR && !captureOrPromotion)
-          {
-              int bonus = value > alpha ?  stat_bonus(newDepth)
-                                        : -stat_bonus(newDepth);
-
-              if (move == ss->killers[0])
-                  bonus += bonus / 4;
-
-              update_continuation_histories(ss, movedPiece, to_sq(move), bonus);
-          }
-      }
-
-      // 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))))
-      {
-          (ss+1)->pv = pv;
-          (ss+1)->pv[0] = MOVE_NONE;
-
-          value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false);
-      }
-
-      // Step 18. Undo move
-      pos.undo_move(move);
-
-      assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
-
-      // Step 19. 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);
-
-          // PV move or new best move?
-          if (moveCount == 1 || value > alpha)
-          {
-              rm.score = value;
-              rm.selDepth = thisThread->selDepth;
-              rm.pv.resize(1);
-
-              assert((ss+1)->pv);
-
-              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: When
-              // the best move changes frequently, we allocate some more time.
-              if (moveCount > 1)
-                  ++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 > alpha)
-          {
-              bestMove = move;
-
-              if (PvNode && !rootNode) // Update pv even in fail-high case
-                  update_pv(ss->pv, move, (ss+1)->pv);
-
-              if (PvNode && value < beta) // Update alpha! Always alpha < beta
-                  alpha = value;
-              else
-              {
-                  assert(value >= beta); // Fail high
-                  ss->statScore = 0;
-                  break;
-              }
-          }
-      }
-
-      if (move != bestMove)
-      {
-          if (captureOrPromotion && captureCount < 32)
-              capturesSearched[captureCount++] = move;
-
-          else if (!captureOrPromotion && quietCount < 64)
-              quietsSearched[quietCount++] = move;
-      }
-    }
+        assert(is_ok(move));
+
+        if (move == excludedMove)
+            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;
+
+        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;
+
+        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;
+
+        Value delta = beta - alpha;
+
+        Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta);
+
+        // Step 14. Pruning at shallow depth (~120 Elo).
+        // Depth conditions are important for mate finding.
+        if (!rootNode && pos.non_pawn_material(us) && 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;
+
+            if (capture || givesCheck)
+            {
+                // Futility pruning for captures (~2 Elo)
+                if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
+                {
+                    Piece capturedPiece = pos.piece_on(to_sq(move));
+                    int   futilityEval =
+                      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(-187) * depth))
+                    continue;
+            }
+            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)];
+
+                // Continuation history based pruning (~2 Elo)
+                if (lmrDepth < 6 && history < -3752 * depth)
+                    continue;
+
+                history += 2 * thisThread->mainHistory[us][from_to(move)];
+
+                lmrDepth += history / 7838;
+                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
+                         <= alpha)
+                    continue;
+
+                lmrDepth = std::max(lmrDepth, 0);
+
+                // Prune moves with negative SEE (~4 Elo)
+                if (!pos.see_ge(move, Value(-26 * 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 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 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())
+                && 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;
+                Depth singularDepth = newDepth / 2;
+
+                ss->excludedMove = move;
+                value =
+                  search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
+                ss->excludedMove = MOVE_NONE;
 
-    // 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;
-    */
+                if (value < singularBeta)
+                {
+                    extension        = 1;
+                    singularQuietLMR = !ttCapture;
+
+                    // Avoid search explosion by limiting the number of double extensions
+                    if (!PvNode && value < singularBeta - 17 && ss->doubleExtensions <= 11)
+                    {
+                        extension = 2;
+                        depth += depth < 15;
+                    }
+                }
+
+                // Multi-cut pruning
+                // 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;
+
+                // 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 current beta (~7 Elo)
+                else if (ttValue >= beta)
+                    extension = -2 - !PvNode;
+
+                // 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 ttMove is assumed to fail low over the value of the reduced search (~1 Elo)
+                else if (ttValue <= value)
+                    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][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)))]
+                          > 4146)
+                extension = 1;
+        }
+
+        // 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)));
+
+        // 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);
+
+        // 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--;
+
+        // Increase reduction for cut nodes (~3 Elo)
+        if (cutNode)
+            r += 2;
+
+        // Increase reduction if ttMove is a capture (~3 Elo)
+        if (ttCapture)
+            r++;
+
+        // Decrease reduction for PvNodes (~2 Elo)
+        if (PvNode)
+            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;
+
+        // Increase reduction if next ply has a lot of fail high (~5 Elo)
+        if ((ss + 1)->cutoffCnt > 3)
+            r++;
+
+        // 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;
+
+        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;
+
+        // Decrease/increase reduction for moves with a good/bad history (~25 Elo)
+        r -= ss->statScore / 14767;
 
-    // Step 20. Check for mate and stalemate
+        // 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)))
+        {
+            // 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.
+            // 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<NonPV>(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 + 53 + 2 * newDepth);  // (~1 Elo)
+                const bool doShallowerSearch = value < bestValue + newDepth;             // (~2 Elo)
+
+                newDepth += doDeeperSearch - doShallowerSearch;
+
+                if (newDepth > d)
+                    value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode);
+
+                int bonus = value <= alpha ? -stat_malus(newDepth)
+                          : value >= beta  ? stat_bonus(newDepth)
+                                           : 0;
+
+                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 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
+            value = -search<NonPV>(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;
+
+            value = -search<PV>(pos, ss + 1, -beta, -alpha, newDepth, false);
+        }
+
+        // Step 19. Undo move
+        pos.undo_move(move);
+
+        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;
+
+        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;
+
+            // 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;
+                }
+
+                rm.pv.resize(1);
+
+                assert((ss + 1)->pv);
+
+                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;
+        }
+
+        if (value > bestValue)
+        {
+            bestValue = value;
+
+            if (value > alpha)
+            {
+                bestMove = move;
+
+                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 < 13782 && value > -11541)
+                        depth -= 2;
+
+                    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;
+
+            else
+                quietsSearched[quietCount++] = move;
+        }
+    }
+
+    // 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
     // return a fail low score.
 
-    assert(moveCount || !inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
+    assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
 
     if (!moveCount)
-        bestValue = excludedMove ? alpha
-                   :     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 (   (depth >= 3 || PvNode)
-             && !priorCapture)
-        update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth));
+    else if (!priorCapture && prevSq != SQ_NONE)
+    {
+        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);
+        thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)]
+          << stat_bonus(depth) * bonus / 2;
+    }
 
     if (PvNode)
         bestValue = std::min(bestValue, maxValue);
 
-    if (!excludedMove)
-        tte->save(posKey, value_to_tt(bestValue, ss->ply), ttPv,
-                  bestValue >= beta ? BOUND_LOWER :
-                  PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
+    // 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);
+
+    // 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,
                   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.
-  template <NodeType NT>
-  Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
+// 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<NodeType nodeType>
+Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
 
-    constexpr bool PvNode = NT == PV;
+    static_assert(nodeType != Root);
+    constexpr bool PvNode = nodeType == PV;
 
     assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
     assert(PvNode || (alpha == beta - 1));
     assert(depth <= 0);
 
-    Move pv[MAX_PLY+1];
+    // 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))
+    {
+        alpha = value_draw(pos.this_thread());
+        if (alpha >= beta)
+            return alpha;
+    }
+
+    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, oldAlpha;
-    bool ttHit, pvHit, inCheck, givesCheck, captureOrPromotion, evasionPrunable;
-    int moveCount;
+    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)
     {
-        oldAlpha = alpha; // To flag BOUND_EXACT when eval above alpha and no available moves
-        (ss+1)->pv = pv;
-        ss->pv[0] = MOVE_NONE;
+        (ss + 1)->pv = pv;
+        ss->pv[0]    = MOVE_NONE;
     }
 
     Thread* thisThread = pos.this_thread();
-    (ss+1)->ply = ss->ply + 1;
-    bestMove = MOVE_NONE;
-    inCheck = pos.checkers();
-    moveCount = 0;
+    bestMove           = MOVE_NONE;
+    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;
 
-    // Check for an immediate draw or maximum ply reached
-    if (   pos.is_draw(ss->ply)
-        || ss->ply >= MAX_PLY)
-        return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) : VALUE_DRAW;
+    // 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;
 
     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.
-    ttDepth = inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS
-                                                  : DEPTH_QS_NO_CHECKS;
-    // Transposition table lookup
-    posKey = pos.key();
-    tte = TT.probe(posKey, ttHit);
-    ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
-    ttMove = ttHit ? tte->move() : MOVE_NONE;
-    pvHit = ttHit && tte->is_pv();
-
-    if (  !PvNode
-        && ttHit
-        && tte->depth() >= ttDepth
-        && ttValue != VALUE_NONE // Only in case of TT access race
-        && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
-                            : (tte->bound() & BOUND_UPPER)))
+    // 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
+    posKey  = pos.key();
+    tte     = TT.probe(posKey, ss->ttHit);
+    ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
+    ttMove  = ss->ttHit ? tte->move() : MOVE_NONE;
+    pvHit   = ss->ttHit && tte->is_pv();
+
+    // At non-PV nodes we check for an early TT cutoff
+    if (!PvNode && 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;
 
-    // Evaluate the position statically
-    if (inCheck)
-    {
-        ss->staticEval = VALUE_NONE;
+    // Step 4. Static evaluation of the position
+    if (ss->inCheck)
         bestValue = futilityBase = -VALUE_INFINITE;
-    }
     else
     {
-        if (ttHit)
+        if (ss->ttHit)
         {
             // Never assume anything about values stored in TT
             if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE)
                 ss->staticEval = bestValue = evaluate(pos);
 
-            // Can ttValue be used as a better position evaluation?
-            if (    ttValue != VALUE_NONE
+            // ttValue can be used as a better position evaluation (~13 Elo)
+            if (ttValue != VALUE_NONE
                 && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)))
                 bestValue = ttValue;
         }
         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 + 2 * Eval::Tempo;
+              (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 (!ttHit)
-                tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, 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;
         }
 
-        if (PvNode && bestValue > alpha)
+        if (bestValue > alpha)
             alpha = bestValue;
 
-        futilityBase = bestValue + 154;
+        futilityBase = ss->staticEval + 182;
     }
 
-    const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
-                                          nullptr                   , (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,
-    // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will
-    // be generated.
-    MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
-                                      &thisThread->captureHistory,
-                                      contHist,
-                                      to_sq((ss-1)->currentMove));
-
-    // Loop through the moves until no moves remain or a beta cutoff occurs
+    // 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, &thisThread->pawnHistory);
+
+    int quietCheckEvasions = 0;
+
+    // Step 5. Loop through all pseudo-legal moves until no moves remain
+    // or a beta cutoff occurs.
     while ((move = mp.next_move()) != MOVE_NONE)
     {
-      assert(is_ok(move));
-
-      givesCheck = pos.gives_check(move);
-      captureOrPromotion = pos.capture_or_promotion(move);
-
-      moveCount++;
-
-      // Futility pruning
-      if (   !inCheck
-          && !givesCheck
-          &&  futilityBase > -VALUE_KNOWN_WIN
-          && !pos.advanced_pawn_push(move))
-      {
-          assert(type_of(move) != ENPASSANT); // Due to !pos.advanced_pawn_push
-
-          futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))];
-
-          if (futilityValue <= alpha)
-          {
-              bestValue = std::max(bestValue, futilityValue);
-              continue;
-          }
-
-          if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1))
-          {
-              bestValue = std::max(bestValue, futilityBase);
-              continue;
-          }
-      }
-
-      // Detect non-capture evasions that are candidates to be pruned
-      evasionPrunable =    inCheck
-                       &&  (depth != 0 || moveCount > 2)
-                       &&  bestValue > VALUE_MATED_IN_MAX_PLY
-                       && !pos.capture(move);
-
-      // Don't search moves with negative SEE values
-      if (  (!inCheck || evasionPrunable) && !pos.see_ge(move))
-          continue;
-
-      // Speculative prefetch as early as possible
-      prefetch(TT.first_entry(pos.key_after(move)));
-
-      // Check for legality just before making the move
-      if (!pos.legal(move))
-      {
-          moveCount--;
-          continue;
-      }
-
-      ss->currentMove = move;
-      ss->continuationHistory = &thisThread->continuationHistory[inCheck]
-                                                                [captureOrPromotion]
-                                                                [pos.moved_piece(move)]
-                                                                [to_sq(move)];
-
-      // Make and search the move
-      pos.do_move(move, st, givesCheck);
-      value = -qsearch<NT>(pos, ss+1, -beta, -alpha, depth - 1);
-      pos.undo_move(move);
-
-      assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
-
-      // Check for a new best move
-      if (value > bestValue)
-      {
-          bestValue = value;
-
-          if (value > alpha)
-          {
-              bestMove = move;
-
-              if (PvNode) // Update pv even in fail-high case
-                  update_pv(ss->pv, move, (ss+1)->pv);
-
-              if (PvNode && value < beta) // Update alpha here!
-                  alpha = value;
-              else
-                  break; // Fail high
-          }
-       }
+        assert(is_ok(move));
+
+        // Check for legality
+        if (!pos.legal(move))
+            continue;
+
+        givesCheck = pos.gives_check(move);
+        capture    = pos.capture_stage(move);
+
+        moveCount++;
+
+        // 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 && to_sq(move) != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY
+                && type_of(move) != PROMOTION)
+            {
+                if (moveCount > 2)
+                    continue;
+
+                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
+            // implicitly checked through the counter, and being a 'quiet move' apart from
+            // being a tt move is assumed after an increment because captures are pushed ahead.
+            if (quietCheckEvasions > 1)
+                break;
+
+            // Continuation history based pruning (~3 Elo)
+            if (!capture && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0
+                && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0)
+                continue;
+
+            // Do not search moves with bad enough SEE values (~5 Elo)
+            if (!pos.see_ge(move, Value(-77)))
+                continue;
+        }
+
+        // Speculative prefetch as early as possible
+        prefetch(TT.first_entry(pos.key_after(move)));
+
+        // Update the current move
+        ss->currentMove = move;
+        ss->continuationHistory =
+          &thisThread
+             ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][to_sq(move)];
+
+        quietCheckEvasions += !capture && ss->inCheck;
+
+        // Step 7. Make and search the move
+        pos.do_move(move, st, givesCheck);
+        value = -qsearch<nodeType>(pos, ss + 1, -beta, -alpha, depth - 1);
+        pos.undo_move(move);
+
+        assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
+
+        // Step 8. Check for a new best move
+        if (value > bestValue)
+        {
+            bestValue = value;
+
+            if (value > alpha)
+            {
+                bestMove = move;
+
+                if (PvNode)  // Update pv even in fail-high case
+                    update_pv(ss->pv, move, (ss + 1)->pv);
+
+                if (value < beta)  // Update alpha here!
+                    alpha = value;
+                else
+                    break;  // Fail high
+            }
+        }
     }
 
-    // All legal moves have been searched. A special case: If we're in check
+    // Step 9. Check for mate
+    // All legal moves have been searched. A special case: if we're in check
     // and no legal moves were found, it is checkmate.
-    if (inCheck && bestValue == -VALUE_INFINITE)
-        return mated_in(ss->ply); // Plies to mate from the root
+    if (ss->inCheck && bestValue == -VALUE_INFINITE)
+    {
+        assert(!MoveList<LEGAL>(pos).size());
+
+        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 :
-              PvNode && bestValue > oldAlpha  ? BOUND_EXACT : 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 score from "plies to mate from the root" to
-  // "plies to mate from the current position". Non-mate scores are unchanged.
-  // The function is called before storing a value in the transposition table.
 
-  Value value_to_tt(Value v, int ply) {
+// 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);
 
-    return  v >= VALUE_MATE_IN_MAX_PLY  ? v + ply
-          : v <= VALUE_MATED_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 score
-  // from the transposition table (which refers to the plies to mate/be mated
-  // from current position) to "plies to mate/be mated from the root".
+// 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 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) {
+Value value_from_tt(Value v, int ply, int r50c) {
 
-    return  v == VALUE_NONE             ? VALUE_NONE
-          : v >= VALUE_MATE_IN_MAX_PLY  ? VALUE_MATE - v > 99 - r50c ? VALUE_MATE_IN_MAX_PLY  : v - ply
-          : v <= VALUE_MATED_IN_MAX_PLY ? VALUE_MATE + v > 99 - r50c ? VALUE_MATED_IN_MAX_PLY : v + ply : v;
-  }
+    if (v == VALUE_NONE)
+        return VALUE_NONE;
 
+    // handle TB win or better
+    if (v >= VALUE_TB_WIN_IN_MAX_PLY)
+    {
+        // 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;
 
-  // update_pv() adds current move and appends child pv[]
+        // Downgrade a potentially false TB score.
+        if (VALUE_TB - v > 100 - r50c)
+            return VALUE_TB_WIN_IN_MAX_PLY - 1;
 
-  void update_pv(Move* pv, Move move, Move* childPv) {
+        return v - ply;
+    }
 
-    for (*pv++ = move; childPv && *childPv != MOVE_NONE; )
-        *pv++ = *childPv++;
-    *pv = MOVE_NONE;
-  }
+    // handle TB loss or worse
+    if (v <= VALUE_TB_LOSS_IN_MAX_PLY)
+    {
+        // 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;
+    }
 
+    return v;
+}
 
-  // 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) {
+// Adds current move and appends child pv[]
+void update_pv(Move* pv, Move move, const Move* childPv) {
 
-    int bonus1, bonus2;
-    Color us = pos.side_to_move();
-    Thread* thisThread = pos.this_thread();
+    for (*pv++ = move; childPv && *childPv != MOVE_NONE;)
+        *pv++ = *childPv++;
+    *pv = MOVE_NONE;
+}
+
+
+// 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) {
+
+    Color                  us             = pos.side_to_move();
+    Thread*                thisThread     = pos.this_thread();
     CapturePieceToHistory& captureHistory = thisThread->captureHistory;
-    Piece moved_piece = pos.moved_piece(bestMove);
-    PieceType captured = type_of(pos.piece_on(to_sq(bestMove)));
+    Piece                  moved_piece    = pos.moved_piece(bestMove);
+    PieceType              captured;
 
-    bonus1 = stat_bonus(depth + 1);
-    bonus2 = bestValue > beta + PawnValueMg ? bonus1               // larger bonus
-                                            : stat_bonus(depth);   // smaller bonus
+    int quietMoveBonus = stat_bonus(depth + 1);
+    int quietMoveMalus = stat_malus(depth);
 
-    if (!pos.capture_or_promotion(bestMove))
+    if (!pos.capture_stage(bestMove))
     {
-        update_quiet_stats(pos, ss, bestMove, bonus2);
+        int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus      // larger bonus
+                                                   : stat_bonus(depth);  // smaller bonus
 
-        // Decrease all the non-best quiet moves
+        // 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->mainHistory[us][from_to(quietsSearched[i])] << -bonus2;
-            update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2);
+            thisThread->pawnHistory[pawn_structure(pos)][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);
         }
     }
     else
-        captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
+    {
+        // 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;
+    }
 
-    // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted
-    if (   ((ss-1)->moveCount == 1 || ((ss-1)->currentMove == (ss-1)->killers[0]))
+    // 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]))
         && !pos.captured_piece())
-            update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1);
+        update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus);
 
-    // Decrease all the non-best capture moves
+    // 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;
+        captured    = type_of(pos.piece_on(to_sq(capturesSearched[i])));
+        captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveMalus;
     }
-  }
-
-
-  // update_continuation_histories() updates histories of the move pairs formed
-  // by moves at ply -1, -2, and -4 with current move.
+}
 
-  void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
 
-    for (int i : {1, 2, 4, 6})
-        if (is_ok((ss-i)->currentMove))
-            (*(ss-i)->continuationHistory)[pc][to] << bonus;
-  }
+// Updates histories of the move pairs formed
+// 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})
+    {
+        // 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));
+    }
+}
 
-  // update_quiet_stats() updates move sorting heuristics
 
-  void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
+// Updates move sorting heuristics
+void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
 
+    // Update killers
     if (ss->killers[0] != move)
     {
         ss->killers[1] = ss->killers[0];
         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);
 
-    if (type_of(pos.moved_piece(move)) != PAWN)
-        thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus;
-
-    if (is_ok((ss-1)->currentMove))
+    // Update countermove history
+    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 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) {
+// 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
+    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, PawnValueMg);
-    int weakness = 120 - 2 * level;
-    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
     // weakness. One is deterministic and bigger for weaker levels, and one is
@@ -1696,118 +1814,118 @@ moves_loop: // When in check, search starts from here
     for (size_t i = 0; i < multiPV; ++i)
     {
         // This is our magic formula
-        int push = (  weakness * int(topScore - rootMoves[i].score)
-                    + delta * (rng.rand<unsigned>() % weakness)) / 128;
+        int push = int((weakness * int(topScore - rootMoves[i].score)
+                        + delta * (rng.rand<unsigned>() % 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,
-/// to detect when we are out of available time and thus stop the search.
 
+// 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)
-      return;
+    if (--callsCnt > 0)
+        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;
+    // 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() - 10 || 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;
 }
 
 
-/// UCI::pv() formats PV information according to the UCI protocol. UCI requires
-/// that all (if any) unsearched PV lines are sent using a previous search score.
-
-string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
+// 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);
+    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)
-          continue;
+        if (depth == 1 && !updated && i > 0)
+            continue;
 
-      Depth d = updated ? depth : depth - 1;
-      Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore;
+        Depth d = updated ? depth : std::max(1, depth - 1);
+        Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore;
 
-      bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY;
-      v = tb ? rootMoves[i].tbScore : v;
+        if (v == -VALUE_INFINITE)
+            v = VALUE_ZERO;
 
-      if (ss.rdbuf()->in_avail()) // Not at first line
-          ss << "\n";
+        bool tb = TB::RootInTB && std::abs(v) <= VALUE_TB;
+        v       = tb ? rootMoves[i].tbScore : v;
 
-      ss << "info"
-         << " depth "    << d
-         << " seldepth " << rootMoves[i].selDepth
-         << " multipv "  << i + 1
-         << " score "    << UCI::value(v);
+        if (ss.rdbuf()->in_avail())  // Not at first line
+            ss << "\n";
 
-      if (!tb && i == pvIdx)
-          ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : "");
+        ss << "info"
+           << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1
+           << " score " << UCI::value(v);
 
-      ss << " nodes "    << nodesSearched
-         << " nps "      << nodesSearched * 1000 / elapsed;
+        if (Options["UCI_ShowWDL"])
+            ss << UCI::wdl(v, pos.game_ply());
 
-      if (elapsed > 1000) // Earlier makes little sense
-          ss << " hashfull " << TT.hashfull();
+        if (i == pvIdx && !tb && updated)  // tablebase- and previous-scores are exact
+            ss << (rootMoves[i].scoreLowerbound
+                     ? " lowerbound"
+                     : (rootMoves[i].scoreUpperbound ? " upperbound" : ""));
 
-      ss << " 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();
 }
 
 
-/// 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.
-
+// 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;
+    ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
+
     bool ttHit;
 
     assert(pv.size() == 1);
@@ -1820,7 +1938,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<LEGAL>(pos).contains(m))
             pv.push_back(m);
     }
@@ -1831,10 +1949,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
@@ -1842,7 +1960,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))
@@ -1854,15 +1972,15 @@ 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);
         }
     }
 
     if (RootInTB)
     {
         // Sort moves according to TB rank
-        std::sort(rootMoves.begin(), rootMoves.end(),
-                  [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } );
+        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)
@@ -1875,3 +1993,5 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
             m.tbRank = 0;
     }
 }
+
+}  // namespace Stockfish
index a900d094b0c88fdaf03172a5d550716b5518b042..b2d22e612c45598b80284ebbc51aaeaa24f8b6a0 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 #ifndef SEARCH_H_INCLUDED
 #define SEARCH_H_INCLUDED
 
+#include <cstdint>
 #include <vector>
 
 #include "misc.h"
 #include "movepick.h"
 #include "types.h"
 
+namespace Stockfish {
+
 class Position;
 
 namespace Search {
 
-/// Threshold used for countermoves based pruning
-constexpr int CounterMovePruneThreshold = 0;
-
-
-/// Stack struct keeps track of the information we need to remember from nodes
-/// shallower and deeper in the tree during the search. Each search thread has
-/// 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;
-  PieceToHistory* continuationHistory;
-  int ply;
-  Move currentMove;
-  Move excludedMove;
-  Move killers[2];
-  Value staticEval;
-  int statScore;
-  int moveCount;
+    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;
 };
 
 
-/// 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 {
 
-  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;
-  int selDepth = 0;
-  int tbRank = 0;
-  int bestMoveCount = 0;
-  Value tbScore;
-  std::vector<Move> pv;
+    explicit RootMove(Move m) :
+        pv(1, m) {}
+    bool extract_ponder_from_tt(Position& pos);
+    bool operator==(const Move& m) const { return pv[0] == m; }
+    // Sort in descending order
+    bool operator<(const RootMove& m) const {
+        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<Move> pv;
 };
 
-typedef std::vector<RootMove> RootMoves;
+using RootMoves = std::vector<RootMove>;
 
 
-/// 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 {
 
-  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;
-  }
+    // 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;
+    }
 
-  bool use_time_management() const {
-    return !(mate | movetime | depth | nodes | perft | infinite);
-  }
+    bool use_time_management() const { return time[WHITE] || time[BLACK]; }
 
-  std::vector<Move> searchmoves;
-  TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
-  int movestogo, depth, mate, perft, infinite;
-  int64_t nodes;
+    std::vector<Move> searchmoves;
+    TimePoint         time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
+    int               movestogo, depth, mate, perft, infinite;
+    int64_t           nodes;
 };
 
 extern LimitsType Limits;
@@ -104,6 +108,8 @@ extern LimitsType Limits;
 void init();
 void clear();
 
-} // namespace Search
+}  // namespace Search
+
+}  // namespace Stockfish
 
-#endif // #ifndef SEARCH_H_INCLUDED
+#endif  // #ifndef SEARCH_H_INCLUDED
index 721a0ef5ba62fc950af0877fc961f0f24a229bf1..e23631575e6bb47d4810dcfd344cfb59182c6cc8 100644 (file)
@@ -1,7 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (c) 2013 Ronald de Man
-  Copyright (C) 2016-2020 Marco Costalba, Lucas Braesch
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "tbprobe.h"
+
+#include <sys/stat.h>
 #include <algorithm>
 #include <atomic>
+#include <cassert>
 #include <cstdint>
-#include <cstring>   // For std::memset and std::memcpy
+#include <cstdlib>
+#include <cstring>
 #include <deque>
 #include <fstream>
+#include <initializer_list>
 #include <iostream>
-#include <list>
+#include <mutex>
 #include <sstream>
+#include <string_view>
 #include <type_traits>
-#include <mutex>
+#include <utility>
+#include <vector>
 
 #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 <fcntl.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
+    #include <fcntl.h>
+    #include <sys/mman.h>
+    #include <unistd.h>
 #else
-#define WIN32_LEAN_AND_MEAN
-#ifndef NOMINMAX
-#  define NOMINMAX // Disable macros min() and max()
-#endif
-#include <windows.h>
+    #define WIN32_LEAN_AND_MEAN
+    #ifndef NOMINMAX
+        #define NOMINMAX  // Disable macros min() and max()
+    #endif
+    #include <windows.h>
 #endif
 
-using namespace Tablebases;
+using namespace Stockfish::Tablebases;
+
+int Stockfish::Tablebases::MaxCardinality;
 
-int Tablebases::MaxCardinality;
+namespace Stockfish {
 
 namespace {
 
-constexpr int TBPIECES = 7; // Max number of supported pieces
+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 { KEY, 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 s = Square(int(s) ^ i); }
-inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
+inline Square   operator^(Square s, int i) { return Square(int(s) ^ i); }
 
-const std::string PieceToChar = " PNBRQK  pnbrqk";
+constexpr std::string_view PieceToChar = " PNBRQK  pnbrqk";
 
 int MapPawns[SQUARE_NB];
 int MapB1H1H7[SQUARE_NB];
 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); }
-
-constexpr Value WDL_to_value[] = {
-   -VALUE_MATE + MAX_PLY + 1,
-    VALUE_DRAW - 2,
-    VALUE_DRAW,
-    VALUE_DRAW + 2,
-    VALUE_MATE - MAX_PLY - 1
-};
+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};
 
 template<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>
-inline void swap_endian(T& x)
-{
-    static_assert(std::is_unsigned<T>::value, "Argument of swap_endian not unsigned");
+inline void swap_endian(T& x) {
+    static_assert(std::is_unsigned_v<T>, "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>(uint8_t&) {}
-
-template<typename T, int LE> T number(void* addr)
-{
-    static const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
-    static const bool IsLittleEndian = (Le.c[0] == 4);
+template<>
+inline void swap_endian<uint8_t>(uint8_t&) {}
 
+template<typename T, int LE>
+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);
@@ -124,18 +137,20 @@ template<typename T, int LE> 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 <typename T> int sign_of(T val) {
+template<typename T>
+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
@@ -143,18 +158,22 @@ struct SparseEntry {
 
 static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");
 
-typedef uint16_t Sym; // Huffman symbol
+using Sym = uint16_t;  // Huffman symbol
 
 struct LR {
-    enum Side { Left, Right };
-
-    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,
-                   // then the left-hand symbol is the stored value.
+    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.
     template<Side S>
     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));
     }
 };
 
@@ -169,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.
@@ -190,9 +209,10 @@ public:
         constexpr char SepChar = ';';
 #endif
         std::stringstream ss(Paths);
-        std::string path;
+        std::string       path;
 
-        while (std::getline(ss, path, SepChar)) {
+        while (std::getline(ss, path, SepChar))
+        {
             fname = path + "/" + f;
             std::ifstream::open(fname);
             if (is_open())
@@ -200,17 +220,14 @@ public:
         }
     }
 
-    // Memory map the file and check it. File should be already open and will be
-    // closed after mapping.
+    // Memory map the file and check it.
     uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {
-
-        assert(is_open());
-
-        close(); // Need to re-open to get native file descriptor
+        if (is_open())
+            close();  // Need to re-open to get native file descriptor
 
 #ifndef _WIN32
         struct stat statbuf;
-        int fd = ::open(fname.c_str(), O_RDONLY);
+        int         fd = ::open(fname.c_str(), O_RDONLY);
 
         if (fd == -1)
             return *baseAddress = nullptr, nullptr;
@@ -223,9 +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)
         madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);
+    #endif
         ::close(fd);
 
         if (*baseAddress == MAP_FAILED)
@@ -235,8 +254,8 @@ public:
         }
 #else
         // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.
-        HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
-                               OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
+        HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
+                                OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
 
         if (fd == INVALID_HANDLE_VALUE)
             return *baseAddress = nullptr, nullptr;
@@ -259,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)
@@ -269,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))
         {
@@ -281,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) {
@@ -290,36 +308,38 @@ public:
         munmap(baseAddress, mapping);
 #else
         UnmapViewOfFile(baseAddress);
-        CloseHandle((HANDLE)mapping);
+        CloseHandle((HANDLE) mapping);
 #endif
     }
 };
 
 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
-    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<uint64_t> base64;  // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
-    std::vector<uint8_t> 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<uint64_t>
+      base64;  // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
+    std::vector<uint8_t>
+             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.
@@ -328,27 +348,27 @@ struct PairsData {
 // first access, when the corresponding file is memory mapped.
 template<TBType Type>
 struct TBTable {
-    typedef typename std::conditional<Type == WDL, WDLScore, int>::type Ret;
+    using Ret = std::conditional_t<Type == WDL, WDLScore, int>;
 
     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]
-
-    PairsData* get(int stm, int f) {
-        return &items[stm % Sides][hasPawns ? f : 0];
-    }
-
-    TBTable() : ready(false), baseAddress(nullptr) {}
+    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]; }
+
+    TBTable() :
+        ready(false),
+        baseAddress(nullptr) {}
     explicit TBTable(const std::string& code);
     explicit TBTable(const TBTable<WDL>& wdl);
 
@@ -359,26 +379,26 @@ struct TBTable {
 };
 
 template<>
-TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
+TBTable<WDL>::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<ALL_PIECES>();
-    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 less pawns because this leads to better compression.
-    bool c =   !pos.count<PAWN>(BLACK)
-            || (   pos.count<PAWN>(WHITE)
-                && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));
+    // is the side with fewer pawns because this leads to better compression.
+    bool c = !pos.count<PAWN>(BLACK)
+          || (pos.count<PAWN>(WHITE) && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));
 
     pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);
     pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);
@@ -387,26 +407,36 @@ TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
 }
 
 template<>
-TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) : TBTable() {
+TBTable<DTZ>::TBTable(const TBTable<WDL>& 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
-// 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 {
 
-    typedef std::tuple<Key, TBTable<WDL>*, TBTable<DTZ>*> Entry;
+    struct Entry {
+        Key           key;
+        TBTable<WDL>* wdl;
+        TBTable<DTZ>* dtz;
+
+        template<TBType Type>
+        TBTable<Type>* get() const {
+            return (TBTable<Type>*) (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];
@@ -415,23 +445,26 @@ class TBTables {
     std::deque<TBTable<DTZ>> dtzTable;
 
     void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
-        uint32_t homeBucket = (uint32_t)key & (Size - 1);
-        Entry entry = std::make_tuple(key, wdl, dtz);
+        uint32_t homeBucket = uint32_t(key) & (Size - 1);
+        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) {
-            Key otherKey = std::get<KEY>(hashTable[bucket]);
-            if (otherKey == key || !std::get<WDL>(hashTable[bucket])) {
+        for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)
+        {
+            Key otherKey = hashTable[bucket].key;
+            if (otherKey == key || !hashTable[bucket].get<WDL>())
+            {
                 hashTable[bucket] = entry;
                 return;
             }
 
             // 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) {
-                swap(entry, hashTable[bucket]);
-                key = otherKey;
+            uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1);
+            if (otherHomeBucket > homeBucket)
+            {
+                std::swap(entry, hashTable[bucket]);
+                key        = otherKey;
                 homeBucket = otherHomeBucket;
             }
         }
@@ -439,12 +472,13 @@ class TBTables {
         exit(EXIT_FAILURE);
     }
 
-public:
+   public:
     template<TBType Type>
     TBTable<Type>* get(Key key) {
-        for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) {
-            if (std::get<KEY>(*entry) == key || !std::get<Type>(*entry))
-                return std::get<Type>(*entry);
+        for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry)
+        {
+            if (entry->key == key || !entry->get<Type>())
+                return entry->get<Type>();
         }
     }
 
@@ -454,7 +488,7 @@ public:
         dtzTable.clear();
     }
     size_t size() const { return wdlTable.size(); }
-    void add(const std::vector<PieceType>& pieces);
+    void   add(const std::vector<PieceType>& pieces);
 };
 
 TBTables TBTables;
@@ -468,20 +502,20 @@ void TBTables::add(const std::vector<PieceType>& 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();
 
-    MaxCardinality = std::max((int)pieces.size(), MaxCardinality);
+    MaxCardinality = std::max(int(pieces.size()), MaxCardinality);
 
     wdlTable.emplace_back(code);
     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());
 }
 
@@ -497,9 +531,9 @@ void TBTables::add(const std::vector<PieceType>& 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
@@ -521,13 +555,13 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
     //       I(k) = k * d->span + d->span / 2      (1)
 
     // First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)
-    uint32_t k = idx / d->span;
+    uint32_t k = uint32_t(idx / d->span);
 
     // Then we read the corresponding SparseIndex[] entry
-    uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
-    int offset     = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);
+    uint32_t block  = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
+    int      offset = number<uint16_t, LittleEndian>(&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)
     //
@@ -537,7 +571,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;
@@ -546,17 +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 bits sequence.
-    uint64_t buf64 = number<uint64_t, BigEndian>(ptr); ptr += 2;
+    // is at the beginning of this 64-bit sequence.
+    uint64_t buf64 = number<uint64_t, BigEndian>(ptr);
+    ptr += 2;
     int buf64Size = 64;
     Sym sym;
 
-    while (true) {
-        int len = 0; // This is the symbol length - d->min_sym_len
+    while (true)
+    {
+        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
@@ -567,43 +603,45 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
         // All the symbols of a given length are consecutive integers (numerical
         // sequence property), so we can compute the offset of our symbol of
         // length len, stored at the beginning of buf64.
-        sym = (buf64 - d->base64[len]) >> (64 - len - d->minSymLen);
+        sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen));
 
         // Now add the value of the lowest symbol of length len to get our symbol
         sym += number<Sym, LittleEndian>(&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;
 
         // ...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<uint32_t, BigEndian>(ptr++) << (64 - buf64Size);
+            buf64 |= uint64_t(number<uint32_t, BigEndian>(ptr++)) << (64 - buf64Size);
         }
     }
 
-    // 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.
-    while (d->symlen[sym]) {
-
+    while (d->symlen[sym])
+    {
         Sym left = d->btree[sym].get<LR::Left>();
 
         // 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;
-        else {
+        else
+        {
             offset -= d->symlen[left] + 1;
             sym = d->btree[sym].get<LR::Right>();
         }
@@ -617,68 +655,79 @@ bool check_dtz_stm(TBTable<WDL>*, int, File) { return true; }
 bool check_dtz_stm(TBTable<DTZ>* 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
 // 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<WDL>*, File, int value, WDLScore) { return WDLScore(value - 2); }
 
 int map_score(TBTable<DTZ>* 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 convert to plies when needed.
-    if (   (wdl == WDLWin  && !(flags & TBFlag::WinPlies))
-        || (wdl == WDLLoss && !(flags & TBFlag::LossPlies))
-        ||  wdl == WDLCursedWin
-        ||  wdl == WDLBlessedLoss)
+    // 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)
         value *= 2;
 
     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 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]
 //
 template<typename T, typename Ret = typename T::Ret>
-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];
-    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
-    // 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);
 
@@ -689,7 +738,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.
@@ -699,14 +749,14 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
 
         leadPawns = b = pos.pieces(color_of(pc), PAWN);
         do
-            squares[size++] = pop_lsb(&b) ^ flipSquares;
+            squares[size++] = pop_lsb(b) ^ flipSquares;
         while (b);
 
         leadPawnsCnt = size;
 
         std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));
 
-        tbFile = map_to_queenside(file_of(squares[0]));
+        tbFile = File(edge_distance(file_of(squares[0])));
     }
 
     // DTZ tables are one-sided, i.e. they store positions only for white to
@@ -718,9 +768,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);
 
@@ -743,34 +794,36 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
     // the triangle A1-D1-D4.
     if (file_of(squares[0]) > FILE_D)
         for (int i = 0; i < size; ++i)
-            squares[i] ^= 7; // Horizontal flip: SQ_H1 -> SQ_A1
+            squares[i] = flip_file(squares[i]);
 
     // 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::sort(squares + 1, squares + leadPawnsCnt, pawns_comp);
+        std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp);
 
         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 withouth pawns, we further flip the squares to ensure leading
+    // In positions without pawns, we further flip the squares to ensure leading
     // piece is below RANK_5.
     if (rank_of(squares[0]) > RANK_4)
         for (int i = 0; i < size; ++i)
-            squares[i] ^= SQ_A8; // Vertical flip: SQ_A8 -> SQ_A1
+            squares[i] = flip_rank(squares[i]);
 
     // 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_C3
+        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;
@@ -801,43 +854,38 @@ 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 (inlcuded kings) we encode them
+    // 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 occurence to
+        // 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]];
@@ -846,19 +894,19 @@ encode_remaining:
     idx *= d->groupIdx[0];
     Square* groupSq = squares + d->groupLen[0];
 
-    // Encode remainig 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])
     {
-        std::sort(groupSq, groupSq + d->groupLen[next]);
+        std::stable_sort(groupSq, groupSq + d->groupLen[next]);
         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; };
+            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];
         }
@@ -873,8 +921,8 @@ 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
-// group that, in case of positions withouth pawns, can be formed by 3 different
+// 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[].
 //
@@ -896,7 +944,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
@@ -906,27 +954,26 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
     //
     // This ensures unique encoding for the whole position. The order of the
     // groups is a per-table parameter and could not follow the canonical leading
-    // pawns/pieces -> remainig pawns -> remaining pieces. In particular the
+    // 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 // Remainig pieces
+        else  // Remaining pieces
         {
             d->groupIdx[next] = idx;
             idx *= Binomial[d->groupLen[next]][freeSquares];
@@ -936,13 +983,13 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
     d->groupIdx[n] = idx;
 }
 
-// In Recursive Pairing each symbol represents a pair of childern symbols. So
+// 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<bool>& visited) {
 
-    visited[s] = true; // We can set it now because tree is acyclic
-    Sym sr = d->btree[s].get<LR::Right>();
+    visited[s] = true;  // We can set it now because tree is acyclic
+    Sym sr     = d->btree[s].get<LR::Right>();
 
     if (sr == 0xFFF)
         return 0;
@@ -962,10 +1009,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;
     }
 
@@ -973,47 +1021,57 @@ 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 = (tbSize + d->span - 1) / d->span; // Round up
-    auto padding = number<uint8_t, LittleEndian>(data++);
-    d->blocksNum = number<uint32_t, LittleEndian>(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<uint8_t, LittleEndian>(data++);
+    d->blocksNum       = number<uint32_t, LittleEndian>(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
     // 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 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 http://www.eecs.harvard.edu/~michaelm/E210/huffman.pdf
-    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<int>(d->base64.size());
+    for (int i = base64_size - 2; i >= 0; --i)
+    {
         d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])
-                                         - number<Sym, LittleEndian>(&d->lowestSym[i + 1])) / 2;
+                        - number<Sym, LittleEndian>(&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
     // 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)
-        d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits
+    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);
-    d->symlen.resize(number<uint16_t, LittleEndian>(data)); data += sizeof(uint16_t);
-    d->btree = (LR*)data;
+    data += base64_size * sizeof(Sym);
+    d->symlen.resize(number<uint16_t, LittleEndian>(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,
     // 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<bool> visited(d->symlen.size());
 
     for (Sym sym = 0; sym < d->symlen.size(); ++sym)
@@ -1029,67 +1087,77 @@ uint8_t* set_dtz_map(TBTable<DTZ>& 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) {
-                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);
+        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);
                     data += 2 * number<uint16_t, LittleEndian>(data) + 2;
                 }
             }
-            else {
-                for (int i = 0; i < 4; ++i) {
-                    e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1);
+            else
+            {
+                for (int i = 0; i < 4; ++i)
+                {
+                    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.
+// Populate entry's PairsData records with data from the just memory-mapped file.
 // Called at first access.
 template<typename T>
 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++)
@@ -1098,28 +1166,31 @@ 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;
         }
 }
 
-// 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<TBType Type>
 void* mapped(TBTable<Type>& e, const Position& pos) {
@@ -1129,22 +1200,23 @@ void* mapped(TBTable<Type>& 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::unique_lock<std::mutex> lk(mutex);
+    std::scoped_lock<std::mutex> 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);
 
@@ -1158,7 +1230,7 @@ void* mapped(TBTable<Type>& e, const Position& pos) {
 template<TBType Type, typename Ret = typename TBTable<Type>::Ret>
 Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) {
 
-    if (pos.count<ALL_PIECES>() == 2) // KvK
+    if (pos.count<ALL_PIECES>() == 2)  // KvK
         return Ret(WDLDraw);
 
     TBTable<Type>* entry = TBTables.get<Type>(pos.material_key());
@@ -1170,7 +1242,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
@@ -1179,22 +1251,21 @@ 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<bool CheckZeroingMoves>
 WDLScore search(Position& pos, ProbeState* result) {
 
-    WDLScore value, bestValue = WDLLoss;
+    WDLScore  value, bestValue = WDLLoss;
     StateInfo st;
 
-    auto moveList = MoveList<LEGAL>(pos);
+    auto   moveList   = MoveList<LEGAL>(pos);
     size_t totalCount = moveList.size(), moveCount = 0;
 
-    for (const Move& move : moveList)
+    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++;
@@ -1212,7 +1283,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;
             }
         }
@@ -1238,23 +1309,22 @@ 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
-/// "SyzygyPath" UCI option to (re)create the various tables. It is not thread
-/// safe, nor it needs to be.
+// 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();
     MaxCardinality = 0;
-    TBFile::Paths = paths;
+    TBFile::Paths  = paths;
 
     if (paths.empty() || paths == "<empty>")
         return;
@@ -1279,21 +1349,21 @@ void Tablebases::init(const std::string& paths) {
     for (auto s : diagonal)
         MapA1D1D4[s] = code++;
 
-    // MapKK[] encodes all the 461 possible legal positions of two kings where
+    // MapKK[] encodes all the 462 possible legal positions of two kings where
     // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4
-    // diagonal, the other one shall not to be above the a1-h8 diagonal.
+    // diagonal, the other one shall not be above the a1-h8 diagonal.
     std::vector<std::pair<int, Square>> bothOnDiagonal;
     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);
@@ -1302,31 +1372,31 @@ 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 diagonal are encoded as last ones
     for (auto p : bothOnDiagonal)
         MapKK[p.first][p.second] = code++;
 
-    // Binomial[] stores the Binomial Coefficents using Pascal rule. There
+    // Binomial[] stores the Binomial Coefficients using Pascal rule. There
     // 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 same file, the one with lowest rank.
-    int availableSquares = 47; // Available squares when lead pawn is in a2
+    // 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
     // can have up to 5 leading pawns (KPPPPPK).
     for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)
         for (File f = FILE_A; f <= FILE_D; ++f)
         {
-            // Restart the index at every file because TB table is splitted
+            // Restart the index at every file because TB table is split
             // by file, so we can reuse the same index for different files.
             int idx = 0;
 
@@ -1343,8 +1413,8 @@ 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 ^ 7] = availableSquares--; // Horizontal flip
+                    MapPawns[sq]            = availableSquares--;
+                    MapPawns[flip_file(sq)] = availableSquares--;
                 }
                 LeadPawnIdx[leadPawnsCnt][sq] = idx;
                 idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];
@@ -1353,21 +1423,25 @@ void Tablebases::init(const std::string& paths) {
             LeadPawnsSize[leadPawnsCnt][f] = idx;
         }
 
-    // Add entries in TB tables if the corresponding ".rtbw" file exsists
-    for (PieceType p1 = PAWN; p1 < KING; ++p1) {
+    // Add entries in TB tables if the corresponding ".rtbw" file exists
+    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)
@@ -1377,7 +1451,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)
@@ -1430,19 +1505,19 @@ WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) {
 // If n = 100 immediately after a capture or pawn move, then the position
 // is also certainly a win, and during the whole phase until the next
 // capture or pawn move, the inequality to be preserved is
-// dtz + 50-movecounter <= 100.
+// dtz + 50-move-counter <= 100.
 //
 // In short, if a move is available resulting in dtz + 50-move-counter <= 99,
 // 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<true>(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
+    // 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);
@@ -1458,9 +1533,9 @@ 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<LEGAL>(pos))
+    for (const Move move : MoveList<LEGAL>(pos))
     {
         bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;
 
@@ -1469,9 +1544,8 @@ 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).
-        dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result))
-                      : -probe_dtz(pos, result);
+        // winning position we could make a losing capture or go for a draw).
+        dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result)) : -probe_dtz(pos, result);
 
         // If the move mates, force minDTZ to 1
         if (dtz == 1 && pos.checkers() && MoveList<LEGAL>(pos).size() == 0)
@@ -1502,8 +1576,8 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
 // A return value false indicates that not all probes were successful.
 bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
 
-    ProbeState result;
-    StateInfo st;
+    ProbeState result = OK;
+    StateInfo  st;
 
     // Obtain 50-move counter for the root position
     int cnt50 = pos.rule50_count();
@@ -1511,7 +1585,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
     // Check whether a position was repeated since the last zeroing move.
     bool rep = pos.has_repeated();
 
-    int dtz, bound = Options["Syzygy50MoveRule"] ? 900 : 1;
+    int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1;
 
     // Probe and rank each move
     for (auto& m : rootMoves)
@@ -1523,20 +1597,24 @@ 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))
+        {
+            // 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
         {
             // 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<LEGAL>(pos).size() == 0)
+        if (pos.checkers() && dtz == 2 && MoveList<LEGAL>(pos).size() == 0)
             dtz = 1;
 
         pos.undo_move(m.pv[0]);
@@ -1546,19 +1624,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 ? 1000 : 1000 - (dtz + cnt50))
-               : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -1000 : -1000 + (-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 - 800) * int(PawnValueEg)) / 200)
-                   : r == 0     ? VALUE_DRAW
-                   : r > -bound ? Value((std::min(-3, r + 800) * int(PawnValueEg)) / 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;
@@ -1571,10 +1649,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[] = { -1000, -899, 0, 899, 1000 };
+    static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};
 
-    ProbeState result;
-    StateInfo st;
+    ProbeState result = OK;
+    StateInfo  st;
+    WDLScore   wdl;
 
     bool rule50 = Options["Syzygy50MoveRule"];
 
@@ -1583,7 +1662,10 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
     {
         pos.do_move(m.pv[0], st);
 
-        WDLScore wdl = -probe_wdl(pos, &result);
+        if (pos.is_draw(1))
+            wdl = WDLDraw;
+        else
+            wdl = -probe_wdl(pos, &result);
 
         pos.undo_move(m.pv[0]);
 
@@ -1593,10 +1675,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
index df3ca4feccc6b9625e68948d181638fce2d4bc40..3b7c8aa70fda619b9523f0f2ca5b456d9aac4ec7 100644 (file)
@@ -1,7 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (c) 2013 Ronald de Man
-  Copyright (C) 2016-2020 Marco Costalba, Lucas Braesch
+  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
 #ifndef TBPROBE_H
 #define TBPROBE_H
 
-#include <ostream>
+#include <string>
 
 #include "../search.h"
 
-namespace Tablebases {
+namespace Stockfish {
+class Position;
+}
 
-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
+namespace Stockfish::Tablebases {
 
-    WDLScoreNone  = -1000
+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
 };
 
 // Possible states after a probing operation
 enum ProbeState {
-    FAIL              =  0, // Probe failed (missing file table)
-    OK                =  1, // Probe succesful
-    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);
-
-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");
+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);
 
-    return os;
-}
-
-}
+}  // namespace Stockfish::Tablebases
 
 #endif
index 615d482cafa811a4046eaacf7f21676b28f46ed6..de8de87d8a2b9d94f6f57b9876d658f459abfbc0 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include <cassert>
+#include "thread.h"
 
-#include <algorithm> // For std::count
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdlib>
+#include <deque>
+#include <initializer_list>
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "evaluate.h"
+#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"
 
-ThreadPool Threads; // Global object
+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::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
+// 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) {
 
-  wait_for_search_finished();
+    wait_for_search_finished();
 }
 
 
-/// Thread destructor wakes up the thread in idle_loop() and waits
-/// for its termination. Thread should be already waiting.
-
+// Destructor wakes up the thread in idle_loop() and waits
+// for its termination. Thread should be already waiting.
 Thread::~Thread() {
 
-  assert(!searching);
+    assert(!searching);
 
-  exit = true;
-  start_searching();
-  stdThread.join();
+    exit = true;
+    start_searching();
+    stdThread.join();
 }
 
-/// Thread::bestMoveCount(Move move) return best move counter for the given root move
-
-int Thread::best_move_count(Move move) {
-
-  auto rm = std::find(rootMoves.begin() + pvIdx,
-                      rootMoves.begin() + pvLast, move);
-
-  return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0;
-}
-
-/// Thread::clear() reset histories, usually before a new game
 
+// Reset histories, usually before a new game
 void Thread::clear() {
 
-  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(0);
+    counterMoves.fill(MOVE_NONE);
+    mainHistory.fill(0);
+    captureHistory.fill(0);
+    pawnHistory.fill(0);
 
-  for (bool inCheck : { false, true })
-    for (StatsType c : { NoCaptures, Captures })
-      continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
+    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
 
+// Wakes up the thread that will start the search
 void Thread::start_searching() {
-
-  std::lock_guard<std::mutex> lk(mutex);
-  searching = true;
-  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()
 }
 
 
-/// Thread::wait_for_search_finished() blocks on the condition variable
-/// until the thread has finished searching.
-
+// Blocks on the condition variable
+// until the thread has finished searching.
 void Thread::wait_for_search_finished() {
 
-  std::unique_lock<std::mutex> lk(mutex);
-  cv.wait(lk, [&]{ return !searching; });
+    std::unique_lock<std::mutex> lk(mutex);
+    cv.wait(lk, [&] { return !searching; });
 }
 
 
-/// Thread::idle_loop() is where the thread is parked, blocked on the
-/// condition variable, when it has no work to do.
+// Thread gets parked here, 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
-  // 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<std::mutex> lk(mutex);
-      searching = false;
-      cv.notify_one(); // Wake up anyone waiting for search finished
-      cv.wait(lk, [&]{ return searching; });
+    while (true)
+    {
+        std::unique_lock<std::mutex> 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.
-/// Created and launched threads will immediately go to sleep in idle_loop.
-/// Upon resizing, threads are recreated to allow for binding if necessary.
-
+// 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 (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 (size() > 0)
-          delete back(), pop_back();
-  }
+        while (threads.size() > 0)
+            delete threads.back(), threads.pop_back();
+    }
 
-  if (requested > 0) { // create new thread(s)
-      push_back(new MainThread(0));
+    if (requested > 0)  // create new thread(s)
+    {
+        threads.push_back(new MainThread(0));
 
-      while (size() < requested)
-          push_back(new Thread(size()));
-      clear();
+        while (threads.size() < requested)
+            threads.push_back(new Thread(threads.size()));
+        clear();
 
-      // Reallocate the hash with the new threadpool size
-      TT.resize(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();
+    }
 }
 
-/// ThreadPool::clear() sets threadPool data to initial values.
 
+// Sets threadPool data to initial values
 void ThreadPool::clear() {
 
-  for (Thread* th : *this)
-      th->clear();
+    for (Thread* th : threads)
+        th->clear();
 
-  main()->callsCnt = 0;
-  main()->previousScore = 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) {
-
-  main()->wait_for_search_finished();
+// 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) {
+
+    main()->wait_for_search_finished();
+
+    main()->stopOnPonderhit = stop = false;
+    increaseDepth                  = true;
+    main()->ponder                 = ponderMode;
+    Search::Limits                 = limits;
+    Search::RootMoves rootMoves;
+
+    for (const auto& m : MoveList<LEGAL>(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);
+
+    // 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
+
+    // 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()->stopOnPonderhit = stop = false;
-  increaseDepth = true;
-  main()->ponder = ponderMode;
-  Search::Limits = limits;
-  Search::RootMoves rootMoves;
+Thread* ThreadPool::get_best_thread() const {
+
+    Thread*                 bestThread = threads.front();
+    std::map<Move, int64_t> votes;
+    Value                   minScore = VALUE_NONE;
+
+    // Find the minimum score of all 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);
+    };
+
+    for (Thread* th : threads)
+        votes[th->rootMoves[0].pv[0]] += thread_value(th);
+
+    for (Thread* th : threads)
+        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)
+                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)))))
+            bestThread = th;
+
+    return bestThread;
+}
 
-  for (const auto& m : MoveList<LEGAL>(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);
+// Start non-main threads
 
-  // After ownership transfer 'states' becomes empty, so if we stop the search
-  // and call 'go' again without setting a new position states.get() == NULL.
-  assert(states.get() || setupStates.get());
+void ThreadPool::start_searching() {
 
-  if (states.get())
-      setupStates = std::move(states); // Ownership transfer, states is now empty
+    for (Thread* th : threads)
+        if (th != threads.front())
+            th->start_searching();
+}
 
-  // 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 to not lose the info
-  // we need to backup and later restore setupStates->back(). Note that setupStates
-  // is shared by threads but is accessed in read-only mode.
-  StateInfo tmp = setupStates->back();
 
-  for (Thread* th : *this)
-  {
-      th->nodes = th->tbHits = th->nmpMinPly = 0;
-      th->rootDepth = th->completedDepth = 0;
-      th->rootMoves = rootMoves;
-      th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
-  }
+// Wait for non-main threads
 
-  setupStates->back() = tmp;
+void ThreadPool::wait_for_search_finished() const {
 
-  main()->start_searching();
+    for (Thread* th : threads)
+        if (th != threads.front())
+            th->wait_for_search_finished();
 }
+
+}  // namespace Stockfish
index aea86fd5c6941312fcf9a5b04ac2c56d2cc67fbd..cb2f6db1d4138b3e2010d2e54bd15619836c8294 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 
 #include <atomic>
 #include <condition_variable>
+#include <cstddef>
+#include <cstdint>
 #include <mutex>
-#include <thread>
 #include <vector>
 
-#include "material.h"
 #include "movepick.h"
-#include "pawns.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. 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.
 class Thread {
 
-  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();
-  int best_move_count(Move move);
-
-  Pawns::Table pawnsTable;
-  Material::Table materialTable;
-  size_t pvIdx, pvLast;
-  uint64_t ttHitAverage;
-  int selDepth, nmpMinPly;
-  Color nmpColor;
-  std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
-
-  Position rootPos;
-  Search::RootMoves rootMoves;
-  Depth rootDepth, completedDepth;
-  CounterMoveHistory counterMoves;
-  ButterflyHistory mainHistory;
-  CapturePieceToHistory captureHistory;
-  ContinuationHistory continuationHistory[2][2];
-  Score contempt;
+    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; }
+
+    size_t                pvIdx, pvLast;
+    std::atomic<uint64_t> 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];
+    PawnHistory           pawnHistory;
 };
 
 
-/// MainThread is a derived class specific for main thread
-
-struct MainThread : public Thread {
+// MainThread is a derived class specific for main 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 previousScore;
-  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;
 };
 
 
-/// 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 {
 
-struct ThreadPool : public std::vector<Thread*> {
+    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<MainThread*>(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<MainThread*>(front()); }
-  uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
-  uint64_t tb_hits()        const { return accumulate(&Thread::tbHits); }
+    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(); }
 
-private:
-  StateListPtr setupStates;
+   private:
+    StateListPtr         setupStates;
+    std::vector<Thread*> threads;
 
-  uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
+    uint64_t accumulate(std::atomic<uint64_t> Thread::*member) const {
 
-    uint64_t sum = 0;
-    for (Thread* th : *this)
-        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;
 
-#endif // #ifndef THREAD_H_INCLUDED
+}  // namespace Stockfish
+
+#endif  // #ifndef THREAD_H_INCLUDED
index 0ef5c98132ae395c617f9b6e5dbd9ee1938f9409..248e4a674506248f4d3fc891841df493bc651a36 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 
 #include <thread>
 
-/// 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__)
+#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
 
-#include <pthread.h>
+    #include <pthread.h>
+
+namespace Stockfish {
 
 static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
 
-template <class T, class P = std::pair<T*, void(T::*)()>>
-void* start_routine(void* ptr)
-{
-   P* p = reinterpret_cast<P*>(ptr);
-   (p->first->*(p->second))(); // Call member function pointer
-   delete p;
-   return NULL;
+template<class T, class P = std::pair<T*, void (T::*)()>>
+void* start_routine(void* ptr) {
+    P* p = reinterpret_cast<P*>(ptr);
+    (p->first->*(p->second))();  // Call member function pointer
+    delete p;
+    return nullptr;
 }
 
 class NativeThread {
 
-   pthread_t thread;
-
-public:
-  template<class T, class P = std::pair<T*, void(T::*)()>>
-  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<T>, new P(obj, fun));
-  }
-  void join() { pthread_join(thread, NULL); }
+    pthread_t thread;
+
+   public:
+    template<class T, class P = std::pair<T*, void (T::*)()>>
+    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<T>, new P(obj, fun));
+    }
+    void join() { pthread_join(thread, nullptr); }
 };
 
-#else // Default case: use STL classes
+}  // namespace Stockfish
+
+#else  // Default case: use STL classes
+
+namespace Stockfish {
+
+using NativeThread = std::thread;
 
-typedef std::thread NativeThread;
+}  // namespace Stockfish
 
 #endif
 
-#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
+#endif  // #ifndef THREAD_WIN32_OSX_H_INCLUDED
index 0848be420c056beef1356bb85cca99c43ff1c244..f404ee0c353eb96215db47c14c500e3bc1c58246 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "timeman.h"
+
 #include <algorithm>
-#include <cfloat>
 #include <cmath>
 
 #include "search.h"
-#include "timeman.h"
 #include "uci.h"
 
-TimeManagement Time; // Our global time management object
-
-namespace {
-
-  enum TimeType { OptimumTime, MaxTime };
-
-  constexpr int MoveHorizon   = 50;   // Plan time management at most this many moves ahead
-  constexpr double MaxRatio   = 7.3;  // When in trouble, we can step over reserved time with this ratio
-  constexpr double StealRatio = 0.34; // However we must not steal time from remaining moves over this ratio
-
-
-  // move_importance() is a skew-logistic function based on naive statistical
-  // analysis of "how many games are still undecided after n half-moves". Game
-  // is considered "undecided" as long as neither side has >275cp advantage.
-  // Data was extracted from the CCRL game database with some simple filtering criteria.
-
-  double move_importance(int ply) {
-
-    constexpr double XScale = 6.85;
-    constexpr double XShift = 64.5;
-    constexpr double Skew   = 0.171;
+namespace Stockfish {
 
-    return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
-  }
+TimeManagement Time;  // Our global time management object
 
-  template<TimeType T>
-  TimePoint remaining(TimePoint myTime, int movesToGo, int ply, TimePoint slowMover) {
-
-    constexpr double TMaxRatio   = (T == OptimumTime ? 1.0 : MaxRatio);
-    constexpr double TStealRatio = (T == OptimumTime ? 0.0 : StealRatio);
-
-    double moveImportance = (move_importance(ply) * slowMover) / 100.0;
-    double otherMovesImportance = 0.0;
-
-    for (int i = 1; i < movesToGo; ++i)
-        otherMovesImportance += move_importance(ply + 2 * i);
-
-    double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance);
-    double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance);
-
-    return TimePoint(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast
-  }
-
-} // namespace
-
-
-/// init() is called at the beginning of the search and calculates the allowed
-/// thinking time out of the time control and current game ply. We support four
-/// different kinds of time controls, passed in 'limits':
-///
-///  inc == 0 && movestogo == 0 means: x basetime  [sudden death!]
-///  inc == 0 && movestogo != 0 means: x moves in y minutes
-///  inc >  0 && movestogo == 0 means: x basetime + z increment
-///  inc >  0 && movestogo != 0 means: x moves in y minutes + z increment
 
+// 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) {
 
-  TimePoint minThinkingTime = Options["Minimum Thinking Time"];
-  TimePoint moveOverhead    = Options["Move Overhead"];
-  TimePoint slowMover       = Options["Slow Mover"];
-  TimePoint npmsec          = Options["nodestime"];
-  TimePoint hypMyTime;
-
-  // 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;
-  }
-
-  startTime = limits.startTime;
-  optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime);
-
-  const int maxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon;
-
-  // We calculate optimum time usage for different hypothetical "moves to go" values
-  // and choose the minimum of calculated search time values. Usually the greatest
-  // hypMTG gives the minimum values.
-  for (int hypMTG = 1; hypMTG <= maxMTG; ++hypMTG)
-  {
-      // Calculate thinking time for hypothetical "moves to go"-value
-      hypMyTime =  limits.time[us]
-                 + limits.inc[us] * (hypMTG - 1)
-                 - moveOverhead * (2 + std::min(hypMTG, 40));
-
-      hypMyTime = std::max(hypMyTime, TimePoint(0));
-
-      TimePoint t1 = minThinkingTime + remaining<OptimumTime>(hypMyTime, hypMTG, ply, slowMover);
-      TimePoint t2 = minThinkingTime + remaining<MaxTime    >(hypMyTime, hypMTG, ply, slowMover);
-
-      optimumTime = std::min(t1, optimumTime);
-      maximumTime = std::min(t2, maximumTime);
-  }
-
-  if (Options["Ponder"])
-      optimumTime += optimumTime / 4;
+    // 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"]);
+
+    // 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
+
+        // 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;
+
+    // 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.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)
+    {
+        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.9, maxConstant + ply / 12.2);
+    }
+
+    // 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);
+    }
+
+    // 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;
+
+    if (Options["Ponder"])
+        optimumTime += optimumTime / 4;
 }
+
+}  // namespace Stockfish
index 9301dc946f734674d0b1f2bbaf143e373a77d0c5..6c56d506b3f1ce04e6080330f90338c63aae268c 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 #ifndef TIMEMAN_H_INCLUDED
 #define TIMEMAN_H_INCLUDED
 
+#include <cstdint>
+
 #include "misc.h"
 #include "search.h"
 #include "thread.h"
+#include "types.h"
 
-/// The TimeManagement class computes the optimal time to think depending on
-/// the maximum available time, the game move number and other parameters.
+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);
-  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
-
-private:
-  TimePoint startTime;
-  TimePoint optimumTime;
-  TimePoint maximumTime;
+   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
+
+   private:
+    TimePoint startTime;
+    TimePoint optimumTime;
+    TimePoint maximumTime;
 };
 
 extern TimeManagement Time;
 
-#endif // #ifndef TIMEMAN_H_INCLUDED
+}  // namespace Stockfish
+
+#endif  // #ifndef TIMEMAN_H_INCLUDED
index 0b4a59de55915962e7ed9409ae462be2556366f3..816d43f86036505d70369c99ba4df6003392a9a4 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include <cstring>   // For std::memset
+#include "tt.h"
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
 #include <iostream>
 #include <thread>
+#include <vector>
 
-#include "bitboard.h"
 #include "misc.h"
 #include "thread.h"
-#include "tt.h"
 #include "uci.h"
 
-TranspositionTable TT; // Our global transposition table
+namespace Stockfish {
 
-/// TTEntry::save populates the TTEntry with a new node's data, possibly
-/// overwriting an old position. Update is not atomic and can be racy.
+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) {
 
-  // Preserve any existing move for the same position
-  if (m || (k >> 48) != key16)
-      move16 = (uint16_t)m;
-
-  // Overwrite less valuable entries
-  if (  (k >> 48) != key16
-      || d - DEPTH_OFFSET > depth8 - 4
-      || b == BOUND_EXACT)
-  {
-      assert(d >= DEPTH_OFFSET);
-
-      key16     = (uint16_t)(k >> 48);
-      value16   = (int16_t)v;
-      eval16    = (int16_t)ev;
-      genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
-      depth8    = (uint8_t)(d - DEPTH_OFFSET);
-  }
+    // 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);
+
+        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);
+    }
 }
 
 
-/// 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.
-
+// 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();
+    Threads.main()->wait_for_search_finished();
 
-  clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
+    aligned_large_pages_free(table);
 
-  free(mem);
-  mem = malloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1);
+    clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
 
-  if (!mem)
-  {
-      std::cerr << "Failed to allocate " << mbSize
-                << "MB for transposition table." << std::endl;
-      exit(EXIT_FAILURE);
-  }
+    table = static_cast<Cluster*>(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 = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1));
-  clear();
+    clear();
 }
 
 
-/// 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<std::thread> threads;
-
-  for (size_t idx = 0; idx < Options["Threads"]; ++idx)
-  {
-      threads.emplace_back([this, idx]() {
+    std::vector<std::thread> threads;
 
-          // Thread binding gives faster search on systems with a first-touch policy
-          if (Options["Threads"] > 8)
-              WinProcGroup::bindThisThread(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);
 
-          // Each thread will zero its part of the hash table
-          const size_t stride = clusterCount / Options["Threads"],
-                       start  = stride * idx,
-                       len    = idx != 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();
 }
 
-/// 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.
 
+// 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);
-  const uint16_t key16 = key >> 48;  // Use the high 16 bits as key inside the cluster
-
-  for (int i = 0; i < ClusterSize; ++i)
-      if (!tte[i].key16 || tte[i].key16 == key16)
-      {
-          tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & 0x7)); // Refresh
-
-          return found = (bool)tte[i].key16, &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 263 (256 is the modulus plus 7 to keep the unrelated
-      // lowest three bits from affecting the result) to calculate the entry
-      // age correctly even after generation8 overflows into the next cycle.
-      if (  replace->depth8 - ((263 + generation8 - replace->genBound8) & 0xF8)
-          >   tte[i].depth8 - ((263 + generation8 -   tte[i].genBound8) & 0xF8))
-          replace = &tte[i];
-
-  return found = false, replace;
+    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
+
+            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];
+
+    return found = false, replace;
 }
 
 
-/// TranspositionTable::hashfull() returns an approximation of the hashtable
-/// occupation during a search. The hash is x permill full, as per UCI protocol.
+// Returns an approximation of the hashtable
+// occupation during a search. The hash is x permill full, as per UCI protocol.
 
 int TranspositionTable::hashfull() const {
 
-  int cnt = 0;
-  for (int i = 0; i < 1000 / ClusterSize; ++i)
-      for (int j = 0; j < ClusterSize; ++j)
-          cnt += (table[i].entry[j].genBound8 & 0xF8) == 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 * 1000 / (ClusterSize * (1000 / ClusterSize));
+    return cnt / ClusterSize;
 }
+
+}  // namespace Stockfish
index 98b054d397207525a45978e6bae6a15055e29181..12fedd2d42ffa686997d4cbcf9835885b223f0c7 100644 (file)
--- a/src/tt.h
+++ b/src/tt.h
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 #ifndef TT_H_INCLUDED
 #define TT_H_INCLUDED
 
+#include <cstddef>
+#include <cstdint>
+
 #include "misc.h"
 #include "types.h"
 
-/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
-///
-/// key        16 bit
-/// move       16 bit
-/// value      16 bit
-/// eval value 16 bit
-/// generation  5 bit
-/// pv node     1 bit
-/// bound type  2 bit
-/// depth       8 bit
-
+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
 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);
-
-private:
-  friend class TranspositionTable;
-
-  uint16_t key16;
-  uint16_t move16;
-  int16_t  value16;
-  int16_t  eval16;
-  uint8_t  genBound8;
-  uint8_t  depth8;
+    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;
+
+    uint16_t key16;
+    uint8_t  depth8;
+    uint8_t  genBound8;
+    uint16_t move16;
+    int16_t  value16;
+    int16_t  eval16;
 };
 
 
-/// A TranspositionTable consists of a power of 2 number of clusters and each
-/// cluster consists of ClusterSize number of TTEntry. Each non-empty entry
-/// contains information of exactly one position. The size of a cluster should
-/// divide the size of a cache line size, to ensure that clusters never cross
-/// cache lines. This ensures best cache performance, as the cacheline is
-/// prefetched, as soon as 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 {
 
-  static constexpr int CacheLineSize = 64;
-  static constexpr int ClusterSize = 3;
-
-  struct Cluster {
-    TTEntry entry[ClusterSize];
-    char padding[2]; // Align to a divisor of the cache line size
-  };
-
-  static_assert(CacheLineSize % sizeof(Cluster) == 0, "Cluster size incorrect");
-
-public:
- ~TranspositionTable() { free(mem); }
-  void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound
-  TTEntry* probe(const Key key, bool& found) const;
-  int hashfull() const;
-  void resize(size_t mbSize);
-  void clear();
-
-  // The 32 lowest order bits of the key are used to get the index of the cluster
-  TTEntry* first_entry(const Key key) const {
-    return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0];
-  }
-
-private:
-  friend struct TTEntry;
-
-  size_t clusterCount;
-  Cluster* table;
-  void* mem;
-  uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
+    static constexpr int ClusterSize = 3;
+
+    struct Cluster {
+        TTEntry entry[ClusterSize];
+        char    padding[2];  // Pad to 32 bytes
+    };
+
+    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
+
+   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];
+    }
+
+   private:
+    friend struct TTEntry;
+
+    size_t   clusterCount;
+    Cluster* table;
+    uint8_t  generation8;  // Size must be not bigger than TTEntry::genBound8
 };
 
 extern TranspositionTable TT;
 
-#endif // #ifndef TT_H_INCLUDED
+}  // namespace Stockfish
+
+#endif  // #ifndef TT_H_INCLUDED
diff --git a/src/tune.cpp b/src/tune.cpp
new file mode 100644 (file)
index 0000000..cf80b9d
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "tune.h"
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "uci.h"
+
+namespace Stockfish {
+enum Value : int;
+}
+
+using std::string;
+
+namespace Stockfish {
+
+bool                              Tune::update_on_last;
+const UCI::Option*                LastOption = nullptr;
+static std::map<std::string, int> TuneResults;
+
+string Tune::next(string& names, bool pop) {
+
+    string name;
+
+    do
+    {
+        string token = names.substr(0, names.find(','));
+
+        if (pop)
+            names.erase(0, token.size() + 1);
+
+        std::stringstream ws(token);
+        name += (ws >> token, token);  // Remove trailing whitespace
+
+    } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')'));
+
+    return name;
+}
+
+static void on_tune(const UCI::Option& o) {
+
+    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;
+
+    if (TuneResults.count(n))
+        v = TuneResults[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;
+}
+
+template<>
+void Tune::Entry<int>::init_option() {
+    make_option(name, value, range);
+}
+
+template<>
+void Tune::Entry<int>::read_option() {
+    if (Options.count(name))
+        value = int(Options[name]);
+}
+
+template<>
+void Tune::Entry<Value>::init_option() {
+    make_option(name, value, range);
+}
+
+template<>
+void Tune::Entry<Value>::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<Tune::PostUpdate>::init_option() {}
+template<>
+void Tune::Entry<Tune::PostUpdate>::read_option() {
+    value();
+}
+
+}  // namespace Stockfish
+
+
+// Init options with tuning session results instead of default values. Useful to
+// get correct bench signature after a tuning session or to test tuned values.
+// Just copy fishtest tuning results in a result.txt file and extract the
+// values with:
+//
+// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/  TuneResults["\1"] = int(round(\2));/'
+//
+// Then paste the output below, as the function body
+
+
+namespace Stockfish {
+
+void Tune::read_results() { /* ...insert your values here... */
+}
+
+}  // namespace Stockfish
diff --git a/src/tune.h b/src/tune.h
new file mode 100644 (file)
index 0000000..480aea1
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+  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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TUNE_H_INCLUDED
+#define TUNE_H_INCLUDED
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <type_traits>  // IWYU pragma: keep
+#include <utility>
+#include <vector>
+
+namespace Stockfish {
+enum Value : int;
+
+using Range    = std::pair<int, int>;  // 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); }
+
+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; }
+
+    RangeFun* fun;
+    Range     range;
+};
+
+#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.
+
+class Tune {
+
+    using PostUpdate = void();  // Post-update function
+
+    Tune() { read_results(); }
+    Tune(const Tune&)           = delete;
+    void operator=(const Tune&) = delete;
+    void read_results();
+
+    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;
+    };
+
+    template<typename T>
+    struct Entry: public EntryBase {
+
+        static_assert(!std::is_const_v<T>, "Parameter cannot be const!");
+
+        static_assert(std::is_same_v<T, int> || std::is_same_v<T, Value>
+                        || std::is_same_v<T, PostUpdate>,
+                      "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;
+
+        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);
+
+    int add(const SetRange&, std::string&&) { return 0; }
+
+    template<typename T, typename... Args>
+    int add(const SetRange& range, std::string&& names, T& value, Args&&... args) {
+        list.push_back(std::unique_ptr<EntryBase>(new Entry<T>(next(names), value, range)));
+        return add(range, std::move(names), args...);
+    }
+
+    // Template specialization for arrays: recursively handle multi-dimensional arrays
+    template<typename T, size_t N, typename... Args>
+    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<typename... Args>
+    int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) {
+        return add(value, (next(names), std::move(names)), args...);
+    }
+
+    std::vector<std::unique_ptr<EntryBase>> list;
+
+   public:
+    template<typename... Args>
+    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 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
+
+#endif  // #ifndef TUNE_H_INCLUDED
index 902c2cfce87f49881f4183bc9107e9b085dedbce..3e00d68d19dcc8bad266ef8b03ba11872f24dd93 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 */
 
 #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.
-
-#include <cassert>
-#include <cctype>
-#include <climits>
-#include <cstdint>
-#include <cstdlib>
-#include <algorithm>
-
-#if defined(_MSC_VER)
-// Disable some silly and noisy warning 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:
-///
-/// __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
-
-#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
-#  include <intrin.h> // Microsoft header for _BitScanForward64()
-#  define IS_64BIT
-#endif
-
-#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
-#  include <nmmintrin.h> // Intel and Microsoft header for _mm_popcnt_u64()
-#endif
-
-#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
-#  include <xmmintrin.h> // Intel and Microsoft header for _mm_prefetch()
-#endif
-
-#if defined(USE_PEXT)
-#  include <immintrin.h> // Header for _pext_u64() intrinsic
-#  define pext(b, m) _pext_u64(b, m)
-#else
-#  define pext(b, m) 0
-#endif
-
-#ifdef USE_POPCNT
+    #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.
+
+    #include <cassert>
+    #include <cstdint>
+
+    #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:
+//
+// __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
+    #endif
+
+    #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
+
+    #if defined(_WIN64) && defined(_MSC_VER)  // No Makefile used
+        #include <intrin.h>                   // Microsoft header for _BitScanForward64()
+        #define IS_64BIT
+    #endif
+
+    #if defined(USE_POPCNT) && defined(_MSC_VER)
+        #include <nmmintrin.h>  // Microsoft header for _mm_popcnt_u64()
+    #endif
+
+    #if !defined(NO_PREFETCH) && defined(_MSC_VER)
+        #include <xmmintrin.h>  // Microsoft header for _mm_prefetch()
+    #endif
+
+    #if defined(USE_PEXT)
+        #include <immintrin.h>  // 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
 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
 
-typedef uint64_t Key;
-typedef uint64_t Bitboard;
+using Key      = uint64_t;
+using Bitboard = uint64_t;
 
 constexpr int MAX_MOVES = 256;
 constexpr int MAX_PLY   = 246;
 
-/// 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,
-  MOVE_NULL = 65
+    MOVE_NONE,
+    MOVE_NULL = 65
 };
 
 enum MoveType {
-  NORMAL,
-  PROMOTION = 1 << 14,
-  ENPASSANT = 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,
-
-  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
-};
-
-enum Phase {
-  PHASE_ENDGAME,
-  PHASE_MIDGAME = 128,
-  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
+    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,
+
+    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_KNOWN_WIN = 10000,
-  VALUE_MATE      = 32000,
-  VALUE_INFINITE  = 32001,
-  VALUE_NONE      = 32002,
-
-  VALUE_MATE_IN_MAX_PLY  =  VALUE_MATE - 2 * MAX_PLY,
-  VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY,
-
-  PawnValueMg   = 128,   PawnValueEg   = 213,
-  KnightValueMg = 781,   KnightValueEg = 854,
-  BishopValueMg = 825,   BishopValueEg = 915,
-  RookValueMg   = 1276,  RookValueEg   = 1380,
-  QueenValueMg  = 2538,  QueenValueEg  = 2682,
-
-  MidgameLimit  = 15258, EndgameLimit  = 3915
+    VALUE_ZERO     = 0,
+    VALUE_DRAW     = 0,
+    VALUE_NONE     = 32002,
+    VALUE_INFINITE = 32001,
+
+    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,
+
+    // 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 = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
-  B_PAWN = 9, 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
 
-extern Value PieceValue[PHASE_NB][PIECE_NB];
+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};
 
-typedef int Depth;
+using Depth = int;
 
 enum : int {
+    DEPTH_QS_CHECKS    = 0,
+    DEPTH_QS_NO_CHECKS = -1,
 
-  DEPTH_QS_CHECKS     =  0,
-  DEPTH_QS_NO_CHECKS  = -1,
-  DEPTH_QS_RECAPTURES = -5,
+    DEPTH_NONE = -6,
 
-  DEPTH_NONE   = -6,
-  DEPTH_OFFSET = DEPTH_NONE,
+    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,
-
-  SQUARE_NB = 64
+    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
 };
+// clang-format on
 
 enum Direction : int {
-  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 = 8,
+    EAST  = 1,
+    SOUTH = -NORTH,
+    WEST  = -EAST,
+
+    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 {
 
-/// 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);
-}
+    // Number of changed pieces
+    int dirty_num;
 
-/// 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);
-}
+    // 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];
 
-inline Value mg_value(Score s) {
-  union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) };
-  return Value(mg.s);
-}
+    // 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, T d2) { return T(int(d1) + int(d2)); } \
-constexpr T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \
-constexpr T operator-(T d) { return T(-int(d)); }                  \
-inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; }         \
-inline T& operator-=(T& d1, T 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); }
+    #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(Value)
 ENABLE_FULL_OPERATORS_ON(Direction)
 
 ENABLE_INCR_OPERATORS_ON(PieceType)
-ENABLE_INCR_OPERATORS_ON(Piece)
 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
 
-#undef ENABLE_FULL_OPERATORS_ON
-#undef ENABLE_INCR_OPERATORS_ON
-#undef ENABLE_BASE_OPERATORS_ON
-
-/// Additional operators to add integers to a Value
-constexpr Value operator+(Value v, int i) { return Value(int(v) + i); }
-constexpr Value operator-(Value v, int i) { return Value(int(v) - i); }
-inline Value& operator+=(Value& v, int i) { return v = v + i; }
-inline Value& operator-=(Value& v, int i) { return v = v - i; }
-
-/// 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; }
-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; }
 
-/// 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;
+// Toggle color
+constexpr Color operator~(Color c) { return Color(c ^ BLACK); }
 
-/// 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 Score(int(s) * int(b));
-}
-
-constexpr Color operator~(Color c) {
-  return Color(c ^ BLACK); // Toggle color
-}
-
-constexpr Square operator~(Square s) {
-  return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8
-}
+// Swap A1 <-> A8
+constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); }
 
-constexpr Piece operator~(Piece pc) {
-  return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT
-}
+// Swap A1 <-> H1
+constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); }
 
-inline File map_to_queenside(File f) {
-  return std::min(f, File(FILE_H - f)); // Map files ABCDEFGH to files ABCDDCBA
-}
+// 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);
+    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(Square s) {
-  return s >= SQ_A1 && s <= SQ_H8;
-}
+constexpr bool is_ok(Move m) { return m != MOVE_NONE && m != MOVE_NULL; }
 
-constexpr File file_of(Square s) {
-  return File(s & 7);
-}
+constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; }
 
-constexpr Rank rank_of(Square s) {
-  return Rank(s >> 3);
-}
+constexpr File file_of(Square s) { return File(s & 7); }
 
-constexpr Square relative_square(Color c, Square s) {
-  return Square(s ^ (c * 56));
-}
+constexpr Rank rank_of(Square s) { return Rank(s >> 3); }
 
-constexpr Rank relative_rank(Color c, Rank r) {
-  return Rank(r ^ (c * 7));
-}
+constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); }
 
-constexpr Rank relative_rank(Color c, Square s) {
-  return relative_rank(c, rank_of(s));
-}
+constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); }
 
-constexpr Direction pawn_push(Color c) {
-  return c == WHITE ? NORTH : SOUTH;
-}
+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 Square from_sq(Move m) {
-  return Square((m >> 6) & 0x3F);
+    assert(is_ok(m));
+    return Square((m >> 6) & 0x3F);
 }
 
 constexpr Square to_sq(Move 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 reverse_move(Move m) {
-  return make_move(to_sq(m), from_sq(m));
-}
+constexpr Move make_move(Square from, Square to) { return Move((from << 6) + to); }
 
 template<MoveType T>
 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);
 }
 
-constexpr bool is_ok(Move m) {
-  return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE
+// Based on a congruential pseudo-random number generator
+constexpr Key make_key(uint64_t seed) {
+    return seed * 6364136223846793005ULL + 1442695040888963407ULL;
 }
 
-#endif // #ifndef TYPES_H_INCLUDED
+}  // namespace Stockfish
+
+#endif  // #ifndef TYPES_H_INCLUDED
+
+#include "tune.h"  // Global visibility to tuning setup
index 8b35e6fdd56a96869de49aa2c8c925cf9cbc2e3a..5f250a3617aea6116555698ca9e137e491892391 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "uci.h"
+
+#include <algorithm>
 #include <cassert>
+#include <cctype>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
+#include <deque>
 #include <iostream>
+#include <memory>
+#include <optional>
 #include <sstream>
 #include <string>
+#include <vector>
 
+#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"
 
-using namespace std;
-
-extern vector<string> setup_bench(const Position&, istream&);
+namespace Stockfish {
 
 namespace {
 
-  // FEN string of the initial position, normal 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 engine receives the "position" UCI command.
-  // The function sets up the position described in the given FEN string ("fen")
-  // or the starting position ("startpos") and then makes the moves given in the
-  // following move list ("moves").
 
-  void position(Position& pos, istringstream& is, StateListPtr& states) {
+// 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;
-    string token, fen;
+    Move        m;
+    std::string token, fen;
 
     is >> token;
 
     if (token == "startpos")
     {
         fen = StartFEN;
-        is >> token; // Consume "moves" token if any
+        is >> token;  // Consume the "moves" token, if any
     }
     else if (token == "fen")
         while (is >> token && token != "moves")
@@ -66,32 +71,47 @@ namespace {
     else
         return;
 
-    states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
+    states = StateListPtr(new std::deque<StateInfo>(1));  // Drop the old state and create a new one
     pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main());
 
-    // Parse move list (if any)
+    // Parse the move list, if any
     while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
     {
         states->emplace_back();
         pos.do_move(m, states->back());
     }
-  }
+}
+
+// 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<StateInfo>(1));
+    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 engine receives the "setoption" UCI command. The
-  // function updates the UCI option ("name") to the given value ("value").
+// 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) {
 
-    string token, name, value;
+    Threads.main()->wait_for_search_finished();
 
-    is >> token; // Consume "name" token
+    std::string token, name, value;
 
-    // Read option name (can contain spaces)
+    is >> token;  // Consume the "name" token
+
+    // Read the option name (can contain spaces)
     while (is >> token && token != "value")
         name += (name.empty() ? "" : " ") + token;
 
-    // Read option value (can contain spaces)
+    // Read the option value (can contain spaces)
     while (is >> token)
         value += (value.empty() ? "" : " ") + token;
 
@@ -99,222 +119,315 @@ namespace {
         Options[name] = value;
     else
         sync_cout << "No such option: " << name << sync_endl;
-  }
+}
 
 
-  // go() is called when engine receives the "go" UCI command. The function sets
-  // the thinking time and other parameters from the input string, then starts
-  // the search.
+// 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, istringstream& is, StateListPtr& states) {
+void go(Position& pos, std::istringstream& is, StateListPtr& states) {
 
     Search::LimitsType limits;
-    string token;
-    bool ponderMode = false;
+    std::string        token;
+    bool               ponderMode = false;
 
-    limits.startTime = now(); // As early as possible!
+    limits.startTime = now();  // The search starts as early as possible
 
     while (is >> token)
-        if (token == "searchmoves")
+        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 engine receives the "bench" command. Firstly
-  // a list of UCI commands is setup according to bench parameters, then
-  // it is run one by one printing a summary at the end.
+// 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, istream& args, StateListPtr& states) {
+void bench(Position& pos, std::istream& args, StateListPtr& states) {
 
-    string token;
-    uint64_t num, nodes = 0, cnt = 1;
+    std::string token;
+    uint64_t    num, nodes = 0, cnt = 1;
 
-    vector<string> list = setup_bench(pos, args);
-    num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0 || s.find("eval") == 0; });
+    std::vector<std::string> 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 << 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
-               sync_cout << "\n" << Eval::trace(pos) << sync_endl;
+                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 some 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(); // Just before exiting
+    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
+// 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;
 
-} // namespace
+    // The coefficients of a third-order polynomial fit is based on the fishtest data
+    // for two parameters that need to transform eval to the argument of a logistic
+    // function.
+    constexpr double as[] = {0.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]));
 
-/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate
-/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the
-/// GUI dies unexpectedly. When called with some command line arguments, e.g. to
-/// run 'bench', once the command is executed the function returns immediately.
-/// In addition to the UCI ones, also some additional debug commands are supported.
+    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)));
+}
+
+}  // 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;
-  string token, cmd;
-  StateListPtr states(new std::deque<StateInfo>(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(cin, cmd)) // Block here waiting for input or EOF
-          cmd = "quit";
-
-      istringstream is(cmd);
-
-      token.clear(); // Avoid a stale if getline() returns empty or blank line
-      is >> skipws >> token;
-
-      if (    token == "quit"
-          ||  token == "stop")
-          Threads.stop = true;
-
-      // The GUI sends 'ponderhit' to tell us the user has played the expected move.
-      // So 'ponderhit' will be sent if we were told to ponder on the same move the
-      // user has played. We should continue searching but switch from pondering to
-      // normal search.
-      else if (token == "ponderhit")
-          Threads.main()->ponder = false; // Switch to 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;
-
-      // Additional custom non-UCI commands, mainly for debugging.
-      // Do not use these commands 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")     sync_cout << Eval::trace(pos) << sync_endl;
-      else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
-      else
-          sync_cout << "Unknown command: " << cmd << sync_endl;
-
-  } while (token != "quit" && argc == 1); // Command line args are one-shot
+    Position     pos;
+    std::string  token, cmd;
+    StateListPtr states(new std::deque<StateInfo>(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<std::string> 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
 }
 
 
-/// UCI::value() converts a Value to a string suitable for use with the UCI
-/// protocol specification:
-///
-/// cp <x>    The score from the engine's point of view in centipawns.
-/// mate <y>  Mate in y moves, not plies. If the engine is getting mated
-///           use negative values for y.
+// 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; }
 
-string UCI::value(Value v) {
+// Converts a Value to a string by adhering to the UCI protocol specification:
+//
+// cp <x>    The score from the engine's point of view in centipawns.
+// mate <y>  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);
+    assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
 
-  stringstream ss;
+    std::stringstream ss;
 
-  if (abs(v) < VALUE_MATE - MAX_PLY)
-      ss << "cp " << v * 100 / PawnValueEg;
-  else
-      ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
+    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();
+    return ss.str();
 }
 
 
-/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.)
+// 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::string UCI::square(Square s) {
-  return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) };
+    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();
 }
 
 
-/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q).
-/// The only special case is castling, where we print in the e1g1 notation in
-/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all
-/// castling moves are always encoded as 'king captures rook'.
+// 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))};
+}
 
-string UCI::move(Move m, bool chess960) {
 
-  Square from = from_sq(m);
-  Square to = to_sq(m);
+// 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_NONE)
+        return "(none)";
 
-  if (m == MOVE_NULL)
-      return "0000";
+    if (m == MOVE_NULL)
+        return "0000";
 
-  if (type_of(m) == CASTLING && !chess960)
-      to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
+    Square from = from_sq(m);
+    Square to   = to_sq(m);
 
-  string move = UCI::square(from) + UCI::square(to);
+    if (type_of(m) == CASTLING && !chess960)
+        to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
 
-  if (type_of(m) == PROMOTION)
-      move += " pnbrqk"[promotion_type(m)];
+    std::string move = UCI::square(from) + UCI::square(to);
 
-  return move;
-}
+    if (type_of(m) == PROMOTION)
+        move += " pnbrqk"[promotion_type(m)];
 
+    return move;
+}
 
-/// 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) {
+// 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) // Junior could send promotion piece in uppercase
-      str[4] = char(tolower(str[4]));
+    if (str.length() == 5)
+        str[4] = char(tolower(str[4]));  // The promotion piece character must be lowercased
 
-  for (const auto& m : MoveList<LEGAL>(pos))
-      if (str == UCI::move(m, pos.is_chess960()))
-          return m;
+    for (const auto& m : MoveList<LEGAL>(pos))
+        if (str == UCI::move(m, pos.is_chess960()))
+            return m;
 
-  return MOVE_NONE;
+    return MOVE_NONE;
 }
+
+}  // namespace Stockfish
index b845889bcc158779dbaad997c0451adcaf60b00e..55fb47c29ef64ef328d8698a96b817f185598264 100644 (file)
--- a/src/uci.h
+++ b/src/uci.h
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 #ifndef UCI_H_INCLUDED
 #define UCI_H_INCLUDED
 
+#include <cstddef>
+#include <iosfwd>
 #include <map>
 #include <string>
 
 #include "types.h"
 
+namespace Stockfish {
+
 class Position;
 
 namespace UCI {
 
+// Normalizes the internal value as reported by evaluate or search
+// to the UCI centipawn result used in output. This value is derived from
+// the win_rate_model() such that Stockfish outputs an advantage of
+// "100 centipawns" for a position if the engine has a 50% probability to win
+// from this position in self-play at fishtest LTC time control.
+const int NormalizeToPawnValue = 328;
+
 class Option;
 
-/// Custom comparator because UCI options should be case insensitive
+// Define a custom comparator, because the UCI options should be case-insensitive
 struct CaseInsensitiveLess {
-  bool operator() (const std::string&, const std::string&) const;
+    bool operator()(const std::string&, const std::string&) const;
 };
 
-/// Our options container is actually a std::map
-typedef std::map<std::string, Option, CaseInsensitiveLess> OptionsMap;
+// The options container is defined as a std::map
+using OptionsMap = std::map<std::string, Option, CaseInsensitiveLess>;
 
-/// Option class implements an option as defined by UCI protocol
+// The Option class implements each option as specified by the UCI protocol
 class Option {
 
-  typedef void (*OnChange)(const Option&);
+    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 double() 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[]);
+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, Value alpha, Value beta);
-Move to_move(const Position& pos, std::string& str);
+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
+}  // namespace UCI
 
 extern UCI::OptionsMap Options;
 
-#endif // #ifndef UCI_H_INCLUDED
+}  // namespace Stockfish
+
+#endif  // #ifndef UCI_H_INCLUDED
index 26fcf30227a0cf209bffe006230a49ea336ce569..2c84084813f1d8ae369054c5b9c9fa535b75d532 100644 (file)
@@ -1,8 +1,6 @@
 /*
   Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-  Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-  Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-  Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+  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
 
 #include <algorithm>
 #include <cassert>
+#include <cctype>
+#include <cstddef>
+#include <iosfwd>
+#include <istream>
+#include <map>
 #include <ostream>
 #include <sstream>
+#include <string>
 
+#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"
+#include "hashprobe.h"
 
 using std::string;
 
-UCI::OptionsMap Options; // Global object
+namespace Stockfish {
 
-namespace UCI {
+UCI::OptionsMap Options;  // Global object
+std::unique_ptr<HashProbeThread> hash_probe_thread;
 
-/// 'On change' actions, triggered by an option's value change
-void on_clear_hash(const Option&) { Search::clear(); }
-void on_hash_size(const Option& o) { TT.resize(o); }
-void on_logger(const Option& o) { start_logger(o); }
-void on_threads(const Option& o) { Threads.set(o); }
-void on_tb_path(const Option& o) { Tablebases::init(o); }
+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_use_NNUE(const Option& ) { Eval::NNUE::init(); }
+static void on_eval_file(const Option& ) { Eval::NNUE::init(); }
+static void on_rpc_server_address(const Option& o) {
+       if (hash_probe_thread) {
+               hash_probe_thread->Shutdown();
+       }
+       std::string addr = o;
+       hash_probe_thread.reset(new HashProbeThread(addr));
+}
 
-/// Our case insensitive less() function as required by UCI protocol
-bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
+// 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 tolower(c1) < tolower(c2); });
 }
 
 
-/// 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) {
 
-  // at most 2^32 clusters.
-  constexpr int MaxHashMB = Is64Bit ? 131072 : 2048;
-
-  o["Debug Log File"]        << Option("", on_logger);
-  o["Contempt"]              << Option(24, -100, 100);
-  o["Analysis Contempt"]     << Option("Both var Off var White var Black var Both", "Both");
-  o["Threads"]               << Option(1, 1, 512, 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(30, 0, 5000);
-  o["Minimum Thinking Time"] << Option(20, 0, 5000);
-  o["Slow Mover"]            << Option(84, 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(1350, 1350, 2850);
-  o["SyzygyPath"]            << Option("<empty>", on_tb_path);
-  o["SyzygyProbeDepth"]      << Option(1, 1, 100);
-  o["Syzygy50MoveRule"]      << Option(true);
-  o["SyzygyProbeLimit"]      << Option(7, 0, 7);
+    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["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("<empty>", 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["RPCServerAddress"] << Option("<empty>", on_rpc_server_address);
 }
 
 
-/// 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.
-
+// 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;
+    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 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(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f)
-{ defaultValue = currentValue = (v ? "true" : "false"); }
+Option::Option(const char* v, OnChange f) :
+    type("string"),
+    min(0),
+    max(0),
+    on_change(f) {
+    defaultValue = currentValue = v;
+}
 
-Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f)
-{}
+Option::Option(bool v, OnChange f) :
+    type("check"),
+    min(0),
+    max(0),
+    on_change(f) {
+    defaultValue = currentValue = (v ? "true" : "false");
+}
 
-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(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(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 double() const {
-  assert(type == "check" || type == "spin");
-  return (type == "spin" ? stof(currentValue) : currentValue == "true");
+Option::operator int() const {
+    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);
 }
 
 
-/// 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) {
 
-  static size_t insert_order = 0;
+    static size_t insert_order = 0;
 
-  *this = o;
-  idx = insert_order++;
+    *this = o;
+    idx   = insert_order++;
 }
 
 
-/// 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.
-
+// 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());
+    assert(!type.empty());
 
-  if (   (type != "button" && 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
index ae6d5c4b905f2e79464a1a36c45ddc72a323a184..637d19f9d63a330b330b4de62d84671a4e59ec03 100755 (executable)
@@ -13,14 +13,14 @@ case $1 in
   --valgrind)
     echo "valgrind testing started"
     prefix=''
-    exeprefix='valgrind --error-exitcode=42'
+    exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full'
     postfix='1>/dev/null'
     threads="1"
   ;;
   --valgrind-thread)
     echo "valgrind-thread testing started"
     prefix=''
-    exeprefix='valgrind --error-exitcode=42'
+    exeprefix='valgrind --fair-sched=try --error-exitcode=42'
     postfix='1>/dev/null'
     threads="2"
   ;;
@@ -39,16 +39,16 @@ case $1 in
     threads="2"
 
 cat << EOF > tsan.supp
-race:TTEntry::move
-race:TTEntry::depth
-race:TTEntry::bound
-race:TTEntry::save
-race:TTEntry::value
-race:TTEntry::eval
-race:TTEntry::is_pv
+race:Stockfish::TTEntry::move
+race:Stockfish::TTEntry::depth
+race:Stockfish::TTEntry::bound
+race:Stockfish::TTEntry::save
+race:Stockfish::TTEntry::value
+race:Stockfish::TTEntry::eval
+race:Stockfish::TTEntry::is_pv
 
-race:TranspositionTable::probe
-race:TranspositionTable::hashfull
+race:Stockfish::TranspositionTable::probe
+race:Stockfish::TranspositionTable::hashfull
 
 EOF
 
@@ -64,13 +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" \
-            "bench 128 $threads 10 default depth"
+            "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" \
+            "bench 128 $threads 3 bench_tmp.epd depth" \
+            "export_net verify.nnue" \
+            "d" \
+            "compiler" \
+            "license" \
+            "uci"
 do
 
    echo "$prefix $exeprefix ./stockfish $args $postfix"
@@ -78,14 +97,20 @@ do
 
 done
 
+# verify the generated net equals the base net
+network=`./stockfish uci | grep 'option name EvalFile type string default' | awk '{print $NF}'`
+echo "Comparing $network to the written verify.nnue"
+diff $network verify.nnue
+
 # more general testing, following an uci protocol exchange
 cat << EOF > game.exp
- set timeout 10
+ set timeout 240
  spawn $exeprefix ./stockfish
 
  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"
@@ -98,9 +123,31 @@ cat << EOF > game.exp
  expect "bestmove"
 
  send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n"
- send "go depth 30\n"
+ 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
 
@@ -121,7 +168,14 @@ cat << EOF > syzygy.exp
  send "uci\n"
  send "setoption name SyzygyPath value ../tests/syzygy/\n"
  expect "info string Found 35 tablebases" {} timeout {exit 1}
- send "bench 128 1 10 default depth\n"
+ 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
 
@@ -140,6 +194,6 @@ do
 
 done
 
-rm -f tsan.supp
+rm -f tsan.supp bench_tmp.epd
 
 echo "instrumented testing OK"
index 9fd847ff3a9c0a6dc72c50e0f4b2c3a92dc33b2a..e16ba4aed91849fe7ce5f70f9cc6c811bc768b86 100755 (executable)
@@ -10,7 +10,7 @@ trap 'error ${LINENO}' ERR
 
 echo "reprosearch testing started"
 
-# repeat two short games, separated by ucinewgame. 
+# repeat two short games, separated by ucinewgame.
 # with go nodes $nodes they should result in exactly
 # the same node count for each iteration.
 cat << EOF > repeat.exp
@@ -43,7 +43,7 @@ cat << EOF > repeat.exp
  expect eof
 EOF
 
-# to increase the likelyhood of finding a non-reproducible case,
+# to increase the likelihood of finding a non-reproducible case,
 # the allowed number of nodes are varied systematically
 for i in `seq 1 20`
 do
index 2e5c183a073cc8094ffb5f06e2526e01999a4c6a..06bd1892e6c90ea73a626224a90f384871085b17 100755 (executable)
@@ -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