diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8981efca --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Files from build +**/*.o +**/*.s +src/.depend + +# Built binary +src/stockfish* +src/-lstdc++.res + +# Neural network for the NNUE evaluation +**/*.nnue + diff --git a/AUTHORS b/AUTHORS index b31a36e9..c12b98a0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,4 @@ -# List of authors for Stockfish, as of August 4, 2020 +# List of authors for Stockfish, as of March 31, 2021 # Founders of the Stockfish project and fishtest infrastructure Tord Romstad (romstad) @@ -24,6 +24,7 @@ Ali AlZhrani (Cooffe) Andrew Grant (AndyGrant) Andrey Neporada (nepal) Andy Duplain +Antoine Champion (antoinechampion) Aram Tumanian (atumanian) Arjun Temurnikar Auguste Pop @@ -33,6 +34,7 @@ Bill Henry (VoyagerOne) Bojun Guo (noobpwnftw, Nooby) braich Brian Sheppard (SapphireBrand, briansheppard-toast) +Bruno de Melo Costa (BM123499) Bryan Cross (crossbr) candirufish Chess13234 @@ -45,6 +47,7 @@ Dariusz Orzechowski (dorzechowski) David Zar Daylen Yang (daylen) Deshawn Mohan-Smith (GoldenRare) +Dieter Dobbelaere (ddobbelaere) DiscanX Dominik Schlösser (domschl) double-beep @@ -98,6 +101,7 @@ Ken Takusagawa kinderchocolate Kiran Panditrao (Krgp) Kojirion +Krystian Kuzniarek (kuzkry) Leonardo Ljubičić (ICCF World Champion) Leonid Pechenik (lp--) Linus Arver (listx) @@ -110,6 +114,7 @@ Maciej Żenczykowski (zenczykowski) Malcolm Campbell (xoto10) Mark Tenzer (31m059) marotear +Matt Ginsberg (mattginsberg) Matthew Lai (matthewlai) Matthew Sullivan (Matt14916) Maxim Molchanov (Maxim) @@ -163,6 +168,7 @@ Sergio Vieri (sergiovieri) sf-x Shane Booth (shane31) Shawn Varghese (xXH4CKST3RXx) +Siad Daboul (Topologist) Stefan Geschwentner (locutus2) Stefano Cardanobile (Stefano80) Steinar Gunderson (sesse) diff --git a/README.md b/README.md index fe6ce27c..10d43595 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,32 @@ -

- -

- -

Stockfish NNUE

- ## Overview +[![Build Status](https://travis-ci.org/official-stockfish/Stockfish.svg?branch=master)](https://travis-ci.org/official-stockfish/Stockfish) +[![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master) + +[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine +derived from Glaurung 2.1. Stockfish is not a complete chess program and requires a +UCI-compatible graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid, +Cute Chess, eboard, Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order +to be used comfortably. Read the documentation for your GUI of choice for information +about how to use Stockfish with it. + +The Stockfish engine features two evaluation functions for chess, the classical +evaluation based on handcrafted terms, and the NNUE evaluation based on efficiently +updatable neural networks. The classical evaluation runs efficiently on almost all +CPU architectures, while the NNUE evaluation benefits from the vector +intrinsics available on most CPUs (sse2, avx2, neon, or similar). + + +## Files + +This distribution of Stockfish consists of the following files: + + * Readme.md, the file you are currently reading. + + * Copying.txt, a text file containing the GNU General Public License version 3. + + * AUTHORS, a text file with the list of authors for the project + Stockfish NNUE is a port of a shogi neural network named NNUE (efficiently updateable neural network backwards) to Stockfish 11. To learn more about the Stockfish chess engine, look [here](stockfish.md) for an overview and [here](https://github.com/official-stockfish/Stockfish) for the official repository. ======= @@ -33,8 +54,6 @@ armv7`, `armv7-neon`, `armv8`, `apple-silicon`, `general-64`, `general-32`. Additional options: -- `blas=[yes/no]` - whether to use an external BLAS library. Default is `no`. Using an external BLAS library may have a significantly improve learning performance and by default expects openBLAS to be installed. - ### Building Instructions for Mac 1. Ensure that you have OpenBlas Installed @@ -78,49 +97,8 @@ This will create a file named "generated_kifu.binpack" in the same folder as the You will also need validation data that is used for loss calculation and accuracy computation. Validation data is generated in the same way as training data, but generally at most 1 million positions should be used as there's no need for more and it would just slow the learning process down. It may also be better to slightly increase the depth for validation data. After generation you can rename the validation data file to "val.binpack" and drop it in a folder named "validationdata" in the same directory to make it easier. -More information about gensfen and available options can be found in the [docs](docs/gensfen.md) - -### Training a network - -#### Training a Completely New Network - -Whether a new network is created or not is controlled by the UCI option `SkipLoadingEval`. If set to true then a new network will be created, which allows learning from scratch. If left at its default (false) then a network will be loaded and trained further. The second scenario is described in the reinforcement learning paragraph. - -A simple command chain to start with training could look like this: - -``` -uci -setoption name EnableTranspositionTable value false -setoption name PruneAtShallowDepth value false -setoption name SkipLoadingEval value true -setoption name Use NNUE value pure -setoption name Threads value x -isready -learn targetdir trainingdata epochs 10000 batchsize 1000000 use_draw_in_training 1 use_draw_in_validation 1 lr 1 lambda 1 eval_limit 32000 nn_batch_size 1000 newbob_decay 0.5 eval_save_interval 250000000 loss_output_interval 1000000 validation_set_file_name validationdata\val.binpack -``` - -This will utilize training data files in the "trainingdata" directory and validation data from file "validationdata\val.bin". Produced nets are saved in the "evalsave" folder. - -More information about learn and available parameters can be found in the [docs](docs/learn.md) - -#### Reinforcement Learning - -If you would like to do some reinforcement learning on your original network, you must first generate training data with the setting `Use NNUE` set to `pure` and using the previous network (either name it "nn.bin" and put into alongside the binary or provide the `EvalFile` UCI option). Use the commands specified above. You should aim to generate less positions than the first run, around 1/10 of the number of positions generated in the first run. The depth should be higher as well. You should also do the same for validation data, with the depth being higher than the last run. - -After you have generated the training data, you must move it into your training data folder and move the older data so that the binary does not train on the same data again. Do the same for the validation data. Make sure the "evalsave" folder is empty. Then, using the same binary, type in the training commands shown above. Do __NOT__ set `SkipLoadingEval` to true, it must be false or you will get a completely new network, instead of a network trained with reinforcement learning. You should also set `eval_save_interval` to a number that is lower than the amount of positions in your training data, perhaps also 1/10 of the original value. - -After training is finished, your new net should be located in the "final" folder under the "evalsave" directory. You should test this new network against the older network to see if there are any improvements. Don't rely on the automatic rejection for network quality, sometimes even rejected nets can be better than the previous ones. - -## Using Your Trained Net - -If you want to use your generated net, copy the net located in the "final" folder under the "evalsave" directory and move it into a new folder named "eval" under the directory with the binaries. You can then use the halfkp_256x2 binaries pertaining to your CPU with a standard chess GUI, such as Cutechess. Refer to the [releases page](https://abrok.eu/stockfish) to find out which binary is best for your CPU. - -If the engine does not load any net file, or shows "Error! *** not found or wrong format", please try to specify the net with the full file path with the `EvalFile` UCI option by typing the command `setoption name EvalFile value path` where path is the full file path. The `Use NNUE` UCI option must be set either to `true` or `pure` with the command `setoption name Use NNUE value true/pure`. - ## Training data formats. -Currently there are 3 training data formats. Two of them are supported directly. - - `.bin` - the original training data format. Uses 40 bytes per entry. Is supported directly by the `gensfen` and `learn` commands. - `.plain` - a human readable training data format. This one is not supported directly by the `gensfen` and `learn` commands. It should not be used for data exchange because it's less compact than other formats. It is mostly useful for inspection of the data. - `.binpack` - a compact binary training data format that exploits positions chains to further reduce size. It uses on average between 2 to 3 bytes per entry when generating data with `gensfen`. It is supported directly by `gensfen` and `learn` commands. It is currently the default for the `gensfen` command. A more in depth description can be found [here](docs/binpack.md) @@ -129,14 +107,4 @@ Currently there are 3 training data formats. Two of them are supported directly. There is a builting converted that support all 3 formats described above. Any of them can be converted to any other. For more information and usage guide see [here](docs/convert.md). -## Resources - -- [Training NNUE for SF](https://docs.google.com/document/d/1os5GH8GGJbV0nKAfXD-qySBclFzKKtXKHbAnA-un8tA/edit) google document with important information and coding priorities -- [Gensfen data (vondele)](https://drive.google.com/drive/folders/1mftuzYdl9o6tBaceR3d_VBQIrgKJsFpl) over 2b fens available -- [Stockfish NNUE Wiki](https://www.qhapaq.org/shogi/shogiwiki/stockfish-nnue/) -- [Training instructions](https://twitter.com/mktakizawa/status/1273042640280252416) from the creator of the Elmo shogi engine -- [Original Talkchess thread](http://talkchess.com/forum3/viewtopic.php?t=74059) discussing Stockfish NNUE -- [Guide to Stockfish NNUE](http://yaneuraou.yaneu.com/2020/06/19/stockfish-nnue-the-complete-guide/) -- [Unofficial Stockfish Discord](https://discord.gg/nv8gDtt) - A more updated list can be found in the #sf-nnue-resources channel in the Discord. diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 482e9000..f5347ea1 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,173 +1,189 @@ -Contributors with >10,000 CPU hours as of Sept 2, 2020 +Contributors to Fishtest with >10,000 CPU hours, as of Feb 15, 2021. Thank you! -Username CPU Hours Games played --------------------------------------------------- -noobpwnftw 19352969 1231459677 -mlang 957168 61657446 -dew 949885 56893432 -mibere 703817 46865007 -crunchy 427035 27344275 -cw 416006 27521077 -JojoM 415904 24479564 -fastgm 404873 23953472 -CSU_Dynasty 335774 22850550 -tvijlbrief 335199 21871270 -Fisherman 325053 21786603 -gvreuls 311480 20751516 -ctoks 275877 18710423 -velislav 241267 15596372 -glinscott 217799 13780820 -nordlandia 211692 13484886 -bcross 206213 14934233 -bking_US 198894 11876016 -leszek 189170 11446821 -mgrabiak 183896 11778092 -drabel 181408 12489478 -TueRens 181349 12192000 -Thanar 179852 12365359 -vdv 175171 9881246 -robal 166948 10702862 -spams 157128 10319326 -marrco 149947 9376421 -sqrt2 147963 9724586 -vdbergh 137041 8926915 -CoffeeOne 136294 5004100 -malala 136182 8002293 -mhoram 128934 8177193 -davar 122092 7960001 -dsmith 122059 7570238 -xoto 119696 8222144 -grandphish2 116481 7582197 -Data 113305 8220352 -BrunoBanani 112960 7436849 -ElbertoOne 99028 7023771 -MaZePallas 98571 6362619 -brabos 92118 6186135 -psk 89957 5984901 -sunu 88463 6007033 -sterni1971 86948 5613788 -Vizvezdenec 83752 5343724 -BRAVONE 81239 5054681 -nssy 76497 5259388 -teddybaer 75125 5407666 -Pking_cda 73776 5293873 -jromang 70695 4940891 -solarlight 70517 5028306 -dv8silencer 70287 3883992 -Bobo1239 68515 4652287 -racerschmacer 67468 4935996 -manap 66273 4121774 -tinker 63458 4213726 -linrock 59082 4516053 -robnjr 57262 4053117 -Freja 56938 3733019 -ttruscott 56005 3679485 -renouve 53811 3501516 -cuistot 52532 3014920 -finfish 51360 3370515 -eva42 51272 3599691 -rkl 50759 3840947 -rap 49985 3219146 -pb00067 49727 3298270 -ronaldjerum 47654 3240695 -bigpen0r 47278 3291647 -biffhero 46564 3111352 -VoyagerOne 45386 3445881 -speedycpu 43842 3003273 -jbwiebe 43305 2805433 -Antihistamine 41788 2761312 -mhunt 41735 2691355 -eastorwest 40387 2812173 -homyur 39893 2850481 -gri 39871 2515779 -oryx 38228 2941656 -0x3C33 37773 2529097 -SC 37290 2731014 -csnodgrass 36207 2688994 -jmdana 36108 2205261 -strelock 34716 2074055 -Garf 33800 2747562 -EthanOConnor 33370 2090311 -slakovv 32915 2021889 -Spprtr 32591 2139601 -Prcuvu 30377 2170122 -anst 30301 2190091 -jkiiski 30136 1904470 -hyperbolic.tom 29840 2017394 -Pyafue 29650 1902349 -OuaisBla 27629 1578000 -chriswk 26902 1868317 -achambord 26582 1767323 -Patrick_G 26276 1801617 -yorkman 26193 1992080 -SFTUser 25182 1675689 -nabildanial 24942 1519409 -Sharaf_DG 24765 1786697 -ncfish1 24411 1520927 -agg177 23890 1395014 -JanErik 23408 1703875 -Isidor 23388 1680691 -Norabor 22976 1587862 -cisco2015 22880 1759669 -Zirie 22542 1472937 -team-oh 22272 1636708 -MazeOfGalious 21978 1629593 -sg4032 21945 1643065 -ianh2105 21725 1632562 -xor12 21628 1680365 -dex 21612 1467203 -nesoneg 21494 1463031 -horst.prack 20878 1465656 -0xB00B1ES 20590 1208666 -j3corre 20405 941444 -Adrian.Schmidt123 20316 1281436 -wei 19973 1745989 -rstoesser 19569 1293588 -eudhan 19274 1283717 -Ente 19070 1373058 -jundery 18445 1115855 -iisiraider 18247 1101015 -ville 17883 1384026 -chris 17698 1487385 -purplefishies 17595 1092533 -DragonLord 17014 1162790 -dju 16515 929427 -IgorLeMasson 16064 1147232 -ako027ako 15671 1173203 -Nikolay.IT 15154 1068349 -Andrew Grant 15114 895539 -yurikvelo 15027 1165616 -OssumOpossum 14857 1007129 -enedene 14476 905279 -bpfliegel 14298 884523 -jpulman 13982 870599 -joster 13794 950160 -Nesa92 13786 1114691 -Dark_wizzie 13422 1007152 -Hjax 13350 900887 -Fifis 13313 965473 -mabichito 12903 749391 -thijsk 12886 722107 -crocogoat 12876 1048802 -AdrianSA 12860 804972 -Flopzee 12698 894821 -fatmurphy 12547 853210 -SapphireBrand 12416 969604 -modolief 12386 896470 -scuzzi 12362 833465 -pgontarz 12151 848794 -stocky 11954 699440 -mschmidt 11941 803401 -infinity 11470 727027 -torbjo 11387 728873 -Thomas A. Anderson 11372 732094 -snicolet 11106 869170 -amicic 10779 733593 -rpngn 10712 688203 -d64 10680 771144 -basepi 10637 744851 -jjoshua2 10559 670905 -dzjp 10343 732529 -ols 10259 570669 -lbraesch 10252 647825 +Username CPU Hours Games played +---------------------------------------------------- +noobpwnftw 23930906 1560559941 +dew 1169948 70333008 +mlang 957168 61657446 +mibere 703840 46867607 +tvijlbrief 517888 33379462 +JojoM 515404 30334272 +cw 443276 29385549 +crunchy 427035 27344275 +grandphish2 425794 26347253 +fastgm 414133 24519696 +gvreuls 377843 24708884 +CSU_Dynasty 338718 23030006 +Fisherman 326795 21820747 +TueRens 313730 19490246 +ctoks 298442 20052551 +velislav 270519 17355456 +bcross 241064 17196165 +glinscott 217799 13780820 +nordlandia 211692 13484886 +bking_US 198894 11876016 +drabel 191096 13129722 +leszek 189170 11446821 +mgrabiak 187153 12013300 +robal 181389 11539242 +Thanar 179852 12365359 +vdv 175274 9889046 +spams 157128 10319326 +marrco 150292 9401741 +sqrt2 147963 9724586 +CoffeeOne 137086 5022516 +vdbergh 137041 8926915 +malala 136182 8002293 +mhoram 132780 8398229 +xoto 124729 8652088 +davar 122092 7960001 +dsmith 122059 7570238 +Data 113305 8220352 +BrunoBanani 112960 7436849 +pemo 109598 5036441 +Dantist 106768 6431396 +MaZePallas 102741 6630419 +ElbertoOne 99028 7023771 +brabos 92118 6186135 +linrock 90903 6708639 +psk 89957 5984901 +sunu 88614 6020673 +sterni1971 86948 5613788 +Vizvezdenec 83761 5344740 +BRAVONE 81239 5054681 +nssy 76497 5259388 +cuistot 76366 4370584 +racerschmacer 75753 5442626 +teddybaer 75125 5407666 +Pking_cda 73776 5293873 +0x3C33 73133 4670293 +jromang 72117 5054915 +solarlight 70517 5028306 +dv8silencer 70287 3883992 +Bobo1239 68515 4652287 +manap 66273 4121774 +tinker 64321 4268390 +robnjr 57262 4053117 +Freja 56938 3733019 +ttruscott 56010 3680085 +rkl 54986 4150767 +renouve 53811 3501516 +finfish 51360 3370515 +eva42 51272 3599691 +rap 49985 3219146 +pb00067 49727 3298270 +amicic 49691 3042481 +ronaldjerum 47654 3240695 +bigpen0r 47278 3291647 +biffhero 46564 3111352 +VoyagerOne 45476 3452465 +eastorwest 45033 3071805 +speedycpu 43842 3003273 +jbwiebe 43305 2805433 +Antihistamine 41788 2761312 +mhunt 41735 2691355 +homyur 39893 2850481 +gri 39871 2515779 +oryx 38282 2944400 +Spprtr 38157 2470529 +SC 37290 2731014 +csnodgrass 36207 2688994 +jmdana 36157 2210661 +strelock 34716 2074055 +Garf 33800 2747562 +skiminki 33515 2055584 +EthanOConnor 33370 2090311 +slakovv 32915 2021889 +yurikvelo 32600 2255966 +Prcuvu 30377 2170122 +manapbk 30326 1770143 +anst 30301 2190091 +jkiiski 30136 1904470 +hyperbolic.tom 29840 2017394 +Pyafue 29650 1902349 +qurashee 27758 1509620 +OuaisBla 27636 1578800 +chriswk 26902 1868317 +achambord 26582 1767323 +Fifis 26376 1776853 +Patrick_G 26276 1801617 +yorkman 26193 1992080 +SFTUser 25182 1675689 +nabildanial 24942 1519409 +Sharaf_DG 24765 1786697 +ncfish1 24411 1520927 +agg177 23890 1395014 +JanErik 23408 1703875 +Isidor 23388 1680691 +Norabor 23164 1591830 +cisco2015 22895 1762069 +Zirie 22542 1472937 +team-oh 22272 1636708 +MazeOfGalious 21978 1629593 +sg4032 21945 1643065 +ianh2105 21725 1632562 +xor12 21628 1680365 +dex 21612 1467203 +nesoneg 21494 1463031 +jjoshua2 20997 1422689 +horst.prack 20878 1465656 +0xB00B1ES 20590 1208666 +sphinx 20515 1352368 +j3corre 20405 941444 +Adrian.Schmidt123 20316 1281436 +Ente 20017 1432602 +wei 19973 1745989 +rstoesser 19569 1293588 +eudhan 19274 1283717 +jundery 18445 1115855 +iisiraider 18247 1101015 +ville 17883 1384026 +chris 17698 1487385 +purplefishies 17595 1092533 +DMBK 17357 1279152 +DragonLord 17014 1162790 +dju 16515 929427 +IgorLeMasson 16064 1147232 +ako027ako 15671 1173203 +Nikolay.IT 15154 1068349 +Andrew Grant 15114 895539 +OssumOpossum 14857 1007129 +enedene 14476 905279 +bpfliegel 14298 884523 +jpulman 13982 870599 +joster 13794 950160 +Nesa92 13786 1114691 +crocogoat 13753 1114622 +Hjax 13535 915487 +Dark_wizzie 13422 1007152 +mpx86 12941 693640 +mabichito 12903 749391 +thijsk 12886 722107 +AdrianSA 12860 804972 +Flopzee 12698 894821 +fatmurphy 12547 853210 +scuzzi 12511 845761 +Karby 12429 735880 +SapphireBrand 12416 969604 +modolief 12386 896470 +pgontarz 12151 848794 +stocky 11954 699440 +mschmidt 11941 803401 +infinity 11470 727027 +torbjo 11395 729145 +Thomas A. Anderson 11372 732094 +d64 11263 789184 +Maxim 11129 804704 +snicolet 11106 869170 +MooTheCow 11008 694942 +savage84 10965 641068 +Rudolphous 10915 741268 +Wolfgang 10809 580032 +rpngn 10712 688203 +basepi 10637 744851 +michaelrpg 10409 735127 +dzjp 10343 732529 +ali-al-zhrani 10324 726502 +ols 10259 570669 +lbraesch 10252 647825 diff --git a/src/Makefile b/src/Makefile index 33a2434d..86d00d95 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,5 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 -# Copyright (C) 2004-2008 Tord Romstad (Glaurung author) -# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad -# Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad +# Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -39,9 +37,13 @@ PREFIX = /usr/local BINDIR = $(PREFIX)/bin ### Built-in benchmark for pgo-builds -PGO_TRAINING_DATA_FILE = pgo_training_data.bin -PGOBENCH = ./$(EXE) bench -PGOGENSFEN = ./$(EXE) gensfen depth 3 loop 1000 sfen_format bin output_file_name $(PGO_TRAINING_DATA_FILE) +ifeq ($(SDE_PATH),) + PGOBENCH = ./$(EXE) bench + PGOGENSFEN = ./$(EXE) gensfen depth 3 loop 1000 sfen_format bin output_file_name $(PGO_TRAINING_DATA_FILE) +else + PGOBENCH = $(SDE_PATH) -- ./$(EXE) bench + PGOGENSFEN = $(SDE_PATH) -- ./$(EXE) gensfen depth 3 loop 1000 sfen_format bin output_file_name $(PGO_TRAINING_DATA_FILE) +endif ### Source and object files SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \ @@ -377,9 +379,11 @@ ifeq ($(COMP),clang) ifneq ($(KERNEL),Darwin) ifneq ($(KERNEL),OpenBSD) + ifneq ($(KERNEL),FreeBSD) LDFLAGS += -latomic endif endif + endif ifeq ($(arch),$(filter $(arch),armv7 armv8)) ifeq ($(OS),Android) @@ -512,6 +516,10 @@ ifeq ($(optimize),yes) CXXFLAGS += -mdynamic-no-pic endif endif + + ifeq ($(comp),clang) + CXXFLAGS += -fexperimental-new-pass-manager + endif endif ### 3.4 Bits @@ -621,7 +629,7 @@ endif ifeq ($(optimize),yes) ifeq ($(debug), no) ifeq ($(comp),clang) - CXXFLAGS += -flto=thin + CXXFLAGS += -flto ifneq ($(findstring MINGW,$(KERNEL)),) CXXFLAGS += -fuse-ld=lld else ifneq ($(findstring MSYS,$(KERNEL)),) @@ -641,7 +649,7 @@ ifeq ($(debug), no) LDFLAGS += -save-temps endif else - CXXFLAGS += -flto=thin + CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) endif diff --git a/src/benchmark.cpp b/src/benchmark.cpp index ffb631a2..7945a453 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -92,6 +92,8 @@ const vector Defaults = { } // namespace +namespace Stockfish { + /// setup_bench() builds a list of UCI commands to be run by bench. There /// are five parameters: TT size in MB, number of search threads that /// should be used, the limit value spent for each position, a file name @@ -168,3 +170,5 @@ vector setup_bench(const Position& current, istream& is) { return list; } + +} // namespace Stockfish diff --git a/src/bitbase.cpp b/src/bitbase.cpp index bbe8e9a7..27bf4095 100644 --- a/src/bitbase.cpp +++ b/src/bitbase.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include "bitboard.h" #include "types.h" +namespace Stockfish { + namespace { // There are 24 possible pawn squares: files A to D and ranks from 2 to 7. @@ -66,7 +68,6 @@ namespace { } // namespace - bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) { assert(file_of(wpsq) <= FILE_D); @@ -96,7 +97,6 @@ void Bitbases::init() { KPKBitbase.set(idx); } - namespace { KPKPosition::KPKPosition(unsigned idx) { @@ -150,8 +150,8 @@ namespace { Bitboard b = attacks_bb(ksq[stm]); while (b) - r |= stm == WHITE ? db[index(BLACK, ksq[BLACK] , pop_lsb(&b), psq)] - : db[index(WHITE, pop_lsb(&b), ksq[WHITE], psq)]; + r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)] + : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)]; if (stm == WHITE) { @@ -168,3 +168,5 @@ namespace { } } // namespace + +} // namespace Stockfish diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 80206b58..6b84b51e 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,11 +22,14 @@ #include "bitboard.h" #include "misc.h" +namespace Stockfish { + uint8_t PopCnt16[1 << 16]; uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; Bitboard SquareBB[SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; @@ -42,7 +45,6 @@ namespace { } - /// safe_destination() returns the bitboard of target square for the given step /// from the given square. If the step is off the board, returns empty bitboard. @@ -55,7 +57,7 @@ inline Bitboard safe_destination(Square s, int step) { /// Bitboards::pretty() returns an ASCII representation of a bitboard suitable /// to be printed to standard output. Useful for debugging. -const std::string Bitboards::pretty(Bitboard b) { +std::string Bitboards::pretty(Bitboard b) { std::string s = "+---+---+---+---+---+---+---+---+\n"; @@ -106,12 +108,17 @@ void Bitboards::init() { for (PieceType pt : { BISHOP, ROOK }) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { if (PseudoAttacks[pt][s1] & s2) - LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + { + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + } + BetweenBB[s1][s2] |= s2; + } } } - namespace { Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { @@ -123,7 +130,7 @@ namespace { for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) { Square s = sq; - while(safe_destination(s, d) && !(occupied & s)) + while (safe_destination(s, d) && !(occupied & s)) attacks |= (s += d); } @@ -211,3 +218,5 @@ namespace { } } } + +} // namespace Stockfish diff --git a/src/bitboard.h b/src/bitboard.h index 29d8f66d..b29f3e24 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,19 +23,21 @@ #include "types.h" +namespace Stockfish { + namespace Bitbases { void init(); bool probe(Square wksq, Square wpsq, Square bksq, Color us); -} +} // namespace Stockfish::Bitbases namespace Bitboards { void init(); -const std::string pretty(Bitboard b); +std::string pretty(Bitboard b); -} +} // namespace Stockfish::Bitboards constexpr Bitboard AllSquares = ~Bitboard(0); constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; @@ -73,6 +75,7 @@ extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern Bitboard SquareBB[SQUARE_NB]; +extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; @@ -209,23 +212,29 @@ constexpr Bitboard adjacent_files_bb(Square s) { inline Bitboard line_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); + return LineBB[s1][s2]; } -/// between_bb() returns a bitboard representing squares that are linearly -/// between the two given squares (excluding the given squares). If the given -/// squares are not on a same file/rank/diagonal, we return 0. For instance, -/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6. +/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open +/// segment between the squares s1 and s2 (excluding s1 but including s2). If the +/// given squares are not on a same file/rank/diagonal, it returns s2. For instance, +/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but +/// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick +/// allows to generate non-king evasion moves faster: the defending piece must either +/// interpose itself to cover the check or capture the checking piece. inline Bitboard between_bb(Square s1, Square s2) { - Bitboard b = line_bb(s1, s2) & ((AllSquares << s1) ^ (AllSquares << s2)); - return b & (b - 1); //exclude lsb + + assert(is_ok(s1) && is_ok(s2)); + + return BetweenBB[s1][s2]; } -/// forward_ranks_bb() returns a bitboard representing the squares on the ranks -/// in front of the given one, from the point of view of the given color. For instance, +/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in +/// front of the given one, from the point of view of the given color. For instance, /// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. constexpr Bitboard forward_ranks_bb(Color c, Square s) { @@ -412,13 +421,20 @@ inline Square msb(Bitboard b) { #endif +/// least_significant_square_bb() returns the bitboard of the least significant +/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). + +inline Bitboard least_significant_square_bb(Bitboard b) { + assert(b); + return b & -b; +} /// pop_lsb() finds and clears the least significant bit in a non-zero bitboard -inline Square pop_lsb(Bitboard* b) { - assert(*b); - const Square s = lsb(*b); - *b &= *b - 1; +inline Square pop_lsb(Bitboard& b) { + assert(b); + const Square s = lsb(b); + b &= b - 1; return s; } @@ -430,4 +446,6 @@ inline Square frontmost_sq(Color c, Bitboard b) { return c == WHITE ? msb(b) : lsb(b); } +} // namespace Stockfish + #endif // #ifndef BITBOARD_H_INCLUDED diff --git a/src/endgame.cpp b/src/endgame.cpp index c8be2198..a44d3a1c 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,6 +22,8 @@ #include "endgame.h" #include "movegen.h" +namespace Stockfish { + namespace { // Used to drive the king towards the edge of the board @@ -553,8 +555,8 @@ ScaleFactor Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, RookValueMg, 2)); assert(verify_material(pos, weakSide, RookValueMg, 1)); - Square strongPawn1 = pos.squares(strongSide)[0]; - Square strongPawn2 = pos.squares(strongSide)[1]; + Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); + Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); Square weakKing = pos.square(weakSide); // Does the stronger side have a passed pawn? @@ -638,8 +640,8 @@ ScaleFactor Endgame::operator()(const Position& pos) const { return SCALE_FACTOR_NONE; Square weakKing = pos.square(weakSide); - Square strongPawn1 = pos.squares(strongSide)[0]; - Square strongPawn2 = pos.squares(strongSide)[1]; + Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); + Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); Square blockSq1, blockSq2; if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2)) @@ -741,3 +743,5 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // it's probably at least a draw even with the pawn. return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; } + +} // namespace Stockfish diff --git a/src/endgame.h b/src/endgame.h index 1351d88a..146111b9 100644 --- a/src/endgame.h +++ b/src/endgame.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ #include "position.h" #include "types.h" +namespace Stockfish { /// EndgameCode lists all supported endgame functions by corresponding codes @@ -120,4 +121,6 @@ namespace Endgames { } } +} // namespace Stockfish + #endif // #ifndef ENDGAME_H_INCLUDED diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 709a50ff..e4558730 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -122,7 +122,7 @@ namespace { // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on // squares of the same color as our bishop. constexpr Score BishopPawns[int(FILE_NB) / 2] = { - S(3, 8), S(3, 9), S(1, 8), S(3, 7) + S(3, 8), S(3, 9), S(2, 8), S(3, 8) }; // KingProtector[knight/bishop] contains penalty for each distance unit to own king @@ -130,16 +130,15 @@ namespace { // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. - constexpr Score Outpost[] = { S(56, 34), S(31, 23) }; + constexpr Score Outpost[] = { S(57, 38), S(31, 24) }; // PassedRank[Rank] contains a bonus according to the rank of a passed pawn constexpr Score PassedRank[RANK_NB] = { - S(0, 0), S(9, 28), S(15, 31), S(17, 39), S(64, 70), S(171, 177), S(277, 260) + S(0, 0), S(7, 27), S(16, 32), S(17, 40), S(64, 71), S(170, 174), S(278, 262) }; - // RookOnFile[semiopen/open] contains bonuses for each rook when there is - // no (friendly) pawn on the rook file. - constexpr Score RookOnFile[] = { S(19, 7), S(48, 27) }; + constexpr Score RookOnClosedFile = S(10, 5); + constexpr Score RookOnOpenFile[] = { S(19, 6), S(47, 26) }; // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to // which piece type attacks which one. Attacks on lesser pieces which are @@ -152,11 +151,12 @@ namespace { S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43) }; + constexpr Value CorneredBishop = Value(50); + // Assorted bonuses and penalties - constexpr Score BadOutpost = S( -7, 36); + constexpr Score UncontestedOutpost = S( 1, 10); constexpr Score BishopOnKingRing = S( 24, 0); constexpr Score BishopXRayPawns = S( 4, 5); - constexpr Score CorneredBishop = S( 50, 50); constexpr Score FlankAttacks = S( 8, 0); constexpr Score Hanging = S( 69, 36); constexpr Score KnightOnQueen = S( 16, 11); @@ -285,15 +285,16 @@ namespace { constexpr Direction Down = -pawn_push(Us); constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB : Rank5BB | Rank4BB | Rank3BB); - const Square* pl = pos.squares(Us); - + Bitboard b1 = pos.pieces(Us, Pt); Bitboard b, bb; Score score = SCORE_ZERO; attackedBy[Us][Pt] = 0; - for (Square s = *pl; s != SQ_NONE; s = *++pl) + while (b1) { + Square s = pop_lsb(b1); + // Find attacked squares, including x-ray attacks for bishops and rooks b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) @@ -320,13 +321,12 @@ namespace { score += BishopOnKingRing; int mob = popcount(b & mobilityArea[Us]); - mobility[Us] += MobilityBonus[Pt - 2][mob]; if (Pt == BISHOP || Pt == KNIGHT) { // Bonus if the piece is on an outpost square or can reach one - // Reduced bonus for knights (BadOutpost) if few relevant targets + // Bonus for knights (UncontestedOutpost) if few relevant targets bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) & ~pe->pawn_attacks_span(Them); Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); @@ -335,7 +335,7 @@ namespace { && bb & s & ~CenterFiles // on a side outpost && !(b & targets) // no relevant attacks && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) - score += BadOutpost; + score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide)); else if (bb & s) score += Outpost[Pt == BISHOP]; else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) @@ -348,7 +348,7 @@ namespace { // Penalty if the piece is far from the king score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); - if (Pt == BISHOP) + if constexpr (Pt == BISHOP) { // Penalty according to the number of our pawns on the same color square as the // bishop, bigger when the center files are blocked with pawns and smaller @@ -373,29 +373,40 @@ namespace { { Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); if (pos.piece_on(s + d) == make_piece(Us, PAWN)) - score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4 - : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2 - : CorneredBishop; + score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop) + : 3 * make_score(CorneredBishop, CorneredBishop); } } } - if (Pt == ROOK) + if constexpr (Pt == ROOK) { - // Bonus for rook on an open or semi-open file + // Bonuses for rook on a (semi-)open or closed file if (pos.is_on_semiopen_file(Us, s)) - score += RookOnFile[pos.is_on_semiopen_file(Them, s)]; - - // Penalty when trapped by the king, even more if the king cannot castle - else if (mob <= 3) { - File kf = file_of(pos.square(Us)); - if ((kf < FILE_E) == (file_of(s) < kf)) - score -= TrappedRook * (1 + !pos.castling_rights(Us)); + score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)]; + } + else + { + // If our pawn on this file is blocked, increase penalty + if ( pos.pieces(Us, PAWN) + & shift(pos.pieces()) + & file_bb(s)) + { + score -= RookOnClosedFile; + } + + // Penalty when trapped by the king, even more if the king cannot castle + if (mob <= 3) + { + File kf = file_of(pos.square(Us)); + if ((kf < FILE_E) == (file_of(s) < kf)) + score -= TrappedRook * (1 + !pos.castling_rights(Us)); + } } } - if (Pt == QUEEN) + if constexpr (Pt == QUEEN) { // Penalty if any relative pin or discovered attack against the queen Bitboard queenPinners; @@ -403,7 +414,7 @@ namespace { score -= WeakQueen; } } - if (T) + if constexpr (T) Trace::add(Pt, Us, score); return score; @@ -480,7 +491,7 @@ namespace { int kingFlankDefense = popcount(b3); kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo) - + 185 * popcount(kingRing[Us] & weak) // (~15 Elo) + + 183 * popcount(kingRing[Us] & weak) // (~15 Elo) + 148 * popcount(unsafeChecks) // (~4 Elo) + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo) + 69 * kingAttacksCount[Them] // (~0.5 Elo) @@ -503,7 +514,7 @@ namespace { // Penalty if king flank is under attack, potentially moving toward the king score -= FlankAttacks * kingFlankAttack; - if (T) + if constexpr (T) Trace::add(KING, Us, score); return score; @@ -542,11 +553,11 @@ namespace { { b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); while (b) - score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(&b)))]; + score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))]; b = weak & attackedBy[Us][ROOK]; while (b) - score += ThreatByRook[type_of(pos.piece_on(pop_lsb(&b)))]; + score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))]; if (weak & attackedBy[Us][KING]) score += ThreatByKing; @@ -604,7 +615,7 @@ namespace { score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); } - if (T) + if constexpr (T) Trace::add(THREAT, Us, score); return score; @@ -644,7 +655,7 @@ namespace { while (b) { - Square s = pop_lsb(&b); + Square s = pop_lsb(b); assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); @@ -674,14 +685,16 @@ namespace { bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); if (!(pos.pieces(Them) & bb)) - unsafeSquares &= attackedBy[Them][ALL_PIECES]; + unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); - // If there are no enemy attacks on passed pawn span, assign a big bonus. + // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus. + // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus. // Otherwise assign a smaller bonus if the path to queen is not attacked // and even smaller bonus if it is attacked but block square is not. - int k = !unsafeSquares ? 35 : - !(unsafeSquares & squaresToQueen) ? 20 : - !(unsafeSquares & blockSq) ? 9 : + int k = !unsafeSquares ? 36 : + !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 : + !(unsafeSquares & squaresToQueen) ? 17 : + !(unsafeSquares & blockSq) ? 7 : 0 ; // Assign a larger bonus if the block square is defended @@ -695,7 +708,7 @@ namespace { score += bonus - PassedFile * edge_distance(file_of(s)); } - if (T) + if constexpr (T) Trace::add(PASSED, Us, score); return score; @@ -730,11 +743,13 @@ namespace { behind |= shift(behind); behind |= shift(behind); + // Compute space score based on the number of safe squares and number of our pieces + // increased with number of total blocked pawns in position. int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); Score score = make_score(bonus * weight * weight / 16, 0); - if (T) + if constexpr (T) Trace::add(SPACE, Us, score); return score; @@ -749,7 +764,7 @@ namespace { Value Evaluation::winnable(Score score) const { int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) - - distance(pos.square(WHITE), pos.square(BLACK)); + + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) && (pos.pieces(PAWN) & KingSide); @@ -791,24 +806,36 @@ namespace { { if (pos.opposite_bishops()) { + // For pure opposite colored bishops endgames use scale factor + // based on the number of passed pawns of the strong side. if ( pos.non_pawn_material(WHITE) == BishopValueMg && pos.non_pawn_material(BLACK) == BishopValueMg) sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); + // For every other opposite colored bishops endgames use scale factor + // based on the number of all pieces of the strong side. else sf = 22 + 3 * pos.count(strongSide); } + // For rook endgames with strong side not having overwhelming pawn number advantage + // and its pawns being on one flank and weak side protecting its pieces with a king + // use lower scale factor. else if ( pos.non_pawn_material(WHITE) == RookValueMg && pos.non_pawn_material(BLACK) == RookValueMg && pos.count(strongSide) - pos.count(~strongSide) <= 1 && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) sf = 36; + // For queen vs no queen endgames use scale factor + // based on number of minors of side that doesn't have queen. else if (pos.count() == 1) sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) : pos.count(WHITE) + pos.count(WHITE)); + // In every other case use scale factor based on + // the number of pawns of the strong side reduced if pawns are on a single flank. else sf = std::min(sf, 36 + 7 * pos.count(strongSide)) - 4 * !pawnsOnBothFlanks; + // Reduce scale factor in case of pawns being on a single flank sf -= 4 * !pawnsOnBothFlanks; } @@ -817,7 +844,7 @@ namespace { + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; v /= PHASE_MIDGAME; - if (T) + if constexpr (T) { Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); @@ -889,7 +916,7 @@ make_v: Value v = winnable(score); // In case of tracing add all remaining individual evaluation terms - if (T) + if constexpr (T) { Trace::add(MATERIAL, pos.psq_score()); Trace::add(IMBALANCE, me->imbalance()); @@ -906,7 +933,43 @@ make_v: return v; } -} // namespace + + /// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE + + Value fix_FRC(const Position& pos) { + + constexpr Bitboard Corners = 1ULL << SQ_A1 | 1ULL << SQ_H1 | 1ULL << SQ_A8 | 1ULL << SQ_H8; + + if (!(pos.pieces(BISHOP) & Corners)) + return VALUE_ZERO; + + int correction = 0; + + if ( pos.piece_on(SQ_A1) == W_BISHOP + && pos.piece_on(SQ_B2) == W_PAWN) + correction += !pos.empty(SQ_B3) ? -CorneredBishop * 4 + : -CorneredBishop * 3; + + if ( pos.piece_on(SQ_H1) == W_BISHOP + && pos.piece_on(SQ_G2) == W_PAWN) + correction += !pos.empty(SQ_G3) ? -CorneredBishop * 4 + : -CorneredBishop * 3; + + if ( pos.piece_on(SQ_A8) == B_BISHOP + && pos.piece_on(SQ_B7) == B_PAWN) + correction += !pos.empty(SQ_B6) ? CorneredBishop * 4 + : CorneredBishop * 3; + + if ( pos.piece_on(SQ_H8) == B_BISHOP + && pos.piece_on(SQ_G7) == B_PAWN) + correction += !pos.empty(SQ_G6) ? CorneredBishop * 4 + : CorneredBishop * 3; + + return pos.side_to_move() == WHITE ? Value(correction) + : -Value(correction); + } + +} // namespace Eval /// evaluate() is the evaluator for the outer world. It returns a static @@ -931,25 +994,41 @@ Value Eval::evaluate(const Position& pos) { else { // Scale and shift NNUE for compatibility with search and classical evaluation - auto adjusted_NNUE = [&](){ - int mat = pos.non_pawn_material() + PawnValueMg * pos.count(); - return NNUE::evaluate(pos) * (720 + mat / 32) / 1024 + Tempo; + auto adjusted_NNUE = [&]() + { + int material = pos.non_pawn_material() + 4 * PawnValueMg * pos.count(); + int scale = 580 + + material / 32 + - 4 * pos.rule50_count(); + + Value nnue = NNUE::evaluate(pos) * scale / 1024 + Tempo; + + if (pos.is_chess960()) + nnue += fix_FRC(pos); + + return nnue; }; - // If there is PSQ imbalance use classical eval, with small probability if it is small + // If there is PSQ imbalance we use the classical eval. We also introduce + // a small probability of using the classical eval when PSQ imbalance is small. Value psq = Value(abs(eg_value(pos.psq_score()))); int r50 = 16 + pos.rule50_count(); bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50; bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB)); - bool strongClassical = pos.non_pawn_material() < 2 * RookValueMg && pos.count() < 2; + // Use classical evaluation for really low piece endgames. + // One critical case is the draw for bishop + A/H file pawn vs naked king. + bool lowPieceEndgame = pos.non_pawn_material() == BishopValueMg + || (pos.non_pawn_material() < 2 * RookValueMg && pos.count() < 2); - v = classical || strongClassical ? Evaluation(pos).value() : adjusted_NNUE(); + v = classical || lowPieceEndgame ? Evaluation(pos).value() + : adjusted_NNUE(); // If the classical eval is small and imbalance large, use NNUE nevertheless. - // For the case of opposite colored bishops, switch to NNUE eval with - // small probability if the classical eval is less than the threshold. - if ( largePsq && !strongClassical + // For the case of opposite colored bishops, switch to NNUE eval with small + // probability if the classical eval is less than the threshold. + if ( largePsq + && !lowPieceEndgame && ( abs(v) * 16 < NNUEThreshold2 * r50 || ( pos.opposite_bishops() && abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50 @@ -1024,3 +1103,5 @@ std::string Eval::trace(const Position& pos) { return ss.str(); } + +} // namespace Stockfish diff --git a/src/evaluate.h b/src/evaluate.h index f5d3efa7..f3766f45 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include "types.h" +namespace Stockfish { + class Position; namespace Eval { @@ -32,8 +34,10 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-c3ca321c51c9.nnue" + #define EvalFileDefaultName "nn-62ef826d1a6d.nnue" } // namespace Eval +} // namespace Stockfish + #endif // #ifndef EVALUATE_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index 1a13dc62..ff6564a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,15 +23,14 @@ #include "bitboard.h" #include "endgame.h" #include "position.h" +#include "psqt.h" #include "search.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" #include "uci.h" -#include "syzygy/tbprobe.h" -namespace PSQT { - void init(); -} +using namespace Stockfish; int main(int argc, char* argv[]) { diff --git a/src/material.cpp b/src/material.cpp index 870a5e11..9d17af20 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,32 +24,39 @@ using namespace std; +namespace Stockfish { + namespace { + #define S(mg, eg) make_score(mg, eg) // Polynomial material imbalance parameters - constexpr int QuadraticOurs[][PIECE_TYPE_NB] = { - // OUR PIECES - // pair pawn knight bishop rook queen - {1438 }, // Bishop pair - { 40, 38 }, // Pawn - { 32, 255, -62 }, // Knight OUR PIECES - { 0, 104, 4, 0 }, // Bishop - { -26, -2, 47, 105, -208 }, // Rook - {-189, 24, 117, 133, -134, -6 } // Queen + // One Score parameter for each pair (our piece, another of our pieces) + constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = { + // OUR PIECE 2 + // bishop pair pawn knight bishop rook queen + {S(1419, 1455) }, // Bishop pair + {S( 101, 28), S( 37, 39) }, // Pawn + {S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1 + {S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop + {S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook + {S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen }; - constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = { - // THEIR PIECES - // pair pawn knight bishop rook queen - { }, // Bishop pair - { 36, }, // Pawn - { 9, 63, }, // Knight OUR PIECES - { 59, 65, 42, }, // Bishop - { 46, 39, 24, -24, }, // Rook - { 97, 100, -42, 137, 268, } // Queen + // One Score parameter for each pair (our piece, their piece) + constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = { + // THEIR PIECE + // bishop pair pawn knight bishop rook queen + { }, // Bishop pair + {S( 33, 30) }, // Pawn + {S( 46, 18), S(106, 84) }, // Knight OUR PIECE + {S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop + {S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook + {S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen }; + #undef S + // Endgame evaluation and scaling functions are accessed directly and not through // the function maps because they correspond to more than one material hash key. Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; @@ -67,7 +74,7 @@ namespace { bool is_KBPsK(const Position& pos, Color us) { return pos.non_pawn_material(us) == BishopValueMg - && pos.count(us) >= 1; + && pos.count(us) >= 1; } bool is_KQKRPs(const Position& pos, Color us) { @@ -82,11 +89,11 @@ namespace { /// piece type for both colors. template - int imbalance(const int pieceCount[][PIECE_TYPE_NB]) { + Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) { constexpr Color Them = ~Us; - int bonus = 0; + Score bonus = SCORE_ZERO; // Second-degree polynomial material imbalance, by Tord Romstad for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) @@ -213,8 +220,10 @@ Entry* probe(const Position& pos) { { pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK), pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } }; - e->value = int16_t((imbalance(pieceCount) - imbalance(pieceCount)) / 16); + e->score = (imbalance(pieceCount) - imbalance(pieceCount)) / 16; return e; } } // namespace Material + +} // namespace Stockfish diff --git a/src/material.h b/src/material.h index 80d01655..26535a53 100644 --- a/src/material.h +++ b/src/material.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ #include "position.h" #include "types.h" -namespace Material { +namespace Stockfish::Material { /// Material::Entry contains various information about a material configuration. /// It contains a material imbalance evaluation, a function pointer to a special @@ -37,8 +37,8 @@ namespace Material { struct Entry { - Score imbalance() const { return make_score(value, value); } - Phase game_phase() const { return gamePhase; } + Score imbalance() const { return score; } + Phase game_phase() const { return (Phase)gamePhase; } bool specialized_eval_exists() const { return evaluationFunction != nullptr; } Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } @@ -57,15 +57,15 @@ struct Entry { const EndgameBase* evaluationFunction; const EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each // side (e.g. KPKP, KBPsK) - int16_t value; + Score score; + int16_t gamePhase; uint8_t factor[COLOR_NB]; - Phase gamePhase; }; typedef HashTable Table; Entry* probe(const Position& pos); -} // namespace Material +} // namespace Stockfish::Material #endif // #ifndef MATERIAL_H_INCLUDED diff --git a/src/misc.cpp b/src/misc.cpp index eb68e842..e981136b 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -63,6 +63,8 @@ using namespace std; SynchronizedRegionLogger sync_region_cout(std::cout); +namespace Stockfish { + namespace { /// Version number. If Version is left empty, then compile date in the format @@ -140,7 +142,7 @@ public: /// the program was compiled) or "Stockfish ", depending on whether /// Version is empty. -const string engine_info(bool to_uci) { +string engine_info(bool to_uci) { const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); string month, day, year; @@ -163,7 +165,7 @@ const string engine_info(bool to_uci) { /// compiler_info() returns a string trying to describe the compiler we use -const std::string compiler_info() { +std::string compiler_info() { #define stringify2(x) #x #define stringify(x) stringify2(x) @@ -363,7 +365,11 @@ void std_aligned_free(void* ptr) { #if defined(_WIN32) -static void* aligned_large_pages_alloc_win(size_t allocSize) { +static void* aligned_large_pages_alloc_windows(size_t allocSize) { + + #if !defined(_WIN64) + return nullptr; + #else HANDLE hProcessToken { }; LUID luid { }; @@ -406,12 +412,14 @@ static void* aligned_large_pages_alloc_win(size_t allocSize) { CloseHandle(hProcessToken); return mem; + + #endif } void* aligned_large_pages_alloc(size_t allocSize) { // Try to allocate large pages - void* mem = aligned_large_pages_alloc_win(allocSize); + void* mem = aligned_large_pages_alloc_windows(allocSize); // Fall back to regular, page aligned, allocation if necessary if (!mem) @@ -451,8 +459,9 @@ void aligned_large_pages_free(void* mem) { if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) { DWORD err = GetLastError(); - std::cerr << "Failed to free transposition table. Error code: 0x" << - std::hex << err << std::dec << std::endl; + std::cerr << "Failed to free large page memory. Error code: 0x" + << std::hex << err + << std::dec << std::endl; exit(EXIT_FAILURE); } } @@ -628,6 +637,7 @@ void init(int argc, char* argv[]) { } // namespace CommandLine + // Returns a string that represents the current time. (Used when learning evaluation functions) std::string now_string() { @@ -734,3 +744,5 @@ int write_memory_to_file(std::string filename, void* ptr, uint64_t size) fs.close(); return 0; } + +} // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index c7cf3265..6eb8b1ae 100644 --- a/src/misc.h +++ b/src/misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,8 +36,10 @@ #include "types.h" -const std::string engine_info(bool to_uci = false); -const std::string compiler_info(); +namespace Stockfish { + +std::string engine_info(bool to_uci = false); +std::string compiler_info(); void prefetch(void* addr); void start_logger(const std::string& fname); void* std_aligned_alloc(size_t alignment, size_t size); @@ -681,4 +683,6 @@ namespace CommandLine { extern std::string workingDirectory; // path of the working directory } +} // namespace Stockfish + #endif // #ifndef MISC_H_INCLUDED diff --git a/src/movegen.cpp b/src/movegen.cpp index 3340f65c..50496136 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,6 +21,8 @@ #include "movegen.h" #include "position.h" +namespace Stockfish { + namespace { template @@ -61,7 +63,7 @@ namespace { Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; - Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target: + Bitboard enemies = (Type == EVASIONS ? pos.checkers(): Type == CAPTURES ? target : pos.pieces(Them)); // Single and double pawn pushes, no promotions @@ -85,7 +87,7 @@ namespace { // Add pawn pushes which give discovered check. This is possible only // if the pawn is not on the same file as the enemy king, because we - // don't generate captures. Note that a possible discovery check + // don't generate captures. Note that a possible discovered check // promotion has been already generated amongst the captures. Bitboard dcCandidateQuiets = pos.blockers_for_king(Them) & pawnsNotOn7; if (dcCandidateQuiets) @@ -100,13 +102,13 @@ namespace { while (b1) { - Square to = pop_lsb(&b1); + Square to = pop_lsb(b1); *moveList++ = make_move(to - Up, to); } while (b2) { - Square to = pop_lsb(&b2); + Square to = pop_lsb(b2); *moveList++ = make_move(to - Up - Up, to); } } @@ -125,16 +127,16 @@ namespace { Bitboard b3 = shift(pawnsOn7) & emptySquares; while (b1) - moveList = make_promotions(moveList, pop_lsb(&b1), ksq); + moveList = make_promotions(moveList, pop_lsb(b1), ksq); while (b2) - moveList = make_promotions(moveList, pop_lsb(&b2), ksq); + moveList = make_promotions(moveList, pop_lsb(b2), ksq); while (b3) - moveList = make_promotions(moveList, pop_lsb(&b3), ksq); + moveList = make_promotions(moveList, pop_lsb(b3), ksq); } - // Standard and en-passant captures + // Standard and en passant captures if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { Bitboard b1 = shift(pawnsNotOn7) & enemies; @@ -142,13 +144,13 @@ namespace { while (b1) { - Square to = pop_lsb(&b1); + Square to = pop_lsb(b1); *moveList++ = make_move(to - UpRight, to); } while (b2) { - Square to = pop_lsb(&b2); + Square to = pop_lsb(b2); *moveList++ = make_move(to - UpLeft, to); } @@ -156,10 +158,8 @@ namespace { { assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); - // An en passant capture can be an evasion only if the checking piece - // is the double pushed pawn and so is in the target. Otherwise this - // is a discovery check and we are forced to do otherwise. - if (Type == EVASIONS && !(target & (pos.ep_square() - Up))) + // An en passant capture cannot resolve a discovered check + if (Type == EVASIONS && (target & (pos.ep_square() + Up))) return moveList; b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); @@ -167,7 +167,7 @@ namespace { assert(b1); while (b1) - *moveList++ = make(pop_lsb(&b1), pos.ep_square()); + *moveList++ = make(pop_lsb(b1), pos.ep_square()); } } @@ -175,32 +175,23 @@ namespace { } - template - ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { + template + ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard piecesToMove, Bitboard target) { static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); - const Square* pl = pos.squares(Us); + Bitboard bb = piecesToMove & pos.pieces(Pt); - for (Square from = *pl; from != SQ_NONE; from = *++pl) + while (bb) { - if (Checks) - { - if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN) - && !(attacks_bb(from) & target & pos.check_squares(Pt))) - continue; - - if (pos.blockers_for_king(~Us) & from) - continue; - } + Square from = pop_lsb(bb); Bitboard b = attacks_bb(from, pos.pieces()) & target; - - if (Checks) + if constexpr (Checks) b &= pos.check_squares(Pt); while (b) - *moveList++ = make_move(from, pop_lsb(&b)); + *moveList++ = make_move(from, pop_lsb(b)); } return moveList; @@ -209,8 +200,14 @@ namespace { template ExtMove* generate_all(const Position& pos, ExtMove* moveList) { - constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations - Bitboard target; + + static_assert(Type != LEGAL, "Unsupported type in generate_all()"); + + constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations + Bitboard target, piecesToMove = pos.pieces(Us); + + if(Type == QUIET_CHECKS) + piecesToMove &= ~pos.blockers_for_king(~Us); switch (Type) { @@ -222,30 +219,25 @@ namespace { target = ~pos.pieces(); break; case EVASIONS: - { - Square checksq = lsb(pos.checkers()); - target = between_bb(pos.square(Us), checksq) | checksq; + target = between_bb(pos.square(Us), lsb(pos.checkers())); break; - } case NON_EVASIONS: target = ~pos.pieces(Us); break; - default: - static_assert(true, "Unsupported type in generate_all()"); } moveList = generate_pawn_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, piecesToMove, target); + moveList = generate_moves(pos, moveList, piecesToMove, target); + moveList = generate_moves< ROOK, Checks>(pos, moveList, piecesToMove, target); + moveList = generate_moves< QUEEN, Checks>(pos, moveList, piecesToMove, target); if (Type != QUIET_CHECKS && Type != EVASIONS) { Square ksq = pos.square(Us); Bitboard b = attacks_bb(ksq) & target; while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + *moveList++ = make_move(ksq, pop_lsb(b)); if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) @@ -260,7 +252,7 @@ namespace { /// Generates all pseudo-legal captures plus queen and checking knight promotions -/// Generates all pseudo-legal non-captures and underpromotions(except checking knight) +/// Generates all pseudo-legal non-captures and underpromotions (except checking knight) /// Generates all pseudo-legal captures and non-captures /// /// Returns a pointer to the end of the move list. @@ -283,8 +275,8 @@ template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); -/// generate generates all pseudo-legal non-captures. -/// Returns a pointer to the end of the move list. +/// generate generates all pseudo-legal non-captures giving check, +/// except castling. Returns a pointer to the end of the move list. template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { @@ -295,7 +287,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { while (dc) { - Square from = pop_lsb(&dc); + Square from = pop_lsb(dc); PieceType pt = type_of(pos.piece_on(from)); Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces(); @@ -304,7 +296,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { b &= ~attacks_bb(pos.square(~us)); while (b) - *moveList++ = make_move(from, pop_lsb(&b)); + *moveList++ = make_move(from, pop_lsb(b)); } return us == WHITE ? generate_all(pos, moveList) @@ -321,24 +313,16 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { Color us = pos.side_to_move(); Square ksq = pos.square(us); - Bitboard sliderAttacks = 0; - Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN); - // Find all the squares attacked by slider checkers. We will remove them from - // the king evasions in order to skip known illegal moves, which avoids any - // useless legality checks later on. - while (sliders) - sliderAttacks |= line_bb(ksq, pop_lsb(&sliders)) & ~pos.checkers(); - - // Generate evasions for king, capture and non capture moves - Bitboard b = attacks_bb(ksq) & ~pos.pieces(us) & ~sliderAttacks; + // Generate evasions for king + Bitboard b = attacks_bb(ksq) & ~pos.pieces(us); while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + *moveList++ = make_move(ksq, pop_lsb(b)); if (more_than_one(pos.checkers())) return moveList; // Double check, only a king move can save the day - // Generate blocking evasions or captures of the checking piece + // Generate blocking interpositions or captures of the checking piece return us == WHITE ? generate_all(pos, moveList) : generate_all(pos, moveList); } @@ -357,7 +341,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) - if ( (pinned || from_sq(*cur) == ksq || type_of(*cur) == ENPASSANT) + if ( ((pinned && pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) && !pos.legal(*cur)) *cur = (--moveList)->move; else @@ -365,3 +349,5 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { return moveList; } + +} // namespace Stockfish diff --git a/src/movegen.h b/src/movegen.h index 675b7698..704cd3e2 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include "types.h" +namespace Stockfish { + class Position; enum GenType { @@ -73,4 +75,6 @@ private: ExtMove moveList[MAX_MOVES], *last; }; +} // namespace Stockfish + #endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp index f5e02385..4ff4cff4 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,8 @@ #include "movepick.h" +namespace Stockfish { + namespace { enum Stages { @@ -99,15 +101,15 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); for (auto& m : *this) - if (Type == CAPTURES) + if constexpr (Type == CAPTURES) m.value = int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6 + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; - else if (Type == QUIETS) + else if constexpr (Type == QUIETS) m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] - + 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] + + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] + (ply < MAX_LPH ? std::min(4, depth / 3) * (*lowPlyHistory)[ply][from_to(m)] : 0); @@ -117,8 +119,8 @@ void MovePicker::score() { m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))); else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - (1 << 28); } } @@ -142,7 +144,7 @@ Move MovePicker::select(Pred filter) { } /// MovePicker::next_move() is the most important method of the MovePicker class. It -/// returns a new pseudo legal move every time it is called until there are no more +/// returns a new pseudo-legal move every time it is called until there are no more /// moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { @@ -263,3 +265,5 @@ top: assert(false); return MOVE_NONE; // Silence warning } + +} // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 4c0ad551..c76d4957 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,6 +27,8 @@ #include "position.h" #include "types.h" +namespace Stockfish { + /// StatsEntry stores the stat table value. It is usually a number but could /// be a move or even a nested history. We use a class instead of naked value /// to directly call history update operator<<() on the entry so to use stats @@ -84,7 +86,7 @@ enum StatsType { NoCaptures, Captures }; /// unsuccessful during the current search, and is used for reduction and move /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards -typedef Stats ButterflyHistory; +typedef Stats ButterflyHistory; /// At higher depths LowPlyHistory records successful quiet moves near the root /// and quiet moves which are/were in the PV (ttPv). It is cleared with each new @@ -108,12 +110,12 @@ typedef Stats PieceToHistory; typedef Stats ContinuationHistory; -/// MovePicker class is used to pick one pseudo legal move at a time from the +/// MovePicker class is used to pick one pseudo-legal move at a time from the /// current position. The most important method is next_move(), which returns a -/// new pseudo legal move each time it is called, until there are no moves left, -/// when MOVE_NONE is returned. In order to improve the efficiency of the alpha -/// beta algorithm, MovePicker attempts to return the moves which are most likely -/// to get a cut-off first. +/// new pseudo-legal move each time it is called, until there are no moves left, +/// when MOVE_NONE is returned. In order to improve the efficiency of the +/// alpha-beta algorithm, MovePicker attempts to return the moves which are most +/// likely to get a cut-off first. class MovePicker { enum PickType { Next, Best }; @@ -156,4 +158,6 @@ private: ExtMove moves[MAX_MOVES]; }; +} // namespace Stockfish + #endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/src/nnue/architectures/halfkp_256x2-32-32.h b/src/nnue/architectures/halfkp_256x2-32-32.h index 333feb83..21308368 100644 --- a/src/nnue/architectures/halfkp_256x2-32-32.h +++ b/src/nnue/architectures/halfkp_256x2-32-32.h @@ -1,6 +1,6 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ #include "nnue/layers/affine_transform.h" #include "nnue/layers/clipped_relu.h" -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using RawFeatures = Features::FeatureSet< @@ -49,6 +49,6 @@ namespace Eval::NNUE { using Network = Layers::OutputLayer; -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 569df292..bbc6ebc2 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -38,28 +38,7 @@ #include "evaluate_nnue.h" -namespace Eval::NNUE { - - const uint32_t kpp_board_index[PIECE_NB][COLOR_NB] = { - // convention: W - us, B - them - // viewed from other side, W and B are reversed - { PS_NONE, PS_NONE }, - { PS_W_PAWN, PS_B_PAWN }, - { PS_W_KNIGHT, PS_B_KNIGHT }, - { PS_W_BISHOP, PS_B_BISHOP }, - { PS_W_ROOK, PS_B_ROOK }, - { PS_W_QUEEN, PS_B_QUEEN }, - { PS_W_KING, PS_B_KING }, - { PS_NONE, PS_NONE }, - { PS_NONE, PS_NONE }, - { PS_B_PAWN, PS_W_PAWN }, - { PS_B_KNIGHT, PS_W_KNIGHT }, - { PS_B_BISHOP, PS_W_BISHOP }, - { PS_B_ROOK, PS_W_ROOK }, - { PS_B_QUEEN, PS_W_QUEEN }, - { PS_B_KING, PS_W_KING }, - { PS_NONE, PS_NONE } - }; +namespace Stockfish::Eval::NNUE { // Input feature converter LargePagePtr feature_transformer; @@ -293,66 +272,4 @@ void init() { } } -#undef stringify2 -#undef stringify -} - -/// NNUE::verify() verifies that the last net used was loaded successfully -void verify_eval_file_loaded() { - - std::string eval_file = std::string(Options["EvalFile"]); - - if (useNNUE != UseNNUEMode::False && eval_file_loaded != eval_file) - { - UCI::OptionsMap defaults; - UCI::init(defaults); - - std::string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; - std::string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(defaults["EvalFile"]); - std::string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; - - std::exit(EXIT_FAILURE); - } - - if (useNNUE != UseNNUEMode::False) - sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; - else - sync_cout << "info string classical evaluation enabled" << sync_endl; -} - -/// In training we override eval file so this is useful. -void verify_any_net_loaded() { - - if (!Options["SkipLoadingEval"] && useNNUE != UseNNUEMode::False && eval_file_loaded.empty()) - { - UCI::OptionsMap defaults; - UCI::init(defaults); - - std::string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; - std::string msg2 = "The option is set to true, but the network file was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - std::string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; - - std::exit(EXIT_FAILURE); - } - - if (useNNUE != UseNNUEMode::False) - sync_cout << "info string NNUE evaluation using " << eval_file_loaded << " enabled" << sync_endl; - else - sync_cout << "info string classical evaluation enabled" << sync_endl; -} - -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index a1051abe..b6070fae 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ #include -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { enum struct UseNNUEMode { @@ -63,48 +63,6 @@ namespace Eval::NNUE { template using LargePagePtr = std::unique_ptr>; - // Input feature converter - extern LargePagePtr feature_transformer; - - // Evaluation function - extern AlignedPtr network; - - // Evaluation function file name - extern std::string fileName; - - // Saved evaluation function file name - extern std::string savedfileName; - - extern UseNNUEMode useNNUE; - - extern std::string eval_file_loaded; - - // Get a string that represents the structure of the evaluation function - std::string get_architecture_string(); - - std::string get_layers_info(); - - // read the header - bool read_header(std::istream& stream, - std::uint32_t* hash_value, std::string* architecture); - - // write the header - bool write_header(std::ostream& stream, - std::uint32_t hash_value, const std::string& architecture); - - // read evaluation function parameters - bool ReadParameters(std::istream& stream); - - // write evaluation function parameters - bool WriteParameters(std::ostream& stream); - - Value evaluate(const Position& pos); - bool load_eval(std::string name, std::istream& stream); - void init(); - - void verify_eval_file_loaded(); - void verify_any_net_loaded(); - -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/nnue/features/feature_set.h b/src/nnue/features/feature_set.h index 6602eae7..c293661b 100644 --- a/src/nnue/features/feature_set.h +++ b/src/nnue/features/feature_set.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ #include "features_common.h" #include -namespace Eval::NNUE::Features { +namespace Stockfish::Eval::NNUE::Features { // Class template that represents a list of values template @@ -297,6 +297,6 @@ namespace Eval::NNUE::Features { friend class FeatureSet; }; -} // namespace Eval::NNUE::Features +} // namespace Stockfish::Eval::NNUE::Features #endif // #ifndef NNUE_FEATURE_SET_H_INCLUDED diff --git a/src/nnue/features/features_common.h b/src/nnue/features/features_common.h index 656502a3..0e2c0a84 100644 --- a/src/nnue/features/features_common.h +++ b/src/nnue/features/features_common.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ #include "../../evaluate.h" #include "../nnue_common.h" -namespace Eval::NNUE::Features { +namespace Stockfish::Eval::NNUE::Features { class IndexList; @@ -45,6 +45,6 @@ namespace Eval::NNUE::Features { kEnemy, // opponent }; -} // namespace Eval::NNUE::Features +} // namespace Stockfish::Eval::NNUE::Features #endif // #ifndef NNUE_FEATURES_COMMON_H_INCLUDED diff --git a/src/nnue/features/half_kp.cpp b/src/nnue/features/half_kp.cpp index 743a6378..612b42bd 100644 --- a/src/nnue/features/half_kp.cpp +++ b/src/nnue/features/half_kp.cpp @@ -1,6 +1,6 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,74 +21,66 @@ #include "half_kp.h" #include "index_list.h" -namespace Eval::NNUE::Features { +namespace Stockfish::Eval::NNUE::Features { - // Orient a square according to perspective (rotate the board 180° for black) - // this has to stay until we find a better arch that works with "flip". - // allows us to use current master net for gensfen (primarily needed for higher quality data) - inline Square orient(Color perspective, Square s) { - return Square(int(s) ^ (bool(perspective) * 63)); + // Orient a square according to perspective (rotates by 180 for black) + inline Square orient(Color perspective, Square s) { + return Square(int(s) ^ (bool(perspective) * 63)); + } + + // Index of a feature for a given king position and another piece on some square + inline IndexType make_index(Color perspective, Square s, Piece pc, Square ksq) { + return IndexType(orient(perspective, s) + kpp_board_index[perspective][pc] + PS_END * ksq); + } + + // Get a list of indices for active features + template + void HalfKP::AppendActiveIndices( + const Position& pos, Color perspective, IndexList* active) { + + Square ksq = orient(perspective, pos.square(perspective)); + Bitboard bb = pos.pieces() & ~pos.pieces(KING); + while (bb) + { + Square s = pop_lsb(bb); + active->push_back(make_index(perspective, s, pos.piece_on(s), ksq)); + } + } + + + // AppendChangedIndices() : get a list of indices for recently changed features + + // IMPORTANT: The `pos` in this function is pretty much useless as it + // is not always the position the features are updated to. The feature + // transformer code right now can update multiple accumulators per move, + // but since Stockfish only keeps the full state of the current leaf + // search position it is not possible to always pass here the position for + // which the accumulator is being updated. Therefore the only thing that + // can be reliably extracted from `pos` is the king square for the king + // of the `perspective` color (note: not even the other king's square will + // match reality in all cases, this is also the reason why `dp` is passed + // as a parameter and not extracted from pos.state()). This is of particular + // problem for future nets with other feature sets, where updating the active + // feature might require more information from the intermediate positions. In + // this case the only easy solution is to remove the multiple updates from + // the feature transformer update code and only update the accumulator for + // the current leaf position (the position after the move). + + template + void HalfKP::AppendChangedIndices( + const Position& pos, const DirtyPiece& dp, Color perspective, + IndexList* removed, IndexList* added) { + + Square ksq = orient(perspective, pos.square(perspective)); + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + if (type_of(pc) == KING) continue; + if (dp.from[i] != SQ_NONE) + removed->push_back(make_index(perspective, dp.from[i], pc, ksq)); + if (dp.to[i] != SQ_NONE) + added->push_back(make_index(perspective, dp.to[i], pc, ksq)); } - // Find the index of the feature quantity from the king position and PieceSquare - template - inline IndexType HalfKP::make_index( - Color perspective, - Square s, - Piece pc, - Square ksq) { + template class HalfKP; - return IndexType(orient(perspective, s) + kpp_board_index[pc][perspective] + PS_END * ksq); - } - - // Get a list of indices for active features - template - void HalfKP::append_active_indices( - const Position& pos, - Color perspective, - IndexList* active) { - - Square ksq = orient( - perspective, - pos.square( - AssociatedKing == Side::kFriend ? perspective : ~perspective)); - - Bitboard bb = pos.pieces() & ~pos.pieces(KING); - while (bb) { - Square s = pop_lsb(&bb); - active->push_back(make_index(perspective, s, pos.piece_on(s), ksq)); - } - } - - // Get a list of indices for recently changed features - template - void HalfKP::append_changed_indices( - const Position& pos, - Color perspective, - IndexList* removed, - IndexList* added) { - - Square ksq = orient( - perspective, - pos.square( - AssociatedKing == Side::kFriend ? perspective : ~perspective)); - - const auto& dp = pos.state()->dirtyPiece; - for (int i = 0; i < dp.dirty_num; ++i) { - Piece pc = dp.piece[i]; - - if (type_of(pc) == KING) - continue; - - if (dp.from[i] != SQ_NONE) - removed->push_back(make_index(perspective, dp.from[i], pc, ksq)); - - if (dp.to[i] != SQ_NONE) - added->push_back(make_index(perspective, dp.to[i], pc, ksq)); - } - } - - template class HalfKP; - template class HalfKP; - -} // namespace Eval::NNUE::Features +} // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_kp.h b/src/nnue/features/half_kp.h index 4a4329e8..3c94778d 100644 --- a/src/nnue/features/half_kp.h +++ b/src/nnue/features/half_kp.h @@ -1,6 +1,6 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,55 +21,36 @@ #include "features_common.h" -#include "evaluate.h" +namespace Stockfish::Eval::NNUE::Features { -//Definition of input features HalfKP of NNUE evaluation function -namespace Eval::NNUE::Features { + // Feature HalfKP: Combination of the position of own king + // and the position of pieces other than kings + template + class HalfKP { - // Feature HalfKP: Combination of the position of own king - // and the position of pieces other than kings - template - class HalfKP { + public: + // Feature name + static constexpr const char* kName = "HalfKP(Friend)"; + // Hash value embedded in the evaluation file + static constexpr std::uint32_t kHashValue = + 0x5D69D5B9u ^ (AssociatedKing == Side::kFriend); + // Number of feature dimensions + static constexpr IndexType kDimensions = + static_cast(SQUARE_NB) * static_cast(PS_END); + // Maximum number of simultaneously active features + static constexpr IndexType kMaxActiveDimensions = 30; // Kings don't count + // Trigger for full calculation instead of difference calculation + static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kFriendKingMoved; - public: - // Feature name - static constexpr const char* kName = (AssociatedKing == Side::kFriend) ? - "HalfKP(Friend)" : "HalfKP(Enemy)"; + // Get a list of indices for active features + static void AppendActiveIndices(const Position& pos, Color perspective, + IndexList* active); - // Hash value embedded in the evaluation file - static constexpr std::uint32_t kHashValue = - 0x5D69D5B9u ^ (AssociatedKing == Side::kFriend); + // Get a list of indices for recently changed features + static void AppendChangedIndices(const Position& pos, const DirtyPiece& dp, Color perspective, + IndexList* removed, IndexList* added); + }; - // Number of feature dimensions - static constexpr IndexType kDimensions = - static_cast(SQUARE_NB) * static_cast(PS_END); - - // Maximum number of simultaneously active features - static constexpr IndexType kMaxActiveDimensions = 30; // Kings don't count - - // Trigger for full calculation instead of difference calculation - static constexpr TriggerEvent kRefreshTrigger = - (AssociatedKing == Side::kFriend) ? - TriggerEvent::kFriendKingMoved : TriggerEvent::kEnemyKingMoved; - - // Get a list of indices for active features - static void append_active_indices( - const Position& pos, - Color perspective, - IndexList* active); - - // Get a list of indices for recently changed features - static void append_changed_indices( - const Position& pos, - Color perspective, - IndexList* removed, - IndexList* added); - - private: - // Index of a feature for a given king position and another piece on some square - static IndexType make_index(Color perspective, Square s, Piece pc, Square sq_k); - }; - -} // namespace Eval::NNUE::Features +} // namespace Stockfish::Eval::NNUE::Features #endif // #ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED diff --git a/src/nnue/features/index_list.h b/src/nnue/features/index_list.h index d9ad680a..9f03993b 100644 --- a/src/nnue/features/index_list.h +++ b/src/nnue/features/index_list.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ #include "../../position.h" #include "../nnue_architecture.h" -namespace Eval::NNUE::Features { +namespace Stockfish::Eval::NNUE::Features { // Class template used for feature index list template @@ -59,6 +59,6 @@ namespace Eval::NNUE::Features { : public ValueList { }; -} // namespace Eval::NNUE::Features +} // namespace Stockfish::Eval::NNUE::Features #endif // NNUE_FEATURES_INDEX_LIST_H_INCLUDED diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 1f2ff7c5..b6fb5928 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,11 +24,7 @@ #include #include "../nnue_common.h" -#include -#include -#include - -namespace Eval::NNUE::Layers { +namespace Stockfish::Eval::NNUE::Layers { // Affine transformation layer template @@ -45,6 +41,11 @@ namespace Eval::NNUE::Layers { static constexpr IndexType kOutputDimensions = OutputDimensions; static constexpr IndexType kPaddedInputDimensions = CeilToMultiple(kInputDimensions, kMaxSimdWidth); +#if defined (USE_AVX512) + static constexpr const IndexType kOutputSimdWidth = kSimdWidth / 2; +#elif defined (USE_SSSE3) + static constexpr const IndexType kOutputSimdWidth = kSimdWidth / 4; +#endif // Size of forward propagation buffer used in this layer static constexpr std::size_t kSelfBufferSize = @@ -92,7 +93,58 @@ namespace Eval::NNUE::Layers { for (std::size_t i = 0; i < kOutputDimensions; ++i) biases_[i] = read_little_endian(stream); for (std::size_t i = 0; i < kOutputDimensions * kPaddedInputDimensions; ++i) +#if !defined (USE_SSSE3) weights_[i] = read_little_endian(stream); +#else + weights_[ + (i / 4) % (kPaddedInputDimensions / 4) * kOutputDimensions * 4 + + i / kPaddedInputDimensions * 4 + + i % 4 + ] = read_little_endian(stream); + + // Determine if eights of weight and input products can be summed using 16bits + // without saturation. We assume worst case combinations of 0 and 127 for all inputs. + if (kOutputDimensions > 1 && !stream.fail()) + { + canSaturate16.count = 0; +#if !defined(USE_VNNI) + for (IndexType i = 0; i < kPaddedInputDimensions; i += 16) + for (IndexType j = 0; j < kOutputDimensions; ++j) + for (int x = 0; x < 2; ++x) + { + WeightType* w = &weights_[i * kOutputDimensions + j * 4 + x * 2]; + int sum[2] = {0, 0}; + for (int k = 0; k < 8; ++k) + { + IndexType idx = k / 2 * kOutputDimensions * 4 + k % 2; + sum[w[idx] < 0] += w[idx]; + } + for (int sign : { -1, 1 }) + while (sign * sum[sign == -1] > 258) + { + int maxK = 0, maxW = 0; + for (int k = 0; k < 8; ++k) + { + IndexType idx = k / 2 * kOutputDimensions * 4 + k % 2; + if (maxW < sign * w[idx]) + maxK = k, maxW = sign * w[idx]; + } + + IndexType idx = maxK / 2 * kOutputDimensions * 4 + maxK % 2; + sum[sign == -1] -= w[idx]; + canSaturate16.add(j, i + maxK / 2 * 4 + maxK % 2 + x * 2, w[idx]); + w[idx] = 0; + } + } + + // Non functional optimization for faster more linear access + std::sort(canSaturate16.ids, canSaturate16.ids + canSaturate16.count, + [](const typename CanSaturate::Entry& e1, const typename CanSaturate::Entry& e2) + { return e1.in == e2.in ? e1.out < e2.out : e1.in < e2.in; }); +#endif + } +#endif + return !stream.fail(); } @@ -125,111 +177,33 @@ namespace Eval::NNUE::Layers { return _mm512_reduce_add_epi32(sum) + bias; }; - // This function takes - // sum0 = [xmm0a, xmm0b, xmm0c, xmm0d] - // sum1 = [xmm1a, xmm1b, xmm1c, xmm1d] - // sum2 = [xmm2a, xmm2b, xmm2c, xmm2d] - // sum3 = [xmm3a, xmm3b, xmm3c, xmm3d] - // and returns - // ret = [ - // reduce_add_epi32(xmm0a), reduce_add_epi32(xmm1a), reduce_add_epi32(xmm2a), reduce_add_epi32(xmm3a), - // reduce_add_epi32(xmm0b), reduce_add_epi32(xmm1b), reduce_add_epi32(xmm2b), reduce_add_epi32(xmm3b), - // reduce_add_epi32(xmm0c), reduce_add_epi32(xmm1c), reduce_add_epi32(xmm2c), reduce_add_epi32(xmm3c), - // reduce_add_epi32(xmm0d), reduce_add_epi32(xmm1d), reduce_add_epi32(xmm2d), reduce_add_epi32(xmm3d) - // ] - [[maybe_unused]] auto m512_hadd128x16_interleave = []( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) -> __m512i { - - __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1); - __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1); - - __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3); - __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3); - - __m512i sum01 = _mm512_add_epi32(sum01a, sum01b); - __m512i sum23 = _mm512_add_epi32(sum23a, sum23b); - - __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23); - __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23); - - return _mm512_add_epi32(sum0123a, sum0123b); - }; - - [[maybe_unused]] auto m512_haddx4 = [m512_hadd128x16_interleave]( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3, __m128i bias) -> __m128i { - - __m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3); - - __m256i sum256lo = _mm512_castsi512_si256(sum); - __m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1); - - sum256lo = _mm256_add_epi32(sum256lo, sum256hi); - - __m128i sum128lo = _mm256_castsi256_si128(sum256lo); - __m128i sum128hi = _mm256_extracti128_si256(sum256lo, 1); - - return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias); - }; - - [[maybe_unused]] auto m512_haddx8 = [m512_hadd128x16_interleave]( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3, - __m512i sum4, __m512i sum5, __m512i sum6, __m512i sum7, __m256i bias) -> __m256i { - - __m512i suma = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3); - __m512i sumb = m512_hadd128x16_interleave(sum4, sum5, sum6, sum7); - - __m512i indices0 = _mm512_setr_epi64(0, 1, 8, 9, 4, 5, 12, 13); - __m512i indices1 = _mm512_setr_epi64(2, 3, 10, 11, 6, 7, 14, 15); - __m512i x = _mm512_add_epi32( - _mm512_permutex2var_epi64(suma, indices0, sumb), - _mm512_permutex2var_epi64(suma, indices1, sumb)); - - __m256i sum256lo = _mm512_castsi512_si256(x); - __m256i sum256hi = _mm512_extracti64x4_epi64(x, 1); - - return _mm256_add_epi32(_mm256_add_epi32(sum256lo, sum256hi), bias); - }; - - [[maybe_unused]] auto m512_hadd256x8 =[m512_hadd128x16_interleave]( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3, __m256i bias) -> __m256i { - - __m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3); - - __m512i indices = _mm512_setr_epi32( - 0, 4, 8, 12, 2, 6, 10, 14, - 1, 5, 9, 13, 3, 7, 11, 15); - sum = _mm512_permutexvar_epi32(indices, sum); - - __m256i sum256lo = _mm512_castsi512_si256(sum); - __m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1); - - return _mm256_add_epi32(_mm256_hadd_epi32(sum256lo, sum256hi), bias); - }; - - [[maybe_unused]] auto m512_hadd256x16 = [m512_hadd128x16_interleave]( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3, - __m512i sum4, __m512i sum5, __m512i sum6, __m512i sum7, __m512i bias) -> __m512i { - - __m512i suma = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3); - __m512i sumb = m512_hadd128x16_interleave(sum4, sum5, sum6, sum7); - - __m512i indices0 = _mm512_setr_epi64(0, 1, 8, 9, 4, 5, 12, 13); - __m512i indices1 = _mm512_setr_epi64(2, 3, 10, 11, 6, 7, 14, 15); - __m512i x = _mm512_add_epi32( - _mm512_permutex2var_epi64(suma, indices0, sumb), - _mm512_permutex2var_epi64(suma, indices1, sumb)); - - __m512i indices = _mm512_setr_epi32(0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15); - return _mm512_add_epi32(_mm512_permutexvar_epi32(indices, x), bias); - }; - -#if defined (USE_VNNI) [[maybe_unused]] auto m512_add_dpbusd_epi32 = [=](__m512i& acc, __m512i a, __m512i b) { +#if defined (USE_VNNI) acc = _mm512_dpbusd_epi32(acc, a, b); #else - [[maybe_unused]] auto m512_dpbusd_epi32 = [=](__m512i a, __m512i b) -> __m512i { __m512i product0 = _mm512_maddubs_epi16(a, b); - return _mm512_madd_epi16(product0, kOnes512); + product0 = _mm512_madd_epi16(product0, kOnes512); + acc = _mm512_add_epi32(acc, product0); +#endif + }; + + [[maybe_unused]] auto m512_add_dpbusd_epi32x4 = [=](__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1, + __m512i a2, __m512i b2, __m512i a3, __m512i b3) { +#if defined (USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a0, b0); + acc = _mm512_dpbusd_epi32(acc, a1, b1); + acc = _mm512_dpbusd_epi32(acc, a2, b2); + acc = _mm512_dpbusd_epi32(acc, a3, b3); +#else + __m512i product0 = _mm512_maddubs_epi16(a0, b0); + __m512i product1 = _mm512_maddubs_epi16(a1, b1); + __m512i product2 = _mm512_maddubs_epi16(a2, b2); + __m512i product3 = _mm512_maddubs_epi16(a3, b3); + product0 = _mm512_add_epi16(product0, product1); + product2 = _mm512_add_epi16(product2, product3); + product0 = _mm512_add_epi16(product0, product2); + product0 = _mm512_madd_epi16(product0, kOnes512); + acc = _mm512_add_epi32(acc, product0); #endif }; @@ -245,29 +219,37 @@ namespace Eval::NNUE::Layers { return _mm_cvtsi128_si32(sum128) + bias; }; - [[maybe_unused]] auto m256_haddx4 = [](__m256i sum0, __m256i sum1, __m256i sum2, __m256i sum3, __m128i bias) -> __m128i { - sum0 = _mm256_hadd_epi32(sum0, sum1); - sum2 = _mm256_hadd_epi32(sum2, sum3); - - sum0 = _mm256_hadd_epi32(sum0, sum2); - - __m128i sum128lo = _mm256_castsi256_si128(sum0); - __m128i sum128hi = _mm256_extracti128_si256(sum0, 1); - - return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias); - }; -#if defined (USE_VNNI) [[maybe_unused]] auto m256_add_dpbusd_epi32 = [=](__m256i& acc, __m256i a, __m256i b) { +#if defined (USE_VNNI) acc = _mm256_dpbusd_epi32(acc, a, b); #else - [[maybe_unused]] auto m256_dpbusd_epi32 = [=](__m256i a, __m256i b) -> __m256i { __m256i product0 = _mm256_maddubs_epi16(a, b); - return _mm256_madd_epi16(product0, kOnes256); + product0 = _mm256_madd_epi16(product0, kOnes256); + acc = _mm256_add_epi32(acc, product0); +#endif + }; + + [[maybe_unused]] auto m256_add_dpbusd_epi32x4 = [=](__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1, + __m256i a2, __m256i b2, __m256i a3, __m256i b3) { +#if defined (USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a0, b0); + acc = _mm256_dpbusd_epi32(acc, a1, b1); + acc = _mm256_dpbusd_epi32(acc, a2, b2); + acc = _mm256_dpbusd_epi32(acc, a3, b3); +#else + __m256i product0 = _mm256_maddubs_epi16(a0, b0); + __m256i product1 = _mm256_maddubs_epi16(a1, b1); + __m256i product2 = _mm256_maddubs_epi16(a2, b2); + __m256i product3 = _mm256_maddubs_epi16(a3, b3); + product0 = _mm256_add_epi16(product0, product1); + product2 = _mm256_add_epi16(product2, product3); + product0 = _mm256_add_epi16(product0, product2); + product0 = _mm256_madd_epi16(product0, kOnes256); + acc = _mm256_add_epi32(acc, product0); #endif }; #endif - #if defined (USE_SSSE3) [[maybe_unused]] const __m128i kOnes128 = _mm_set1_epi16(1); @@ -278,413 +260,119 @@ namespace Eval::NNUE::Layers { return _mm_cvtsi128_si32(sum) + bias; }; - [[maybe_unused]] auto m128_haddx4 = [](__m128i sum0, __m128i sum1, __m128i sum2, __m128i sum3, __m128i bias) -> __m128i { - sum0 = _mm_hadd_epi32(sum0, sum1); - sum2 = _mm_hadd_epi32(sum2, sum3); - - sum0 = _mm_hadd_epi32(sum0, sum2); - - return _mm_add_epi32(sum0, bias); + [[maybe_unused]] auto m128_add_dpbusd_epi32 = [=](__m128i& acc, __m128i a, __m128i b) { + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, kOnes128); + acc = _mm_add_epi32(acc, product0); }; - [[maybe_unused]] auto m128_dpbusd_epi32 = [=](__m128i a, __m128i b) -> __m128i { - __m128i product0 = _mm_maddubs_epi16(a, b); - return _mm_madd_epi16(product0, kOnes128); + [[maybe_unused]] auto m128_add_dpbusd_epi32x4 = [=](__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1, + __m128i a2, __m128i b2, __m128i a3, __m128i b3) { + __m128i product0 = _mm_maddubs_epi16(a0, b0); + __m128i product1 = _mm_maddubs_epi16(a1, b1); + __m128i product2 = _mm_maddubs_epi16(a2, b2); + __m128i product3 = _mm_maddubs_epi16(a3, b3); + product0 = _mm_add_epi16(product0, product1); + product2 = _mm_add_epi16(product2, product3); + product0 = _mm_add_epi16(product0, product2); + product0 = _mm_madd_epi16(product0, kOnes128); + acc = _mm_add_epi32(acc, product0); }; #endif #if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + auto& vec_add_dpbusd_32 = m512_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m512_add_dpbusd_epi32x4; + auto& vec_hadd = m512_hadd; +#elif defined (USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + auto& vec_add_dpbusd_32 = m256_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m256_add_dpbusd_epi32x4; + auto& vec_hadd = m256_hadd; +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + auto& vec_add_dpbusd_32 = m128_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m128_add_dpbusd_epi32x4; + auto& vec_hadd = m128_hadd; +#endif - constexpr IndexType kNumChunks512 = kPaddedInputDimensions / (kSimdWidth * 2); - constexpr IndexType kNumChunks256 = kPaddedInputDimensions / kSimdWidth; +#if defined (USE_SSSE3) const auto output = reinterpret_cast(buffer); + const auto input_vector = reinterpret_cast(input); - // Since to saturate a zmm register it takes 64 bytes we - // cannot use AVX512 for the smaller affine transforms. - // Instead we fallback to a AVX2 implementation if the - // kInputDimensions isn't a multiple of 64. - // Note that this means that for example for - // kInputDimensions of 96 we fallback to AVX2 even though - // the first 64 elements could be processed with AVX512. - // This is caused by mixing the __m256 and __m512 variables - // required to better handle that case and it would - // require handling more cases statically not to lose performance. - // This should be revisited if such input dimensions are to be considered. - [[maybe_unused]] const auto input_vector512 = reinterpret_cast(input); - [[maybe_unused]] const auto input_vector256 = reinterpret_cast(input); + static_assert(kOutputDimensions % kOutputSimdWidth == 0 || kOutputDimensions == 1); // kOutputDimensions is either 1 or a multiple of kSimdWidth // because then it is also an input dimension. - if constexpr (kOutputDimensions % 16 == 0 && kNumChunks256 == 1) + if constexpr (kOutputDimensions % kOutputSimdWidth == 0) { - for (IndexType i = 0; i < kOutputDimensions; i += 16) - { - const IndexType offset01a = (i + 0) * kPaddedInputDimensions; - const IndexType offset23a = (i + 2) * kPaddedInputDimensions; - const IndexType offset45a = (i + 4) * kPaddedInputDimensions; - const IndexType offset67a = (i + 6) * kPaddedInputDimensions; - const IndexType offset01b = (i + 8) * kPaddedInputDimensions; - const IndexType offset23b = (i + 10) * kPaddedInputDimensions; - const IndexType offset45b = (i + 12) * kPaddedInputDimensions; - const IndexType offset67b = (i + 14) * kPaddedInputDimensions; + constexpr IndexType kNumChunks = kPaddedInputDimensions / 4; - const __m512i bias = *reinterpret_cast(&biases_[i]); - __m512i* outptr = reinterpret_cast<__m512i*>(&output[i]); + const auto input32 = reinterpret_cast(input); + vec_t* outptr = reinterpret_cast(output); + std::memcpy(output, biases_, kOutputDimensions * sizeof(OutputType)); - const auto row01a = *reinterpret_cast(&weights_[offset01a]); - const auto row23a = *reinterpret_cast(&weights_[offset23a]); - const auto row45a = *reinterpret_cast(&weights_[offset45a]); - const auto row67a = *reinterpret_cast(&weights_[offset67a]); - const auto row01b = *reinterpret_cast(&weights_[offset01b]); - const auto row23b = *reinterpret_cast(&weights_[offset23b]); - const auto row45b = *reinterpret_cast(&weights_[offset45b]); - const auto row67b = *reinterpret_cast(&weights_[offset67b]); - - const __m256i in256 = input_vector256[0]; - const __m512i in = _mm512_inserti64x4(_mm512_castsi256_si512(in256), in256, 1); - -#if defined (USE_VNNI) - __m512i sum01a = _mm512_setzero_si512(); - __m512i sum23a = _mm512_setzero_si512(); - __m512i sum45a = _mm512_setzero_si512(); - __m512i sum67a = _mm512_setzero_si512(); - __m512i sum01b = _mm512_setzero_si512(); - __m512i sum23b = _mm512_setzero_si512(); - __m512i sum45b = _mm512_setzero_si512(); - __m512i sum67b = _mm512_setzero_si512(); - - m512_add_dpbusd_epi32(sum01a, in, row01a); - m512_add_dpbusd_epi32(sum23a, in, row23a); - m512_add_dpbusd_epi32(sum45a, in, row45a); - m512_add_dpbusd_epi32(sum67a, in, row67a); - m512_add_dpbusd_epi32(sum01b, in, row01b); - m512_add_dpbusd_epi32(sum23b, in, row23b); - m512_add_dpbusd_epi32(sum45b, in, row45b); - m512_add_dpbusd_epi32(sum67b, in, row67b); -#else - __m512i sum01a = m512_dpbusd_epi32(in, row01a); - __m512i sum23a = m512_dpbusd_epi32(in, row23a); - __m512i sum45a = m512_dpbusd_epi32(in, row45a); - __m512i sum67a = m512_dpbusd_epi32(in, row67a); - __m512i sum01b = m512_dpbusd_epi32(in, row01b); - __m512i sum23b = m512_dpbusd_epi32(in, row23b); - __m512i sum45b = m512_dpbusd_epi32(in, row45b); - __m512i sum67b = m512_dpbusd_epi32(in, row67b); -#endif - - *outptr = m512_hadd256x16( - sum01a, sum23a, sum45a, sum67a, - sum01b, sum23b, sum45b, sum67b, bias); - } - } - else if constexpr (kOutputDimensions % 4 == 0) - { - for (IndexType i = 0; i < kOutputDimensions; i += 4) - { - const IndexType offset0 = (i + 0) * kPaddedInputDimensions; - const IndexType offset1 = (i + 1) * kPaddedInputDimensions; - const IndexType offset2 = (i + 2) * kPaddedInputDimensions; - const IndexType offset3 = (i + 3) * kPaddedInputDimensions; - - const __m128i bias = *reinterpret_cast(&biases_[i]); - __m128i* outptr = reinterpret_cast<__m128i*>(&output[i]); - - if constexpr (kPaddedInputDimensions % (kSimdWidth * 2) == 0) + for (int i = 0; i < (int)kNumChunks - 3; i += 4) { - const auto row0 = reinterpret_cast(&weights_[offset0]); - const auto row1 = reinterpret_cast(&weights_[offset1]); - const auto row2 = reinterpret_cast(&weights_[offset2]); - const auto row3 = reinterpret_cast(&weights_[offset3]); + const vec_t in0 = vec_set_32(input32[i + 0]); + const vec_t in1 = vec_set_32(input32[i + 1]); + const vec_t in2 = vec_set_32(input32[i + 2]); + const vec_t in3 = vec_set_32(input32[i + 3]); + const auto col0 = reinterpret_cast(&weights_[(i + 0) * kOutputDimensions * 4]); + const auto col1 = reinterpret_cast(&weights_[(i + 1) * kOutputDimensions * 4]); + const auto col2 = reinterpret_cast(&weights_[(i + 2) * kOutputDimensions * 4]); + const auto col3 = reinterpret_cast(&weights_[(i + 3) * kOutputDimensions * 4]); + for (int j = 0; j * kOutputSimdWidth < kOutputDimensions; ++j) + vec_add_dpbusd_32x4(outptr[j], in0, col0[j], in1, col1[j], in2, col2[j], in3, col3[j]); + } + for (int i = 0; i < canSaturate16.count; ++i) + output[canSaturate16.ids[i].out] += input[canSaturate16.ids[i].in] * canSaturate16.ids[i].w; + } + else if constexpr (kOutputDimensions == 1) + { +#if defined (USE_AVX512) + if constexpr (kPaddedInputDimensions % (kSimdWidth * 2) != 0) + { + constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; + const auto input_vector256 = reinterpret_cast(input); -#if defined (USE_VNNI) - __m512i sum0 = _mm512_setzero_si512(); - __m512i sum1 = _mm512_setzero_si512(); - __m512i sum2 = _mm512_setzero_si512(); - __m512i sum3 = _mm512_setzero_si512(); - const IndexType kStart = 0; -#else - __m512i sum0 = m512_dpbusd_epi32(input_vector512[0], row0[0]); - __m512i sum1 = m512_dpbusd_epi32(input_vector512[0], row1[0]); - __m512i sum2 = m512_dpbusd_epi32(input_vector512[0], row2[0]); - __m512i sum3 = m512_dpbusd_epi32(input_vector512[0], row3[0]); - const IndexType kStart = 1; -#endif + __m256i sum0 = _mm256_setzero_si256(); + const auto row0 = reinterpret_cast(&weights_[0]); - for (IndexType j = kStart; j < kNumChunks512; ++j) - { - const __m512i in = input_vector512[j]; - -#if defined (USE_VNNI) - m512_add_dpbusd_epi32(sum0, in, row0[j]); - m512_add_dpbusd_epi32(sum1, in, row1[j]); - m512_add_dpbusd_epi32(sum2, in, row2[j]); - m512_add_dpbusd_epi32(sum3, in, row3[j]); -#else - sum0 = _mm512_add_epi32(sum0, m512_dpbusd_epi32(in, row0[j])); - sum1 = _mm512_add_epi32(sum1, m512_dpbusd_epi32(in, row1[j])); - sum2 = _mm512_add_epi32(sum2, m512_dpbusd_epi32(in, row2[j])); - sum3 = _mm512_add_epi32(sum3, m512_dpbusd_epi32(in, row3[j])); -#endif - } - - *outptr = m512_haddx4(sum0, sum1, sum2, sum3, bias); + for (int j = 0; j < (int)kNumChunks; ++j) + { + const __m256i in = input_vector256[j]; + m256_add_dpbusd_epi32(sum0, in, row0[j]); + } + output[0] = m256_hadd(sum0, biases_[0]); } else +#endif { - const auto row0 = reinterpret_cast(&weights_[offset0]); - const auto row1 = reinterpret_cast(&weights_[offset1]); - const auto row2 = reinterpret_cast(&weights_[offset2]); - const auto row3 = reinterpret_cast(&weights_[offset3]); - -#if defined (USE_VNNI) - __m256i sum0 = _mm256_setzero_si256(); - __m256i sum1 = _mm256_setzero_si256(); - __m256i sum2 = _mm256_setzero_si256(); - __m256i sum3 = _mm256_setzero_si256(); - const IndexType kStart = 0; +#if defined (USE_AVX512) + constexpr IndexType kNumChunks = kPaddedInputDimensions / (kSimdWidth * 2); #else - __m256i sum0 = m256_dpbusd_epi32(input_vector256[0], row0[0]); - __m256i sum1 = m256_dpbusd_epi32(input_vector256[0], row1[0]); - __m256i sum2 = m256_dpbusd_epi32(input_vector256[0], row2[0]); - __m256i sum3 = m256_dpbusd_epi32(input_vector256[0], row3[0]); - const IndexType kStart = 1; + constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; #endif + vec_t sum0 = vec_setzero(); + const auto row0 = reinterpret_cast(&weights_[0]); - for (IndexType j = kStart; j < kNumChunks256; ++j) - { - const __m256i in = input_vector256[j]; - -#if defined (USE_VNNI) - m256_add_dpbusd_epi32(sum0, in, row0[j]); - m256_add_dpbusd_epi32(sum1, in, row1[j]); - m256_add_dpbusd_epi32(sum2, in, row2[j]); - m256_add_dpbusd_epi32(sum3, in, row3[j]); -#else - sum0 = _mm256_add_epi32(sum0, m256_dpbusd_epi32(in, row0[j])); - sum1 = _mm256_add_epi32(sum1, m256_dpbusd_epi32(in, row1[j])); - sum2 = _mm256_add_epi32(sum2, m256_dpbusd_epi32(in, row2[j])); - sum3 = _mm256_add_epi32(sum3, m256_dpbusd_epi32(in, row3[j])); -#endif - } - - *outptr = m256_haddx4(sum0, sum1, sum2, sum3, bias); + for (int j = 0; j < (int)kNumChunks; ++j) + { + const vec_t in = input_vector[j]; + vec_add_dpbusd_32(sum0, in, row0[j]); + } + output[0] = vec_hadd(sum0, biases_[0]); } - } - } - else if constexpr (kOutputDimensions == 1) - { - if constexpr (kPaddedInputDimensions % (kSimdWidth * 2) == 0) - { - const auto row0 = reinterpret_cast(&weights_[0]); - -#if defined (USE_VNNI) - __m512i sum0 = _mm512_setzero_si512(); - const IndexType kStart = 0; -#else - __m512i sum0 = m512_dpbusd_epi32(input_vector512[0], row0[0]); - const IndexType kStart = 1; -#endif - - for (IndexType j = kStart; j < kNumChunks512; ++j) - { - const __m512i in = input_vector512[j]; - -#if defined (USE_VNNI) - m512_add_dpbusd_epi32(sum0, in, row0[j]); -#else - sum0 = _mm512_add_epi32(sum0, m512_dpbusd_epi32(in, row0[j])); -#endif - } - - output[0] = m512_hadd(sum0, biases_[0]); - } - else - { - const auto row0 = reinterpret_cast(&weights_[0]); - -#if defined (USE_VNNI) - __m256i sum0 = _mm256_setzero_si256(); - const IndexType kStart = 0; -#else - __m256i sum0 = m256_dpbusd_epi32(input_vector256[0], row0[0]); - const IndexType kStart = 1; -#endif - - for (IndexType j = kStart; j < kNumChunks256; ++j) - { - const __m256i in = input_vector256[j]; - -#if defined (USE_VNNI) - m256_add_dpbusd_epi32(sum0, in, row0[j]); -#else - sum0 = _mm256_add_epi32(sum0, m256_dpbusd_epi32(in, row0[j])); -#endif - } - - output[0] = m256_hadd(sum0, biases_[0]); - } - } - else - { - // This case can never happen because kOutputDimensions - // is always 1 or a multiple of kSimdWidth. - assert(false); - } - -#elif defined (USE_AVX2) - - constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; - - const auto output = reinterpret_cast(buffer); - const auto input_vector = reinterpret_cast(input); - - // kOutputDimensions is either 1 or a multiple of kSimdWidth - // because then it is also an input dimension. - if constexpr (kOutputDimensions % 4 == 0) - { - for (IndexType i = 0; i < kOutputDimensions; i += 4) - { - const IndexType offset0 = (i + 0) * kPaddedInputDimensions; - const IndexType offset1 = (i + 1) * kPaddedInputDimensions; - const IndexType offset2 = (i + 2) * kPaddedInputDimensions; - const IndexType offset3 = (i + 3) * kPaddedInputDimensions; - - const __m128i bias = *reinterpret_cast(&biases_[i]); - __m128i* outptr = reinterpret_cast<__m128i*>(&output[i]); - - const auto row0 = reinterpret_cast(&weights_[offset0]); - const auto row1 = reinterpret_cast(&weights_[offset1]); - const auto row2 = reinterpret_cast(&weights_[offset2]); - const auto row3 = reinterpret_cast(&weights_[offset3]); - -#if defined (USE_VNNI) - __m256i sum0 = _mm256_setzero_si256(); - __m256i sum1 = _mm256_setzero_si256(); - __m256i sum2 = _mm256_setzero_si256(); - __m256i sum3 = _mm256_setzero_si256(); - const IndexType kStart = 0; -#else - __m256i sum0 = m256_dpbusd_epi32(input_vector[0], row0[0]); - __m256i sum1 = m256_dpbusd_epi32(input_vector[0], row1[0]); - __m256i sum2 = m256_dpbusd_epi32(input_vector[0], row2[0]); - __m256i sum3 = m256_dpbusd_epi32(input_vector[0], row3[0]); - const IndexType kStart = 1; -#endif - - for (IndexType j = kStart; j < kNumChunks; ++j) - { - const __m256i in = input_vector[j]; - -#if defined (USE_VNNI) - m256_add_dpbusd_epi32(sum0, in, row0[j]); - m256_add_dpbusd_epi32(sum1, in, row1[j]); - m256_add_dpbusd_epi32(sum2, in, row2[j]); - m256_add_dpbusd_epi32(sum3, in, row3[j]); -#else - sum0 = _mm256_add_epi32(sum0, m256_dpbusd_epi32(in, row0[j])); - sum1 = _mm256_add_epi32(sum1, m256_dpbusd_epi32(in, row1[j])); - sum2 = _mm256_add_epi32(sum2, m256_dpbusd_epi32(in, row2[j])); - sum3 = _mm256_add_epi32(sum3, m256_dpbusd_epi32(in, row3[j])); -#endif - } - - *outptr = m256_haddx4(sum0, sum1, sum2, sum3, bias); - } - } - else if constexpr (kOutputDimensions == 1) - { - const auto row0 = reinterpret_cast(&weights_[0]); - -#if defined (USE_VNNI) - __m256i sum0 = _mm256_setzero_si256(); - const IndexType kStart = 0; -#else - __m256i sum0 = m256_dpbusd_epi32(input_vector[0], row0[0]); - const IndexType kStart = 1; -#endif - - for (IndexType j = kStart; j < kNumChunks; ++j) - { - const __m256i in = input_vector[j]; - -#if defined (USE_VNNI) - m256_add_dpbusd_epi32(sum0, in, row0[j]); -#else - sum0 = _mm256_add_epi32(sum0, m256_dpbusd_epi32(in, row0[j])); -#endif - } - - output[0] = m256_hadd(sum0, biases_[0]); - } - else - { - // This case can never happen because kOutputDimensions - // is always 1 or a multiple of kSimdWidth. - assert(false); - } - -#elif defined (USE_SSSE3) - - constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; - - auto output = reinterpret_cast(buffer); - const auto input_vector = reinterpret_cast(input); - - // kOutputDimensions is either 1 or a multiple of kSimdWidth - // because then it is also an input dimension. - if constexpr (kOutputDimensions % 4 == 0) - { - for (IndexType i = 0; i < kOutputDimensions; i += 4) - { - const IndexType offset0 = (i + 0) * kPaddedInputDimensions; - const IndexType offset1 = (i + 1) * kPaddedInputDimensions; - const IndexType offset2 = (i + 2) * kPaddedInputDimensions; - const IndexType offset3 = (i + 3) * kPaddedInputDimensions; - - const __m128i bias = *reinterpret_cast(&biases_[i]); - __m128i* outptr = reinterpret_cast<__m128i*>(&output[i]); - - const auto row0 = reinterpret_cast(&weights_[offset0]); - const auto row1 = reinterpret_cast(&weights_[offset1]); - const auto row2 = reinterpret_cast(&weights_[offset2]); - const auto row3 = reinterpret_cast(&weights_[offset3]); - - __m128i sum0 = m128_dpbusd_epi32(input_vector[0], row0[0]); - __m128i sum1 = m128_dpbusd_epi32(input_vector[0], row1[0]); - __m128i sum2 = m128_dpbusd_epi32(input_vector[0], row2[0]); - __m128i sum3 = m128_dpbusd_epi32(input_vector[0], row3[0]); - - for (int j = 1; j < (int)kNumChunks; ++j) - { - const __m128i in = input_vector[j]; - - sum0 = _mm_add_epi32(sum0, m128_dpbusd_epi32(in, row0[j])); - sum1 = _mm_add_epi32(sum1, m128_dpbusd_epi32(in, row1[j])); - sum2 = _mm_add_epi32(sum2, m128_dpbusd_epi32(in, row2[j])); - sum3 = _mm_add_epi32(sum3, m128_dpbusd_epi32(in, row3[j])); - } - - *outptr = m128_haddx4(sum0, sum1, sum2, sum3, bias); - } - } - else if constexpr (kOutputDimensions == 1) - { - const auto row0 = reinterpret_cast(&weights_[0]); - - __m128i sum0 = m128_dpbusd_epi32(input_vector[0], row0[0]); - - for (int j = 1; j < (int)kNumChunks; ++j) - sum0 = _mm_add_epi32(sum0, m128_dpbusd_epi32(input_vector[j], row0[j])); - - output[0] = m128_hadd(sum0, biases_[0]); - } - else - { - // This case can never happen because kOutputDimensions - // is always 1 or a multiple of kSimdWidth. - assert(false); } #else @@ -695,11 +383,7 @@ namespace Eval::NNUE::Layers { #if defined(USE_SSE2) constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth; -#ifndef USE_SSSE3 const __m128i kZeros = _mm_setzero_si128(); -#else - const __m128i kOnes = _mm_set1_epi16(1); -#endif const auto input_vector = reinterpret_cast(input); #elif defined(USE_MMX) @@ -722,9 +406,8 @@ namespace Eval::NNUE::Layers { for (IndexType j = 0; j < kNumChunks; ++j) { __m128i row_j = _mm_load_si128(&row[j]); __m128i input_j = _mm_load_si128(&input_vector[j]); - __m128i row_signs = _mm_cmpgt_epi8(kZeros, row_j); - __m128i extended_row_lo = _mm_unpacklo_epi8(row_j, row_signs); - __m128i extended_row_hi = _mm_unpackhi_epi8(row_j, row_signs); + __m128i extended_row_lo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); + __m128i extended_row_hi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); __m128i extended_input_lo = _mm_unpacklo_epi8(input_j, kZeros); __m128i extended_input_hi = _mm_unpackhi_epi8(input_j, kZeros); __m128i product_lo = _mm_madd_epi16(extended_row_lo, extended_input_lo); @@ -746,9 +429,8 @@ namespace Eval::NNUE::Layers { for (IndexType j = 0; j < kNumChunks; ++j) { __m64 row_j = row[j]; __m64 input_j = input_vector[j]; - __m64 row_signs = _mm_cmpgt_pi8(kZeros, row_j); - __m64 extended_row_lo = _mm_unpacklo_pi8(row_j, row_signs); - __m64 extended_row_hi = _mm_unpackhi_pi8(row_j, row_signs); + __m64 extended_row_lo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8); + __m64 extended_row_hi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8); __m64 extended_input_lo = _mm_unpacklo_pi8(input_j, kZeros); __m64 extended_input_hi = _mm_unpackhi_pi8(input_j, kZeros); __m64 product_lo = _mm_madd_pi16(extended_row_lo, extended_input_lo); @@ -798,10 +480,26 @@ namespace Eval::NNUE::Layers { PreviousLayer previous_layer_; alignas(kCacheLineSize) BiasType biases_[kOutputDimensions]; - alignas(kCacheLineSize) - WeightType weights_[kOutputDimensions * kPaddedInputDimensions]; + alignas(kCacheLineSize) WeightType weights_[kOutputDimensions * kPaddedInputDimensions]; +#if defined (USE_SSSE3) + struct CanSaturate { + int count; + struct Entry { + uint16_t out; + uint16_t in; + int8_t w; + } ids[kPaddedInputDimensions * kOutputDimensions * 3 / 4]; + + void add(int i, int j, int8_t w) { + ids[count].out = i; + ids[count].in = j; + ids[count].w = w; + ++count; + } + } canSaturate16; +#endif }; -} // namespace Eval::NNUE::Layers +} // namespace Stockfish::Eval::NNUE::Layers #endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 3e9ce655..7efb0a0b 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,11 +23,7 @@ #include "../nnue_common.h" -#include -#include -#include - -namespace Eval::NNUE::Layers { +namespace Stockfish::Eval::NNUE::Layers { // Clipped ReLU template @@ -195,6 +191,6 @@ namespace Eval::NNUE::Layers { PreviousLayer previous_layer_; }; -} // namespace Eval::NNUE::Layers +} // namespace Stockfish::Eval::NNUE::Layers #endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/src/nnue/layers/input_slice.h b/src/nnue/layers/input_slice.h index 7a4ef045..f7dab19e 100644 --- a/src/nnue/layers/input_slice.h +++ b/src/nnue/layers/input_slice.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ #include "../nnue_common.h" -namespace Eval::NNUE::Layers { +namespace Stockfish::Eval::NNUE::Layers { // Input layer template @@ -89,6 +89,6 @@ class InputSlice { private: }; -} // namespace Layers +} // namespace Stockfish::Eval::NNUE::Layers #endif // #ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 3d2f5bb4..e84e26f1 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ #include "nnue_architecture.h" -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { // Class that holds the result of affine transformation of input features struct alignas(kCacheLineSize) Accumulator { @@ -31,6 +31,6 @@ namespace Eval::NNUE { bool computed_accumulation; }; -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 91cdc4bd..1680368e 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ // Defines the network structure #include "architectures/halfkp_256x2-32-32.h" -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { static_assert(kTransformedFeatureDimensions % kMaxSimdWidth == 0, ""); static_assert(Network::kOutputDimensions == 1, ""); @@ -33,6 +33,6 @@ namespace Eval::NNUE { // Trigger for full calculation instead of difference calculation constexpr auto kRefreshTriggers = RawFeatures::kRefreshTriggers; -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index e72168f8..1479d6b3 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,7 +52,7 @@ namespace sys = std::filesystem; #include #endif -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { // Version of the evaluation file constexpr std::uint32_t kVersion = 0x7AF32F16u; @@ -99,7 +99,14 @@ namespace Eval::NNUE { PS_END2 = 12 * SQUARE_NB + 1 }; - extern const uint32_t kpp_board_index[PIECE_NB][COLOR_NB]; + constexpr uint32_t kpp_board_index[COLOR_NB][PIECE_NB] = { + // convention: W - us, B - them + // viewed from other side, W and B are reversed + { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_KING, PS_NONE, + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_KING, PS_NONE }, + { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_B_KING, PS_NONE, + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_W_KING, PS_NONE } + }; // Type of input feature after conversion using TransformedFeatureType = std::uint8_t; @@ -133,6 +140,6 @@ namespace Eval::NNUE { return result; } -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 8c17c959..3082c1df 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ #include #include -namespace Eval::NNUE { +namespace Stockfish::Eval::NNUE { // If vector instructions are enabled, we update and refresh the // accumulator tile by tile such that each tile fits in the CPU's @@ -510,6 +510,6 @@ namespace Eval::NNUE { WeightType weights_[kHalfDimensions * kInputDimensions]; }; -} // namespace Eval::NNUE +} // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/src/pawns.cpp b/src/pawns.cpp index 68aaf331..70fb6f23 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,27 +24,30 @@ #include "position.h" #include "thread.h" +namespace Stockfish { + namespace { #define V Value #define S(mg, eg) make_score(mg, eg) // Pawn penalties - constexpr Score Backward = S( 8, 25); - constexpr Score Doubled = S(10, 55); + constexpr Score Backward = S( 9, 22); + constexpr Score Doubled = S(13, 51); + constexpr Score DoubledEarly = S(20, 7); constexpr Score Isolated = S( 3, 15); - constexpr Score WeakLever = S( 3, 55); - constexpr Score WeakUnopposed = S(13, 25); + constexpr Score WeakLever = S( 4, 58); + constexpr Score WeakUnopposed = S(13, 24); // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[2] = { S(-13, -4), S(-5, 2) }; + constexpr Score BlockedPawn[2] = { S(-17, -6), S(-9, 2) }; constexpr Score BlockedStorm[RANK_NB] = { - S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2) + S(0, 0), S(0, 0), S(75, 78), S(-8, 16), S(-6, 10), S(-6, 6), S(0, 2) }; // Connected pawn bonus - constexpr int Connected[RANK_NB] = { 0, 5, 7, 11, 24, 48, 86 }; + constexpr int Connected[RANK_NB] = { 0, 5, 7, 11, 23, 48, 87 }; // Strength of pawn shelter for our king by [distance from edge][rank]. // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. @@ -66,10 +69,11 @@ namespace { { V(-17), V( -13), V( 100), V( 4), V( 9), V(-16), V(-31) } }; + // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties // for king when the king is on a semi-open or open file. - constexpr Score KingOnFile[2][2] = {{ S(-19,12), S(-6, 7) }, - { S( 0, 2), S( 6,-5) }}; + constexpr Score KingOnFile[2][2] = {{ S(-21,10), S(-7, 1) }, + { S( 0,-3), S( 9,-4) }}; #undef S #undef V @@ -85,13 +89,14 @@ namespace { constexpr Color Them = ~Us; constexpr Direction Up = pawn_push(Us); + constexpr Direction Down = -Up; Bitboard neighbours, stoppers, support, phalanx, opposed; Bitboard lever, leverPush, blocked; Square s; bool backward, passed, doubled; Score score = SCORE_ZERO; - const Square* pl = pos.squares(Us); + Bitboard b = pos.pieces(Us, PAWN); Bitboard ourPawns = pos.pieces( Us, PAWN); Bitboard theirPawns = pos.pieces(Them, PAWN); @@ -104,8 +109,10 @@ namespace { e->blockedCount += popcount(shift(ourPawns) & (theirPawns | doubleAttackThem)); // Loop through all pawns of the current color and score each pawn - while ((s = *pl++) != SQ_NONE) + while (b) { + s = pop_lsb(b); + assert(pos.piece_on(s) == make_piece(Us, PAWN)); Rank r = relative_rank(Us, s); @@ -121,6 +128,13 @@ namespace { phalanx = neighbours & rank_bb(s); support = neighbours & rank_bb(s - Up); + if (doubled) + { + // Additional doubled penalty if none of their pawns is fixed + if (!(ourPawns & shift(theirPawns | pawn_attacks_bb(theirPawns)))) + score -= DoubledEarly; + } + // A pawn is backward when it is behind all pawns of the same color on // the adjacent files and cannot safely advance. backward = !(neighbours & forward_ranks_bb(Them, s + Up)) @@ -170,7 +184,7 @@ namespace { else if (backward) score -= Backward - + WeakUnopposed * !opposed; + + WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s); if (!support) score -= Doubled * doubled @@ -277,7 +291,7 @@ Score Entry::do_king_safety(const Position& pos) { if (pawns & attacks_bb(ksq)) minPawnDist = 1; else while (pawns) - minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns))); + minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns))); return shelter - make_score(0, 16 * minPawnDist); } @@ -287,3 +301,5 @@ template Score Entry::do_king_safety(const Position& pos); template Score Entry::do_king_safety(const Position& pos); } // namespace Pawns + +} // namespace Stockfish diff --git a/src/pawns.h b/src/pawns.h index 5499826e..124619d6 100644 --- a/src/pawns.h +++ b/src/pawns.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ #include "position.h" #include "types.h" -namespace Pawns { +namespace Stockfish::Pawns { /// Pawns::Entry contains various information about a pawn structure. A lookup /// to the pawn hash table (performed by calling the probe function) returns a @@ -65,6 +65,6 @@ typedef HashTable Table; Entry* probe(const Position& pos); -} // namespace Pawns +} // namespace Stockfish::Pawns #endif // #ifndef PAWNS_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp index 2fb5f4f7..57bc7c7b 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +39,8 @@ using std::string; +namespace Stockfish { + namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; @@ -76,7 +78,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { << std::setfill(' ') << std::dec << "\nCheckers: "; for (Bitboard b = pos.checkers(); b; ) - os << UCI::square(pop_lsb(&b)) << " "; + os << UCI::square(pop_lsb(b)) << " "; if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) @@ -202,7 +204,6 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th std::memset(this, 0, sizeof(Position)); std::memset(si, 0, sizeof(StateInfo)); - std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE); st = si; ss >> std::noskipws; @@ -255,6 +256,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th set_castling_right(c, rsq); } + set_state(st); + // 4. En passant square. // Ignore if square is invalid or not on side to move relative rank 6. bool enpassant = false; @@ -268,12 +271,24 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // a) side to move have a pawn threatening epSquare // b) there is an enemy pawn in front of epSquare // c) there is no piece on epSquare or behind epSquare + // d) enemy pawn didn't block a check of its own color by moving forward enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))) + && ( file_of(square(sideToMove)) == file_of(st->epSquare) + || !(blockers_for_king(sideToMove) & (st->epSquare + pawn_push(~sideToMove)))); } - if (!enpassant) + // It's necessary for st->previous to be intialized in this way because legality check relies on its existence + if (enpassant) { + st->previous = new StateInfo(); + remove_piece(st->epSquare - pawn_push(sideToMove)); + st->previous->checkersBB = attackers_to(square(~sideToMove)) & pieces(sideToMove); + st->previous->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), st->previous->pinners[BLACK]); + st->previous->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), st->previous->pinners[WHITE]); + put_piece(make_piece(~sideToMove, PAWN), st->epSquare - pawn_push(sideToMove)); + } + else st->epSquare = SQ_NONE; // 5-6. Halfmove clock and fullmove number @@ -285,7 +300,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th chess960 = isChess960; thisThread = th; - set_state(st); + st->accumulator.state[WHITE] = Eval::NNUE::INIT; + st->accumulator.state[BLACK] = Eval::NNUE::INIT; assert(pos_is_ok()); @@ -309,7 +325,7 @@ void Position::set_castling_right(Color c, Square rfrom) { Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); - castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto) + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom); } @@ -348,7 +364,7 @@ void Position::set_state(StateInfo* si) const { for (Bitboard b = pieces(); b; ) { - Square s = pop_lsb(&b); + Square s = pop_lsb(b); Piece pc = piece_on(s); si->key ^= Zobrist::psq[pc][s]; @@ -399,7 +415,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { /// Position::fen() returns a FEN representation of the position. In case of /// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. -const string Position::fen() const { +string Position::fen() const { int emptyCnt; std::ostringstream ss; @@ -465,7 +481,7 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners while (snipers) { - Square sniperSq = pop_lsb(&snipers); + Square sniperSq = pop_lsb(snipers); Bitboard b = between_bb(s, sniperSq) & occupancy; if (b && !more_than_one(b)) @@ -506,23 +522,11 @@ bool Position::legal(Move m) const { assert(color_of(moved_piece(m)) == us); assert(piece_on(square(us)) == make_piece(us, KING)); - // En passant captures are a tricky special case. Because they are rather - // uncommon, we do it simply by testing whether the king is attacked after - // the move is made. - if (type_of(m) == ENPASSANT) - { - Square ksq = square(us); - Square capsq = to - pawn_push(us); - Bitboard occupied = (pieces() ^ from ^ capsq) | to; - - assert(to == ep_square()); - assert(moved_piece(m) == make_piece(us, PAWN)); - assert(piece_on(capsq) == make_piece(~us, PAWN)); - assert(piece_on(to) == NO_PIECE); - - return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK)) - && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); - } + // st->previous->blockersForKing consider capsq as empty. + // If pinned, it has to move along the king ray. + if (type_of(m) == EN_PASSANT) + return !(st->previous->blockersForKing[sideToMove] & from) + || aligned(from, to, square(us)); // Castling moves generation does not check if the castling path is clear of // enemy attacks, it is delayed at a later time: now! @@ -537,22 +541,20 @@ bool Position::legal(Move m) const { if (attackers_to(s) & pieces(~us)) return false; - // In case of Chess960, verify that when moving the castling rook we do - // not discover some hidden checker. + // In case of Chess960, verify if the Rook blocks some checks // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 - || !(attacks_bb(to, pieces() ^ to_sq(m)) & pieces(~us, ROOK, QUEEN)); + return !chess960 || !(blockers_for_king(us) & to_sq(m)); } // If the moving piece is a king, check whether the destination square is // attacked by the opponent. if (type_of(piece_on(from)) == KING) - return !(attackers_to(to) & pieces(~us)); + return !(attackers_to(to, pieces() ^ from) & pieces(~us)); // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) - || aligned(from, to, square(us)); + return !(blockers_for_king(us) & from) + || aligned(from, to, square(us)); } @@ -568,8 +570,10 @@ bool Position::pseudo_legal(const Move m) const { Piece pc = moved_piece(m); // Use a slower but simpler function for uncommon cases + // yet we skip the legality check of MoveList(). if (type_of(m) != NORMAL) - return MoveList(*this).contains(m); + return checkers() ? MoveList< EVASIONS>(*this).contains(m) + : MoveList(*this).contains(m); // Is not a promotion, so promotion piece must be empty if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) @@ -614,8 +618,8 @@ bool Position::pseudo_legal(const Move m) const { if (more_than_one(checkers())) return false; - // Our move must be a blocking evasion or a capture of the checking piece - if (!((between_bb(lsb(checkers()), square(us)) | checkers()) & to)) + // Our move must be a blocking interposition or a capture of the checking piece + if (!(between_bb(square(us), lsb(checkers())) & to)) return false; } // In case of king moves under check we have to remove king so as to catch @@ -655,31 +659,24 @@ bool Position::gives_check(Move m) const { case PROMOTION: return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); - // En passant capture with check? We have already handled the case - // of direct checks and ordinary discovered check, so the only case we - // need to handle is the unusual case of a discovered check through - // the captured pawn. - case ENPASSANT: - { - Square capsq = make_square(file_of(to), rank_of(from)); - Bitboard b = (pieces() ^ from ^ capsq) | to; + // The double-pushed pawn blocked a check? En Passant will remove the blocker. + // The only discovery check that wasn't handle is through capsq and fromsq + // So the King must be in the same rank as fromsq to consider this possibility. + // st->previous->blockersForKing consider capsq as empty. + case EN_PASSANT: + return st->previous->checkersBB + || ( rank_of(square(~sideToMove)) == rank_of(from) + && st->previous->blockersForKing[~sideToMove] & from); - return (attacks_bb< ROOK>(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) - | (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP)); - } - case CASTLING: + default: //CASTLING { - Square kfrom = from; - Square rfrom = to; // Castling is encoded as 'king captures the rook' - Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1); - Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1); + // Castling is encoded as 'king captures the rook' + Square ksq = square(~sideToMove); + Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); - return (attacks_bb(rto) & square(~sideToMove)) - && (attacks_bb(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square(~sideToMove)); + return (attacks_bb(rto) & ksq) + && (attacks_bb(rto, pieces() ^ from ^ to) & ksq); } - default: - assert(false); - return false; } } @@ -719,7 +716,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Square from = from_sq(m); Square to = to_sq(m); Piece pc = piece_on(from); - Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to); + Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); assert(color_of(pc) == us); assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); @@ -745,7 +742,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // update non-pawn material. if (type_of(captured) == PAWN) { - if (type_of(m) == ENPASSANT) + if (type_of(m) == EN_PASSANT) { capsq -= pawn_push(us); @@ -772,7 +769,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update board and piece lists remove_piece(capsq); - if (type_of(m) == ENPASSANT) + if (type_of(m) == EN_PASSANT) board[capsq] = NO_PIECE; // Update material hash key and prefetch access to materialTable @@ -818,7 +815,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) { - // Set en-passant square if the moved pawn can be captured + // Set en passant square if the moved pawn can be captured if ( (int(to) ^ int(from)) == 16 && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) { @@ -941,7 +938,7 @@ void Position::undo_move(Move m) { { Square capsq = to; - if (type_of(m) == ENPASSANT) + if (type_of(m) == EN_PASSANT) { capsq -= pawn_push(us); @@ -1020,7 +1017,7 @@ void Position::do_null_move(StateInfo& newSt) { } st->key ^= Zobrist::side; - prefetch(TT.first_entry(st->key)); + prefetch(TT.first_entry(key())); ++st->rule50; st->pliesFromNull = 0; @@ -1045,7 +1042,7 @@ void Position::undo_null_move() { /// Position::key_after() computes the new hash key after the given move. Needed /// for speculative prefetch. It doesn't recognize special moves like castling, -/// en-passant and promotions. +/// en passant and promotions. Key Position::key_after(Move m) const { @@ -1070,7 +1067,7 @@ bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); - // Only deal with normal moves, assume others pass a simple see + // Only deal with normal moves, assume others pass a simple SEE if (type_of(m) != NORMAL) return VALUE_ZERO >= threshold; @@ -1116,7 +1113,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = PawnValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } @@ -1125,7 +1122,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = KnightValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) @@ -1133,7 +1130,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = BishopValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } @@ -1142,7 +1139,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = RookValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } @@ -1151,7 +1148,7 @@ bool Position::see_ge(Move m, Value threshold) const { if ((swap = QueenValueMg - swap) < res) break; - occupied ^= lsb(bb); + occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); } @@ -1241,7 +1238,7 @@ bool Position::has_game_cycle(int ply) const { Square s1 = from_sq(move); Square s2 = to_sq(move); - if (!(between_bb(s1, s2) & pieces())) + if (!((between_bb(s1, s2) ^ s2) & pieces())) { if (ply > i) return true; @@ -1345,16 +1342,10 @@ bool Position::pos_is_ok() const { assert(0 && "pos_is_ok: State"); for (Piece pc : Pieces) - { if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) assert(0 && "pos_is_ok: Pieces"); - for (int i = 0; i < pieceCount[pc]; ++i) - if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) - assert(0 && "pos_is_ok: Index"); - } - for (Color c : { WHITE, BLACK }) for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) { @@ -1383,3 +1374,5 @@ void Position::sfen_pack(Tools::PackedSfen& sfen) { sfen = Tools::sfen_pack(*this); } + +} // namespace Stockfish diff --git a/src/position.h b/src/position.h index 9e979914..27a0a596 100644 --- a/src/position.h +++ b/src/position.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ #include "bitboard.h" #include "evaluate.h" +#include "psqt.h" #include "types.h" #include "nnue/nnue_accumulator.h" @@ -33,6 +34,7 @@ #include "tools/packed_sfen.h" #include "tools/sfen_packer.h" +namespace Stockfish { /// StateInfo struct stores information needed to restore a Position object to /// its previous state when we retract a move. Whenever a move is made on the @@ -89,7 +91,7 @@ public: // FEN string input/output Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); Position& set(const std::string& code, Color c, StateInfo* si); - const std::string fen() const; + std::string fen() const; // Position representation Bitboard pieces(PieceType pt) const; @@ -102,7 +104,6 @@ public: bool empty(Square s) const; template int count(Color c) const; template int count() const; - template const Square* squares(Color c) const; template Square square(Color c) const; bool is_on_semiopen_file(Color c, Square s) const; @@ -117,7 +118,7 @@ public: Bitboard blockers_for_king(Color c) const; Bitboard check_squares(PieceType pt) const; Bitboard pinners(Color c) const; - bool is_discovery_check_on_king(Color c, Move m) const; + bool is_discovered_check_on_king(Color c, Move m) const; // Attacks to/from a given square Bitboard attackers_to(Square s) const; @@ -130,7 +131,6 @@ public: bool capture(Move m) const; bool capture_or_promotion(Move m) const; bool gives_check(Move m) const; - bool advanced_pawn_push(Move m) const; Piece moved_piece(Move m) const; Piece captured_piece() const; @@ -217,8 +217,6 @@ private: Bitboard byTypeBB[PIECE_TYPE_NB]; Bitboard byColorBB[COLOR_NB]; int pieceCount[PIECE_NB]; - Square pieceList[PIECE_NB][16]; - int index[SQUARE_NB]; int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB]; @@ -230,10 +228,6 @@ private: bool chess960; }; -namespace PSQT { - extern Score psq[PIECE_NB][SQUARE_NB]; -} - extern std::ostream& operator<<(std::ostream& os, const Position& pos); inline Color Position::side_to_move() const { @@ -281,13 +275,9 @@ template inline int Position::count() const { return count(WHITE) + count(BLACK); } -template inline const Square* Position::squares(Color c) const { - return pieceList[make_piece(c, Pt)]; -} - template inline Square Position::square(Color c) const { - assert(pieceCount[make_piece(c, Pt)] == 1); - return squares(c)[0]; + assert(count(c) == 1); + return lsb(pieces(c, Pt)); } inline Square Position::ep_square() const { @@ -338,7 +328,7 @@ inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } -inline bool Position::is_discovery_check_on_king(Color c, Move m) const { +inline bool Position::is_discovered_check_on_king(Color c, Move m) const { return st->blockersForKing[c] & from_sq(m); } @@ -346,17 +336,13 @@ inline bool Position::pawn_passed(Color c, Square s) const { return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); } -inline bool Position::advanced_pawn_push(Move m) const { - return type_of(moved_piece(m)) == PAWN - && relative_rank(sideToMove, to_sq(m)) > RANK_5; -} - inline int Position::pawns_on_same_color_squares(Color c, Square s) const { return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); } inline Key Position::key() const { - return st->key; + return st->rule50 < 14 ? st->key + : st->key ^ make_key((st->rule50 - 14) / 8); } inline Key Position::pawn_key() const { @@ -405,7 +391,7 @@ inline bool Position::capture_or_promotion(Move m) const { inline bool Position::capture(Move m) const { assert(is_ok(m)); // Castling is encoded as "king captures rook" - return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == ENPASSANT; + return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; } inline Piece Position::captured_piece() const { @@ -421,35 +407,25 @@ inline void Position::put_piece(Piece pc, Square s) { board[s] = pc; byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; byColorBB[color_of(pc)] |= s; - index[s] = pieceCount[pc]++; - pieceList[pc][index[s]] = s; + pieceCount[pc]++; pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; psq += PSQT::psq[pc][s]; } inline void Position::remove_piece(Square s) { - // WARNING: This is not a reversible operation. If we remove a piece in - // do_move() and then replace it in undo_move() we will put it at the end of - // the list and not in its original place, it means index[] and pieceList[] - // are not invariant to a do_move() + undo_move() sequence. Piece pc = board[s]; byTypeBB[ALL_PIECES] ^= s; byTypeBB[type_of(pc)] ^= s; byColorBB[color_of(pc)] ^= s; /* board[s] = NO_PIECE; Not needed, overwritten by the capturing one */ - Square lastSquare = pieceList[pc][--pieceCount[pc]]; - index[lastSquare] = index[s]; - pieceList[pc][index[lastSquare]] = lastSquare; - pieceList[pc][pieceCount[pc]] = SQ_NONE; + pieceCount[pc]--; pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; psq -= PSQT::psq[pc][s]; } inline void Position::move_piece(Square from, Square to) { - // index[from] is not updated and becomes stale. This works as long as index[] - // is accessed just by known occupied squares. Piece pc = board[from]; Bitboard fromTo = from | to; byTypeBB[ALL_PIECES] ^= fromTo; @@ -457,8 +433,6 @@ inline void Position::move_piece(Square from, Square to) { byColorBB[color_of(pc)] ^= fromTo; board[from] = NO_PIECE; board[to] = pc; - index[to] = index[from]; - pieceList[pc][index[to]] = to; psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; } @@ -471,4 +445,6 @@ inline StateInfo* Position::state() const { return st; } +} // namespace Stockfish + #endif // #ifndef POSITION_H_INCLUDED diff --git a/src/psqt.cpp b/src/psqt.cpp index eb36e75e..33a3e00c 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,19 +16,23 @@ along with this program. If not, see . */ + +#include "psqt.h" + #include -#include "types.h" #include "bitboard.h" +#include "types.h" -namespace PSQT { +namespace Stockfish { -#define S(mg, eg) make_score(mg, eg) +namespace +{ -// Bonus[PieceType][Square / 2] contains Piece-Square scores. For each piece -// type on a given square a (middlegame, endgame) score pair is assigned. Table -// is defined for files A..D and white side: it is symmetric for black side and -// second half of the files. +auto constexpr S = make_score; + +// 'Bonus' contains Piece-Square parameters. +// Scores are explicit for files A to D, implicitly mirrored for E to H. constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { }, { }, @@ -43,14 +47,14 @@ constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) } }, { // Bishop - { S(-53,-57), S( -5,-30), S( -8,-37), S(-23,-12) }, - { S(-15,-37), S( 8,-13), S( 19,-17), S( 4, 1) }, - { S( -7,-16), S( 21, -1), S( -5, -2), S( 17, 10) }, - { S( -5,-20), S( 11, -6), S( 25, 0), S( 39, 17) }, - { S(-12,-17), S( 29, -1), S( 22,-14), S( 31, 15) }, - { S(-16,-30), S( 6, 6), S( 1, 4), S( 11, 6) }, - { S(-17,-31), S(-14,-20), S( 5, -1), S( 0, 1) }, - { S(-48,-46), S( 1,-42), S(-14,-37), S(-23,-24) } + { S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) }, + { S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) }, + { S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) }, + { S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) }, + { S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) }, + { S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) }, + { S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) }, + { S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) } }, { // Rook { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) }, @@ -64,13 +68,13 @@ constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { }, { // Queen { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) }, - { S(-3,-55), S( 5,-31), S( 8,-22), S(12, -4) }, + { S(-3,-54), S( 5,-31), S( 8,-22), S(12, -4) }, { S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) }, { S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) }, { S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) }, - { S(-4,-38), S(10,-18), S( 6,-12), S( 8, 1) }, + { S(-4,-38), S(10,-18), S( 6,-11), S( 8, 1) }, { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) }, - { S(-2,-75), S(-2,-52), S( 1,-43), S(-2,-36) } + { S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) } }, { // King { S(271, 1), S(327, 45), S(271, 85), S(198, 76) }, @@ -87,19 +91,22 @@ constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { constexpr Score PBonus[RANK_NB][FILE_NB] = { // Pawn (asymmetric distribution) { }, - { S( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) }, - { S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) }, - { S( -4, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S( -8, -9) }, - { S( 13, 10), S( 0, 5), S(-13, 4), S( 1, -5), S( 11, -5), S( -2, -5), S(-13, 14), S( 5, 9) }, - { S( 5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S( -8, 13) }, - { S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) } + { S( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) }, + { S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) }, + { S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) }, + { S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) }, + { S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) }, + { S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) } }; -#undef S +} // namespace + + +namespace PSQT +{ Score psq[PIECE_NB][SQUARE_NB]; - // PSQT::init() initializes piece-square tables: the white halves of the tables are // copied from Bonus[] and PBonus[], adding the piece value, then the black halves of // the tables are initialized by flipping and changing the sign of the white scores. @@ -107,16 +114,18 @@ void init() { for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING}) { - Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); + Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); - for (Square s = SQ_A1; s <= SQ_H8; ++s) - { - File f = File(edge_distance(file_of(s))); - psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] - : Bonus[pc][rank_of(s)][f]); - psq[~pc][flip_rank(s)] = -psq[pc][s]; - } + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + File f = File(edge_distance(file_of(s))); + psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] + : Bonus[pc][rank_of(s)][f]); + psq[~pc][flip_rank(s)] = -psq[pc][s]; + } } } } // namespace PSQT + +} // namespace Stockfish diff --git a/src/psqt.h b/src/psqt.h new file mode 100644 index 00000000..7abb1483 --- /dev/null +++ b/src/psqt.h @@ -0,0 +1,38 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifndef PSQT_H_INCLUDED +#define PSQT_H_INCLUDED + + +#include "types.h" + + +namespace Stockfish::PSQT +{ + +extern Score psq[PIECE_NB][SQUARE_NB]; + +// Fill psqt array from a set of internally linked parameters +extern void init(); + +} // namespace Stockfish::PSQT + + +#endif // PSQT_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index a5e5ee24..6f2cfc7b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -37,6 +37,8 @@ #include "uci.h" #include "syzygy/tbprobe.h" +namespace Stockfish { + namespace Search { LimitsType Limits; @@ -56,8 +58,7 @@ namespace { constexpr uint64_t TtHitAverageWindow = 4096; constexpr uint64_t TtHitAverageResolution = 1024; - // Razor and futility margins - constexpr int RazorMargin = 510; + // Futility margin Value futility_margin(Depth d, bool improving) { return Value(234 * (d - improving)); } @@ -76,10 +77,10 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return d > 13 ? 29 : 17 * d * d + 134 * d - 134; + return d > 14 ? 66 : 6 * d * d + 231 * d - 206; } - // Add a small random component to draw evaluations to avoid 3fold-blindness + // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(Thread* thisThread) { return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); } @@ -420,7 +421,7 @@ void Thread::search() { while (true) { Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter); - bestValue = ::search(rootPos, ss, alpha, beta, adjustedDepth, false); + bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -608,12 +609,12 @@ namespace { // Step 1. Initialize node Thread* thisThread = pos.this_thread(); - ss->inCheck = pos.checkers(); - priorCapture = pos.captured_piece(); - Color us = pos.side_to_move(); - moveCount = captureCount = quietCount = ss->moveCount = 0; - bestValue = -VALUE_INFINITE; - maxValue = VALUE_INFINITE; + ss->inCheck = pos.checkers(); + priorCapture = pos.captured_piece(); + Color us = pos.side_to_move(); + moveCount = captureCount = quietCount = ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; // Check for the available remaining time if (thisThread == Threads.main()) @@ -673,6 +674,7 @@ namespace { ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); formerPv = ss->ttPv && !PvNode; + // Update low ply history for previous move if we are near root and position is or has been in PV if ( ss->ttPv && depth > 12 && ss->ply - 1 < MAX_LPH @@ -697,6 +699,7 @@ namespace { { if (ttValue >= beta) { + // Bonus for a quiet ttMove that fails high if (!pos.capture_or_promotion(ttMove)) update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth); @@ -713,6 +716,8 @@ namespace { } } + // Partial workaround for the graph history interaction problem + // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) return ttValue; } @@ -786,6 +791,7 @@ namespace { if (eval == VALUE_NONE) ss->staticEval = eval = evaluate(pos); + // Randomize draw evaluation if (eval == VALUE_DRAW) eval = value_draw(thisThread); @@ -796,38 +802,46 @@ namespace { } else { + // In case of null move search use previous static eval with a different sign + // and addition of two tempos if ((ss-1)->currentMove != MOVE_NULL) ss->staticEval = eval = evaluate(pos); else ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo; + // Save static evaluation into transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } - // Step 7. Razoring (~1 Elo) - if ( !rootNode // The required rootNode PV handling is not available in qsearch - && depth == 1 - && eval <= alpha - RazorMargin) - return qsearch(pos, ss, alpha, beta); + // Use static evaluation difference to improve quiet move ordering + if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) + { + int bonus = std::clamp(-depth * 4 * int((ss-1)->staticEval + ss->staticEval - 2 * Tempo), -1000, 1000); + thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; + } + // Set up improving flag that is used in various pruning heuristics + // We define position as improving if static evaluation of position is better + // Than the previous static evaluation at our turn + // In case of us being in check at our previous move we look at move prior to it improving = (ss-2)->staticEval == VALUE_NONE ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE : ss->staticEval > (ss-2)->staticEval; - // Step 8. Futility pruning: child node (~50 Elo) + // Step 7. Futility pruning: child node (~50 Elo) if ( !PvNode - && depth < 8 + && depth < 9 && eval - futility_margin(depth, improving) >= beta && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; - // Step 9. Null move search with verification search (~40 Elo) + // Step 8. Null move search with verification search (~40 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 22977 + && (ss-1)->statScore < 24185 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 30 * depth - 28 * improving + 84 * ss->ttPv + 168 + && ss->staticEval >= beta - 24 * depth - 34 * improving + 162 * ss->ttPv + 159 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -835,7 +849,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and value - Depth R = (1015 + 85 * depth) / 256 + std::min(int(eval - beta) / 191, 3); + Depth R = (1062 + 68 * depth) / 256 + std::min(int(eval - beta) / 190, 3); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -871,9 +885,9 @@ namespace { } } - probCutBeta = beta + 183 - 49 * improving; + probCutBeta = beta + 209 - 44 * improving; - // Step 10. ProbCut (~10 Elo) + // Step 9. ProbCut (~10 Elo) // If we have a good enough capture and a reduced search returns a value // much above beta, we can (almost) safely prune the previous move. if ( !PvNode @@ -899,6 +913,7 @@ namespace { return probCutBeta; assert(probCutBeta < VALUE_INFINITE); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); int probCutCount = 0; bool ttPv = ss->ttPv; @@ -946,7 +961,7 @@ namespace { ss->ttPv = ttPv; } - // Step 11. If the position is not in TT, decrease depth by 2 + // Step 10. If the position is not in TT, decrease depth by 2 if ( PvNode && depth >= 6 && !ttMove) @@ -954,6 +969,23 @@ namespace { moves_loop: // When in check, search starts from here + ttCapture = ttMove && pos.capture_or_promotion(ttMove); + + // Step 11. A small Probcut idea, when we are in check + probCutBeta = beta + 400; + if ( ss->inCheck + && !PvNode + && depth >= 4 + && ttCapture + && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3 + && ttValue >= probCutBeta + && abs(ttValue) <= 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 }; @@ -970,7 +1002,6 @@ moves_loop: // When in check, search starts from here value = bestValue; singularQuietLMR = moveCountPruning = false; - ttCapture = ttMove && pos.capture_or_promotion(ttMove); // Mark this node as being searched ThreadHolding th(thisThread, posKey, ss->ply); @@ -1012,6 +1043,14 @@ moves_loop: // When in check, search starts from here movedPiece = pos.moved_piece(move); givesCheck = pos.gives_check(move); + // Indicate PvNodes that will probably fail low if node was searched with non-PV search + // at depth equal or greater to current depth and result of this search was far below alpha + bool likelyFailLow = PvNode + && ttMove + && (tte->bound() & BOUND_UPPER) + && ttValue < alpha + 200 + 100 * depth + && tte->depth() >= depth; + // Calculate new depth for this move newDepth = depth - 1; @@ -1027,8 +1066,20 @@ moves_loop: // When in check, search starts from here // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0); - if ( !captureOrPromotion - && !givesCheck) + if ( captureOrPromotion + || givesCheck) + { + // Capture history based pruning when the move doesn't give check + if ( !givesCheck + && lmrDepth < 1 + && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) + continue; + + // SEE based pruning + if (!pos.see_ge(move, Value(-218) * depth)) // (~25 Elo) + continue; + } + else { // Countermoves based pruning (~20 Elo) if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) @@ -1039,29 +1090,17 @@ moves_loop: // When in check, search starts from here // Futility pruning: parent node (~5 Elo) if ( lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 266 + 170 * lmrDepth <= alpha + && ss->staticEval + 174 + 157 * lmrDepth <= alpha && (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - + (*contHist[5])[movedPiece][to_sq(move)] / 2 < 27376) + + (*contHist[5])[movedPiece][to_sq(move)] / 3 < 28255) continue; // Prune moves with negative SEE (~20 Elo) if (!pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth))) continue; } - else - { - // Capture history based pruning when the move doesn't give check - if ( !givesCheck - && lmrDepth < 1 - && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) - continue; - - // SEE based pruning - if (!pos.see_ge(move, Value(-213) * depth)) // (~25 Elo) - continue; - } } // Step 14. Extensions (~75 Elo) @@ -1082,6 +1121,7 @@ moves_loop: // When in check, search starts from here { Value singularBeta = ttValue - ((formerPv + 4) * depth) / 2; Depth singularDepth = (depth - 1 + 3 * formerPv) / 2; + ss->excludedMove = move; value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); ss->excludedMove = MOVE_NONE; @@ -1115,20 +1155,9 @@ moves_loop: // When in check, search starts from here // Check extension (~2 Elo) else if ( givesCheck - && (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move))) + && (pos.is_discovered_check_on_king(~us, move) || pos.see_ge(move))) extension = 1; - // Last captures extension - else if ( PieceValue[EG][pos.captured_piece()] > PawnValueEg - && pos.non_pawn_material() <= 2 * RookValueMg) - extension = 1; - - // Late irreversible move extension - if ( move == ttMove - && pos.rule50_count() > 80 - && (captureOrPromotion || type_of(movedPiece) == PAWN)) - extension = 2; - // Add extension to new depth newDepth += extension; @@ -1145,14 +1174,17 @@ moves_loop: // When in check, search starts from here // Step 15. Make the move pos.do_move(move, st, givesCheck); - // Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be - // re-searched at full depth. + // Step 16. Late moves reduction / extension (LMR, ~200 Elo) + // We use various heuristics for the sons of a node after the first son has + // been searched. In general we would like to reduce them, but there are many + // cases where we extend a son if it has good chances to be "interesting". if ( depth >= 3 && moveCount > 1 + 2 * rootNode && ( !captureOrPromotion || moveCountPruning || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha || cutNode + || (!PvNode && !formerPv && captureHistory[movedPiece][to_sq(move)][type_of(pos.captured_piece())] < 3678) || thisThread->ttHitAverage < 432 * TtHitAverageResolution * TtHitAverageWindow / 1024)) { Depth r = reduction(improving, depth, moveCount); @@ -1165,15 +1197,21 @@ moves_loop: // When in check, search starts from here if (th.marked()) r++; - // Decrease reduction if position is or has been on the PV (~10 Elo) - if (ss->ttPv) + // Decrease reduction if position is or has been on the PV + // and node is not likely to fail low. (~10 Elo) + if ( ss->ttPv + && !likelyFailLow) r -= 2; // Increase reduction at root and non-PV nodes when the best move does not change frequently - if ((rootNode || !PvNode) && depth > 10 && thisThread->bestMoveChanges <= 2) + if ( (rootNode || !PvNode) + && thisThread->rootDepth > 10 + && thisThread->bestMoveChanges <= 2) r++; - if (moveCountPruning && !formerPv) + // More reductions for late moves if position was not in previous PV + if ( moveCountPruning + && !formerPv) r++; // Decrease reduction if opponent's move count is high (~5 Elo) @@ -1184,7 +1222,14 @@ moves_loop: // When in check, search starts from here if (singularQuietLMR) r--; - if (!captureOrPromotion) + if (captureOrPromotion) + { + // Increase reduction for non-checking captures likely to be bad + if ( !givesCheck + && ss->staticEval + PieceValue[EG][pos.captured_piece()] + 210 * depth <= alpha) + r++; + } + else { // Increase reduction if ttMove is a capture (~5 Elo) if (ttCapture) @@ -1199,7 +1244,7 @@ moves_loop: // When in check, search starts from here // Decrease reduction for moves that escape a capture. Filter out // castling moves, because they are coded as "king captures rook" and - // hence break make_move(). (~2 Elo) + // hence break reverse_move() (~2 Elo) else if ( type_of(move) == NORMAL && !pos.see_ge(reverse_move(move))) r -= 2 + ss->ttPv - (type_of(movedPiece) == PAWN); @@ -1208,42 +1253,39 @@ moves_loop: // When in check, search starts from here + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 5287; + - 4741; // Decrease/increase reduction by comparing opponent's stat score (~10 Elo) - if (ss->statScore >= -105 && (ss-1)->statScore < -103) + if (ss->statScore >= -89 && (ss-1)->statScore < -116) r--; - else if ((ss-1)->statScore >= -122 && ss->statScore < -129) + else if ((ss-1)->statScore >= -112 && ss->statScore < -100) r++; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / 14884; - } - else - { - // Increase reduction for captures/promotions if late move and at low depth - if (depth < 8 && moveCount > 2) - r++; - - // Unless giving check, this capture is likely bad - if ( !givesCheck - && ss->staticEval + PieceValue[EG][pos.captured_piece()] + 210 * depth <= alpha) - r++; + // If we are not in check use statScore, but if we are in check we use + // the sum of main history and first continuation history with an offset. + if (ss->inCheck) + r -= (thisThread->mainHistory[us][from_to(move)] + + (*contHist[0])[movedPiece][to_sq(move)] - 3833) / 16384; + else + r -= ss->statScore / 14790; } - Depth d = std::clamp(newDepth - r, 1, newDepth); + // In general we want to cap the LMR depth search at newDepth. But if + // reductions are really negative and movecount is low, we allow this move + // to be searched deeper than the first move. + Depth d = std::clamp(newDepth - r, 1, newDepth + (r < -1 && moveCount <= 5)); value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); - doFullDepthSearch = value > alpha && d != newDepth; - + // If the son is reduced and fails high it will be re-searched at full depth + doFullDepthSearch = value > alpha && d < newDepth; didLMR = true; } else { doFullDepthSearch = !PvNode || moveCount > 1; - didLMR = false; } @@ -1252,14 +1294,12 @@ moves_loop: // When in check, search starts from here { value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + // If the move passed LMR update its stats if (didLMR && !captureOrPromotion) { int bonus = value > alpha ? stat_bonus(newDepth) : -stat_bonus(newDepth); - if (move == ss->killers[0]) - bonus += bonus / 4; - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); } } @@ -1306,8 +1346,7 @@ moves_loop: // When in check, search starts from here rm.pv.push_back(*m); // We record how often the best move has been changed in each - // iteration. This information is used for time management: when - // the best move changes frequently, we allocate some more time. + // iteration. This information is used for time management and LMR if (moveCount > 1) ++thisThread->bestMoveChanges; } @@ -1340,6 +1379,7 @@ moves_loop: // When in check, search starts from here } } + // If the move is worse than some previously searched move, remember it to update its stats later if (move != bestMove) { if (captureOrPromotion && captureCount < 32) @@ -1366,9 +1406,11 @@ moves_loop: // When in check, search starts from here assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); if (!moveCount) - bestValue = excludedMove ? alpha - : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; + bestValue = excludedMove ? alpha : + ss->inCheck ? mated_in(ss->ply) + : VALUE_DRAW; + // If there is a move which produces search value greater than alpha we update stats of searched moves else if (bestMove) update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, capturesSearched, captureCount, depth); @@ -1390,6 +1432,7 @@ moves_loop: // When in check, search starts from here else if (depth > 3) ss->ttPv = ss->ttPv && (ss+1)->ttPv; + // Write gathered information in transposition table if (!excludedMove && !(rootNode && thisThread->pvIdx)) tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, bestValue >= beta ? BOUND_LOWER : @@ -1485,6 +1528,8 @@ moves_loop: // When in check, search starts from here bestValue = ttValue; } else + // In case of null move search use previous static eval with a different sign + // and addition of two tempos ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval + 2 * Tempo; @@ -1492,6 +1537,7 @@ moves_loop: // When in check, search starts from here // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { + // Save gathered info in transposition table if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval); @@ -1528,15 +1574,13 @@ moves_loop: // When in check, search starts from here moveCount++; - // Futility pruning + // Futility pruning and moveCount pruning if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY && !givesCheck && futilityBase > -VALUE_KNOWN_WIN - && !pos.advanced_pawn_push(move)) + && type_of(move) != PROMOTION) { - assert(type_of(move) != ENPASSANT); // Due to !pos.advanced_pawn_push - // moveCount pruning if (moveCount > 2) continue; @@ -1557,7 +1601,6 @@ moves_loop: // When in check, search starts from here // Do not search moves with negative SEE values if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && !(givesCheck && pos.is_discovery_check_on_king(~pos.side_to_move(), move)) && !pos.see_ge(move)) continue; @@ -1620,6 +1663,7 @@ moves_loop: // When in check, search starts from here return mated_in(ss->ply); // Plies to mate from the root } + // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, bestValue >= beta ? BOUND_LOWER : PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, @@ -1698,14 +1742,15 @@ moves_loop: // When in check, search starts from here PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); bonus1 = stat_bonus(depth + 1); - bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus - : stat_bonus(depth); // smaller bonus + bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus + : std::min(bonus1, stat_bonus(depth)); // smaller bonus if (!pos.capture_or_promotion(bestMove)) { + // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bonus2, depth); - // Decrease all the non-best quiet moves + // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; @@ -1713,14 +1758,16 @@ moves_loop: // When in check, search starts from here } } else + // Increase stats for the best move in case it was a capture move 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 + // 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])) && !pos.captured_piece()) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); - // Decrease all the non-best capture moves + // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); @@ -1737,6 +1784,7 @@ moves_loop: // When in check, search starts from here for (int i : {1, 2, 4, 6}) { + // Only update first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (is_ok((ss-i)->currentMove)) @@ -1749,6 +1797,7 @@ moves_loop: // When in check, search starts from here void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth) { + // Update killers if (ss->killers[0] != move) { ss->killers[1] = ss->killers[0]; @@ -1760,15 +1809,18 @@ moves_loop: // When in check, search starts from here thisThread->mainHistory[us][from_to(move)] << bonus; update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); + // Penalty for reversed move in case of moved piece not being a pawn if (type_of(pos.moved_piece(move)) != PAWN) thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus; + // Update countermove history if (is_ok((ss-1)->currentMove)) { Square prevSq = to_sq((ss-1)->currentMove); thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } + // Update low ply history if (depth > 11 && ss->ply < MAX_LPH) thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 7); } @@ -2249,3 +2301,5 @@ namespace Search } } + +} // namespace Stockfish diff --git a/src/search.h b/src/search.h index b9036f56..04d71e76 100644 --- a/src/search.h +++ b/src/search.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,8 @@ #include "types.h" #include "uci.h" +namespace Stockfish { + class Position; namespace Search { @@ -118,4 +120,6 @@ ValueAndPV search(Position& pos, int depth_, size_t multiPV = 1, uint64_t nodesL } +} // namespace Stockfish + #endif // #ifndef SEARCH_H_INCLUDED diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index f4b9447f..57c9204b 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -50,9 +50,11 @@ #include #endif -using namespace Tablebases; +using namespace Stockfish::Tablebases; -int Tablebases::MaxCardinality = 0; +int Stockfish::Tablebases::MaxCardinality = 0; + +namespace Stockfish { namespace { @@ -190,7 +192,8 @@ public: std::stringstream ss(Paths); std::string path; - while (std::getline(ss, path, SepChar)) { + while (std::getline(ss, path, SepChar)) + { fname = path + "/" + f; std::ifstream::open(fname); if (is_open()) @@ -565,7 +568,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { int buf64Size = 64; Sym sym; - while (true) { + while (true) + { int len = 0; // This is the symbol length - d->min_sym_len // Now get the symbol length. For any symbol s64 of length l right-padded @@ -603,8 +607,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // We binary-search for our value recursively expanding into the left and // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 // that will store the value we need. - while (d->symlen[sym]) { - + while (d->symlen[sym]) + { Sym left = d->btree[sym].get(); // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and @@ -709,7 +713,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu leadPawns = b = pos.pieces(color_of(pc), PAWN); do - squares[size++] = pop_lsb(&b) ^ flipSquares; + squares[size++] = pop_lsb(b) ^ flipSquares; while (b); leadPawnsCnt = size; @@ -729,7 +733,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // directly map them to the correct color and square. b = pos.pieces() ^ leadPawns; do { - Square s = pop_lsb(&b); + Square s = pop_lsb(b); squares[size] = s ^ flipSquares; pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); } while (b); @@ -1000,7 +1004,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. - // See http://www.eecs.harvard.edu/~michaelm/E210/huffman.pdf + // See https://en.wikipedia.org/wiki/Huffman_coding for (int i = d->base64.size() - 2; i >= 0; --i) { d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) - number(&d->lowestSym[i + 1])) / 2; @@ -1141,7 +1145,7 @@ void* mapped(TBTable& e, const Position& pos) { if (e.ready.load(std::memory_order_acquire)) return e.baseAddress; // Could be nullptr if file does not exist - std::unique_lock lk(mutex); + std::scoped_lock lk(mutex); if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock return e.baseAddress; @@ -1440,7 +1444,7 @@ WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { // If n = 100 immediately after a capture or pawn move, then the position // is also certainly a win, and during the whole phase until the next // capture or pawn move, the inequality to be preserved is -// dtz + 50-movecounter <= 100. +// dtz + 50-move-counter <= 100. // // In short, if a move is available resulting in dtz + 50-move-counter <= 99, // then do not accept moves leading to dtz + 50-move-counter == 100. @@ -1610,3 +1614,5 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { return true; } + +} // namespace Stockfish diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index b998989b..56734af9 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ #include "../search.h" -namespace Tablebases { +namespace Stockfish::Tablebases { enum WDLScore { WDLLoss = -2, // Loss @@ -73,6 +73,6 @@ inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { return os; } -} +} // namespace Stockfish::Tablebases #endif diff --git a/src/thread.cpp b/src/thread.cpp index 8f727ea8..5051708d 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,8 @@ #include "syzygy/tbprobe.h" #include "tt.h" +namespace Stockfish { + ThreadPool Threads; // Global object @@ -151,14 +153,16 @@ void Thread::idle_loop() { void ThreadPool::set(size_t requested) { - if (size() > 0) { // destroy any existing thread(s) + if (size() > 0) // destroy any existing thread(s) + { main()->wait_for_search_finished(); while (size() > 0) delete back(), pop_back(); } - if (requested > 0) { // create new thread(s) + if (requested > 0) // create new thread(s) + { push_back(new MainThread(0)); while (size() < requested) @@ -294,3 +298,5 @@ void ThreadPool::wait_for_workers_finished() const { for (Thread* th : *this) th->wait_for_worker_finished(); } + +} // namespace Stockfish diff --git a/src/thread.h b/src/thread.h index 6eb38136..06fc18f4 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,6 +33,7 @@ #include "search.h" #include "thread_win32_osx.h" +namespace Stockfish { /// Thread class keeps together all the thread-related stuff. We use /// per-thread pawn and material hash tables so that once we get a @@ -216,4 +217,6 @@ private: extern ThreadPool Threads; +} // namespace Stockfish + #endif // #ifndef THREAD_H_INCLUDED diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 75ef5d9a..a21674cc 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,6 +31,8 @@ #include +namespace Stockfish { + static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; template > @@ -57,10 +59,16 @@ public: void join() { pthread_join(thread, NULL); } }; +} // namespace Stockfish + #else // Default case: use STL classes +namespace Stockfish { + typedef std::thread NativeThread; +} // namespace Stockfish + #endif #endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED diff --git a/src/timeman.cpp b/src/timeman.cpp index da08f12d..f742d1e4 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include "timeman.h" #include "uci.h" +namespace Stockfish { + TimeManagement Time; // Our global time management object @@ -95,3 +97,5 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { if (Options["Ponder"]) optimumTime += optimumTime / 4; } + +} // namespace Stockfish diff --git a/src/timeman.h b/src/timeman.h index 5ad72b32..b1878d65 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include "search.h" #include "thread.h" +namespace Stockfish { + /// The TimeManagement class computes the optimal time to think depending on /// the maximum available time, the game move number and other parameters. @@ -44,4 +46,6 @@ private: extern TimeManagement Time; +} // namespace Stockfish + #endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/src/tt.cpp b/src/tt.cpp index 718587a8..981fb4e2 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,8 @@ #include "tt.h" #include "uci.h" +namespace Stockfish { + TranspositionTable TT; // Our global transposition table bool TranspositionTable::enable_transposition_table = true; @@ -133,7 +135,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16 || !tte[i].depth8) { - tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & 0x7)); // Refresh + tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh return found = (bool)tte[i].depth8, &tte[i]; } @@ -142,11 +144,12 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { TTEntry* replace = tte; for (int i = 1; i < ClusterSize; ++i) // Due to our packed storage format for generation and its cyclic - // nature we add 263 (256 is the modulus plus 7 to keep the unrelated - // lowest three bits from affecting the result) to calculate the entry - // age correctly even after generation8 overflows into the next cycle. - if ( replace->depth8 - ((263 + generation8 - replace->genBound8) & 0xF8) - > tte[i].depth8 - ((263 + generation8 - tte[i].genBound8) & 0xF8)) + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) + > tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) replace = &tte[i]; return found = false, replace; @@ -161,7 +164,9 @@ int TranspositionTable::hashfull() const { int cnt = 0; for (int i = 0; i < 1000; ++i) for (int j = 0; j < ClusterSize; ++j) - cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & 0xF8) == generation8; + cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; return cnt / ClusterSize; } + +} // namespace Stockfish diff --git a/src/tt.h b/src/tt.h index d817f26d..b58cda63 100644 --- a/src/tt.h +++ b/src/tt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,6 +22,8 @@ #include "misc.h" #include "types.h" +namespace Stockfish { + /// TTEntry struct is the 10 bytes transposition table entry, defined as below: /// /// key 16 bit @@ -72,9 +74,15 @@ class TranspositionTable { static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); + // Constants used to refresh the hash table periodically + static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things + static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field + static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length + static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number + public: ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound + void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things TTEntry* probe(const Key key, bool& found) const; int hashfull() const; void resize(size_t mbSize); @@ -96,4 +104,6 @@ private: extern TranspositionTable TT; +} // namespace Stockfish + #endif // #ifndef TT_H_INCLUDED diff --git a/src/tune.cpp b/src/tune.cpp index e94f67f8..d9618efc 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,8 @@ using std::string; +namespace Stockfish { + bool Tune::update_on_last; const UCI::Option* LastOption = nullptr; BoolConditions Conditions; @@ -126,6 +128,8 @@ void BoolConditions::set() { sync_cout << binary[i] << sync_endl; } +} // namespace Stockfish + // Init options with tuning session results instead of default values. Useful to // get correct bench signature after a tuning session or to test tuned values. @@ -138,7 +142,11 @@ void BoolConditions::set() { #include +namespace Stockfish { + void Tune::read_results() { /* ...insert your values here... */ } + +} // namespace Stockfish diff --git a/src/tune.h b/src/tune.h index 1489fa32..c904c09d 100644 --- a/src/tune.h +++ b/src/tune.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include #include +namespace Stockfish { + typedef std::pair Range; // Option's min-max values typedef Range (RangeFun) (int); @@ -130,9 +132,9 @@ class Tune { SetRange range; }; - // Our facilty to fill the container, each Entry corresponds to a parameter to tune. - // We use variadic templates to deal with an unspecified number of entries, each one - // of a possible different type. + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. static std::string next(std::string& names, bool pop = true); int add(const SetRange&, std::string&&) { return 0; } @@ -190,4 +192,6 @@ public: #define TUNE_CONDITIONS() int UNIQUE(c, __LINE__) = (Conditions.init(__COUNTER__), 0); \ TUNE(Conditions, set_conditions) +} // namespace Stockfish + #endif // #ifndef TUNE_H_INCLUDED diff --git a/src/types.h b/src/types.h index 4918e8ff..5c3768c9 100644 --- a/src/types.h +++ b/src/types.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -83,6 +83,8 @@ # define pext(b, m) 0 #endif +namespace Stockfish { + #ifdef USE_POPCNT constexpr bool HasPopCnt = true; #else @@ -113,7 +115,7 @@ constexpr int MAX_PLY = 246; /// bit 6-11: origin square (from 0 to 63) /// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) /// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) -/// NOTE: EN-PASSANT bit is set only when a pawn can be captured +/// NOTE: en passant bit is set only when a pawn can be captured /// /// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in /// any normal move destination square is always different from origin square @@ -127,7 +129,7 @@ enum Move : int { enum MoveType { NORMAL, PROMOTION = 1 << 14, - ENPASSANT = 2 << 14, + EN_PASSANT = 2 << 14, CASTLING = 3 << 14 }; @@ -489,6 +491,8 @@ constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } +} // namespace Stockfish + #endif // #ifndef TYPES_H_INCLUDED #include "tune.h" // Global visibility to tuning setup diff --git a/src/uci.cpp b/src/uci.cpp index ecf4d86d..725056bd 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,6 +41,8 @@ using namespace std; +namespace Stockfish { + extern vector setup_bench(const Position&, istream&); // FEN string of the initial position, normal chess @@ -346,8 +348,7 @@ void UCI::loop(int argc, char* argv[]) { std::cout << th.thread_idx() << '\n'; }); } - - else + else if (!token.empty() && token[0] != '#') sync_cout << "Unknown command: " << cmd << sync_endl; } while (token != "quit" && argc == 1); // Command line args are one-shot @@ -441,3 +442,5 @@ Move UCI::to_move(const Position& pos, string& str) { return MOVE_NONE; } + +} // namespace Stockfish diff --git a/src/uci.h b/src/uci.h index 192963cb..b27445aa 100644 --- a/src/uci.h +++ b/src/uci.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include "types.h" +namespace Stockfish { + class Position; namespace UCI { @@ -81,6 +83,6 @@ void setoption(const std::string& name, const std::string& value); extern UCI::OptionsMap Options; -extern const char* StartFEN; +} // namespace Stockfish #endif // #ifndef UCI_H_INCLUDED diff --git a/src/ucioption.cpp b/src/ucioption.cpp index bdb1c6b1..c42c38c9 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,6 +32,8 @@ using std::string; +namespace Stockfish { + UCI::OptionsMap Options; // Global object namespace UCI { @@ -211,3 +213,5 @@ Option& Option::operator=(const string& v) { } } // namespace UCI + +} // namespace Stockfish diff --git a/tests/instrumented.sh b/tests/instrumented.sh index dffc257a..2f746a86 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -51,16 +51,16 @@ case $1 in tt_size=128 cat << EOF > tsan.supp -race:TTEntry::move -race:TTEntry::depth -race:TTEntry::bound -race:TTEntry::save -race:TTEntry::value -race:TTEntry::eval -race:TTEntry::is_pv +race:Stockfish::TTEntry::move +race:Stockfish::TTEntry::depth +race:Stockfish::TTEntry::bound +race:Stockfish::TTEntry::save +race:Stockfish::TTEntry::value +race:Stockfish::TTEntry::eval +race:Stockfish::TTEntry::is_pv -race:TranspositionTable::probe -race:TranspositionTable::hashfull +race:Stockfish::TranspositionTable::probe +race:Stockfish::TranspositionTable::hashfull EOF