- 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/
- name: Remove non src files
run: rm -f *.o .depend *.nnue
+ - name: Download wiki
+ run: |
+ git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki
+ cd ../wiki
+ rm -rf .git
+
- name: Create tar archive.
run: |
cd ..
mkdir stockfish
+ cp -r wiki stockfish/
cp -r src stockfish/
cp stockfish-$OS-$BINARY$EXT stockfish/
cp "Top CPU Contributors.txt" stockfish/
Ben Koshy (BKSpurgeon)
Bill Henry (VoyagerOne)
Bojun Guo (noobpwnftw, Nooby)
+borg323
Boštjan Mejak (PedanticHacker)
braich
Brian Sheppard (SapphireBrand, briansheppard-toast)
Jacques B. (Timshel)
Jan Ondruš (hxim)
Jared Kish (Kurtbusch, kurt22i)
+Jake Senne (w1wwwwww)
Jarrod Torriero (DU-jdto)
Jean Gauthier (OuaisBla)
Jean-Francois Romang (jromang)
jjoshua2
Jonathan Calovski (Mysseno)
Jonathan Buladas Dumale (SFisGOD)
+Jonathan McDermid (jonathanmcdermid)
Joost VandeVondele (vondele)
Jörg Oster (joergoster)
Joseph Ellis (jhellis3)
Matthew Lai (matthewlai)
Matthew Sullivan (Matt14916)
Max A. (Disservin)
+Maxim Masiutin (maximmasiutin)
Maxim Molchanov (Maxim)
Michael An (man)
Michael Byrne (MichaelB7)
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)
Syine Mineta (MinetaS)
--- /dev/null
+# 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
[![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>
## Overview
-[Stockfish][website-link] is a free, powerful UCI chess engine derived from
-Glaurung 2.1. Stockfish is not a complete chess program and requires a UCI-compatible
-graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard,
-Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably.
-Read the documentation for your GUI of choice for information about how to use
+[Stockfish][website-link] is a **free and strong UCI chess engine** derived from
+Glaurung 2.1 that analyzes chess positions and computes the optimal moves.
+
+Stockfish **does not include a graphical user interface** (GUI) that is required
+to display a chessboard and to make it easy to input moves. These GUIs are
+developed independently from Stockfish and are available online. **Read the
+documentation for your GUI** of choice for information about how to use
Stockfish with it.
-The Stockfish engine features two evaluation functions for chess. The efficiently
-updatable neural network (NNUE) based evaluation is the default and by far the strongest.
-The classical evaluation based on handcrafted terms remains available. The strongest
-network is integrated in the binary and downloaded automatically during the build process.
-The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2,
-avx2, neon, or similar).
+See also the Stockfish [documentation][wiki-usage-link] for further usage help.
## Files
* [README.md][readme-link], the file you are currently reading.
- * [Copying.txt][license-link], a text file containing the GNU General Public License
- version 3.
+ * [Copying.txt][license-link], a text file containing the GNU General Public
+ License version 3.
* [AUTHORS][authors-link], a text file with the list of authors for the project.
- * [src][src-link], a subdirectory containing the full source code, including a Makefile
- that can be used to compile Stockfish on Unix-like systems.
-
- * a file with the .nnue extension, storing the neural network for the NNUE evaluation.
- Binary distributions will have this file embedded.
-
-## The UCI protocol and available options
-
-The Universal Chess Interface (UCI) is a standard protocol used to communicate with
-a chess engine, and is the recommended way to do so for typical graphical user interfaces
-(GUI) or chess tools. Stockfish implements the majority of its options as described
-in [the UCI protocol][uci-link].
-
-Developers can see the default values for UCI options available in Stockfish by typing
-`./stockfish uci` in a terminal, but the majority of users will typically see them and
-change them via a chess GUI. This is a list of available UCI options in Stockfish:
-
- * #### Threads
- The number of CPU threads used for searching a position. For best performance, set
- this equal to the number of CPU cores available.
-
- * #### Hash
- The size of the hash table in MB. It is recommended to set Hash after setting Threads.
-
- * #### 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.
-
- * #### Use NNUE
- Toggle between the NNUE and classical evaluation functions. If set to "true",
- the network parameters must be available to load from file (see also EvalFile),
- if they are not embedded in the binary.
-
- * #### EvalFile
- The name of the file of the NNUE evaluation parameters. Depending on the GUI the
- filename might have to include the full path to the folder/directory that contains
- the file. Other locations, such as the directory that contains the binary and the
- working directory, are also searched.
-
- * #### UCI_AnalyseMode
- An option handled by your GUI.
-
- * #### UCI_Chess960
- An option handled by your GUI. If true, Stockfish will play Chess960.
+ * [src][src-link], a subdirectory containing the full source code, including a
+ Makefile that can be used to compile Stockfish on Unix-like systems.
- * #### UCI_ShowWDL
- If enabled, show approximate WDL statistics as part of the engine output.
- These WDL numbers model expected game outcomes for a given evaluation and
- game ply for engine self-play at fishtest LTC conditions (60+0.6s per game).
+ * a file with the .nnue extension, storing the neural network for the NNUE
+ evaluation. Binary distributions will have this file embedded.
- * #### UCI_LimitStrength
- Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level.
+## The UCI protocol
- * #### 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.
+The [Universal Chess Interface][uci-link] (UCI) is a standard text-based protocol
+used to communicate with a chess engine and is the recommended way to do so for
+typical graphical user interfaces (GUI) or chess tools. Stockfish implements the
+majority of its options.
- * #### 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.
+Developers can see the default values for the UCI options available in Stockfish
+by typing `./stockfish uci` in a terminal, but most users should typically use a
+chess GUI to interact with Stockfish.
- * #### 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 ":".
+For more information on UCI or debug commands, see our [documentation][wiki-commands-link].
- Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6`
+## Compiling Stockfish
- It is recommended to store .rtbw files on an SSD. There is no loss in storing
- the .rtbz files on a regular HDD. It is recommended to verify all md5 checksums
- of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will
- lead to engine crashes.
+Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions,
+big-endian machines such as Power PC, and other platforms.
- * #### SyzygyProbeDepth
- Minimum remaining search depth for which a position is probed. Set this option
- to a higher value to probe less aggressively if you experience too much slowdown
- (in terms of nps) due to tablebase probing.
+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.
- * #### Syzygy50MoveRule
- Disable to let fifty-move rule draws detected by Syzygy tablebase probes count
- as wins or losses. This is useful for ICCF correspondence games.
-
- * #### SyzygyProbeLimit
- Limit Syzygy tablebase probing to positions with at most this many pieces left
- (including kings and pawns).
-
- * #### Move Overhead
- Assume a time delay of x ms due to network and GUI overheads. This is useful to
- avoid losses on time in those cases.
-
- * #### Slow Mover
- Lower values will make Stockfish take less time in games, higher values will
- make it think longer.
-
- * #### nodestime
- Tells the engine to use nodes searched instead of wall time to account for
- elapsed time. Useful for engine testing.
-
- * #### Debug Log File
- Write all communication to and from the engine into a text file.
-
-For developers the following non-standard commands might be of interest, mainly useful for debugging:
-
- * #### bench *ttSize threads limit fenFile limitType evalType*
- Performs a standard benchmark using various options. The signature of a version
- (standard node count) is obtained using all defaults. `bench` is currently
- `bench 16 1 13 default depth mixed`.
-
- * #### compiler
- Give information about the compiler and environment used for building a binary.
-
- * #### d
- Display the current position, with ascii art and fen.
-
- * #### eval
- Return the evaluation of the current position.
-
- * #### export_net [filename]
- Exports the currently loaded network to a file.
- If the currently loaded network is the embedded network and the filename
- is not specified then the network is saved to the file matching the name
- of the embedded network, as defined in evaluate.h.
- If the currently loaded network is not the embedded network (some net set
- through the UCI setoption) then the filename parameter is required and the
- network is saved into that file.
-
- * #### flip
- Flips the side to move.
-
-
-## A note on classical evaluation versus NNUE evaluation
-
-Both approaches assign a value to a position that is used in alpha-beta (PVS) search
-to find the best move. The classical evaluation computes this value as a function
-of various chess concepts, handcrafted by experts, tested and tuned using fishtest.
-The NNUE evaluation computes this value with a neural network based on basic
-inputs (e.g. piece positions only). The network is optimized and trained
-on the evaluations of millions of positions at moderate search depth.
-
-The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward.
-It can be evaluated efficiently on CPUs, and exploits the fact that only parts
-of the neural network need to be updated after a typical chess move.
-[The nodchip repository][nodchip-link] provided the first version of the needed tools
-to train and develop the NNUE networks. Today, more advanced training tools are
-available in [the nnue-pytorch repository][pytorch-link], while data generation tools
-are available in [a dedicated branch][tools-link].
-
-On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation
-results in much stronger playing strength, even if the nodes per second computed by
-the engine is somewhat lower (roughly 80% of nps is typical).
-
-Notes:
-
-1) the NNUE evaluation depends on the Stockfish binary and the network parameter file
-(see the EvalFile UCI option). Not every parameter file is compatible with a given
-Stockfish binary, but the default value of the EvalFile UCI option is the name of a
-network that is guaranteed to be compatible with that binary.
-
-2) to use the NNUE evaluation, the additional data file with neural network parameters
-needs to be available. Normally, this file is already embedded in the binary or it can
-be downloaded. The filename for the default (recommended) net can be found as the default
-value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue`
-(for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from
```
-https://tests.stockfishchess.org/api/nn/[filename]
+cd src
+make -j build ARCH=x86-64-modern
```
-replacing `[filename]` as needed.
-
-## What to expect from the Syzygy tablebases?
-
-If the engine is searching a position that is not in the tablebases (e.g.
-a position with 8 pieces), it will access the tablebases during the search.
-If the engine reports a very large score (typically 153.xx), this means
-it has found a winning line into a tablebase position.
-
-If the engine is given a position to search that is in the tablebases, it
-will use the tablebases at the beginning of the search to preselect all
-good moves, i.e. all moves that preserve the win or preserve the draw while
-taking into account the 50-move rule.
-It will then perform a search only on those moves. **The engine will not move
-immediately**, unless there is only a single good move. **The engine likely
-will not report a mate score, even if the position is known to be won.**
-
-It is therefore clear that this behaviour is not identical to what one might
-be used to with Nalimov tablebases. There are technical reasons for this
-difference, the main technical reason being that Nalimov tablebases use the
-DTM metric (distance-to-mate), while the Syzygy tablebases use a variation of the
-DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move
-counter). This special metric is one of the reasons that the Syzygy tablebases are
-more compact than Nalimov tablebases, while still storing all information
-needed for optimal play and in addition being able to take into account
-the 50-move rule.
-
-## Large Pages
-
-Stockfish supports large pages on Linux and Windows. Large pages make
-the hash access more efficient, improving the engine speed, especially
-on large hash sizes. Typical increases are 5..10% in terms of nodes per
-second, but speed increases up to 30% have been measured. The support is
-automatic. Stockfish attempts to use large pages when available and
-will fall back to regular memory allocation when this is not the case.
-
-### Support on Linux
-
-Large page support on Linux is obtained by the Linux kernel
-transparent huge pages functionality. Typically, transparent huge pages
-are already enabled, and no configuration is needed.
-
-### Support on Windows
-
-The use of large pages requires "Lock Pages in Memory" privilege. See
-[Enable the Lock Pages in Memory Option (Windows)][lockpages-link]
-on how to enable this privilege, then run [RAMMap][rammap-link]
-to double-check that large pages are used. We suggest that you reboot
-your computer after you have enabled large pages, because long Windows
-sessions suffer from memory fragmentation, which may prevent Stockfish
-from getting large pages: a fresh session is better in this regard.
-
-## Compiling Stockfish yourself from the sources
-
-Stockfish has support for 32 or 64-bit CPUs, certain hardware
-instructions, big-endian machines such as Power PC, and other platforms.
-
-On Unix-like systems, it should be easy to compile Stockfish
-directly from the source code with the included Makefile in the folder
-`src`. In general it is recommended to run `make help` to see a list of make
-targets with corresponding descriptions.
-```
- cd src
- make help
- make net
- make build ARCH=x86-64-modern
-```
+Detailed compilation instructions for all platforms can be found in our
+[documentation][wiki-compile-link].
-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 Stockfish version
-and which compiler you used to create your executable. This information
-can be found by typing the following command in a console:
-
-```
- ./stockfish compiler
-```
-
-## Understanding the code base and participating in the project
-
-Stockfish's improvement over the last decade has been a great community
-effort. There are a few ways to help contribute to its growth.
+## Contributing
### Donating hardware
-Improving Stockfish requires a massive amount of testing. You can donate
-your hardware resources by installing the [Fishtest Worker][worker-link]
-and view the current tests on [Fishtest][fishtest-link].
+Improving Stockfish requires a massive amount of testing. You can donate your
+hardware resources by installing the [Fishtest Worker][worker-link] and viewing
+the current tests on [Fishtest][fishtest-link].
### Improving the code
-If you want to help improve the code, there are several valuable resources:
-
-* [In this wiki,][programming-link] many techniques used in
+In the [chessprogramming wiki][programming-link], many techniques used in
Stockfish are explained with a lot of background information.
+The [section on Stockfish][programmingsf-link] describes many features
+and techniques used by Stockfish. However, it is generic rather than
+focused on Stockfish's precise implementation.
-* [The section on Stockfish][programmingsf-link]
-describes many features and techniques used by Stockfish. However, it is
-generic rather than being focused on Stockfish's precise implementation.
-Nevertheless, a helpful resource.
-
-* The latest source can always be found on [GitHub][github-link].
-Discussions about Stockfish take place these days mainly in the [FishCooking][fishcooking-link]
-group and on the [Stockfish Discord channel][discord-link].
The engine testing is done on [Fishtest][fishtest-link].
If you want to help improve Stockfish, please read this [guideline][guideline-link]
first, where the basics of Stockfish development are explained.
+Discussions about Stockfish take place these days mainly in the Stockfish
+[Discord server][discord-link]. This is also the best place to ask questions
+about the codebase and how to improve it.
## Terms of use
-Stockfish is free, and distributed under the **GNU General Public License version 3**
-(GPL v3). Essentially, this means you are free to do almost exactly
-what you want with the program, including distributing it among your
-friends, making it available for download from your website, selling
-it (either by itself or as part of some bigger software package), or
-using it as the starting point for a software project of your own.
-
-The only real limitation is that whenever you distribute Stockfish in
-some way, you MUST always include the license and the full source code
-(or a pointer to where the source code can be found) to generate the
-exact binary you are distributing. If you make any changes to the
-source code, these changes must also be made available under the GPL v3.
+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.
-For full details, read the copy of the GPL v3 found in the file named
-[*Copying.txt*][license-link].
+The only real limitation is that whenever you distribute Stockfish in some way,
+you MUST always include the license and the full source code (or a pointer to
+where the source code can be found) to generate the exact binary you are
+distributing. If you make any changes to the source code, these changes must
+also be made available under GPL v3.
[authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS
[build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml
[commits-link]: https://github.com/official-stockfish/Stockfish/commits/master
[discord-link]: https://discord.gg/GWDRS3kU6R
-[fishcooking-link]: https://groups.google.com/g/fishcooking
+[issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml
+[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new
[fishtest-link]: https://tests.stockfishchess.org/tests
-[github-link]: https://github.com/official-stockfish/Stockfish
[guideline-link]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test
[license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt
-[lockpages-link]: https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows
-[nodchip-link]: https://github.com/nodchip/Stockfish
[programming-link]: https://www.chessprogramming.org/Main_Page
[programmingsf-link]: https://www.chessprogramming.org/Stockfish
-[pytorch-link]: https://github.com/glinscott/nnue-pytorch
-[rammap-link]: https://docs.microsoft.com/en-us/sysinternals/downloads/rammap
[readme-link]: https://github.com/official-stockfish/Stockfish/blob/master/README.md
[release-link]: https://github.com/official-stockfish/Stockfish/releases/latest
[src-link]: https://github.com/official-stockfish/Stockfish/tree/master/src
[stockfish128-logo]: https://stockfishchess.org/images/logo/icon_128x128.png
-[tools-link]: https://github.com/official-stockfish/Stockfish/tree/tools
-[uci-link]: https://www.shredderchess.com/download/div/uci.zip
+[uci-link]: https://backscattering.de/chess/uci/
[website-link]: https://stockfishchess.org
-[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview
+[website-blog-link]: https://stockfishchess.org/blog/
+[wiki-link]: https://github.com/official-stockfish/Stockfish/wiki
+[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage
+[wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source
+[wiki-commands-link]: https://github.com/official-stockfish/Stockfish/wiki/Commands
+[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker
[build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github
[commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-# Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+# Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
#
# Stockfish is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
### Section 2. High-level Configuration
### ==========================================================================
#
-# flag --- Comp switch --- Description
+# flag --- Comp switch --- Description
# ----------------------------------------------------------------------------
#
-# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
+# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
# sanitize = none/<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 --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256
-# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
-# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
+# --- ( undefined ) --- enable undefined behavior checks
+# --- ( thread ) --- enable threading error checks
+# --- ( address ) --- enable memory access checks
+# --- ...etc... --- see compiler documentation for supported sanitizers
+# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations
+# arch = (name) --- (-arch) --- Target architecture
+# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system
+# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction
+# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction
+# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction
+# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions
+# mmx = yes/no --- -mmmx --- Use Intel MMX instructions
+# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2
+# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3
+# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1
+# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2
+# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX
+# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
+# vnni256 = yes/no --- -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
x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \
x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \
- armv7 armv7-neon armv8 apple-silicon general-64 general-32 riscv64))
+ armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64))
SUPPORTED_ARCH=true
else
SUPPORTED_ARCH=false
vnni256 = no
vnni512 = no
neon = no
+dotprod = no
arm_version = 0
STRIP = strip
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 ($(COMP),gcc)
comp=gcc
CXX=g++
- CXXFLAGS += -pedantic -Wextra
+ CXXFLAGS += -pedantic -Wextra -Wmissing-declarations
ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
ifeq ($(OS),Android)
CXX=i686-w64-mingw32-c++-posix
endif
endif
- CXXFLAGS += -pedantic -Wextra -Wshadow
+ 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)
CXX=x86_64-w64-mingw32-clang++
endif
- CXXFLAGS += -pedantic -Wextra -Wshadow
+ CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \
+ -Wconditional-uninitialized
ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)
ifeq ($(target_windows),)
LDFLAGS += -static-libstdc++ -pie -lm -latomic
endif
-ifeq ($(comp),icc)
- profile_make = icc-profile-make
- profile_use = icc-profile-use
+ifeq ($(comp),icx)
+ profile_make = icx-profile-make
+ profile_use = icx-profile-use
else ifeq ($(comp),clang)
profile_make = clang-profile-make
profile_use = clang-profile-use
endif
ifeq ($(KERNEL),Darwin)
- ifeq ($(comp),$(filter $(comp),clang icc))
+ ifeq ($(comp),$(filter $(comp),clang icx))
CXXFLAGS += -mdynamic-no-pic
endif
endif
ifeq ($(comp),clang)
- CXXFLAGS += -fexperimental-new-pass-manager
+ clangmajorversion = $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.)
+ ifeq ($(shell expr $(clangmajorversion) \< 16),1)
+ CXXFLAGS += -fexperimental-new-pass-manager
+ endif
endif
endif
ifeq ($(popcnt),yes)
ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64))
CXXFLAGS += -DUSE_POPCNT
- else ifeq ($(comp),icc)
- CXXFLAGS += -msse3 -DUSE_POPCNT
else
CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT
endif
### 3.6 SIMD architectures
ifeq ($(avx2),yes)
CXXFLAGS += -DUSE_AVX2
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavx2 -mbmi
endif
endif
ifeq ($(avxvnni),yes)
CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavxvnni
endif
endif
ifeq ($(avx512),yes)
CXXFLAGS += -DUSE_AVX512
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavx512f -mavx512bw
endif
endif
ifeq ($(vnni256),yes)
CXXFLAGS += -DUSE_VNNI
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256
endif
endif
ifeq ($(vnni512),yes)
CXXFLAGS += -DUSE_VNNI
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
- CXXFLAGS += -mavx512vnni -mavx512dq -mavx512vl
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512
endif
endif
ifeq ($(sse41),yes)
CXXFLAGS += -DUSE_SSE41
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -msse4.1
endif
endif
ifeq ($(ssse3),yes)
CXXFLAGS += -DUSE_SSSE3
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mssse3
endif
endif
ifeq ($(sse2),yes)
CXXFLAGS += -DUSE_SSE2
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -msse2
endif
endif
ifeq ($(mmx),yes)
CXXFLAGS += -DUSE_MMX
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mmmx
endif
endif
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))
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mbmi2
endif
endif
### 3.7.1 Try to include git commit sha for versioning
GIT_SHA = $(shell git rev-parse --short HEAD 2>/dev/null)
ifneq ($(GIT_SHA), )
- CXXFLAGS += -DGIT_SHA=\"$(GIT_SHA)\"
+ CXXFLAGS += -DGIT_SHA=$(GIT_SHA)
endif
### 3.7.2 Try to include git commit date for versioning
GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null)
ifneq ($(GIT_DATE), )
- CXXFLAGS += -DGIT_DATE=\"$(GIT_DATE)\"
+ CXXFLAGS += -DGIT_DATE=$(GIT_DATE)
endif
### 3.8 Link Time Optimization
### needs access to the optimization flags.
ifeq ($(optimize),yes)
ifeq ($(debug), no)
- ifeq ($(comp),clang)
+ ifeq ($(comp),$(filter $(comp),clang icx))
CXXFLAGS += -flto=full
+ ifeq ($(comp),icx)
+ CXXFLAGS += -fwhole-program-vtables
+ endif
ifeq ($(target_windows),yes)
CXXFLAGS += -fuse-ld=lld
endif
@echo ""
@echo "Supported archs:"
@echo ""
- @echo "x86-64-vnni512 > x86 64-bit with vnni support 512bit wide"
- @echo "x86-64-vnni256 > x86 64-bit with vnni support 256bit wide"
+ @echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support"
+ @echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide"
@echo "x86-64-avx512 > x86 64-bit with avx512 support"
- @echo "x86-64-avxvnni > x86 64-bit with avxvnni support"
+ @echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support"
@echo "x86-64-bmi2 > x86 64-bit with bmi2 support"
@echo "x86-64-avx2 > x86 64-bit with avx2 support"
@echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support"
@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 "gcc > Gnu compiler (default)"
@echo "mingw > Gnu compiler with MinGW under Windows"
@echo "clang > LLVM Clang compiler"
- @echo "icc > Intel compiler"
+ @echo "icx > Intel oneAPI DPC++/C++ Compiler"
@echo "ndk > Google NDK to cross-compile for Android"
@echo ""
@echo "Simple examples. If you don't know what to do, you likely want to run one of: "
.PHONY: help build profile-build strip install clean net objclean profileclean \
- config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
- clang-profile-use clang-profile-make FORCE
+ config-sanity \
+ icx-profile-use icx-profile-make \
+ gcc-profile-use gcc-profile-make \
+ clang-profile-use clang-profile-make FORCE
build: net config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
@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)"
@test "$(vnni256)" = "yes" || test "$(vnni256)" = "no"
@test "$(vnni512)" = "yes" || test "$(vnni512)" = "no"
@test "$(neon)" = "yes" || test "$(neon)" = "no"
- @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \
+ @test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \
|| test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang"
$(EXE): $(OBJS)
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
### GRPC
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "benchmark.h"
+
#include <fstream>
#include <iostream>
#include <istream>
--- /dev/null
+/*
+ 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
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
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];
for (unsigned i = 0; i < (1 << 16); ++i)
PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
- for (Square s = SQ_A1; s <= SQ_H8; ++s)
- SquareBB[s] = (1ULL << s);
-
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
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];
inline Bitboard square_bb(Square s) {
assert(is_ok(s));
- return SquareBB[s];
+ return (1ULL << s);
}
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "timeman.h"
#include "uci.h"
#include "incbin/incbin.h"
-
+#include "nnue/evaluate_nnue.h"
// Macro to embed the default efficiently updatable neural network (NNUE) file
// data in the engine binary (using incbin.h, by Dale Weiler).
eval_file = EvalFileDefaultName;
#if defined(DEFAULT_NNUE_DIRECTORY)
- #define stringify2(x) #x
- #define stringify(x) stringify2(x)
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
#else
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
#endif
- for (string directory : dirs)
+ for (const string& directory : dirs)
if (currentEvalFileName != eval_file)
{
if (directory != "<internal>")
{
ifstream stream(directory + eval_file, ios::binary);
- if (load_eval(eval_file, stream))
+ if (NNUE::load_eval(eval_file, stream))
currentEvalFileName = eval_file;
}
(void) gEmbeddedNNUEEnd; // Silence warning on unused variable
istream stream(&buffer);
- if (load_eval(eval_file, stream))
+ if (NNUE::load_eval(eval_file, stream))
currentEvalFileName = eval_file;
}
}
Score scores[TERM_NB][COLOR_NB];
- double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; }
+ static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; }
- void add(int idx, Color c, Score s) {
+ static void add(int idx, Color c, Score s) {
scores[idx][c] = s;
}
- void add(int idx, Score w, Score b = SCORE_ZERO) {
+ static void add(int idx, Score w, Score b = SCORE_ZERO) {
scores[idx][WHITE] = w;
scores[idx][BLACK] = b;
}
- std::ostream& operator<<(std::ostream& os, Score s) {
+ static std::ostream& operator<<(std::ostream& os, Score s) {
os << std::setw(5) << to_cp(mg_value(s)) << " "
<< std::setw(5) << to_cp(eg_value(s));
return os;
}
- std::ostream& operator<<(std::ostream& os, Term t) {
+ static std::ostream& operator<<(std::ostream& os, Term t) {
if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL)
os << " ---- ----" << " | " << " ---- ----";
namespace {
// Threshold for lazy and space evaluation
- constexpr Value LazyThreshold1 = Value(3631);
- constexpr Value LazyThreshold2 = Value(2084);
+ constexpr Value LazyThreshold1 = Value(3622);
+ constexpr Value LazyThreshold2 = Value(1962);
constexpr Value SpaceThreshold = Value(11551);
// KingAttackWeights[PieceType] contains king attack weights by piece type
template<Tracing T> template<Color Us, PieceType Pt>
Score Evaluation<T>::pieces() {
- constexpr Color Them = ~Us;
- constexpr Direction Down = -pawn_push(Us);
- constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
- : Rank5BB | Rank4BB | Rank3BB);
+ constexpr Color Them = ~Us;
+ [[maybe_unused]] constexpr Direction Down = -pawn_push(Us);
+ [[maybe_unused]] constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
+ : Rank5BB | Rank4BB | Rank3BB);
Bitboard b1 = pos.pieces(Us, Pt);
Bitboard b, bb;
Score score = SCORE_ZERO;
int mob = popcount(b & mobilityArea[Us]);
mobility[Us] += MobilityBonus[Pt - 2][mob];
- if (Pt == BISHOP || Pt == KNIGHT)
+ if constexpr (Pt == BISHOP || Pt == KNIGHT)
{
// Bonus if the piece is on an outpost square or can reach one
// Bonus for knights (UncontestedOutpost) if few relevant targets
// We use the much less accurate but faster Classical eval when the NNUE
// option is set to false. Otherwise we use the NNUE eval unless the
- // PSQ advantage is decisive and several pieces remain. (~3 Elo)
- bool useClassical = !useNNUE || (pos.count<ALL_PIECES>() > 7 && abs(psq) > 1760);
+ // PSQ advantage is decisive. (~4 Elo at STC, 1 Elo at LTC)
+ bool useClassical = !useNNUE || abs(psq) > 2048;
if (useClassical)
v = Evaluation<NO_TRACE>(pos).value();
else
{
int nnueComplexity;
- int scale = 1076 + 96 * pos.non_pawn_material() / 5120;
+ int scale = 1001 + 5 * pos.count<PAWN>() + 61 * pos.non_pawn_material() / 4096;
Color stm = pos.side_to_move();
Value optimism = pos.this_thread()->optimism[stm];
Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
// Blend nnue complexity with (semi)classical complexity
- nnueComplexity = ( 412 * nnueComplexity
- + 428 * abs(psq - nnue)
- + (optimism > 0 ? int(optimism) * int(psq - nnue) : 0)
+ nnueComplexity = ( 406 * nnueComplexity
+ + (424 + optimism) * abs(psq - nnue)
) / 1024;
// Return hybrid NNUE complexity to caller
if (complexity)
*complexity = nnueComplexity;
- optimism = optimism * (278 + nnueComplexity) / 256;
- v = (nnue * scale + optimism * (scale - 755)) / 1024;
+ optimism = optimism * (272 + nnueComplexity) / 256;
+ v = (nnue * scale + optimism * (scale - 748)) / 1024;
}
// Damp down the evaluation linearly when shuffling
- v = v * (197 - pos.rule50_count()) / 214;
+ v = v * (200 - pos.rule50_count()) / 214;
// Guarantee evaluation does not hit the tablebase range
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile.
- #define EvalFileDefaultName "nn-335a9b2d8a80.nnue"
+ #define EvalFileDefaultName "nn-dabb1ed23026.nnue"
namespace NNUE {
- std::string trace(Position& pos);
- Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
-
void init();
void verify();
- bool load_eval(std::string name, std::istream& stream);
- bool save_eval(std::ostream& stream);
- bool save_eval(const std::optional<std::string>& filename);
-
} // namespace NNUE
} // namespace Eval
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Square s = pop_lsb(att_copy);
Move m = make_move(s, to);
if (!pos->pseudo_legal(m) || !pos->legal(m)) {
- attackers &= ~SquareBB[s];
+ attackers &= ~square_bb(s);
}
}
}
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/// Material::Entry contains various information about a material configuration.
/// It contains a material imbalance evaluation, a function pointer to a special
-/// endgame evaluation function (which in most cases is NULL, meaning that the
+/// endgame evaluation function (which in most cases is nullptr, meaning that the
/// standard evaluation function will be used), and scale factors.
///
/// The scale factors are used to scale the evaluation score up or down. For
uint8_t factor[COLOR_NB];
};
-typedef HashTable<Entry, 8192> Table;
+using Table = HashTable<Entry, 8192>;
Entry* probe(const Position& pos);
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
// the calls at compile time), try to load them at runtime. To do this we need
// first to define the corresponding function pointers.
extern "C" {
-typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP,
- PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
-typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY);
-typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
-typedef bool(*fun4_t)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
-typedef WORD(*fun5_t)();
+using fun1_t = bool(*)(LOGICAL_PROCESSOR_RELATIONSHIP,
+ PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
+using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY);
+using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
+using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
+using fun5_t = WORD(*)();
+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 <cmath>
+#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
+#include <string_view>
#include <vector>
-#include <cstdlib>
#if defined(__linux__) && !defined(__ANDROID__)
#include <stdlib.h>
namespace {
/// Version number or dev.
-const string version = "dev";
+constexpr string_view version = "dev";
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
stringstream ss;
ss << "Stockfish " << version << setfill('0');
- if (version == "dev")
+ if constexpr (version == "dev")
{
ss << "-";
#ifdef GIT_DATE
- ss << GIT_DATE;
+ ss << stringify(GIT_DATE);
#else
- const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
+ constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
string month, day, year;
stringstream date(__DATE__); // From compiler, format is "Sep 21 2008"
std::string compiler_info() {
- #define stringify2(x) #x
- #define stringify(x) stringify2(x)
#define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
/// Predefined macros hell:
/// Debug functions used mainly to collect run-time statistics
-static std::atomic<int64_t> hits[2], means[2];
+constexpr int MaxDebugSlots = 32;
+
+namespace {
+
+template<size_t N>
+struct DebugInfo {
+ std::atomic<int64_t> data[N] = { 0 };
-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; }
+ 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) {
+
+ ++mean[slot][0];
+ mean[slot][1] += value;
+}
+
+void dbg_stdev_of(int64_t value, int slot) {
+
+ ++stdev[slot][0];
+ stdev[slot][1] += value;
+ stdev[slot][2] += value * value;
+}
+
+void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
+
+ ++correl[slot][0];
+ correl[slot][1] += value1;
+ correl[slot][2] += value1 * value1;
+ correl[slot][3] += value2;
+ correl[slot][4] += value2 * value2;
+ correl[slot][5] += value1 * value2;
+}
void dbg_print() {
- if (hits[0])
- cerr << "Total " << hits[0] << " Hits " << hits[1]
- << " hit rate (%) " << 100 * hits[1] / hits[0] << endl;
+ int64_t n;
+ auto E = [&n](int64_t x) { return double(x) / n; };
+ auto sqr = [](double x) { return x * x; };
+
+ 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;
+ }
- if (means[0])
- cerr << "Total " << means[0] << " Mean "
- << (double)means[1] / means[0] << endl;
+ for (int i = 0; i < MaxDebugSlots; ++i)
+ if ((n = stdev[i][0]))
+ {
+ double r = sqrtl(E(stdev[i][2]) - sqr(E(stdev[i][1])));
+ std::cerr << "Stdev #" << i
+ << ": Total " << n << " Stdev " << r
+ << std::endl;
+ }
+
+ for (int i = 0; i < MaxDebugSlots; ++i)
+ if ((n = correl[i][0]))
+ {
+ double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3]))
+ / ( sqrtl(E(correl[i][2]) - sqr(E(correl[i][1])))
+ * sqrtl(E(correl[i][4]) - sqr(E(correl[i][3]))));
+ std::cerr << "Correl. #" << i
+ << ": Total " << n << " Coefficient " << r
+ << std::endl;
+ }
}
#if defined(POSIXALIGNEDALLOC)
void *mem;
return posix_memalign(&mem, alignment, size) ? nullptr : mem;
-#elif defined(_WIN32)
+#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)
return _mm_malloc(size, alignment);
+#elif defined(_WIN32)
+ return _aligned_malloc(size, alignment);
#else
return std::aligned_alloc(alignment, size);
#endif
#if defined(POSIXALIGNEDALLOC)
free(ptr);
-#elif defined(_WIN32)
+#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)
_mm_free(ptr);
+#elif defined(_WIN32)
+ _aligned_free(ptr);
#else
free(ptr);
#endif
if (!largePageSize)
return nullptr;
+ // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges
+ HMODULE k32 = GetModuleHandle("Advapi32.dll");
+ auto fun6 = (fun6_t)(void(*)())GetProcAddress(k32, "OpenProcessToken");
+ if (!fun6)
+ return nullptr;
+ auto fun7 = (fun7_t)(void(*)())GetProcAddress(k32, "LookupPrivilegeValueA");
+ if (!fun7)
+ return nullptr;
+ auto fun8 = (fun8_t)(void(*)())GetProcAddress(k32, "AdjustTokenPrivileges");
+ if (!fun8)
+ return nullptr;
+
+
// We need SeLockMemoryPrivilege, so try to enable it for the process
- if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
+ // OpenProcessToken()
+ if (!fun6(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
return nullptr;
- if (LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid))
+ // LookupPrivilegeValueA()
+ if (fun7(nullptr, SE_LOCK_MEMORY_NAME, &luid))
{
TOKEN_PRIVILEGES tp { };
TOKEN_PRIVILEGES prevTp { };
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
- if (AdjustTokenPrivileges(
+ // AdjustTokenPrivileges()
+ if (fun8(
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
GetLastError() == ERROR_SUCCESS)
{
// Round up size to full pages and allocate
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
mem = VirtualAlloc(
- NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
+ nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
// Privilege no longer needed, restore previous state
- AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL);
+ // AdjustTokenPrivileges ()
+ fun8(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
}
}
// Fall back to regular, page aligned, allocation if necessary
if (!mem)
- mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+ mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
return mem;
}
/// API and returns the best node id for the thread with index idx. Original
/// code from Texel by Peter Österlund.
-int best_node(size_t idx) {
+static int best_node(size_t idx) {
int threads = 0;
int nodes = 0;
DWORD byteOffset = 0;
// Early exit if the needed API is not available at runtime
- HMODULE k32 = GetModuleHandle("Kernel32.dll");
+ HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx");
if (!fun1)
return -1;
return;
// Early exit if the needed API are not available at runtime
- HMODULE k32 = GetModuleHandle("Kernel32.dll");
+ HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2");
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "types.h"
+#define stringify2(x) #x
+#define stringify(x) stringify2(x)
+
namespace Stockfish {
std::string engine_info(bool to_uci = false);
void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes
void aligned_large_pages_free(void* mem); // nop if mem == nullptr
-void dbg_hit_on(bool b);
-void dbg_hit_on(bool c, bool b);
-void dbg_mean_of(int v);
+void dbg_hit_on(bool cond, int slot = 0);
+void dbg_mean_of(int64_t value, int slot = 0);
+void dbg_stdev_of(int64_t value, int slot = 0);
+void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0);
void dbg_print();
-typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
+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>
static inline const bool IsLittleEndian = (Le.c[0] == 4);
-// RunningAverage : a class to calculate a running average of a series of values.
-// For efficiency, all computations are done with integers.
-class RunningAverage {
- public:
-
- // Reset the running average to rational value p / q
- void set(int64_t p, int64_t q)
- { average = p * PERIOD * RESOLUTION / q; }
-
- // Update average with value v
- void update(int64_t v)
- { average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; }
-
- // Test if average is strictly greater than rational a / b
- bool is_greater(int64_t a, int64_t b) const
- { return b * average > a * (PERIOD * RESOLUTION); }
-
- int64_t value() const
- { return average / (PERIOD * RESOLUTION); }
-
- private :
- static constexpr int64_t PERIOD = 4096;
- static constexpr int64_t RESOLUTION = 1024;
- int64_t average;
-};
-
template <typename T, std::size_t MaxSize>
class ValueList {
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
#if defined(__GNUC__) && defined(IS_64BIT)
- __extension__ typedef unsigned __int128 uint128;
+ __extension__ using uint128 = unsigned __int128;
return ((uint128)a * (uint128)b) >> 64;
#else
uint64_t aL = (uint32_t)a, aH = a >> 32;
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
namespace {
template<GenType Type, Direction D>
- ExtMove* make_promotions(ExtMove* moveList, Square to) {
+ ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
- if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+ if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
- if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
+ if constexpr (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
{
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
// Single and double pawn pushes, no promotions
- if (Type != CAPTURES)
+ if constexpr (Type != CAPTURES)
{
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)
{
// 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.
Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
- if (Type == EVASIONS)
+ if constexpr (Type == EVASIONS)
b3 &= target;
while (b1)
}
// Standard and en passant captures
- if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+ if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
{
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
Bitboard b2 = shift<UpLeft >(pawnsNotOn7) & enemies;
moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
: generate<NON_EVASIONS>(pos, moveList);
while (cur != moveList)
- if ( ((pinned && pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT)
+ if ( ((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT)
&& !pos.legal(*cur))
*cur = (--moveList)->move;
else
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
!(ttm && pos.pseudo_legal(ttm));
- threatenedPieces = 0;
}
/// MovePicker constructor for quiescence search
{
assert(!pos.checkers());
- stage = PROBCUT_TT + !(ttm && pos.capture(ttm)
+ stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm)
&& pos.pseudo_legal(ttm)
&& pos.see_ge(ttm, threshold));
}
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
- [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook;
+ [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, threatenedPieces;
if constexpr (Type == QUIETS)
{
Color us = pos.side_to_move();
+ bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384;
else // Type == EVASIONS
{
- if (pos.capture(m))
+ if (pos.capture_stage(m))
m.value = PieceValue[MG][pos.piece_on(to_sq(m))]
- Value(type_of(pos.moved_piece(m)))
+ (1 << 28);
while (cur < endMoves)
{
- if (T == Best)
+ if constexpr (T == Best)
std::swap(*cur, *std::max_element(cur, endMoves));
if (*cur != ttMove && filter())
case REFUTATION:
if (select<Next>([&](){ return *cur != MOVE_NONE
- && !pos.capture(*cur)
+ && !pos.capture_stage(*cur)
&& pos.pseudo_legal(*cur); }))
return *(cur - 1);
++stage;
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
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;
+ using stats = Stats<T, D, Size, Sizes...>;
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;
+ using entry = StatsEntry<T, D>;
entry* p = reinterpret_cast<entry*>(this);
std::fill(p, p + sizeof(*this) / sizeof(entry), v);
}
/// ordering decisions. It uses 2 tables (one for each color) indexed by
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
/// (~11 elo)
-typedef Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
+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
-typedef Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
+using CounterMoveHistory = Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB>;
/// 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;
+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]
-typedef Stats<int16_t, 29952, PIECE_NB, SQUARE_NB> PieceToHistory;
+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)
-typedef Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHistory;
+using ContinuationHistory = Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>;
/// MovePicker class is used to pick one pseudo-legal move at a time from the
MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
Move next_move(bool skipQuiets = false);
- Bitboard threatenedPieces;
-
private:
template<PickType T, typename Pred> Move select(Pred);
template<GenType> void score();
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
// Code for calculating NNUE evaluation function
+#include <fstream>
+#include <iomanip>
#include <iostream>
#include <set>
#include <sstream>
-#include <iomanip>
-#include <fstream>
+#include <string_view>
#include "../evaluate.h"
#include "../position.h"
-#include "../misc.h"
#include "../uci.h"
#include "../types.h"
} // namespace Detail
// Initialize the evaluation function parameters
- void initialize() {
+ static void initialize() {
Detail::initialize(featureTransformer);
for (std::size_t i = 0; i < LayerStacks; ++i)
}
// Read network header
- bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc)
+ static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc)
{
std::uint32_t version, size;
}
// Write network header
- bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc)
+ static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc)
{
write_little_endian<std::uint32_t>(stream, Version);
write_little_endian<std::uint32_t>(stream, hashValue);
}
// Read network parameters
- bool read_parameters(std::istream& stream) {
+ static bool read_parameters(std::istream& stream) {
std::uint32_t hashValue;
if (!read_header(stream, &hashValue, &netDescription)) return false;
}
// Write network parameters
- bool write_parameters(std::ostream& stream) {
+ static bool write_parameters(std::ostream& stream) {
if (!write_header(stream, HashValue, netDescription)) return false;
if (!Detail::write_parameters(stream, *featureTransformer)) return false;
return (bool)stream;
}
+ void hint_common_parent_position(const Position& pos) {
+ if (Eval::useNNUE)
+ featureTransformer->hint_common_access(pos);
+ }
+
// Evaluation function. Perform differential calculation.
Value evaluate(const Position& pos, bool adjusted, int* complexity) {
// overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = CacheLineSize;
- int delta = 24 - pos.non_pawn_material() / 9560;
+ constexpr int delta = 24;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformedFeaturesUnaligned[
return t;
}
- static const std::string PieceToChar(" PNBRQK pnbrqk");
+ constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer.
}
- // format_cp_aligned_dot() converts a Value into (centi)pawns and writes it in a buffer,
- // always keeping two decimals. The buffer must have capacity for at least 7 chars.
- static void format_cp_aligned_dot(Value v, char* buffer) {
-
- buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
+ // format_cp_aligned_dot() converts a Value into (centi)pawns, always keeping two decimals.
+ static void format_cp_aligned_dot(Value v, std::stringstream &stream) {
+ const double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue;
- double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue;
- sprintf(&buffer[1], "%6.2f", cp);
+ stream << (v < 0 ? '-' : v > 0 ? '+' : ' ')
+ << std::setiosflags(std::ios::fixed)
+ << std::setw(6)
+ << std::setprecision(2)
+ << cp;
}
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)
{
- char buffer[3][8];
- std::memset(buffer, '\0', sizeof(buffer));
-
- format_cp_aligned_dot(t.psqt[bucket], buffer[0]);
- format_cp_aligned_dot(t.positional[bucket], buffer[1]);
- format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]);
-
- ss << "| " << bucket << " "
- << " | " << buffer[0] << " "
- << " | " << buffer[1] << " "
- << " | " << buffer[2] << " "
+ ss << "| " << bucket << " ";
+ ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss); ss << " "
+ << " | "; format_cp_aligned_dot(t.positional[bucket], ss); ss << " "
+ << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); ss << " "
<< " |";
if (bucket == t.correctBucket)
ss << " <-- this bucket is used";
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
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 {
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
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
const __m64 Zeros = _mm_setzero_si64();
const auto inputVector = reinterpret_cast<const __m64*>(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);
sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum));
output[i] = _mm_cvtsi64_si32(sum);
+# elif defined(USE_NEON_DOTPROD)
+ int32x4_t sum = {biases[i]};
+ const auto row = reinterpret_cast<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]);
static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization should not have been chosen.");
#if defined (USE_AVX512)
- static constexpr const IndexType InputSimdWidth = 64;
- static constexpr const IndexType MaxNumOutputRegs = 16;
+ static constexpr IndexType InputSimdWidth = 64;
+ static constexpr IndexType MaxNumOutputRegs = 16;
#elif defined (USE_AVX2)
- static constexpr const IndexType InputSimdWidth = 32;
- static constexpr const IndexType MaxNumOutputRegs = 8;
+ static constexpr IndexType InputSimdWidth = 32;
+ static constexpr IndexType MaxNumOutputRegs = 8;
#elif defined (USE_SSSE3)
- static constexpr const IndexType InputSimdWidth = 16;
- static constexpr const IndexType MaxNumOutputRegs = 8;
+ static constexpr IndexType InputSimdWidth = 16;
+ static constexpr IndexType MaxNumOutputRegs = 8;
+#elif defined (USE_NEON_DOTPROD)
+ static constexpr IndexType InputSimdWidth = 16;
+ static constexpr IndexType MaxNumOutputRegs = 8;
#elif defined (USE_NEON)
- static constexpr const IndexType InputSimdWidth = 8;
- static constexpr const IndexType MaxNumOutputRegs = 8;
+ static constexpr IndexType InputSimdWidth = 8;
+ static constexpr IndexType MaxNumOutputRegs = 8;
#else
// The fallback implementation will not have permuted weights.
// We define these to avoid a lot of ifdefs later.
- static constexpr const IndexType InputSimdWidth = 1;
- static constexpr const IndexType MaxNumOutputRegs = 1;
+ static constexpr IndexType InputSimdWidth = 1;
+ static constexpr IndexType MaxNumOutputRegs = 1;
#endif
// A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs].
// A small block is a region of size [InputSimdWidth, 1]
- static constexpr const IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions);
- static constexpr const IndexType SmallBlockSize = InputSimdWidth;
- static constexpr const IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions;
- static constexpr const IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize;
- static constexpr const IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize;
- static constexpr const IndexType NumBigBlocks = OutputDimensions / NumOutputRegs;
+ static constexpr IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions);
+ static constexpr IndexType SmallBlockSize = InputSimdWidth;
+ static constexpr IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions;
+ static constexpr IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize;
+ static constexpr IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize;
+ static constexpr IndexType NumBigBlocks = OutputDimensions / NumOutputRegs;
static_assert(OutputDimensions % NumOutputRegs == 0);
// Read network parameters
bool read_parameters(std::istream& stream) {
- for (IndexType i = 0; i < OutputDimensions; ++i)
- biases[i] = read_little_endian<BiasType>(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);
// Write network parameters
bool write_parameters(std::ostream& stream) const {
- for (IndexType i = 0; i < OutputDimensions; ++i)
- write_little_endian<BiasType>(stream, biases[i]);
+ 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)]);
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
#define vec_hadd Simd::m128_hadd
#define vec_haddx4 Simd::m128_haddx4
+#elif defined (USE_NEON_DOTPROD)
+ using acc_vec_t = int32x4_t;
+ using bias_vec_t = int32x4_t;
+ using weight_vec_t = int8x16_t;
+ using in_vec_t = int8x16_t;
+ #define vec_zero {0}
+ #define vec_add_dpbusd_32x2 Simd::dotprod_m128_add_dpbusd_epi32x2
+ #define vec_hadd Simd::neon_m128_hadd
+ #define vec_haddx4 Simd::neon_m128_haddx4
#elif defined (USE_NEON)
using acc_vec_t = int32x4_t;
using bias_vec_t = int32x4_t;
static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization should not have been chosen.");
#if defined (USE_SSSE3)
- static constexpr const IndexType OutputSimdWidth = SimdWidth / 4;
- static constexpr const IndexType InputSimdWidth = SimdWidth;
+ static constexpr IndexType OutputSimdWidth = SimdWidth / 4;
+ static constexpr IndexType InputSimdWidth = SimdWidth;
#endif
// Hash value embedded in the evaluation file
// Read network parameters
bool read_parameters(std::istream& stream) {
- for (IndexType i = 0; i < OutputDimensions; ++i)
- biases[i] = read_little_endian<BiasType>(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);
// Write network parameters
bool write_parameters(std::ostream& stream) const {
- for (IndexType i = 0; i < OutputDimensions; ++i)
- write_little_endian<BiasType>(stream, biases[i]);
+ 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)]);
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
- #define vec_add_dpbusd_32x4 Simd::m256_add_dpbusd_epi32x4
#define vec_hadd Simd::m256_hadd
- #define vec_haddx4 Simd::m256_haddx4
#elif defined (USE_SSSE3)
using vec_t = __m128i;
#define vec_setzero _mm_setzero_si128
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
- #define vec_add_dpbusd_32x4 Simd::m128_add_dpbusd_epi32x4
#define vec_hadd Simd::m128_hadd
- #define vec_haddx4 Simd::m128_haddx4
#endif
#if defined (USE_SSSE3)
# undef vec_set_32
# undef vec_add_dpbusd_32
# undef vec_add_dpbusd_32x2
-# undef vec_add_dpbusd_32x4
# undef vec_hadd
-# undef vec_haddx4
#else
// Use old implementation for the other architectures.
affine_transform_non_ssse3<
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
asm(
"vpdpbusd %[b0], %[a0], %[acc]\n\t"
"vpdpbusd %[b1], %[a1], %[acc]\n\t"
- : [acc]"+v"(acc)
+ : [acc]"+&v"(acc)
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
);
# else
__m512i tmp0 = _mm512_maddubs_epi16(a0, b0);
__m512i tmp1 = _mm512_maddubs_epi16(a1, b1);
asm(
- "vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
+ "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t"
+ "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t"
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
- : [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
- : [tmp1]"v"(tmp1), [ones]"v"(_mm512_set1_epi16(1))
+ : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1)
+ : [ones]"v"(_mm512_set1_epi16(1))
);
# else
__m512i product0 = _mm512_maddubs_epi16(a0, b0);
__m512i product1 = _mm512_maddubs_epi16(a1, b1);
- product0 = _mm512_adds_epi16(product0, product1);
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
- acc = _mm512_add_epi32(acc, product0);
+ product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1));
+ acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1));
# endif
# endif
}
asm(
VNNI_PREFIX "vpdpbusd %[b0], %[a0], %[acc]\n\t"
VNNI_PREFIX "vpdpbusd %[b1], %[a1], %[acc]\n\t"
- : [acc]"+v"(acc)
+ : [acc]"+&v"(acc)
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
);
# else
__m256i tmp0 = _mm256_maddubs_epi16(a0, b0);
__m256i tmp1 = _mm256_maddubs_epi16(a1, b1);
asm(
- "vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
+ "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t"
+ "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t"
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
- : [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
- : [tmp1]"v"(tmp1), [ones]"v"(_mm256_set1_epi16(1))
+ : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1)
+ : [ones]"v"(_mm256_set1_epi16(1))
);
# else
__m256i product0 = _mm256_maddubs_epi16(a0, b0);
__m256i product1 = _mm256_maddubs_epi16(a1, b1);
- product0 = _mm256_adds_epi16(product0, product1);
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
- acc = _mm256_add_epi32(acc, product0);
+ product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1));
+ acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1));
# endif
# endif
}
__m128i tmp0 = _mm_maddubs_epi16(a0, b0);
__m128i tmp1 = _mm_maddubs_epi16(a1, b1);
asm(
- "paddsw %[tmp1], %[tmp0]\n\t"
"pmaddwd %[ones], %[tmp0]\n\t"
+ "pmaddwd %[ones], %[tmp1]\n\t"
+ "paddd %[tmp1], %[tmp0]\n\t"
"paddd %[tmp0], %[acc]\n\t"
- : [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
- : [tmp1]"v"(tmp1), [ones]"v"(_mm_set1_epi16(1))
+ : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1)
+ : [ones]"v"(_mm_set1_epi16(1))
);
# else
__m128i product0 = _mm_maddubs_epi16(a0, b0);
__m128i product1 = _mm_maddubs_epi16(a1, b1);
- product0 = _mm_adds_epi16(product0, product1);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
- acc = _mm_add_epi32(acc, product0);
+ product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1));
+ acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1));
# endif
}
#endif
+#if defined (USE_NEON_DOTPROD)
+
+ [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2(
+ int32x4_t& acc,
+ int8x16_t a0, int8x16_t b0,
+ int8x16_t a1, int8x16_t b1) {
+
+ acc = vdotq_s32(acc, a0, b0);
+ acc = vdotq_s32(acc, a1, b1);
+ }
+
+#endif
+
#if defined (USE_NEON)
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
for (IndexType i = Start; i < InputDimensions; ++i) {
output[i] = static_cast<OutputType>(
- // realy should be /127 but we need to make it fast
+ // really should be /127 but we need to make it fast
// needs to be accounted for in the trainer
std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128)));
}
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
// Read network parameters
bool read_parameters(std::istream& stream) {
- if (!fc_0.read_parameters(stream)) return false;
- if (!ac_0.read_parameters(stream)) return false;
- if (!fc_1.read_parameters(stream)) return false;
- if (!ac_1.read_parameters(stream)) return false;
- if (!fc_2.read_parameters(stream)) return false;
- return true;
+ return fc_0.read_parameters(stream)
+ && ac_0.read_parameters(stream)
+ && fc_1.read_parameters(stream)
+ && ac_1.read_parameters(stream)
+ && fc_2.read_parameters(stream);
}
- // Read network parameters
+ // Write network parameters
bool write_parameters(std::ostream& stream) const {
- if (!fc_0.write_parameters(stream)) return false;
- if (!ac_0.write_parameters(stream)) return false;
- if (!fc_1.write_parameters(stream)) return false;
- if (!ac_1.write_parameters(stream)) return false;
- if (!fc_2.write_parameters(stream)) return false;
- return true;
+ return fc_0.write_parameters(stream)
+ && ac_0.write_parameters(stream)
+ && fc_1.write_parameters(stream)
+ && ac_1.write_parameters(stream)
+ && fc_2.write_parameters(stream);
}
std::int32_t propagate(const TransformedFeatureType* transformedFeatures)
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "nnue_architecture.h"
#include <cstring> // std::memset()
+#include <utility> // std::pair
namespace Stockfish::Eval::NNUE {
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
#ifdef USE_AVX512
- typedef __m512i vec_t;
- typedef __m256i psqt_vec_t;
+ using vec_t = __m512i;
+ using psqt_vec_t = __m256i;
#define vec_load(a) _mm512_load_si512(a)
#define vec_store(a,b) _mm512_store_si512(a,b)
#define vec_add_16(a,b) _mm512_add_epi16(a,b)
#define MaxChunkSize 64
#elif USE_AVX2
- typedef __m256i vec_t;
- typedef __m256i psqt_vec_t;
+ using vec_t = __m256i;
+ using psqt_vec_t = __m256i;
#define vec_load(a) _mm256_load_si256(a)
#define vec_store(a,b) _mm256_store_si256(a,b)
#define vec_add_16(a,b) _mm256_add_epi16(a,b)
#define MaxChunkSize 32
#elif USE_SSE2
- typedef __m128i vec_t;
- typedef __m128i psqt_vec_t;
+ using vec_t = __m128i;
+ using psqt_vec_t = __m128i;
#define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_epi16(a,b)
#define MaxChunkSize 16
#elif USE_MMX
- typedef __m64 vec_t;
- typedef __m64 psqt_vec_t;
+ using vec_t = __m64;
+ using psqt_vec_t = __m64;
#define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_pi16(a,b)
#define MaxChunkSize 8
#elif USE_NEON
- typedef int16x8_t vec_t;
- typedef int32x4_t psqt_vec_t;
+ using vec_t = int16x8_t;
+ using psqt_vec_t = int32x4_t;
#define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) vaddq_s16(a,b)
#endif
return psqt;
+ } // end of function transform()
- } // 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>
- void update_accumulator(const Position& pos) const {
-
- // The size must be enough to contain the largest possible update.
- // That might depend on the feature set and generally relies on the
- // feature set's update cost calculation to be correct and never
- // allow updates with more added/removed features than MaxActiveDimensions.
-
- #ifdef VECTOR
- // Gcc-10.2 unnecessarily spills AVX2 registers if this array
- // is defined in the VECTOR code below, once in each branch
- vec_t acc[NumRegs];
- psqt_vec_t psqt[NumPsqtRegs];
- #endif
-
+ [[nodiscard]] std::pair<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;
next = st;
st = st->previous;
}
+ return { st, next };
+ }
- if (st->accumulator.computed[Perspective])
- {
- if (next == nullptr)
- return;
+ // NOTE: The parameter states_to_update is an array of position states, ending with nullptr.
+ // All states must be sequential, that is states_to_update[i] must either be reachable
+ // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr.
+ // computed_st must be reachable by 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);
- // Update incrementally in two steps. First, we update the "next"
- // accumulator. Then, we update the current accumulator (pos.state()).
+ #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
- // Gather all features to be updated.
- const Square ksq = pos.square<KING>(Perspective);
- FeatureSet::IndexList removed[2], added[2];
- FeatureSet::append_changed_indices<Perspective>(
- ksq, next->dirtyPiece, removed[0], added[0]);
- for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous)
- FeatureSet::append_changed_indices<Perspective>(
- ksq, st2->dirtyPiece, removed[1], added[1]);
+ if (states_to_update[0] == nullptr)
+ return;
- // Mark the accumulators as computed.
- next->accumulator.computed[Perspective] = true;
- pos.state()->accumulator.computed[Perspective] = true;
+ // Update incrementally going back through states_to_update.
- // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
- StateInfo *states_to_update[3] =
- { next, next == pos.state() ? nullptr : pos.state(), nullptr };
- #ifdef VECTOR
- for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
- {
- // Load accumulator
- auto accTile = reinterpret_cast<vec_t*>(
- &st->accumulator.accumulation[Perspective][j * TileHeight]);
- for (IndexType k = 0; k < NumRegs; ++k)
- acc[k] = vec_load(&accTile[k]);
+ // Gather all features to be updated.
+ const Square ksq = pos.square<KING>(Perspective);
- 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
- accTile = reinterpret_cast<vec_t*>(
- &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]);
- for (IndexType k = 0; k < NumRegs; ++k)
- vec_store(&accTile[k], acc[k]);
- }
- }
+ // 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];
- for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
- {
- // Load accumulator
- auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
- &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
- for (std::size_t k = 0; k < NumPsqtRegs; ++k)
- psqt[k] = vec_load_psqt(&accTilePsqt[k]);
+ {
+ int i = N-2; // last potential state to update. Skip last element because it must be nullptr.
+ while (states_to_update[i] == nullptr)
+ --i;
- 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
- accTilePsqt = 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(&accTilePsqt[k], psqt[k]);
- }
- }
+ StateInfo *st2 = states_to_update[i];
- #else
- for (IndexType i = 0; states_to_update[i]; ++i)
+ for (; i >= 0; --i)
{
- std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective],
- st->accumulator.accumulation[Perspective],
- HalfDimensions * sizeof(BiasType));
+ states_to_update[i]->accumulator.computed[Perspective] = true;
- for (std::size_t k = 0; k < PSQTBuckets; ++k)
- states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k];
+ 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;
- st = states_to_update[i];
+ // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
+#ifdef VECTOR
+ for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
+ {
+ // Load accumulator
+ auto accTile = reinterpret_cast<vec_t*>(
+ &st->accumulator.accumulation[Perspective][j * TileHeight]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ acc[k] = vec_load(&accTile[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;
-
- for (IndexType j = 0; j < HalfDimensions; ++j)
- st->accumulator.accumulation[Perspective][j] -= weights[offset + j];
-
- for (std::size_t k = 0; k < PSQTBuckets; ++k)
- st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k];
+ const IndexType offset = HalfDimensions * index + j * TileHeight;
+ auto column = reinterpret_cast<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;
-
- for (IndexType j = 0; j < HalfDimensions; ++j)
- st->accumulator.accumulation[Perspective][j] += weights[offset + j];
-
- for (std::size_t k = 0; k < PSQTBuckets; ++k)
- st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
- }
- }
- #endif
- }
- else
- {
- // Refresh the accumulator
- auto& accumulator = pos.state()->accumulator;
- accumulator.computed[Perspective] = true;
- FeatureSet::IndexList active;
- FeatureSet::append_active_indices<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)
+ for (IndexType 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++)
+ // Store accumulator
+ accTile = reinterpret_cast<vec_t*>(
+ &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]);
+ for (IndexType k = 0; k < NumRegs; ++k)
vec_store(&accTile[k], acc[k]);
}
+ }
- for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
- {
- for (std::size_t k = 0; k < NumPsqtRegs; ++k)
- psqt[k] = vec_zero_psqt();
+ for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
+ {
+ // Load accumulator
+ auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
+ &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
+ for (std::size_t k = 0; k < NumPsqtRegs; ++k)
+ psqt[k] = vec_load_psqt(&accTilePsqt[k]);
- for (const auto index : active)
+ 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]);
}
- auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
- &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
+ // Store accumulator
+ accTilePsqt = 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(&accTilePsqt[k], psqt[k]);
}
+ }
- #else
- std::memcpy(accumulator.accumulation[Perspective], biases,
+#else
+ for (IndexType i = 0; states_to_update[i]; ++i)
+ {
+ std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective],
+ st->accumulator.accumulation[Perspective],
HalfDimensions * sizeof(BiasType));
for (std::size_t k = 0; k < PSQTBuckets; ++k)
- accumulator.psqtAccumulation[Perspective][k] = 0;
+ states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k];
- for (const auto index : active)
+ st = states_to_update[i];
+
+ // Difference calculation for the deactivated features
+ for (const auto index : removed[i])
{
const IndexType offset = HalfDimensions * index;
for (IndexType j = 0; j < HalfDimensions; ++j)
- accumulator.accumulation[Perspective][j] += weights[offset + j];
+ st->accumulator.accumulation[Perspective][j] -= weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
- accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
+ st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k];
}
+
+ // Difference calculation for the activated features
+ for (const auto index : added[i])
+ {
+ const IndexType offset = HalfDimensions * index;
+
+ for (IndexType j = 0; j < HalfDimensions; ++j)
+ st->accumulator.accumulation[Perspective][j] += weights[offset + j];
+
+ for (std::size_t k = 0; k < PSQTBuckets; ++k)
+ st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
+ }
+ }
+#endif
+
+ #if defined(USE_MMX)
+ _mm_empty();
+ #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
+
#if defined(USE_MMX)
_mm_empty();
#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];
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
int blockedCount;
};
-typedef HashTable<Entry, 131072> Table;
+using Table = HashTable<Entry, 131072>;
Entry* probe(const Position& pos);
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <cstring> // For std::memset, std::memcmp
#include <iomanip>
#include <sstream>
+#include <string_view>
#include "bitboard.h"
#include "misc.h"
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 };
// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition"
// situations. Description of the algorithm in the following paper:
-// https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
+// http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
// First and second hash functions for indexing the cuckoo tables
inline int H1(Key h) { return h & 0x1fff; }
chess960 = isChess960;
thisThread = th;
- set_state(st);
+ set_state();
return *this;
}
/// Position::set_check_info() sets king attacks to detect if a move gives check
-void Position::set_check_info(StateInfo* si) const {
+void Position::set_check_info() const {
- si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), si->pinners[BLACK]);
- si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK), si->pinners[WHITE]);
+ st->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), st->pinners[BLACK]);
+ st->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK), st->pinners[WHITE]);
Square ksq = square<KING>(~sideToMove);
- si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq);
- si->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
- si->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
- si->checkSquares[ROOK] = attacks_bb<ROOK>(ksq, pieces());
- si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
- si->checkSquares[KING] = 0;
+ st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq);
+ st->checkSquares[KNIGHT] = attacks_bb<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;
}
/// The function is only used when a new position is set up, and to verify
/// the correctness of the StateInfo data when running in debug mode.
-void Position::set_state(StateInfo* si) const {
+void Position::set_state() const {
- si->key = si->materialKey = 0;
- si->pawnKey = Zobrist::noPawns;
- si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO;
- si->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
+ st->key = st->materialKey = 0;
+ st->pawnKey = Zobrist::noPawns;
+ st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO;
+ st->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
- set_check_info(si);
+ set_check_info();
for (Bitboard b = pieces(); b; )
{
Square s = pop_lsb(b);
Piece pc = piece_on(s);
- si->key ^= Zobrist::psq[pc][s];
+ st->key ^= Zobrist::psq[pc][s];
if (type_of(pc) == PAWN)
- si->pawnKey ^= Zobrist::psq[pc][s];
+ st->pawnKey ^= Zobrist::psq[pc][s];
else if (type_of(pc) != KING)
- si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc];
+ st->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc];
}
- if (si->epSquare != SQ_NONE)
- si->key ^= Zobrist::enpassant[file_of(si->epSquare)];
+ if (st->epSquare != SQ_NONE)
+ st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
if (sideToMove == BLACK)
- si->key ^= Zobrist::side;
+ st->key ^= Zobrist::side;
- si->key ^= Zobrist::castling[si->castlingRights];
+ st->key ^= Zobrist::castling[st->castlingRights];
for (Piece pc : Pieces)
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
- si->materialKey ^= Zobrist::psq[pc][cnt];
+ st->materialKey ^= Zobrist::psq[pc][cnt];
}
: MoveList<NON_EVASIONS>(*this).contains(m);
// Is not a promotion, so promotion piece must be empty
- if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE)
- return false;
+ assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE);
// If the 'from' square is not occupied by a piece belonging to the side to
// move, the move is obviously not legal.
// Update board and piece lists
remove_piece(capsq);
- if (type_of(m) == EN_PASSANT)
- board[capsq] = NO_PIECE;
-
// Update material hash key and prefetch access to materialTable
k ^= Zobrist::psq[captured][capsq];
st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]];
sideToMove = ~sideToMove;
// Update king attacks used for fast check detection
- set_check_info(st);
+ set_check_info();
// Calculate the repetition info. It is the ply distance from the previous
// occurrence of the same position, negative in the 3-fold case, or zero
sideToMove = ~sideToMove;
- set_check_info(st);
+ set_check_info();
st->repetition = 0;
/// SEE value of move is greater or equal to the given threshold. We'll use an
/// algorithm similar to alpha-beta pruning with a null window.
-bool Position::see_ge(Move m, Value threshold) const {
+bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const {
assert(is_ok(m));
return true;
assert(color_of(piece_on(from)) == sideToMove);
- Bitboard occupied = pieces() ^ from ^ to;
+ occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic
Color stm = sideToMove;
Bitboard attackers = attackers_to(to, occupied);
Bitboard stmAttackers, bb;
// the bitboard 'attackers' any X-ray attackers behind it.
if ((bb = stmAttackers & pieces(PAWN)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = PawnValueMg - swap) < res)
break;
- occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
}
else if ((bb = stmAttackers & pieces(KNIGHT)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = KnightValueMg - swap) < res)
break;
-
- occupied ^= least_significant_square_bb(bb);
}
else if ((bb = stmAttackers & pieces(BISHOP)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = BishopValueMg - swap) < res)
break;
- occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
}
else if ((bb = stmAttackers & pieces(ROOK)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = RookValueMg - swap) < res)
break;
- occupied ^= least_significant_square_bb(bb);
attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
}
else if ((bb = stmAttackers & pieces(QUEEN)))
{
+ occupied ^= least_significant_square_bb(bb);
if ((swap = QueenValueMg - swap) < res)
break;
- occupied ^= least_significant_square_bb(bb);
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
}
return bool(res);
}
+bool Position::see_ge(Move m, Value threshold) const {
+ Bitboard occupied;
+ return see_ge(m, occupied, threshold);
+}
+
/// Position::is_draw() tests whether the position is drawn by 50-move rule
/// or by repetition. It does not detect stalemates.
if (p1 != p2 && (pieces(p1) & pieces(p2)))
assert(0 && "pos_is_ok: Bitboards");
- StateInfo si = *st;
- ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize);
-
- set_state(&si);
- if (std::memcmp(&si, st, sizeof(StateInfo)))
- assert(0 && "pos_is_ok: State");
for (Piece pc : Pieces)
if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/// 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;
+using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
/// Position class stores information regarding the board representation as
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;
void undo_null_move();
// Static Exchange Evaluation
+ bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const;
bool see_ge(Move m, Value threshold = VALUE_ZERO) const;
// Accessing hash keys
private:
// Initialization helpers (used while setting up a position)
void set_castling_right(Color c, Square rfrom);
- void set_state(StateInfo* si) const;
- void set_check_info(StateInfo* si) const;
+ void set_state() const;
+ void set_check_info() const;
// Other helpers
void move_piece(Square from, Square to);
bool chess960;
};
-extern std::ostream& operator<<(std::ostream& os, const Position& pos);
+std::ostream& operator<<(std::ostream& os, const Position& pos);
inline Color Position::side_to_move() const {
return sideToMove;
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) == EN_PASSANT;
+ return (!empty(to_sq(m)) && type_of(m) != CASTLING)
+ || type_of(m) == EN_PASSANT;
+}
+
+// returns true if a move is generated from the capture stage
+// having also queen promotions covered, i.e. consistency with the capture stage move generation
+// is needed to avoid the generation of duplicate moves.
+inline bool Position::capture_stage(Move m) const {
+ assert(is_ok(m));
+ return capture(m) || promotion_type(m) == QUEEN;
}
inline Piece Position::captured_piece() const {
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
extern Score psq[PIECE_NB][SQUARE_NB];
// Fill psqt array from a set of internally linked parameters
-extern void init();
+void init();
} // namespace Stockfish::PSQT
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include "tt.h"
#include "uci.h"
#include "syzygy/tbprobe.h"
+#include "nnue/evaluate_nnue.h"
namespace Stockfish {
// Futility margin
Value futility_margin(Depth d, bool improving) {
- return Value(165 * (d - improving));
+ return Value(154 * (d - improving));
}
// Reductions lookup table, initialized at startup
Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) {
int r = Reductions[d] * Reductions[mn];
- return (r + 1642 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 916);
+ return (r + 1449 - int(delta) * 937 / int(rootDelta)) / 1024 + (!i && r > 941);
}
constexpr int futility_move_count(bool improving, Depth depth) {
// History and stats update bonus, based on depth
int stat_bonus(Depth d) {
- return std::min((12 * d + 282) * d - 349 , 1480);
+ return std::min(341 * d - 470, 1710);
}
// Add a small random component to draw evaluations to avoid 3-fold blindness
struct Skill {
Skill(int skill_level, int uci_elo) {
if (uci_elo)
- level = std::clamp(std::pow((uci_elo - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0);
+ {
+ double e = double(uci_elo - 1320) / (3190 - 1320);
+ level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0);
+ }
else
level = double(skill_level);
}
void Search::init() {
for (int i = 1; i < MAX_MOVES; ++i)
- Reductions[i] = int((20.26 + std::log(Threads.size()) / 2) * std::log(i));
+ Reductions[i] = int((19.47 + std::log(Threads.size()) / 2) * std::log(i));
}
bestPreviousScore = bestThread->rootMoves[0].score;
bestPreviousAverageScore = bestThread->rootMoves[0].averageScore;
- for (Thread* th : Threads)
- th->previousDepth = bestThread->completedDepth;
-
// Send again PV info if we have a new best thread
if (bestThread != this)
sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl;
int iterIdx = 0;
std::memset(ss-7, 0, 10 * sizeof(Stack));
- for (int i = 7; i > 0; i--)
+ for (int i = 7; i > 0; --i)
+ {
(ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
+ (ss-i)->staticEval = VALUE_NONE;
+ }
for (int i = 0; i <= MAX_PLY + 2; ++i)
(ss+i)->ply = i;
bestValue = delta = alpha = -VALUE_INFINITE;
beta = VALUE_INFINITE;
+ optimism[WHITE] = optimism[BLACK] = VALUE_ZERO;
if (mainThread)
{
+
+ int rootComplexity;
+ Eval::evaluate(rootPos, &rootComplexity);
+
+ mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45);
+
if (mainThread->bestPreviousScore == VALUE_INFINITE)
for (int i = 0; i < 4; ++i)
mainThread->iterValue[i] = VALUE_ZERO;
multiPV = std::min(multiPV, rootMoves.size());
- complexityAverage.set(155, 1);
-
- optimism[us] = optimism[~us] = VALUE_ZERO;
-
int searchAgainCounter = 0;
// Iterative deepening loop until requested to stop or the target depth is reached
pvLast = 0;
if (!Threads.increaseDepth)
- searchAgainCounter++;
+ searchAgainCounter++;
// MultiPV loop. We perform a full root search for each PV line
for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx)
if (rootDepth >= 4)
{
Value prev = rootMoves[pvIdx].averageScore;
- delta = Value(10) + int(prev) * prev / 15620;
+ delta = Value(10) + int(prev) * prev / 16502;
alpha = std::max(prev - delta,-VALUE_INFINITE);
beta = std::min(prev + delta, VALUE_INFINITE);
// Adjust optimism based on root move's previousScore
- int opt = 118 * prev / (std::abs(prev) + 169);
+ int opt = 120 * prev / (std::abs(prev) + 161);
optimism[ us] = Value(opt);
optimism[~us] = -optimism[us];
}
if (!Threads.stop)
completedDepth = rootDepth;
- if (rootMoves[0].pv[0] != lastBestMove) {
- lastBestMove = rootMoves[0].pv[0];
- lastBestMoveDepth = rootDepth;
+ if (rootMoves[0].pv[0] != lastBestMove)
+ {
+ lastBestMove = rootMoves[0].pv[0];
+ lastBestMoveDepth = rootDepth;
}
// Have we found a "mate in x"?
&& !Threads.stop
&& !mainThread->stopOnPonderhit)
{
- double fallingEval = (71 + 12 * (mainThread->bestPreviousAverageScore - bestValue)
- + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 656.7;
+ double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue)
+ + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 619.6;
fallingEval = std::clamp(fallingEval, 0.5, 1.5);
// If the bestMove is stable over several iterations, reduce time accordingly
- timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.37 : 0.65;
- double reduction = (1.4 + mainThread->previousTimeReduction) / (2.15 * timeReduction);
- double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size();
- int complexity = mainThread->complexityAverage.value();
- double complexPosition = std::min(1.0 + (complexity - 261) / 1738.7, 1.5);
+ timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65;
+ double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction);
+ double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size();
- double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition;
+ double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * mainThread->complexity;
// Cap used time in case of a single legal move for a better viewer experience in tournaments
// yielding correct scores and sufficiently fast moves.
Threads.stop = true;
}
else if ( !mainThread->ponder
- && Time.elapsed() > totalTime * 0.53)
+ && Time.elapsed() > totalTime * 0.50)
Threads.increaseDepth = false;
else
Threads.increaseDepth = true;
constexpr bool PvNode = nodeType != NonPV;
constexpr bool rootNode = nodeType == Root;
- const Depth maxNextDepth = rootNode ? depth : depth + 1;
// Check if we have an upcoming move which draws by repetition, or
// if the opponent had an alternative move earlier to this position.
assert(0 <= ss->ply && ss->ply < MAX_PLY);
- (ss+1)->ttPv = false;
(ss+1)->excludedMove = bestMove = MOVE_NONE;
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
(ss+2)->cutoffCnt = 0;
ss->doubleExtensions = (ss-1)->doubleExtensions;
- Square prevSq = to_sq((ss-1)->currentMove);
+ Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE;
+ ss->statScore = 0;
- // Initialize statScore to zero for the grandchildren of the current position.
- // So statScore is shared between all grandchildren and only the first grandchild
- // starts with statScore = 0. Later grandchildren start with the last calculated
- // statScore of the previous grandchild. This influences the reduction rules in
- // LMR which are based on the statScore of parent position.
- if (!rootNode)
- (ss+2)->statScore = 0;
-
- // Step 4. Transposition table lookup. We don't want the score of a partial
- // search to overwrite a previous full search TT value, so we use a different
- // position key in case of an excluded move.
+ // Step 4. Transposition table lookup.
excludedMove = ss->excludedMove;
- posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove);
+ posKey = pos.key();
tte = TT.probe(posKey, ss->ttHit);
ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
: ss->ttHit ? tte->move() : MOVE_NONE;
- ttCapture = ttMove && pos.capture(ttMove);
+ 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
&& ss->ttHit
+ && !excludedMove
&& tte->depth() > depth - (tte->bound() == BOUND_EXACT)
&& ttValue != VALUE_NONE // Possible in case of TT access race
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth));
// Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC)
- if ((ss-1)->moveCount <= 2 && !priorCapture)
+ if (prevSq != SQ_NONE && (ss-1)->moveCount <= 2 && !priorCapture)
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1));
}
// Penalty for a quiet ttMove that fails low (~1 Elo)
}
// Step 5. Tablebases probe
- if (!rootNode && TB::Cardinality)
+ if (!rootNode && !excludedMove && TB::Cardinality)
{
int piecesCount = pos.count<ALL_PIECES>();
complexity = 0;
goto moves_loop;
}
+ 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;
+ complexity = abs(ss->staticEval - pos.psq_eg_stm());
+ }
else if (ss->ttHit)
{
// Never assume anything about values stored in TT
if (eval == VALUE_NONE)
ss->staticEval = eval = evaluate(pos, &complexity);
else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost
+ {
complexity = abs(ss->staticEval - pos.psq_eg_stm());
+ if (PvNode)
+ Eval::NNUE::hint_common_parent_position(pos);
+ }
// ttValue can be used as a better position evaluation (~7 Elo)
if ( ttValue != VALUE_NONE
else
{
ss->staticEval = eval = evaluate(pos, &complexity);
-
// Save static evaluation into transposition table
- if (!excludedMove)
- tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
+ tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
}
- thisThread->complexityAverage.update(complexity);
-
// Use static evaluation difference to improve quiet move ordering (~4 Elo)
if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture)
{
- int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1914, 1914);
+ int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1920, 1920);
thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus;
}
// margin and the improving flag are used in various pruning heuristics.
improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval
: (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval
- : 168;
+ : 156;
improving = improvement > 0;
// Step 7. Razoring (~1 Elo).
// If eval is really low check with qsearch if it can exceed alpha, if it can't,
// return a fail low.
- if (eval < alpha - 369 - 254 * depth * depth)
+ if (eval < alpha - 426 - 256 * depth * depth)
{
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
if (value < alpha)
// Step 8. Futility pruning: child node (~40 Elo).
// The depth condition is important for mate finding.
if ( !ss->ttPv
- && depth < 8
- && eval - futility_margin(depth, improving) - (ss-1)->statScore / 303 >= beta
+ && depth < 9
+ && eval - futility_margin(depth, improving) - (ss-1)->statScore / 280 >= beta
&& eval >= beta
- && eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins
+ && eval < 25128) // larger than VALUE_KNOWN_WIN, but smaller than TB wins
return eval;
// Step 9. Null move search with verification search (~35 Elo)
if ( !PvNode
&& (ss-1)->currentMove != MOVE_NULL
- && (ss-1)->statScore < 17139
+ && (ss-1)->statScore < 18755
&& eval >= beta
&& eval >= ss->staticEval
- && ss->staticEval >= beta - 20 * depth - improvement / 13 + 233 + complexity / 25
+ && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25
&& !excludedMove
&& pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
assert(eval - beta >= 0);
// Null move dynamic reduction based on depth, eval and complexity of position
- Depth R = std::min(int(eval - beta) / 168, 7) + depth / 3 + 4 - (complexity > 861);
+ Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4 - (complexity > 825);
ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
}
}
- probCutBeta = beta + 191 - 54 * improving;
+ probCutBeta = beta + 186 - 54 * improving;
// Step 10. ProbCut (~10 Elo)
- // If we have a good enough capture and a reduced search returns a value
+ // If we have a good enough capture (or queen promotion) and a reduced search returns a value
// much above beta, we can (almost) safely prune the previous move.
if ( !PvNode
&& depth > 4
while ((move = mp.next_move()) != MOVE_NONE)
if (move != excludedMove && pos.legal(move))
{
- assert(pos.capture(move) || promotion_type(move) == QUEEN);
+ assert(pos.capture_stage(move));
ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
return value;
}
}
+
+ Eval::NNUE::hint_common_parent_position(pos);
}
- // Step 11. If the position is not in TT, decrease depth by 3.
+ // Step 11. If the position is not in TT, decrease depth by 2 (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth).
// Use qsearch if depth is equal or below zero (~9 Elo)
if ( PvNode
&& !ttMove)
- depth -= 3;
+ depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
if (depth <= 0)
return qsearch<PV>(pos, ss, alpha, beta);
if ( cutNode
- && depth >= 9
+ && depth >= 7
&& !ttMove)
depth -= 2;
moves_loop: // When in check, search starts here
// Step 12. A small Probcut idea, when we are in check (~4 Elo)
- probCutBeta = beta + 417;
+ probCutBeta = beta + 391;
if ( ss->inCheck
&& !PvNode
&& depth >= 2
&& tte->depth() >= depth - 3
&& ttValue >= probCutBeta
&& abs(ttValue) <= VALUE_KNOWN_WIN
- && abs(beta) <= VALUE_KNOWN_WIN
- )
+ && abs(beta) <= VALUE_KNOWN_WIN)
return probCutBeta;
-
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
nullptr , (ss-4)->continuationHistory,
nullptr , (ss-6)->continuationHistory };
- Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
+ Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
&captureHistory,
(ss+1)->pv = nullptr;
extension = 0;
- capture = pos.capture(move);
+ capture = pos.capture_stage(move);
movedPiece = pos.moved_piece(move);
givesCheck = pos.gives_check(move);
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)
moveCountPruning = moveCount >= futility_move_count(improving, depth);
// Reduced depth of the next LMR search
- int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount, delta, thisThread->rootDelta), 0);
+ int lmrDepth = std::max(newDepth - r, 0);
if ( capture
|| givesCheck)
{
// Futility pruning for captures (~2 Elo)
if ( !givesCheck
- && !PvNode
- && lmrDepth < 7
+ && lmrDepth < 6
&& !ss->inCheck
- && ss->staticEval + 180 + 201 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))]
- + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha)
+ && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))]
+ + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha)
continue;
+ Bitboard occupied;
// SEE based pruning (~11 Elo)
- if (!pos.see_ge(move, Value(-222) * depth))
- continue;
+ if (!pos.see_ge(move, occupied, Value(-206) * depth))
+ {
+ if (depth < 2 - capture)
+ continue;
+ // don't prune move if a heavy enemy piece (KQR) is under attack after the exchanges
+ Bitboard leftEnemies = (pos.pieces(~us, QUEEN, ROOK) | pos.pieces(~us, KING)) & occupied;
+ Bitboard attacks = 0;
+ occupied |= to_sq(move);
+ while (leftEnemies && !attacks)
+ {
+ Square sq = pop_lsb(leftEnemies);
+ attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied;
+ // exclude Queen/Rook(s) which were already threatened before SEE
+ if (attacks && (sq != pos.square<KING>(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))))
+ attacks = 0;
+ }
+ if (!attacks)
+ continue;
+ }
}
else
{
// Continuation history based pruning (~2 Elo)
if ( lmrDepth < 5
- && history < -3875 * (depth - 1))
+ && history < -4405 * (depth - 1))
continue;
history += 2 * thisThread->mainHistory[us][from_to(move)];
+ lmrDepth += history / 7278;
+ lmrDepth = std::max(lmrDepth, -2);
+
// Futility pruning: parent node (~13 Elo)
if ( !ss->inCheck
&& lmrDepth < 13
- && ss->staticEval + 106 + 145 * lmrDepth + history / 52 <= alpha)
+ && ss->staticEval + 103 + 138 * lmrDepth <= alpha)
continue;
+ lmrDepth = std::max(lmrDepth, 0);
+
// Prune moves with negative SEE (~4 Elo)
- if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth)))
+ if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 16 * lmrDepth)))
continue;
}
}
// a reduced search on all the other moves but the ttMove and if the
// result is lower than ttValue minus a margin, then we will extend the ttMove.
if ( !rootNode
- && depth >= 4 - (thisThread->previousDepth > 24) + 2 * (PvNode && tte->is_pv())
+ && depth >= 4 - (thisThread->completedDepth > 21) + 2 * (PvNode && tte->is_pv())
&& move == ttMove
&& !excludedMove // Avoid recursive singular search
/* && ttValue != VALUE_NONE Already implicit in the next condition */
&& (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
{
- Value singularBeta = ttValue - (3 + (ss->ttPv && !PvNode)) * depth;
+ Value singularBeta = ttValue - (3 + 2 * (ss->ttPv && !PvNode)) * depth / 2;
Depth singularDepth = (depth - 1) / 2;
ss->excludedMove = move;
&& ss->doubleExtensions <= 10)
{
extension = 2;
- depth += depth < 12;
+ depth += depth < 13;
}
}
else if (singularBeta >= beta)
return singularBeta;
- // If the eval of ttMove is greater than beta, we reduce it (negative extension)
+ // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo)
else if (ttValue >= beta)
- extension = -2;
+ extension = -2 - !PvNode;
- // If the eval of ttMove is less than alpha and value, we reduce it (negative extension)
- else if (ttValue <= alpha && ttValue <= value)
+ // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo)
+ else if (ttValue <= value)
+ extension = -1;
+
+ // If the eval of ttMove is less than alpha, we reduce it (negative extension) (~1 Elo)
+ else if (ttValue <= alpha)
extension = -1;
}
// Check extensions (~1 Elo)
else if ( givesCheck
- && depth > 9
- && abs(ss->staticEval) > 82)
+ && depth > 10
+ && abs(ss->staticEval) > 88)
extension = 1;
// Quiet ttMove extensions (~1 Elo)
else if ( PvNode
&& move == ttMove
&& move == ss->killers[0]
- && (*contHist[0])[movedPiece][to_sq(move)] >= 5177)
+ && (*contHist[0])[movedPiece][to_sq(move)] >= 5705)
extension = 1;
}
// Step 16. Make the move
pos.do_move(move, st, givesCheck);
- Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta);
-
// Decrease reduction if position is or has been on the PV
// and node is not likely to fail low. (~3 Elo)
if ( ss->ttPv
if (ttCapture)
r++;
- // Decrease reduction for PvNodes based on depth
+ // Decrease reduction for PvNodes based on depth (~2 Elo)
if (PvNode)
- r -= 1 + 11 / (3 + depth);
+ r -= 1 + 12 / (3 + depth);
// Decrease reduction if ttMove has been singularly extended (~1 Elo)
if (singularQuietLMR)
r--;
- // Decrease reduction if we move a threatened piece (~1 Elo)
- if ( depth > 9
- && (mp.threatenedPieces & from_sq(move)))
- r--;
-
- // Increase reduction if next ply has a lot of fail high
+ // Increase reduction if next ply has a lot of fail high (~5 Elo)
if ((ss+1)->cutoffCnt > 3)
r++;
+ // Decrease reduction if move is a killer and we have a good history (~1 Elo)
+ if (move == ss->killers[0]
+ && (*contHist[0])[movedPiece][to_sq(move)] >= 3722)
+ r--;
+
ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)]
+ (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)]
- - 4433;
+ - 4082;
- // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
- r -= ss->statScore / (13000 + 4152 * (depth > 7 && depth < 19));
+ // Decrease/increase reduction for moves with a good/bad history (~25 Elo)
+ r -= ss->statScore / (11079 + 4626 * (depth > 6 && depth < 19));
// Step 17. Late moves reduction / extension (LMR, ~117 Elo)
// We use various heuristics for the sons of a node after the first son has
{
// Adjust full depth search based on LMR results - if result
// was good enough search deeper, if it was bad enough search shallower
- const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d));
- const bool doEvenDeeperSearch = value > alpha + 582 && ss->doubleExtensions <= 5;
+ const bool doDeeperSearch = value > (alpha + 58 + 12 * (newDepth - d));
+ const bool doEvenDeeperSearch = value > alpha + 588 && ss->doubleExtensions <= 5;
const bool doShallowerSearch = value < bestValue + newDepth;
ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch;
int bonus = value > alpha ? stat_bonus(newDepth)
: -stat_bonus(newDepth);
- if (capture)
- bonus /= 6;
-
update_continuation_histories(ss, movedPiece, to_sq(move), bonus);
}
}
// Step 18. Full depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1.
else if (!PvNode || moveCount > 1)
{
- value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode);
+ // Increase reduction for cut nodes and not ttMove (~1 Elo)
+ if (!ttMove && cutNode)
+ r += 2;
+
+ value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode);
}
// For PV nodes only, do a full PV search on the first move or after a fail
(ss+1)->pv = pv;
(ss+1)->pv[0] = MOVE_NONE;
- value = -search<PV>(pos, ss+1, -beta, -alpha,
- std::min(maxNextDepth, newDepth), false);
+ value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false);
+
+ if (moveCount > 1 && newDepth >= depth && !capture)
+ update_continuation_histories(ss, movedPiece, to_sq(move), -stat_bonus(newDepth));
}
// Step 19. Undo move
rm.selDepth = thisThread->selDepth;
rm.scoreLowerbound = rm.scoreUpperbound = false;
- if (value >= beta) {
- rm.scoreLowerbound = true;
- rm.uciScore = beta;
+ if (value >= beta)
+ {
+ rm.scoreLowerbound = true;
+ rm.uciScore = beta;
}
- else if (value <= alpha) {
- rm.scoreUpperbound = true;
- rm.uciScore = alpha;
+ else if (value <= alpha)
+ {
+ rm.scoreUpperbound = true;
+ rm.uciScore = alpha;
}
+
rm.pv.resize(1);
assert((ss+1)->pv);
if (PvNode && value < beta) // Update alpha! Always alpha < beta
{
- alpha = value;
- // Reduce other moves if we have found at least one score improvement
+ // Reduce other moves if we have found at least one score improvement (~1 Elo)
if ( depth > 1
- && depth < 6
- && beta < VALUE_KNOWN_WIN
- && alpha > -VALUE_KNOWN_WIN)
- depth -= 1;
+ && ((improving && complexity > 971) || (value < (5 * alpha + 75 * beta) / 87) || depth < 6)
+ && beta < 12535
+ && value > -12535) {
+ bool extraReduction = depth > 2 && alpha > -12535 && bestValue != -VALUE_INFINITE && (value - bestValue) > (7 * (beta - alpha)) / 8;
+ depth -= 1 + extraReduction;
+ }
assert(depth > 0);
+ alpha = value;
}
else
{
quietsSearched, quietCount, capturesSearched, captureCount, depth);
// Bonus for prior countermove that caused the fail low
- else if ( (depth >= 5 || PvNode || bestValue < alpha - 62 * depth)
- && !priorCapture)
+ else if (!priorCapture && prevSq != SQ_NONE)
{
- //Assign extra bonus if current node is PvNode or cutNode
- //or fail low was really bad
- bool extraBonus = PvNode
- || cutNode;
-
- bool doubleExtraBonus = extraBonus && bestValue < alpha - 85 * depth;
-
- update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus + doubleExtraBonus));
+ int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10);
+ update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus);
}
if (PvNode)
bestValue = std::min(bestValue, maxValue);
// If no good move is found and the previous position was ttPv, then the previous
- // opponent move is probably good and the new position is added to the search tree.
+ // 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);
// qsearch() is the quiescence search function, which is called by the main search
// function with zero depth, or recursively with further decreasing depth per call.
- // (~155 elo)
+ // (~155 Elo)
template <NodeType nodeType>
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
bool pvHit, givesCheck, capture;
int moveCount;
+ // Step 1. Initialize node
if (PvNode)
{
(ss+1)->pv = pv;
ss->inCheck = pos.checkers();
moveCount = 0;
- // Check for an immediate draw or maximum ply reached
+ // Step 2. Check for an immediate draw or maximum ply reached
if ( pos.is_draw(ss->ply)
|| ss->ply >= MAX_PLY)
return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW;
// TT entry depth that we are going to use. Note that in qsearch we use
// only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS.
ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS
- : DEPTH_QS_NO_CHECKS;
- // Transposition table lookup
+ : 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
&& ss->ttHit
&& tte->depth() >= ttDepth
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
return ttValue;
- // Evaluate the position statically
+ // Step 4. Static evaluation of the position
if (ss->inCheck)
{
ss->staticEval = VALUE_NONE;
if (PvNode && bestValue > alpha)
alpha = bestValue;
- futilityBase = bestValue + 153;
+ futilityBase = bestValue + 168;
}
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
// to search the moves. Because the depth is <= 0 here, only captures,
// queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS)
// will be generated.
- Square prevSq = to_sq((ss-1)->currentMove);
+ Square prevSq = (ss-1)->currentMove != MOVE_NULL ? to_sq((ss-1)->currentMove) : SQ_NONE;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
&thisThread->captureHistory,
contHist,
int quietCheckEvasions = 0;
- // Loop through the moves until no moves remain or a beta cutoff occurs
+ // Step 5. Loop through all pseudo-legal moves until no moves remain
+ // or a beta cutoff occurs.
while ((move = mp.next_move()) != MOVE_NONE)
{
assert(is_ok(move));
continue;
givesCheck = pos.gives_check(move);
- capture = pos.capture(move);
+ capture = pos.capture_stage(move);
moveCount++;
+ // Step 6. Pruning.
+ if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
+ {
// Futility pruning and moveCount pruning (~10 Elo)
- if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY
- && !givesCheck
+ if ( !givesCheck
&& to_sq(move) != prevSq
&& futilityBase > -VALUE_KNOWN_WIN
&& type_of(move) != PROMOTION)
}
}
- // Do not search moves with negative SEE values (~5 Elo)
- if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY
- && !pos.see_ge(move))
+ // We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter
+ // and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead.
+ if (quietCheckEvasions > 1)
+ break;
+
+ // Continuation history based pruning (~3 Elo)
+ if ( !capture
+ && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0
+ && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0)
+ continue;
+
+ // Do not search moves with bad enough SEE values (~5 Elo)
+ if (!pos.see_ge(move, Value(-110)))
continue;
+ }
// Speculative prefetch as early as possible
prefetch(TT.first_entry(pos.key_after(move)));
+ // Update the current move
ss->currentMove = move;
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
[capture]
[pos.moved_piece(move)]
[to_sq(move)];
- // Continuation history based pruning (~3 Elo)
- if ( !capture
- && bestValue > VALUE_TB_LOSS_IN_MAX_PLY
- && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0
- && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0)
- continue;
-
- // We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter
- // and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead.
- if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY
- && quietCheckEvasions > 1)
- break;
-
quietCheckEvasions += !capture && ss->inCheck;
- // Make and search the move
+ // Step 7. Make and search the move
pos.do_move(move, st, givesCheck);
value = -qsearch<nodeType>(pos, ss+1, -beta, -alpha, depth - 1);
pos.undo_move(move);
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
- // Check for a new best move
+ // Step 8. Check for a new best move
if (value > bestValue)
{
bestValue = value;
}
}
+ // Step 9. Check for mate
// All legal moves have been searched. A special case: if we're in check
// and no legal moves were found, it is checkmate.
if (ss->inCheck && bestValue == -VALUE_INFINITE)
Thread* thisThread = pos.this_thread();
CapturePieceToHistory& captureHistory = thisThread->captureHistory;
Piece moved_piece = pos.moved_piece(bestMove);
- PieceType captured = type_of(pos.piece_on(to_sq(bestMove)));
+ PieceType captured;
+
int bonus1 = stat_bonus(depth + 1);
- if (!pos.capture(bestMove))
+ if (!pos.capture_stage(bestMove))
{
- int bonus2 = bestValue > beta + 137 ? bonus1 // larger bonus
+ int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus
: stat_bonus(depth); // smaller bonus
// Increase stats for the best move in case it was a quiet move
}
}
else
+ {
// Increase stats for the best move in case it was a capture move
+ captured = type_of(pos.piece_on(to_sq(bestMove)));
captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
+ }
// Extra penalty for a quiet early move that was not a TT move or
// main killer move in previous ply when it gets refuted.
- if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0]))
+ if ( prevSq != SQ_NONE
+ && ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0]))
&& !pos.captured_piece())
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1);
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
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
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <fstream>
#include <iostream>
#include <list>
+#include <mutex>
#include <sstream>
+#include <string_view>
#include <type_traits>
-#include <mutex>
#include "../bitboard.h"
#include "../movegen.h"
inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
-const std::string PieceToChar = " PNBRQK pnbrqk";
+constexpr std::string_view PieceToChar = " PNBRQK pnbrqk";
int MapPawns[SQUARE_NB];
int MapB1H1H7[SQUARE_NB];
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 };
}
}
- // 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;
}
#else
// Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.
- HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
+ HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
if (fd == INVALID_HANDLE_VALUE)
// 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 = typename std::conditional<Type == WDL, WDLScore, int>::type;
static constexpr int Sides = Type == WDL ? 2 : 1;
// A return value false indicates that not all probes were successful.
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
- ProbeState result;
+ ProbeState result = OK;
StateInfo st;
// Obtain 50-move counter for the root position
static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ };
- ProbeState result;
+ ProbeState result = OK;
StateInfo st;
WDLScore wdl;
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
counterMoves.fill(MOVE_NONE);
mainHistory.fill(0);
captureHistory.fill(0);
- previousDepth = 0;
for (bool inCheck : { false, true })
for (StatsType c : { NoCaptures, Captures })
/// Thread::start_searching() wakes up the thread that will start the search
void Thread::start_searching() {
-
- std::lock_guard<std::mutex> lk(mutex);
+ mutex.lock();
searching = true;
+ mutex.unlock(); // Unlock before notifying saves a few CPU-cycles
cv.notify_one(); // Wake up the thread in idle_loop()
}
void ThreadPool::set(size_t requested) {
- if (size() > 0) // destroy any existing thread(s)
+ if (threads.size() > 0) // destroy any existing thread(s)
{
main()->wait_for_search_finished();
- while (size() > 0)
- delete back(), pop_back();
+ while (threads.size() > 0)
+ delete threads.back(), threads.pop_back();
}
if (requested > 0) // create new thread(s)
{
- push_back(new MainThread(0));
+ threads.push_back(new MainThread(0));
- while (size() < requested)
- push_back(new Thread(size()));
+ while (threads.size() < requested)
+ threads.push_back(new Thread(threads.size()));
clear();
// Reallocate the hash with the new threadpool size
void ThreadPool::clear() {
- for (Thread* th : *this)
+ for (Thread* th : threads)
th->clear();
main()->callsCnt = 0;
Tablebases::rank_root_moves(pos, rootMoves);
// After ownership transfer 'states' becomes empty, so if we stop the search
- // and call 'go' again without setting a new position states.get() == NULL.
+ // and call 'go' again without setting a new position states.get() == nullptr.
assert(states.get() || setupStates.get());
if (states.get())
// be deduced from a fen string, so set() clears them and they are set from
// setupStates->back() later. The rootState is per thread, earlier states are shared
// since they are read-only.
- for (Thread* th : *this)
+ for (Thread* th : threads)
{
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
th->rootDepth = th->completedDepth = 0;
Thread* ThreadPool::get_best_thread() const {
- Thread* bestThread = front();
+ Thread* bestThread = threads.front();
std::map<Move, int64_t> votes;
Value minScore = VALUE_NONE;
// Find minimum score of all threads
- for (Thread* th: *this)
+ for (Thread* th: threads)
minScore = std::min(minScore, th->rootMoves[0].score);
// Vote according to score and depth, and select the best thread
return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
};
- for (Thread* th : *this)
+ for (Thread* th : threads)
votes[th->rootMoves[0].pv[0]] += thread_value(th);
- for (Thread* th : *this)
+ for (Thread* th : threads)
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
{
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
void ThreadPool::start_searching() {
- for (Thread* th : *this)
- if (th != front())
+ for (Thread* th : threads)
+ if (th != threads.front())
th->start_searching();
}
void ThreadPool::wait_for_search_finished() const {
- for (Thread* th : *this)
- if (th != front())
+ for (Thread* th : threads)
+ if (th != threads.front())
th->wait_for_search_finished();
}
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Pawns::Table pawnsTable;
Material::Table materialTable;
size_t pvIdx, pvLast;
- RunningAverage complexityAverage;
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
int selDepth, nmpMinPly;
Color nmpColor;
Position rootPos;
StateInfo rootState;
Search::RootMoves rootMoves;
- Depth rootDepth, completedDepth, previousDepth;
+ Depth rootDepth, completedDepth;
Value rootDelta;
CounterMoveHistory counterMoves;
ButterflyHistory mainHistory;
void search() override;
void check_time();
+ double complexity;
double previousTimeReduction;
Value bestPreviousScore;
Value bestPreviousAverageScore;
/// parking and, most importantly, launching a thread. All the access to threads
/// is done through this class.
-struct ThreadPool : public std::vector<Thread*> {
+struct ThreadPool {
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
void clear();
void set(size_t);
- MainThread* main() const { return static_cast<MainThread*>(front()); }
+ 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;
std::atomic_bool stop, increaseDepth;
+ auto cbegin() const noexcept { return threads.cbegin(); }
+ auto begin() noexcept { return threads.begin(); }
+ auto end() noexcept { return threads.end(); }
+ auto cend() const noexcept { return threads.cend(); }
+ auto size() const noexcept { return threads.size(); }
+ auto empty() const noexcept { return threads.empty(); }
+
private:
StateListPtr setupStates;
+ std::vector<Thread*> threads;
uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
uint64_t sum = 0;
- for (Thread* th : *this)
+ for (Thread* th : threads)
sum += (th->*member).load(std::memory_order_relaxed);
return sum;
}
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
P* p = reinterpret_cast<P*>(ptr);
(p->first->*(p->second))(); // Call member function pointer
delete p;
- return NULL;
+ return nullptr;
}
class NativeThread {
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
}
- void join() { pthread_join(thread, NULL); }
+ void join() { pthread_join(thread, nullptr); }
};
} // namespace Stockfish
namespace Stockfish {
-typedef std::thread NativeThread;
+using NativeThread = std::thread;
} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
namespace Stockfish {
-typedef std::pair<int, int> Range; // Option's min-max values
-typedef Range (RangeFun) (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) {
class Tune {
- typedef void (PostUpdate) (); // Post-update function
+ using PostUpdate = void (); // Post-update function
Tune() { read_results(); }
Tune(const Tune&) = delete;
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
constexpr bool Is64Bit = false;
#endif
-typedef uint64_t Key;
-typedef uint64_t Bitboard;
+using Key = uint64_t;
+using Bitboard = uint64_t;
constexpr int MAX_MOVES = 256;
constexpr int MAX_PLY = 246;
VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO }
};
-typedef int Depth;
+using Depth = int;
enum : int {
DEPTH_QS_CHECKS = 0,
return Color(pc >> 3);
}
+constexpr bool is_ok(Move m) {
+ return m != MOVE_NONE && m != MOVE_NULL;
+}
+
constexpr bool is_ok(Square s) {
return s >= SQ_A1 && s <= SQ_H8;
}
}
constexpr Square from_sq(Move m) {
+ assert(is_ok(m));
return Square((m >> 6) & 0x3F);
}
constexpr Square to_sq(Move m) {
+ assert(is_ok(m));
return Square(m & 0x3F);
}
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;
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <sstream>
#include <string>
+#include "benchmark.h"
#include "evaluate.h"
#include "movegen.h"
#include "position.h"
#include "tt.h"
#include "uci.h"
#include "syzygy/tbprobe.h"
+#include "nnue/evaluate_nnue.h"
using namespace std;
namespace Stockfish {
-extern vector<string> setup_bench(const Position&, istream&);
-
namespace {
// FEN string for the initial position in standard chess
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; });
+ num = count_if(list.begin(), list.end(), [](const string& s) { return s.find("go ") == 0 || s.find("eval") == 0; });
TimePoint elapsed = now();
// The coefficients of a third-order polynomial fit is based on the fishtest data
// for two parameters that need to transform eval to the argument of a logistic
// function.
- constexpr double as[] = { -0.58270499, 2.68512549, 15.24638015, 344.49745382};
- constexpr double bs[] = { -2.65734562, 15.96509799, -20.69040836, 73.61029937 };
+ constexpr double as[] = { 0.33677609, -4.30175627, 33.08810557, 365.60223431};
+ constexpr double bs[] = { -2.50471102, 14.23235405, -14.33066859, 71.42705250 };
// Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64
static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3]));
stringstream ss;
- if (abs(v) < VALUE_MATE_IN_MAX_PLY)
+ if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
ss << "cp " << v * 100 / NormalizeToPawnValue;
+ else if (abs(v) < VALUE_MATE_IN_MAX_PLY)
+ {
+ const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply
+ ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply);
+ }
else
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
string UCI::move(Move m, bool chess960) {
- Square from = from_sq(m);
- Square to = to_sq(m);
-
if (m == MOVE_NONE)
return "(none)";
if (m == MOVE_NULL)
return "0000";
+ Square from = from_sq(m);
+ Square to = to_sq(m);
+
if (type_of(m) == CASTLING && !chess960)
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
// the win_rate_model() such that Stockfish outputs an advantage of
// "100 centipawns" for a position if the engine has a 50% probability to win
// from this position in selfplay at fishtest LTC time control.
-const int NormalizeToPawnValue = 361;
+const int NormalizeToPawnValue = 394;
class Option;
};
/// The options container is defined as a std::map
-typedef std::map<std::string, Option, CaseInsensitiveLess> OptionsMap;
+using OptionsMap = std::map<std::string, Option, CaseInsensitiveLess>;
/// 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);
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
+ Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
namespace UCI {
/// 'On change' actions, triggered by an option's value change
-void on_clear_hash(const Option&) { Search::clear(); }
-void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
-void on_logger(const Option& o) { start_logger(o); }
-void on_threads(const Option& o) { Threads.set(size_t(o)); }
-void on_tb_path(const Option& o) { Tablebases::init(o); }
-void on_use_NNUE(const Option& ) { Eval::NNUE::init(); }
-void on_eval_file(const Option& ) { Eval::NNUE::init(); }
-void on_rpc_server_address(const Option& o) {
+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();
}
o["UCI_Chess960"] << Option(false);
o["UCI_AnalyseMode"] << Option(false);
o["UCI_LimitStrength"] << Option(false);
- o["UCI_Elo"] << Option(1350, 1350, 2850);
+ o["UCI_Elo"] << Option(1320, 1320, 3190);
o["UCI_ShowWDL"] << Option(false);
o["SyzygyPath"] << Option("<empty>", on_tb_path);
o["SyzygyProbeDepth"] << Option(1, 1, 100);