mirror of
https://github.com/opelly27/Stockfish.git
synced 2026-05-20 12:07:43 +00:00
Compare commits
1083 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a4c7cf4e3 | |||
| 8e16592430 | |||
| 073d71a36b | |||
| 399d556c27 | |||
| 6e907f52c5 | |||
| e87358c53d | |||
| f710dc97e2 | |||
| 4c7de9e8ab | |||
| 6ede1bed89 | |||
| 1a168201bd | |||
| 48df0754bc | |||
| f7d1491b3d | |||
| cc7bcd5303 | |||
| 22b7909809 | |||
| 5372f81cc8 | |||
| c079acc26f | |||
| c90279e156 | |||
| 9eb7b607cf | |||
| a32d2086bc | |||
| 285a79eaa0 | |||
| e1f12aa4e6 | |||
| e41f727f0f | |||
| 6e0680efa0 | |||
| c4db7fd1f9 | |||
| e6e324eb28 | |||
| df2f7e7527 | |||
| c25d4c4887 | |||
| c3b67faf98 | |||
| 319af5cf0a | |||
| 19a90b45bc | |||
| 9f6bcb38c0 | |||
| 471d93063a | |||
| 08e0f52b77 | |||
| 910cf8b218 | |||
| e31f97e3ba | |||
| f3a2296e59 | |||
| 004ea2c25e | |||
| 45f2416db4 | |||
| eae0f8dd06 | |||
| 270a0e737f | |||
| 4ac7d726ec | |||
| 174b038bf3 | |||
| 5f781d366e | |||
| 27139dedac | |||
| abef3e86f4 | |||
| 2da1d1bf57 | |||
| 84b1940fca | |||
| 3ec6e1d245 | |||
| cb9c2594fc | |||
| b0b31558a2 | |||
| 08ac4e9db5 | |||
| 4d3950c6eb | |||
| 95d7369e54 | |||
| e178a09c47 | |||
| 50200de5af | |||
| 90d051952f | |||
| 8b4afcf8f7 | |||
| bddd38c45e | |||
| 9083050be6 | |||
| 77cf5704b6 | |||
| 67062637f4 | |||
| 48bf1a386f | |||
| 2b0372319d | |||
| d11101e4c6 | |||
| 7678d63cf2 | |||
| c5d45d3220 | |||
| 44b1ba89a9 | |||
| c5a280c012 | |||
| 9ad0ea7382 | |||
| 2efda17c2a | |||
| ad926d34c0 | |||
| 0b41887527 | |||
| 061f98a9e3 | |||
| 1066119083 | |||
| 93b14a17d1 | |||
| 7d82f0d1f4 | |||
| 0a6168089d | |||
| 88f17a814d | |||
| 22e92d23d2 | |||
| 2c30956a13 | |||
| 74776dbcd5 | |||
| ca51b45649 | |||
| fb7d3ab32e | |||
| 0a318cdddf | |||
| 939b694bfd | |||
| dc5d9bdfee | |||
| 0889210262 | |||
| 3bea736a2a | |||
| c6edf33f53 | |||
| ea1ddb6aef | |||
| d579db34a3 | |||
| 9db6ca8592 | |||
| 8e82345931 | |||
| 9451419912 | |||
| c228f3196a | |||
| 4766dfc395 | |||
| b82d93ece4 | |||
| a3d425cf55 | |||
| 7d44b43b3c | |||
| 18f2b12cd0 | |||
| a6a9d828ab | |||
| 327060232a | |||
| e4b7403f12 | |||
| c9977aa0a8 | |||
| c1f9a359e8 | |||
| 95a2ac1e07 | |||
| 4b86ef8c4f | |||
| 64f21ecdae | |||
| 282644f141 | |||
| ca3c1c5f3a | |||
| e4a0c6c759 | |||
| af050e5eed | |||
| 8bb5a436b2 | |||
| 4bb11e823f | |||
| 9ee58dc7a7 | |||
| 0ac8aca893 | |||
| 092b27a6d0 | |||
| 7218ec4df9 | |||
| a943b1d28d | |||
| a5a89b27c8 | |||
| f5df517145 | |||
| 9048ac00db | |||
| 1a5c21dc56 | |||
| c4a1390f4e | |||
| 7b278aab9f | |||
| a0259d8ab9 | |||
| 45e5e65a28 | |||
| c2b9134c6e | |||
| 11c6cf720d | |||
| 5a223afe4c | |||
| ef4822aa8d | |||
| 0e89d6e754 | |||
| a8330d5c3b | |||
| 717d6c5ed5 | |||
| 7262fd5d14 | |||
| 385deefd80 | |||
| 2c86ae196d | |||
| 8557f35aa5 | |||
| 1163d972a9 | |||
| fc8213c7df | |||
| 927a84d310 | |||
| 2214fcecf7 | |||
| 644f6d4790 | |||
| 8a8640a761 | |||
| 42a895d9c9 | |||
| 4af1ae82c6 | |||
| b37054c310 | |||
| 67d0616483 | |||
| f7494961de | |||
| 8a74c08928 | |||
| 6847be2c75 | |||
| 4231d99ab4 | |||
| 580698e5e5 | |||
| 0bddd942b4 | |||
| 673841301b | |||
| c8459b18ba | |||
| f21a66f70d | |||
| 54a989930e | |||
| 329bdbd9cf | |||
| 371b522e9e | |||
| 135caee606 | |||
| 21ad356c09 | |||
| 919da65d70 | |||
| 00e34a758f | |||
| ff3fa0c664 | |||
| 73018a0337 | |||
| e8788d1b32 | |||
| 5640ad48ae | |||
| 5956efafdd | |||
| cfee179152 | |||
| b165fa0e96 | |||
| 5b47b4e6c0 | |||
| f3c921c854 | |||
| 474b63754d | |||
| 723f48dec0 | |||
| f2dbb3f6c8 | |||
| 79abe1e662 | |||
| fd5e77950e | |||
| 30fdbf4328 | |||
| b7b6b4ba18 | |||
| c31fc8d163 | |||
| be63ce1bb5 | |||
| e404a7d97c | |||
| a02a6bf13e | |||
| f8d1315d90 | |||
| 2807dcfab6 | |||
| ad357e147a | |||
| 69eede7d08 | |||
| f30f231cbf | |||
| af0d82792e | |||
| d754ea50a8 | |||
| 590447d7a1 | |||
| 939ffe454d | |||
| e57d2d9d47 | |||
| 8fc7d9a4d4 | |||
| 18dcf1f097 | |||
| ccf0239bc4 | |||
| 1946a67567 | |||
| f10ebc2bdf | |||
| 2922bcc1a7 | |||
| 5d99239e95 | |||
| 1deb64f0a7 | |||
| d61d38586e | |||
| 7586e49548 | |||
| 2b42d3a55a | |||
| cd26704ae0 | |||
| d76be2f428 | |||
| 368bd2e4f9 | |||
| 51b4e7bd6e | |||
| dabaf2220f | |||
| a1a83f3869 | |||
| 73ef5b8c4a | |||
| 5cd42f6b0b | |||
| 31ebd918ea | |||
| a0fca67da4 | |||
| 26edf9534a | |||
| e973eee919 | |||
| 237ed1ef8f | |||
| 910d26b5c3 | |||
| b939c80513 | |||
| a85928e7ec | |||
| 760b7462bc | |||
| d957179df7 | |||
| bc654257e7 | |||
| 36f8d3806b | |||
| dbd7f602d3 | |||
| f4986f4596 | |||
| 09b6d28391 | |||
| 8fc297c506 | |||
| 516ad1c9bf | |||
| ec8dfe7315 | |||
| d297d1d8a7 | |||
| b51b094419 | |||
| 7cfc1f9b15 | |||
| 773dff0209 | |||
| 2275923d3c | |||
| 49283d3a66 | |||
| b94a651878 | |||
| dc4983327d | |||
| e47b74457e | |||
| 0470bcef0e | |||
| 9b82414b67 | |||
| 2e2865d34b | |||
| ed436a36ba | |||
| 70ac5ecbb6 | |||
| ba01f4b954 | |||
| 2e745956c0 | |||
| 0171b506ec | |||
| adfb23c029 | |||
| 07e6ceacd6 | |||
| 86afb6a7cf | |||
| 14b673d90f | |||
| 07c8448034 | |||
| 3963e3de55 | |||
| 9094255f50 | |||
| 55e69dc88d | |||
| 68bf362ea2 | |||
| 8ec9e10866 | |||
| 4c4e104cad | |||
| 900f249f59 | |||
| f8c779dbe5 | |||
| a44b1115c4 | |||
| ce4c523ad3 | |||
| e1f181ee64 | |||
| 7819412002 | |||
| b84fa04db6 | |||
| c5ed9d1d76 | |||
| cee4ed39bd | |||
| 559942d64d | |||
| 785b708097 | |||
| 999e142c54 | |||
| 3802cdf9b6 | |||
| 98cbaa6c6b | |||
| 58307562b6 | |||
| 8f081c86f7 | |||
| 4445965f97 | |||
| 0b7cc8bd2f | |||
| 9353e72103 | |||
| d53071eff4 | |||
| 4ada291429 | |||
| 95f73ff393 | |||
| 9fd5b44d60 | |||
| e8418bb1b9 | |||
| 5448cad29e | |||
| 4c02998325 | |||
| 6174a37a37 | |||
| f193778446 | |||
| aff5cf9ef7 | |||
| bf187c46c8 | |||
| 1b325bf86d | |||
| 83e0af288a | |||
| 9d53129075 | |||
| 774c0caf12 | |||
| 55ce07b773 | |||
| ed8b381cce | |||
| 911629a118 | |||
| eac1d430b4 | |||
| 5676a50807 | |||
| ca365f17ba | |||
| e044068b43 | |||
| a4605860c6 | |||
| 127c1f2fe2 | |||
| a2f01c07eb | |||
| ff4c22238a | |||
| 49c79aa15c | |||
| fb2d175f97 | |||
| f233ca1af4 | |||
| cbd72299c1 | |||
| cb2877cc7c | |||
| e1189b9bcf | |||
| abb7fa00ab | |||
| c124d55fa6 | |||
| 0f241355da | |||
| 754fc8a8b5 | |||
| 2c3f7619f9 | |||
| 6b9a70ace8 | |||
| 038487f954 | |||
| 0faf81d1f6 | |||
| d37de3cb1d | |||
| 733f22e7c2 | |||
| dc00b6c188 | |||
| 0a464a7c21 | |||
| f89f8bd8ee | |||
| d664ae123f | |||
| a4b598060c | |||
| e8d64af123 | |||
| 640ec5706e | |||
| 8634a5d021 | |||
| 95f066785e | |||
| ddcfaa06fa | |||
| f90274d8ce | |||
| 61e1c66b7c | |||
| c82f6f56a6 | |||
| 24b8b3098b | |||
| bd756ee45c | |||
| 201d324187 | |||
| 2421a88a54 | |||
| 8f0dbc9348 | |||
| 594e2ac999 | |||
| b62af7ac1e | |||
| a0e2debe3f | |||
| 602687801b | |||
| 58054fd0fa | |||
| d777ea79ff | |||
| ca250e969c | |||
| b1c8840f10 | |||
| 33fadb5118 | |||
| 6ad4f485d3 | |||
| 84b42b3ab3 | |||
| 33a858eaa1 | |||
| c0ff241464 | |||
| b748b46714 | |||
| 32d781769d | |||
| fbbd4adc3c | |||
| 6b24954738 | |||
| c2511ffc7b | |||
| ba32bd5d70 | |||
| 19f712cdbb | |||
| 08e255960d | |||
| f1d4c1c896 | |||
| 696e849a30 | |||
| 8169de72e2 | |||
| 3101ae7973 | |||
| 17946c5954 | |||
| a7ab92ec25 | |||
| 255514fb29 | |||
| 14d162d9f4 | |||
| 4889cf22bb | |||
| 79bb28281c | |||
| 744533c2cf | |||
| 44f4d6f617 | |||
| 8748fd49b3 | |||
| dfa53e4062 | |||
| 3dfda1b28e | |||
| f40913f7f6 | |||
| 0b33978e02 | |||
| a93777c4ed | |||
| ad24a8d2b4 | |||
| 9dac979ce8 | |||
| f8d9836ca3 | |||
| 1786be5553 | |||
| e371d133a7 | |||
| e7b3803fd0 | |||
| fcd53684b6 | |||
| b2a5bf4171 | |||
| eda51f19a2 | |||
| 570a0f6f3c | |||
| 7d74185d0b | |||
| f85dbc3fe3 | |||
| f69946cd0b | |||
| 8144fc54fc | |||
| 8365109972 | |||
| 560daefb01 | |||
| f57af4d203 | |||
| 5bb6cdf7ba | |||
| 6afcdaa928 | |||
| b862c8d4be | |||
| c489df6f5b | |||
| 62a0b65ff8 | |||
| f28303d214 | |||
| 876902070d | |||
| bbe338b9fc | |||
| 83eac08e75 | |||
| ec42154ef2 | |||
| ace9632c67 | |||
| 5089061659 | |||
| d58e83695f | |||
| 830f597134 | |||
| 4b509559fb | |||
| 939395729c | |||
| 5fdb48a7cb | |||
| 591609c262 | |||
| f3b296c2e2 | |||
| 03b888e118 | |||
| b74274628c | |||
| 5346f1c6c7 | |||
| d4b864ff12 | |||
| 7ffae17f85 | |||
| 9b1274aba3 | |||
| 0ddad45ab2 | |||
| b68cd36708 | |||
| 0f3f5d85fb | |||
| 7c30091a92 | |||
| 6294db7514 | |||
| a31007c9e7 | |||
| 3597f1942e | |||
| 9f6d69c544 | |||
| 40cb0f076a | |||
| b46813f9b7 | |||
| 76daa88cf8 | |||
| 573f0e364f | |||
| 550fed3343 | |||
| b15e3b3fa9 | |||
| 1f87a9eb6c | |||
| 29ed22de8c | |||
| 5ebdc40f83 | |||
| 9f8058bd26 | |||
| 6617ad6e03 | |||
| dd96095214 | |||
| 0db374777e | |||
| befbcffb4e | |||
| 7d0a16e06d | |||
| 70a818cbd6 | |||
| 1188141aa7 | |||
| b7f643fe39 | |||
| 329ef2a6aa | |||
| 74774c36e1 | |||
| 77eeea407c | |||
| 6dddcecb09 | |||
| ee3f7b6b6e | |||
| 37c2b5685e | |||
| 5f222f1d98 | |||
| 0266e70297 | |||
| 87586b3d0c | |||
| b1bb376c3c | |||
| 303713b560 | |||
| 4d30438400 | |||
| c4d67d77c9 | |||
| 2c1be0be8e | |||
| 23c385ec36 | |||
| d21e421ad7 | |||
| 8ec97d161e | |||
| 8985c210a1 | |||
| c57c71bf5c | |||
| 4262461457 | |||
| 1f7e5d3861 | |||
| 51deae8998 | |||
| acf95c7c98 | |||
| 1b560efabd | |||
| 6d28d97a91 | |||
| c1e69f450e | |||
| bb6188430d | |||
| 4f6fdca31f | |||
| 7636bcccd1 | |||
| 2061be4730 | |||
| 868b4e9421 | |||
| 96b377a90a | |||
| 3f73c40412 | |||
| b50dcd7dde | |||
| b06ef36ae5 | |||
| 45b05328b6 | |||
| 8ca82646a9 | |||
| 6853b4aac2 | |||
| 50df3a7389 | |||
| 994eb5e183 | |||
| ffae19b5a1 | |||
| a9cfaa4d98 | |||
| f56613ebf6 | |||
| 1f3b5b8b54 | |||
| 66a7a8a0cc | |||
| a88a38c3a9 | |||
| 16adcb5374 | |||
| d862ba4069 | |||
| a7378f3249 | |||
| 76fbc5e3d0 | |||
| f4b4430380 | |||
| d706ae62d7 | |||
| c7f0a768cb | |||
| 9c65e868f9 | |||
| d99ba07b81 | |||
| b49fd3ab30 | |||
| ae045e2cd8 | |||
| 055f907315 | |||
| bb26ce5aa1 | |||
| 3a1bd1185f | |||
| 8630d03dd4 | |||
| be7a03a957 | |||
| 28d6d7cb03 | |||
| fafb9557a8 | |||
| 4eb0e77a2a | |||
| 6cd0b03098 | |||
| 99cb869db3 | |||
| cf6bc7ecaf | |||
| 256c4b55ec | |||
| de675e3503 | |||
| 01ae7b1e2c | |||
| cbd973fdaa | |||
| e975889132 | |||
| 891abf5511 | |||
| 8adf00ae6e | |||
| cb812c742c | |||
| 26f19e1429 | |||
| aec6017195 | |||
| a5c20bee5b | |||
| d103867558 | |||
| aa55692b97 | |||
| 539bd2d1c8 | |||
| b71d1e8620 | |||
| 5a58eb803a | |||
| 541fb8177a | |||
| 6ce0245787 | |||
| 1322a9a5fd | |||
| 2aa7f5290e | |||
| a97b65eaef | |||
| 622e0b14c2 | |||
| 34510dd08a | |||
| 0bee8fef64 | |||
| e954b14196 | |||
| 8009973381 | |||
| 49b2dcb1f3 | |||
| 1c8495b54b | |||
| 15c528ca7b | |||
| a3c78691a2 | |||
| 401fc0fbab | |||
| 774b023641 | |||
| cc11375f6d | |||
| 0d4b803b08 | |||
| 4ea8572b6d | |||
| 7364006757 | |||
| 2442ba2b0e | |||
| 045728a7da | |||
| 2bc4ae172a | |||
| 6c429c4d65 | |||
| 66da1e802c | |||
| d6d6972a66 | |||
| 9b7983a452 | |||
| ef4fdb40f9 | |||
| 0b2ae6cb64 | |||
| 92b14a5ba2 | |||
| 89294e2e4f | |||
| 190dd26b9f | |||
| 7615e3485e | |||
| 9fb6383ed8 | |||
| 027626db1e | |||
| c12848d5ac | |||
| 45e3335ee8 | |||
| 9030020a85 | |||
| f978e1bef0 | |||
| ee13cfce67 | |||
| 3cee6881ee | |||
| c29554a120 | |||
| d43cd104b6 | |||
| 38d19eca14 | |||
| 3975fc9c0d | |||
| b0429237a8 | |||
| ea70e378cd | |||
| 777c3a08ab | |||
| f832aa6b6b | |||
| be4cd56146 | |||
| 021f47b00e | |||
| 36c801699f | |||
| 5b3e9b0eb3 | |||
| c04c5b6658 | |||
| b27c51b5cf | |||
| 72fee2f7a4 | |||
| d9dcdc2b73 | |||
| 5f18c88b3d | |||
| e1dbad47ce | |||
| d4350a16f3 | |||
| d793663188 | |||
| 3dbc45bdfc | |||
| 50358e26c7 | |||
| 00bc80c3c4 | |||
| 00797a3d86 | |||
| f9595828eb | |||
| 9b930023fb | |||
| 691da3bdad | |||
| 4e1653d53a | |||
| 69bc3ef9be | |||
| a71623f74c | |||
| 285bf7041a | |||
| b5781150ea | |||
| 2a8576b804 | |||
| 8069963c56 | |||
| 5d88e7bce8 | |||
| 392b529c3f | |||
| 32edb1d009 | |||
| ba35c88ab8 | |||
| 7fc47eeb6f | |||
| 04a320666e | |||
| 3f6451eff7 | |||
| a260c9a8a2 | |||
| 931070b65a | |||
| 75e06a1c89 | |||
| dfc7f88650 | |||
| 987b6c98d4 | |||
| c53be1b23f | |||
| e8907bcfc4 | |||
| db1b33d4ac | |||
| b5714c4084 | |||
| 941897ff2c | |||
| c96743c5bd | |||
| 2c10b1babc | |||
| 7bedf6c5ab | |||
| 8c81bbd3db | |||
| a56d8124d8 | |||
| c56a4a36eb | |||
| ee0917a345 | |||
| f1e96cab55 | |||
| 8fac468259 | |||
| ec9e49e875 | |||
| 0f6c08c73f | |||
| 317fda2516 | |||
| 6328135264 | |||
| bde3505758 | |||
| 680654b254 | |||
| f81fa3d712 | |||
| cde6ec2bf2 | |||
| e4868cb59e | |||
| c229929d26 | |||
| a8066cd4a9 | |||
| f7de49eb66 | |||
| ba390a7f9a | |||
| e01397c674 | |||
| e515f1f61f | |||
| 65e443954a | |||
| 03abfae41f | |||
| 6d4d20c4be | |||
| d77b3d176e | |||
| 21fac7c53c | |||
| cb61dc9c9b | |||
| 3f289546da | |||
| 821b655bc6 | |||
| af238fe132 | |||
| 0e528995c2 | |||
| fe766f4f42 | |||
| 2c477d76ec | |||
| b882423005 | |||
| 4b72658409 | |||
| d824bd8ec5 | |||
| 54dd6a2407 | |||
| cf3edfed82 | |||
| c49ae541c4 | |||
| 8ddef320e6 | |||
| d70408f204 | |||
| a351c1d65e | |||
| ec436d3dfd | |||
| be3937c37b | |||
| 3bf397a569 | |||
| 47a82bfc91 | |||
| 371acaa0b5 | |||
| d31169bab5 | |||
| 8fb208598b | |||
| 31f94a18b3 | |||
| fc3788f630 | |||
| ad3d1b42e4 | |||
| c58aa9696a | |||
| 0636e1256d | |||
| e4a38c18dd | |||
| e4e9f7e39b | |||
| c7ac3688a7 | |||
| f7530de20d | |||
| 9564a52523 | |||
| 2046d5da30 | |||
| 258af8ae44 | |||
| f5dfad5d72 | |||
| 7b4a769cca | |||
| af138d1937 | |||
| 886467e09f | |||
| 11b28ad3b5 | |||
| 8f3e64a6d5 | |||
| ff06d1e0ad | |||
| f2ad307de3 | |||
| 74af287637 | |||
| 71862e2ebb | |||
| fd229c0768 | |||
| 97fb9a89e4 | |||
| 5188c26b20 | |||
| 146a6b056e | |||
| 2398d34e87 | |||
| 69ea3d30b2 | |||
| 9023edc3c8 | |||
| 77624addf2 | |||
| 497f689aa3 | |||
| c286f9cd7d | |||
| ea8eb415de | |||
| 560c776397 | |||
| 281d520cc2 | |||
| 3041adb080 | |||
| 0d4c3014ca | |||
| ca760c3a5b | |||
| c93f8732bf | |||
| 3cf193a90e | |||
| 5db46d0c82 | |||
| e503cc4ea8 | |||
| 5856237e3f | |||
| 904adb9a32 | |||
| 880d23af1c | |||
| 14f83ad7b9 | |||
| 0494adeb2c | |||
| 288a604411 | |||
| 4a5cc1365f | |||
| ba73f8ce0d | |||
| 4a340ad3b2 | |||
| 4a2bf16b30 | |||
| 7d62b3f799 | |||
| de20887e11 | |||
| 2af4bf7eac | |||
| ef57ac78a3 | |||
| adddf339bb | |||
| 3f55b3af42 | |||
| 8830209125 | |||
| 2e57f3fa22 | |||
| d1c44dca04 | |||
| 5fa28b12fa | |||
| 80cbc3ffee | |||
| 31f9d66f12 | |||
| 767b4f4fbe | |||
| 17fb3a8ce0 | |||
| 9382f854b3 | |||
| 91cb4a6770 | |||
| 6f7a228707 | |||
| f848d67341 | |||
| b889debfb5 | |||
| d865159bd6 | |||
| a8b502a975 | |||
| b44d539c94 | |||
| 5af09cfda5 | |||
| 6f0aa186d8 | |||
| 5efbaaba77 | |||
| ba46599aa2 | |||
| a5e68d9b25 | |||
| 36c2886302 | |||
| c065abdcaf | |||
| 1dbd2a1ad5 | |||
| 9d4bf4fe0c | |||
| 96a3180770 | |||
| 5e8a49f7f2 | |||
| d1967bb281 | |||
| f66c381f11 | |||
| 5e6a5e48e6 | |||
| c99541828f | |||
| b6e7733b4c | |||
| 89eeb36835 | |||
| 654b94f0a7 | |||
| 0a3e070ffb | |||
| 9955f51215 | |||
| baf8b5beaf | |||
| d4a5f91766 | |||
| 9f87282c6d | |||
| 9f3de8b40e | |||
| 56f1a2fe49 | |||
| 4abe836896 | |||
| 9827411b7c | |||
| 5be8b573be | |||
| 411adab149 | |||
| 3d5b2c8a51 | |||
| 9a64e737cf | |||
| 485d517c68 | |||
| 16b4578cc1 | |||
| 8559c43914 | |||
| 2931463d3a | |||
| d4737819cd | |||
| da28ce3339 | |||
| 61bc8d12d3 | |||
| e8472b5fbe | |||
| 26f63fe741 | |||
| a47a3bfc7c | |||
| 8b8a510fd6 | |||
| 64a63464d7 | |||
| 0ca93c5b94 | |||
| df43805953 | |||
| d86663af14 | |||
| 5f426d8667 | |||
| 184bde47dc | |||
| efca5d561f | |||
| bc9be5a71f | |||
| 6ae09ba266 | |||
| d160436921 | |||
| 7135678f71 | |||
| 35ab8254b7 | |||
| ea5d437dbb | |||
| f579beec5d | |||
| 5d088e02c8 | |||
| 30a1bc4c64 | |||
| 89f38c938b | |||
| 2e2de7607b | |||
| e4a4f4001f | |||
| 9ee8ce67bf | |||
| bd434b80c6 | |||
| 0f270f7cbf | |||
| fb877c2c3e | |||
| f27c72081b | |||
| cdfa71fa8e | |||
| 3ea2d5ef61 | |||
| a94a076e39 | |||
| 1c84da9caa | |||
| 0a5893d337 | |||
| 50b4ff8354 | |||
| 4b70f4bf23 | |||
| 72164ba59c | |||
| fbae6604b1 | |||
| f049c4776a | |||
| 8d1ad6fbf6 | |||
| a6b02a61b7 | |||
| 9d84af11fe | |||
| 1da452029b | |||
| 1e2fca4040 | |||
| d33e7a9b07 | |||
| 8d499e6efa | |||
| 580b09381b | |||
| bcfe28b2ae | |||
| 3388c22d71 | |||
| 98f24570ab | |||
| 3c87d4fa9b | |||
| 96fa8fa8dc | |||
| a059fa86c4 | |||
| 683c6146ce | |||
| c6f5f6a082 | |||
| c76bb34a96 | |||
| ac6e6f73f2 | |||
| 59402d4a6d | |||
| 7e6901af27 | |||
| 53ad4d8b56 | |||
| a7ca826593 | |||
| 585a5351bf | |||
| 6b76ebc2ca | |||
| 020e66d2e6 | |||
| 1656e419bb | |||
| bb406a4492 | |||
| 94f3cae760 | |||
| e63b6088ba | |||
| 073d437384 | |||
| 69563aeed9 | |||
| e0a9860708 | |||
| 005009f4e5 | |||
| 9dcadfa642 | |||
| 7bd4688747 | |||
| b3a0ded37a | |||
| d993bd36d0 | |||
| 84ba591118 | |||
| 158399da4b | |||
| 8fcf8b97f1 | |||
| 17d42e023e | |||
| 675d336ebb | |||
| 4206a1edd0 | |||
| 1864845811 | |||
| 2583f68972 | |||
| cea17c92f9 | |||
| 8d763fb503 | |||
| eafa569365 | |||
| 5e25702672 | |||
| f52165e1d3 | |||
| dbad9d96e0 | |||
| ef1601218d | |||
| f52fbf8006 | |||
| d37eb63581 | |||
| f3a158725d | |||
| 0271d70775 | |||
| 05d26499b4 | |||
| 82dc68ba9f | |||
| aa2452caf3 | |||
| ec96409176 | |||
| 04a9a951b8 | |||
| 458771a181 | |||
| 1d00d00241 | |||
| 21cfead52c | |||
| e6a6ba5221 | |||
| a6013557f2 | |||
| d25657c439 | |||
| d21424c8d3 | |||
| 0405f35403 | |||
| d2562cde12 | |||
| fc27d158c0 | |||
| 41b7674aee | |||
| 0202218f58 | |||
| a0b2d6a01e | |||
| 1482e5215a | |||
| 832c414b0d | |||
| 58863c3243 | |||
| e5f05fa2b9 | |||
| 6e8f82ad76 | |||
| e638d66bbe | |||
| 4cc98d80f8 | |||
| e004e47e5a | |||
| bccc71afb4 | |||
| 31e8be3008 | |||
| e9e52faae7 | |||
| edbbc1a4df | |||
| 3a06de298b | |||
| 3bf418e63f | |||
| e9e6e47a93 | |||
| 0612adec41 | |||
| d539da19d2 | |||
| 9a063fc3cb | |||
| 9cc482c788 | |||
| 2a69611509 | |||
| d6530f7d49 | |||
| 0e1f734b05 | |||
| 571c2d6d8d | |||
| 327e92aefe | |||
| 2688194d44 | |||
| 9d5dc3d33f | |||
| c306d83869 | |||
| aa2de71230 | |||
| c17f2b15fd | |||
| be87517734 | |||
| a8bbaa1795 | |||
| 61381372ec | |||
| a057f170c6 | |||
| a0afe32d16 | |||
| e0bafa1911 | |||
| 9b5b9ec9a6 | |||
| c02b3a4c7a | |||
| d90d893b5e | |||
| e4ed7d3dd7 | |||
| 7d6668515c | |||
| bc90567e09 | |||
| 406979ea12 | |||
| d258662383 | |||
| 9f2f31632c | |||
| f7bc4e6e45 | |||
| 906c18eb46 | |||
| 5637996f79 | |||
| def6ec4d16 | |||
| 03b43079eb | |||
| 763e72cc9f | |||
| 9fc3ff4c30 | |||
| 242a7d9fea | |||
| 95b8f3f800 | |||
| 9b4967071e | |||
| b0b4ca17db | |||
| 4ce30d9522 | |||
| 530fccbf27 | |||
| 843a961a8c | |||
| f7b3f0e842 | |||
| 701b2427bd | |||
| e453f09f06 | |||
| d5f86b6359 | |||
| cc9d503dde | |||
| 5f1843c9cb | |||
| 3542033342 | |||
| 34f67c5722 | |||
| cbcb05ca09 | |||
| 7ee8a2bbb7 | |||
| 87633b876c | |||
| e64b957274 | |||
| 15abcaedc1 | |||
| 8b45b1c490 | |||
| 17d2b5bf17 | |||
| f057aec4a9 | |||
| e5e5d7d4ab | |||
| 11752d4e63 | |||
| daac86691d | |||
| a1ad8604a1 | |||
| 2deb08a529 | |||
| aaa73b2569 | |||
| 42e8789f0b | |||
| 384d684484 | |||
| fbae5614eb | |||
| 1bcc981a5a | |||
| 581b92e4a7 | |||
| 1c0b7bdf4f | |||
| 65b976439f | |||
| 2fb3f76399 | |||
| 0e17a89e4d | |||
| 81d716f5cc | |||
| 65572de4a7 | |||
| 72dc7a5c54 | |||
| 8cf43c6317 | |||
| cd0b8b4cf2 | |||
| 2646080543 | |||
| 79654ac509 | |||
| 69a95e431b | |||
| 6eb186c97e | |||
| ee823afdad | |||
| e5f450cf0b | |||
| ce009ea1aa | |||
| e8ea215a13 | |||
| 67e48418af | |||
| 69cfe28f31 | |||
| dd63b98fb0 | |||
| 430467db1c | |||
| 44a54b63f1 | |||
| 6bc0256292 | |||
| e12a0cd9eb | |||
| c3224dd9a1 | |||
| 62228e6b18 | |||
| 992f549ae7 | |||
| ee06046412 | |||
| 75b9d6f6b1 | |||
| ea6220f381 | |||
| f46c73040c | |||
| 399cddf444 | |||
| 4ab8b0b738 | |||
| a72cec1ff8 | |||
| 220ef1d27d | |||
| 21df37d7fd | |||
| f948cd008d | |||
| cb0504028e | |||
| 35f04aaf24 | |||
| ad2ad4c657 | |||
| 875183b310 | |||
| c420b327bf | |||
| 8c0429d1e5 | |||
| 84070c02e6 | |||
| 5467ba3c23 | |||
| 87c50c5cbc | |||
| fa5b2aec3a | |||
| a41cbb9ca9 | |||
| 3bd3ef0aea | |||
| 4a87d7b787 | |||
| a54f9011c3 | |||
| bcdf41dadc | |||
| 651ec3b31e | |||
| 12c6c2f550 | |||
| bac96aa04a | |||
| e65c515d6b | |||
| 643be3c6f9 | |||
| 31d4f46f5e | |||
| 53d15e5ec2 | |||
| 4260ed0c7f | |||
| 4f97d3446d | |||
| 27b593a944 | |||
| a6e89293df | |||
| 2bfde55429 | |||
| cd1bb27dd4 | |||
| 320fa1b2f0 | |||
| d7a26899a9 | |||
| 7f1f08d094 | |||
| add890a10b | |||
| 1949eb8604 | |||
| 6d6267c378 | |||
| e663bc5330 | |||
| 3368d03285 | |||
| 450b60a303 | |||
| fcd70a3c81 | |||
| 3b5de9f18b | |||
| 22b85810fe | |||
| 4f94f29f39 | |||
| 9a0b20d3fc | |||
| b0d28ac3ab | |||
| 70d88364fe | |||
| ed4d007e3c | |||
| 2395833c07 | |||
| fa649ba1e2 | |||
| 2c9075e919 | |||
| 55a6b2bdc4 | |||
| 23ecf3d5c6 | |||
| 910f779eb1 | |||
| f4c27cda1a | |||
| 5ccff25df2 | |||
| dc5af66ead | |||
| 1abae04ceb | |||
| bf7d02578e | |||
| 1c23465383 | |||
| 857e045ced | |||
| 615d98da24 | |||
| 7f336dd59b | |||
| af935365e3 | |||
| 8b8412ef87 | |||
| 3dca13a958 | |||
| 84f3e86790 |
@@ -0,0 +1,260 @@
|
|||||||
|
name: Stockfish
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- tools
|
||||||
|
- github_ci
|
||||||
|
- github_ci_armv7
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- tools
|
||||||
|
jobs:
|
||||||
|
Stockfish:
|
||||||
|
name: ${{ matrix.config.name }}
|
||||||
|
runs-on: ${{ matrix.config.os }}
|
||||||
|
env:
|
||||||
|
COMPILER: ${{ matrix.config.compiler }}
|
||||||
|
COMP: ${{ matrix.config.comp }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
config:
|
||||||
|
# set the variable for the required tests:
|
||||||
|
# run_expensive_tests: true
|
||||||
|
# run_32bit_tests: true
|
||||||
|
# run_64bit_tests: true
|
||||||
|
- {
|
||||||
|
name: "Ubuntu 20.04 GCC",
|
||||||
|
os: ubuntu-20.04,
|
||||||
|
compiler: g++,
|
||||||
|
comp: gcc,
|
||||||
|
run_expensive_tests: true,
|
||||||
|
run_64bit_tests: true,
|
||||||
|
shell: 'bash {0}'
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
name: "Ubuntu 20.04 Clang",
|
||||||
|
os: ubuntu-20.04,
|
||||||
|
compiler: clang++,
|
||||||
|
comp: clang,
|
||||||
|
run_64bit_tests: true,
|
||||||
|
shell: 'bash {0}'
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
name: "MacOS 10.15 Apple Clang",
|
||||||
|
os: macos-10.15,
|
||||||
|
compiler: clang++,
|
||||||
|
comp: clang,
|
||||||
|
run_64bit_tests: true,
|
||||||
|
shell: 'bash {0}'
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
name: "MacOS 10.15 GCC 10",
|
||||||
|
os: macos-10.15,
|
||||||
|
compiler: g++-10,
|
||||||
|
comp: gcc,
|
||||||
|
run_64bit_tests: true,
|
||||||
|
shell: 'bash {0}'
|
||||||
|
}
|
||||||
|
- {
|
||||||
|
name: "Windows 2022 Mingw-w64 GCC x86_64",
|
||||||
|
os: windows-2022,
|
||||||
|
compiler: g++,
|
||||||
|
comp: mingw,
|
||||||
|
run_64bit_tests: true,
|
||||||
|
msys_sys: 'mingw64',
|
||||||
|
msys_env: 'x86_64-gcc',
|
||||||
|
shell: 'msys2 {0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: src
|
||||||
|
shell: ${{ matrix.config.shell }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Download required linux packages
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install expect valgrind g++-multilib
|
||||||
|
|
||||||
|
- name: Setup msys and install required packages
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
uses: msys2/setup-msys2@v2
|
||||||
|
with:
|
||||||
|
msystem: ${{matrix.config.msys_sys}}
|
||||||
|
install: mingw-w64-${{matrix.config.msys_env}} make git expect
|
||||||
|
|
||||||
|
- name: Download the used network from the fishtest framework
|
||||||
|
run: |
|
||||||
|
make net
|
||||||
|
|
||||||
|
- name: Extract the bench number from the commit history
|
||||||
|
run: |
|
||||||
|
git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
|
||||||
|
[ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found"
|
||||||
|
|
||||||
|
- name: Check compiler
|
||||||
|
run: |
|
||||||
|
$COMPILER -v
|
||||||
|
|
||||||
|
- name: Test help target
|
||||||
|
run: |
|
||||||
|
make help
|
||||||
|
|
||||||
|
# x86-32 tests
|
||||||
|
|
||||||
|
- name: Test debug x86-32 build
|
||||||
|
if: ${{ matrix.config.run_32bit_tests }}
|
||||||
|
run: |
|
||||||
|
export CXXFLAGS="-D_GLIBCXX_DEBUG"
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-32 optimize=no debug=yes build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test x86-32 build
|
||||||
|
if: ${{ matrix.config.run_32bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-32 build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test x86-32-sse41-popcnt build
|
||||||
|
if: ${{ matrix.config.run_32bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-32-sse41-popcnt build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test x86-32-sse2 build
|
||||||
|
if: ${{ matrix.config.run_32bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-32-sse2 build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test general-32 build
|
||||||
|
if: ${{ matrix.config.run_32bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=general-32 build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
# x86-64 tests
|
||||||
|
|
||||||
|
- name: Test debug x86-64-modern build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
export CXXFLAGS="-D_GLIBCXX_DEBUG"
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-modern optimize=no debug=yes build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test x86-64-modern build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-modern build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test x86-64-ssse3 build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-ssse3 build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test x86-64-sse3-popcnt build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-sse3-popcnt build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test x86-64 build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64 build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
- name: Test general-64 build
|
||||||
|
if: matrix.config.run_64bit_tests
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=general-64 build
|
||||||
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
|
# x86-64 with newer extensions tests
|
||||||
|
|
||||||
|
- name: Compile x86-64-avx2 build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-avx2 build
|
||||||
|
|
||||||
|
- name: Compile x86-64-bmi2 build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-bmi2 build
|
||||||
|
|
||||||
|
- name: Compile x86-64-avx512 build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-avx512 build
|
||||||
|
|
||||||
|
- name: Compile x86-64-vnni512 build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-vnni512 build
|
||||||
|
|
||||||
|
- name: Compile x86-64-vnni256 build
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-vnni256 build
|
||||||
|
|
||||||
|
# Other tests
|
||||||
|
|
||||||
|
- name: Check perft and search reproducibility
|
||||||
|
if: ${{ matrix.config.run_64bit_tests }}
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-modern build
|
||||||
|
../tests/perft.sh
|
||||||
|
../tests/reprosearch.sh
|
||||||
|
|
||||||
|
# Sanitizers
|
||||||
|
|
||||||
|
- name: Run under valgrind
|
||||||
|
if: ${{ matrix.config.run_expensive_tests }}
|
||||||
|
run: |
|
||||||
|
export CXXFLAGS="-O1 -fno-inline"
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-modern debug=yes optimize=no build > /dev/null
|
||||||
|
../tests/instrumented.sh --valgrind
|
||||||
|
../tests/instrumented.sh --valgrind-thread
|
||||||
|
|
||||||
|
- name: Run with UB sanitizer
|
||||||
|
if: ${{ matrix.config.run_expensive_tests }}
|
||||||
|
run: |
|
||||||
|
export CXXFLAGS="-O1 -fno-inline"
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-modern sanitize=undefined optimize=no debug=yes build > /dev/null
|
||||||
|
../tests/instrumented.sh --sanitizer-undefined
|
||||||
|
|
||||||
|
- name: Run with thread sanitizer
|
||||||
|
if: ${{ matrix.config.run_expensive_tests }}
|
||||||
|
run: |
|
||||||
|
export CXXFLAGS="-O1 -fno-inline"
|
||||||
|
make clean
|
||||||
|
make -j2 ARCH=x86-64-modern sanitize=thread optimize=no debug=yes build > /dev/null
|
||||||
|
../tests/instrumented.sh --sanitizer-thread
|
||||||
+12
@@ -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
|
||||||
|
|
||||||
-80
@@ -1,80 +0,0 @@
|
|||||||
language: cpp
|
|
||||||
dist: bionic
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: linux
|
|
||||||
compiler: gcc
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages: ['g++-8', 'g++-8-multilib', 'g++-multilib', 'valgrind', 'expect', 'curl']
|
|
||||||
env:
|
|
||||||
- COMPILER=g++-8
|
|
||||||
- COMP=gcc
|
|
||||||
|
|
||||||
- os: linux
|
|
||||||
compiler: clang
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages: ['clang-10', 'llvm-10-dev', 'g++-multilib', 'valgrind', 'expect', 'curl']
|
|
||||||
env:
|
|
||||||
- COMPILER=clang++-10
|
|
||||||
- COMP=clang
|
|
||||||
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode12
|
|
||||||
compiler: gcc
|
|
||||||
env:
|
|
||||||
- COMPILER=g++
|
|
||||||
- COMP=gcc
|
|
||||||
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode12
|
|
||||||
compiler: clang
|
|
||||||
env:
|
|
||||||
- COMPILER=clang++
|
|
||||||
- COMP=clang
|
|
||||||
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- cd src
|
|
||||||
|
|
||||||
script:
|
|
||||||
# Obtain bench reference from git log
|
|
||||||
- git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
|
|
||||||
- export benchref=$(cat git_sig)
|
|
||||||
- echo "Reference bench:" $benchref
|
|
||||||
|
|
||||||
#
|
|
||||||
# Compiler version string
|
|
||||||
- $COMPILER -v
|
|
||||||
|
|
||||||
#
|
|
||||||
# Verify bench number against various builds
|
|
||||||
- export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
|
|
||||||
- make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref; fi
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref; fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# Check perft and reproducible search
|
|
||||||
- export CXXFLAGS="-Werror"
|
|
||||||
- make clean && make -j2 ARCH=x86-64 build
|
|
||||||
- ../tests/perft.sh
|
|
||||||
- ../tests/reprosearch.sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Valgrind
|
|
||||||
#
|
|
||||||
- export CXXFLAGS="-O1 -fno-inline"
|
|
||||||
- if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi
|
|
||||||
- if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi
|
|
||||||
|
|
||||||
#
|
|
||||||
# Sanitizer
|
|
||||||
#
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# List of authors for Stockfish, as of August 4, 2020
|
# List of authors for Stockfish
|
||||||
|
|
||||||
# Founders of the Stockfish project and fishtest infrastructure
|
# Founders of the Stockfish project and fishtest infrastructure
|
||||||
Tord Romstad (romstad)
|
Tord Romstad (romstad)
|
||||||
@@ -19,32 +19,43 @@ Alain Savard (Rocky640)
|
|||||||
Alayan Feh (Alayan-stk-2)
|
Alayan Feh (Alayan-stk-2)
|
||||||
Alexander Kure
|
Alexander Kure
|
||||||
Alexander Pagel (Lolligerhans)
|
Alexander Pagel (Lolligerhans)
|
||||||
|
Alfredo Menezes (lonfom169)
|
||||||
Ali AlZhrani (Cooffe)
|
Ali AlZhrani (Cooffe)
|
||||||
|
Andrei Vetrov (proukornew)
|
||||||
Andrew Grant (AndyGrant)
|
Andrew Grant (AndyGrant)
|
||||||
Andrey Neporada (nepal)
|
Andrey Neporada (nepal)
|
||||||
Andy Duplain
|
Andy Duplain
|
||||||
|
Antoine Champion (antoinechampion)
|
||||||
Aram Tumanian (atumanian)
|
Aram Tumanian (atumanian)
|
||||||
Arjun Temurnikar
|
Arjun Temurnikar
|
||||||
|
Artem Solopiy (EntityFX)
|
||||||
Auguste Pop
|
Auguste Pop
|
||||||
Balint Pfliegel
|
Balint Pfliegel
|
||||||
|
Ben Chaney (Chaneybenjamini)
|
||||||
Ben Koshy (BKSpurgeon)
|
Ben Koshy (BKSpurgeon)
|
||||||
Bill Henry (VoyagerOne)
|
Bill Henry (VoyagerOne)
|
||||||
Bojun Guo (noobpwnftw, Nooby)
|
Bojun Guo (noobpwnftw, Nooby)
|
||||||
braich
|
braich
|
||||||
Brian Sheppard (SapphireBrand, briansheppard-toast)
|
Brian Sheppard (SapphireBrand, briansheppard-toast)
|
||||||
|
Bruno de Melo Costa (BM123499)
|
||||||
Bryan Cross (crossbr)
|
Bryan Cross (crossbr)
|
||||||
candirufish
|
candirufish
|
||||||
Chess13234
|
Chess13234
|
||||||
Chris Cain (ceebo)
|
Chris Cain (ceebo)
|
||||||
|
Dale Weiler (graphitemaster)
|
||||||
Dan Schmidt (dfannius)
|
Dan Schmidt (dfannius)
|
||||||
Daniel Axtens (daxtens)
|
Daniel Axtens (daxtens)
|
||||||
Daniel Dugovic (ddugovic)
|
Daniel Dugovic (ddugovic)
|
||||||
Dariusz Orzechowski
|
Dariusz Orzechowski (dorzechowski)
|
||||||
David Zar
|
David Zar
|
||||||
Daylen Yang (daylen)
|
Daylen Yang (daylen)
|
||||||
|
Deshawn Mohan-Smith (GoldenRare)
|
||||||
|
Dieter Dobbelaere (ddobbelaere)
|
||||||
DiscanX
|
DiscanX
|
||||||
Dominik Schlösser (domschl)
|
Dominik Schlösser (domschl)
|
||||||
double-beep
|
double-beep
|
||||||
|
Douglas Matos Gomes (dsmsgms)
|
||||||
|
Dubslow
|
||||||
Eduardo Cáceres (eduherminio)
|
Eduardo Cáceres (eduherminio)
|
||||||
Eelco de Groot (KingDefender)
|
Eelco de Groot (KingDefender)
|
||||||
Elvin Liu (solarlight2)
|
Elvin Liu (solarlight2)
|
||||||
@@ -53,12 +64,15 @@ Ernesto Gatti
|
|||||||
Linmiao Xu (linrock)
|
Linmiao Xu (linrock)
|
||||||
Fabian Beuke (madnight)
|
Fabian Beuke (madnight)
|
||||||
Fabian Fichter (ianfab)
|
Fabian Fichter (ianfab)
|
||||||
|
Fanael Linithien (Fanael)
|
||||||
fanon
|
fanon
|
||||||
Fauzi Akram Dabat (FauziAkram)
|
Fauzi Akram Dabat (FauziAkram)
|
||||||
Felix Wittmann
|
Felix Wittmann
|
||||||
gamander
|
gamander
|
||||||
Gary Heckman (gheckman)
|
Gary Heckman (gheckman)
|
||||||
|
George Sobala (gsobala)
|
||||||
gguliash
|
gguliash
|
||||||
|
Giacomo Lorenzetti (G-Lorenz)
|
||||||
Gian-Carlo Pascutto (gcp)
|
Gian-Carlo Pascutto (gcp)
|
||||||
Gontran Lemaire (gonlem)
|
Gontran Lemaire (gonlem)
|
||||||
Goodkov Vasiliy Aleksandrovich (goodkov)
|
Goodkov Vasiliy Aleksandrovich (goodkov)
|
||||||
@@ -79,21 +93,26 @@ Jean Gauthier (OuaisBla)
|
|||||||
Jean-Francois Romang (jromang)
|
Jean-Francois Romang (jromang)
|
||||||
Jekaa
|
Jekaa
|
||||||
Jerry Donald Watson (jerrydonaldwatson)
|
Jerry Donald Watson (jerrydonaldwatson)
|
||||||
|
jjoshua2
|
||||||
Jonathan Calovski (Mysseno)
|
Jonathan Calovski (Mysseno)
|
||||||
Jonathan Dumale (SFisGOD)
|
Jonathan Buladas Dumale (SFisGOD)
|
||||||
Joost VandeVondele (vondele)
|
Joost VandeVondele (vondele)
|
||||||
Jörg Oster (joergoster)
|
Jörg Oster (joergoster)
|
||||||
Joseph Ellis (jhellis3)
|
Joseph Ellis (jhellis3)
|
||||||
Joseph R. Prostko
|
Joseph R. Prostko
|
||||||
|
Julian Willemer (NightlyKing)
|
||||||
jundery
|
jundery
|
||||||
Justin Blanchard (UncombedCoconut)
|
Justin Blanchard (UncombedCoconut)
|
||||||
Kelly Wilson
|
Kelly Wilson
|
||||||
Ken Takusagawa
|
Ken Takusagawa
|
||||||
|
Kian E (KJE-98)
|
||||||
kinderchocolate
|
kinderchocolate
|
||||||
Kiran Panditrao (Krgp)
|
Kiran Panditrao (Krgp)
|
||||||
Kojirion
|
Kojirion
|
||||||
|
Krystian Kuzniarek (kuzkry)
|
||||||
Leonardo Ljubičić (ICCF World Champion)
|
Leonardo Ljubičić (ICCF World Champion)
|
||||||
Leonid Pechenik (lp--)
|
Leonid Pechenik (lp--)
|
||||||
|
Liam Keegan (lkeegan)
|
||||||
Linus Arver (listx)
|
Linus Arver (listx)
|
||||||
loco-loco
|
loco-loco
|
||||||
Lub van den Berg (ElbertoOne)
|
Lub van den Berg (ElbertoOne)
|
||||||
@@ -104,8 +123,11 @@ Maciej Żenczykowski (zenczykowski)
|
|||||||
Malcolm Campbell (xoto10)
|
Malcolm Campbell (xoto10)
|
||||||
Mark Tenzer (31m059)
|
Mark Tenzer (31m059)
|
||||||
marotear
|
marotear
|
||||||
|
Matt Ginsberg (mattginsberg)
|
||||||
Matthew Lai (matthewlai)
|
Matthew Lai (matthewlai)
|
||||||
Matthew Sullivan (Matt14916)
|
Matthew Sullivan (Matt14916)
|
||||||
|
Max A. (Disservin)
|
||||||
|
Maxim Molchanov (Maxim)
|
||||||
Michael An (man)
|
Michael An (man)
|
||||||
Michael Byrne (MichaelB7)
|
Michael Byrne (MichaelB7)
|
||||||
Michael Chaly (Vizvezdenec)
|
Michael Chaly (Vizvezdenec)
|
||||||
@@ -114,6 +136,7 @@ Michael Whiteley (protonspring)
|
|||||||
Michel Van den Bergh (vdbergh)
|
Michel Van den Bergh (vdbergh)
|
||||||
Miguel Lahoz (miguel-l)
|
Miguel Lahoz (miguel-l)
|
||||||
Mikael Bäckman (mbootsector)
|
Mikael Bäckman (mbootsector)
|
||||||
|
Mike Babigian (Farseer)
|
||||||
Mira
|
Mira
|
||||||
Miroslav Fontán (Hexik)
|
Miroslav Fontán (Hexik)
|
||||||
Moez Jellouli (MJZ1977)
|
Moez Jellouli (MJZ1977)
|
||||||
@@ -125,6 +148,8 @@ Niklas Fiekas (niklasf)
|
|||||||
Nikolay Kostov (NikolayIT)
|
Nikolay Kostov (NikolayIT)
|
||||||
Nguyen Pham (nguyenpham)
|
Nguyen Pham (nguyenpham)
|
||||||
Norman Schmidt (FireFather)
|
Norman Schmidt (FireFather)
|
||||||
|
notruck
|
||||||
|
Ofek Shochat (OfekShochat, ghostway)
|
||||||
Ondrej Mosnáček (WOnder93)
|
Ondrej Mosnáček (WOnder93)
|
||||||
Oskar Werkelin Ahlin
|
Oskar Werkelin Ahlin
|
||||||
Pablo Vazquez
|
Pablo Vazquez
|
||||||
@@ -133,6 +158,7 @@ Pascal Romaret
|
|||||||
Pasquale Pigazzini (ppigazzini)
|
Pasquale Pigazzini (ppigazzini)
|
||||||
Patrick Jansen (mibere)
|
Patrick Jansen (mibere)
|
||||||
pellanda
|
pellanda
|
||||||
|
Peter Schneider (pschneider1968)
|
||||||
Peter Zsifkovits (CoffeeOne)
|
Peter Zsifkovits (CoffeeOne)
|
||||||
Praveen Kumar Tummala (praveentml)
|
Praveen Kumar Tummala (praveentml)
|
||||||
Rahul Dsilva (silversolver1)
|
Rahul Dsilva (silversolver1)
|
||||||
@@ -145,19 +171,23 @@ Rodrigo Exterckötter Tjäder
|
|||||||
Ron Britvich (Britvich)
|
Ron Britvich (Britvich)
|
||||||
Ronald de Man (syzygy1, syzygy)
|
Ronald de Man (syzygy1, syzygy)
|
||||||
rqs
|
rqs
|
||||||
|
Rui Coelho (ruicoelhopedro)
|
||||||
Ryan Schmitt
|
Ryan Schmitt
|
||||||
Ryan Takker
|
Ryan Takker
|
||||||
Sami Kiminki (skiminki)
|
Sami Kiminki (skiminki)
|
||||||
Sebastian Buchwald (UniQP)
|
Sebastian Buchwald (UniQP)
|
||||||
Sergei Antonov (saproj)
|
Sergei Antonov (saproj)
|
||||||
Sergei Ivanov (svivanov72)
|
Sergei Ivanov (svivanov72)
|
||||||
|
Sergio Vieri (sergiovieri)
|
||||||
sf-x
|
sf-x
|
||||||
Shane Booth (shane31)
|
Shane Booth (shane31)
|
||||||
Shawn Varghese (xXH4CKST3RXx)
|
Shawn Varghese (xXH4CKST3RXx)
|
||||||
|
Siad Daboul (Topologist)
|
||||||
Stefan Geschwentner (locutus2)
|
Stefan Geschwentner (locutus2)
|
||||||
Stefano Cardanobile (Stefano80)
|
Stefano Cardanobile (Stefano80)
|
||||||
Steinar Gunderson (sesse)
|
Steinar Gunderson (sesse)
|
||||||
Stéphane Nicolet (snicolet)
|
Stéphane Nicolet (snicolet)
|
||||||
|
Prokop Randáček (ProkopRandacek)
|
||||||
Thanar2
|
Thanar2
|
||||||
thaspel
|
thaspel
|
||||||
theo77186
|
theo77186
|
||||||
@@ -165,11 +195,13 @@ Tom Truscott
|
|||||||
Tom Vijlbrief (tomtor)
|
Tom Vijlbrief (tomtor)
|
||||||
Tomasz Sobczyk (Sopel97)
|
Tomasz Sobczyk (Sopel97)
|
||||||
Torsten Franz (torfranz, tfranzer)
|
Torsten Franz (torfranz, tfranzer)
|
||||||
|
Torsten Hellwig (Torom)
|
||||||
Tracey Emery (basepr1me)
|
Tracey Emery (basepr1me)
|
||||||
tttak
|
tttak
|
||||||
Unai Corzo (unaiic)
|
Unai Corzo (unaiic)
|
||||||
Uri Blass (uriblass)
|
Uri Blass (uriblass)
|
||||||
Vince Negri (cuddlestmonkey)
|
Vince Negri (cuddlestmonkey)
|
||||||
|
xefoci7612
|
||||||
zz4032
|
zz4032
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,51 @@
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
[](https://travis-ci.org/official-stockfish/Stockfish)
|
[](https://github.com/official-stockfish/Stockfish/actions)
|
||||||
[](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
|
|
||||||
|
|
||||||
[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
|
[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
|
||||||
derived from Glaurung 2.1. It features two evaluation functions, the classical
|
derived from Glaurung 2.1. Stockfish is not a complete chess program and requires a
|
||||||
evaluation based on handcrafted terms, and the NNUE evaluation based on
|
UCI-compatible graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid,
|
||||||
efficiently updateable neural networks. The classical evaluation runs efficiently
|
Cute Chess, eboard, Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order
|
||||||
on most 64bit CPU architectures, while the NNUE evaluation benefits strongly from the
|
to be used comfortably. Read the documentation for your GUI of choice for information
|
||||||
vector intrinsics available on modern CPUs (avx2 or similar).
|
about how to use Stockfish with it.
|
||||||
|
|
||||||
Stockfish is not a complete chess program and requires a
|
|
||||||
UCI-compatible GUI (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, Arena,
|
|
||||||
Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably.
|
|
||||||
Read the documentation for your GUI of choice for information about how to use
|
|
||||||
Stockfish with it.
|
|
||||||
|
|
||||||
|
The Stockfish engine features two evaluation functions for chess. The efficiently
|
||||||
|
updatable neural network (NNUE) based evaluation is the default and by far the strongest.
|
||||||
|
The classical evaluation based on handcrafted terms remains available. The strongest
|
||||||
|
network is integrated in the binary and downloaded automatically during the build process.
|
||||||
|
The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2,
|
||||||
|
avx2, neon, or similar).
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
This distribution of Stockfish consists of the following files:
|
This distribution of Stockfish consists of the following files:
|
||||||
|
|
||||||
* Readme.md, the file you are currently reading.
|
* [README.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md),
|
||||||
|
the file you are currently reading.
|
||||||
|
|
||||||
* Copying.txt, a text file containing the GNU General Public License version 3.
|
* [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt),
|
||||||
|
a text file containing the GNU General Public License version 3.
|
||||||
|
|
||||||
* src, a subdirectory containing the full source code, including a Makefile
|
* [AUTHORS](https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS),
|
||||||
|
a text file with the list of authors for the project
|
||||||
|
|
||||||
|
* [src](https://github.com/official-stockfish/Stockfish/tree/master/src),
|
||||||
|
a subdirectory containing the full source code, including a Makefile
|
||||||
that can be used to compile Stockfish on Unix-like systems.
|
that can be used to compile Stockfish on Unix-like systems.
|
||||||
|
|
||||||
To use the NNUE evaluation an additional data file with neural network parameters
|
* a file with the .nnue extension, storing the neural network for the NNUE
|
||||||
needs to be downloaded. The filename for the default set can be found as the default
|
evaluation. Binary distributions will have this file embedded.
|
||||||
value of the `EvalFile` UCI option, with the format
|
|
||||||
`nn-[SHA256 first 12 digits].nnue` (e.g. nn-c157e0a5755b.nnue). This file can be downloaded from
|
|
||||||
```
|
|
||||||
https://tests.stockfishchess.org/api/nn/[filename]
|
|
||||||
```
|
|
||||||
replacing `[filename]` as needed.
|
|
||||||
|
|
||||||
|
## The UCI protocol and available options
|
||||||
|
|
||||||
## UCI options
|
The Universal Chess Interface (UCI) is a standard protocol used to communicate with
|
||||||
|
a chess engine, and is the recommended way to do so for typical graphical user interfaces
|
||||||
|
(GUI) or chess tools. Stockfish implements the majority of its options as described
|
||||||
|
in [the UCI protocol](https://www.shredderchess.com/download/div/uci.zip).
|
||||||
|
|
||||||
Currently, Stockfish has the following UCI options:
|
Developers can see the default values for UCI options available in Stockfish by typing
|
||||||
|
`./stockfish uci` in a terminal, but the majority of users will typically see them and
|
||||||
|
change them via a chess GUI. This is a list of available UCI options in Stockfish:
|
||||||
|
|
||||||
* #### Threads
|
* #### Threads
|
||||||
The number of CPU threads used for searching a position. For best performance, set
|
The number of CPU threads used for searching a position. For best performance, set
|
||||||
@@ -49,6 +54,9 @@ Currently, Stockfish has the following UCI options:
|
|||||||
* #### Hash
|
* #### Hash
|
||||||
The size of the hash table in MB. It is recommended to set Hash after setting Threads.
|
The size of the hash table in MB. It is recommended to set Hash after setting Threads.
|
||||||
|
|
||||||
|
* #### Clear Hash
|
||||||
|
Clear the hash table.
|
||||||
|
|
||||||
* #### Ponder
|
* #### Ponder
|
||||||
Let Stockfish ponder its next move while the opponent is thinking.
|
Let Stockfish ponder its next move while the opponent is thinking.
|
||||||
|
|
||||||
@@ -58,19 +66,14 @@ Currently, Stockfish has the following UCI options:
|
|||||||
|
|
||||||
* #### Use NNUE
|
* #### Use NNUE
|
||||||
Toggle between the NNUE and classical evaluation functions. If set to "true",
|
Toggle between the NNUE and classical evaluation functions. If set to "true",
|
||||||
the network parameters must be availabe to load from file (see also EvalFile).
|
the network parameters must be available to load from file (see also EvalFile),
|
||||||
|
if they are not embedded in the binary.
|
||||||
|
|
||||||
* #### EvalFile
|
* #### EvalFile
|
||||||
The name of the file of the NNUE evaluation parameters. Depending on the GUI the
|
The name of the file of the NNUE evaluation parameters. Depending on the GUI the
|
||||||
filename should include the full path to the folder/directory that contains the file.
|
filename might have to include the full path to the folder/directory that contains
|
||||||
|
the file. Other locations, such as the directory that contains the binary and the
|
||||||
* #### Contempt
|
working directory, are also searched.
|
||||||
A positive value for contempt favors middle game positions and avoids draws,
|
|
||||||
effective for the classical evaluation only.
|
|
||||||
|
|
||||||
* #### Analysis Contempt
|
|
||||||
By default, contempt is set to prefer the side to move. Set this option to "White"
|
|
||||||
or "Black" to analyse with contempt for that side, or "Off" to disable contempt.
|
|
||||||
|
|
||||||
* #### UCI_AnalyseMode
|
* #### UCI_AnalyseMode
|
||||||
An option handled by your GUI.
|
An option handled by your GUI.
|
||||||
@@ -103,14 +106,14 @@ Currently, Stockfish has the following UCI options:
|
|||||||
Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6`
|
Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6`
|
||||||
|
|
||||||
It is recommended to store .rtbw files on an SSD. There is no loss in storing
|
It is recommended to store .rtbw files on an SSD. There is no loss in storing
|
||||||
the .rtbz files on a regular HD. It is recommended to verify all md5 checksums
|
the .rtbz files on a regular HDD. It is recommended to verify all md5 checksums
|
||||||
of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will
|
of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will
|
||||||
lead to engine crashes.
|
lead to engine crashes.
|
||||||
|
|
||||||
* #### SyzygyProbeDepth
|
* #### SyzygyProbeDepth
|
||||||
Minimum remaining search depth for which a position is probed. Set this option
|
Minimum remaining search depth for which a position is probed. Set this option
|
||||||
to a higher value to probe less agressively if you experience too much slowdown
|
to a higher value to probe less aggressively if you experience too much slowdown
|
||||||
(in terms of nps) due to TB probing.
|
(in terms of nps) due to tablebase probing.
|
||||||
|
|
||||||
* #### Syzygy50MoveRule
|
* #### Syzygy50MoveRule
|
||||||
Disable to let fifty-move rule draws detected by Syzygy tablebase probes count
|
Disable to let fifty-move rule draws detected by Syzygy tablebase probes count
|
||||||
@@ -132,42 +135,119 @@ Currently, Stockfish has the following UCI options:
|
|||||||
Tells the engine to use nodes searched instead of wall time to account for
|
Tells the engine to use nodes searched instead of wall time to account for
|
||||||
elapsed time. Useful for engine testing.
|
elapsed time. Useful for engine testing.
|
||||||
|
|
||||||
* #### Clear Hash
|
|
||||||
Clear the hash table.
|
|
||||||
|
|
||||||
* #### Debug Log File
|
* #### Debug Log File
|
||||||
Write all communication to and from the engine into a text file.
|
Write all communication to and from the engine into a text file.
|
||||||
|
|
||||||
## classical and NNUE evaluation
|
For developers the following non-standard commands might be of interest, mainly useful for debugging:
|
||||||
|
|
||||||
|
* #### bench *ttSize threads limit fenFile limitType evalType*
|
||||||
|
Performs a standard benchmark using various options. The signature of a version
|
||||||
|
(standard node count) is obtained using all defaults. `bench` is currently
|
||||||
|
`bench 16 1 13 default depth mixed`.
|
||||||
|
|
||||||
|
* #### compiler
|
||||||
|
Give information about the compiler and environment used for building a binary.
|
||||||
|
|
||||||
|
* #### d
|
||||||
|
Display the current position, with ascii art and fen.
|
||||||
|
|
||||||
|
* #### eval
|
||||||
|
Return the evaluation of the current position.
|
||||||
|
|
||||||
|
* #### export_net [filename]
|
||||||
|
Exports the currently loaded network to a file.
|
||||||
|
If the currently loaded network is the embedded network and the filename
|
||||||
|
is not specified then the network is saved to the file matching the name
|
||||||
|
of the embedded network, as defined in evaluate.h.
|
||||||
|
If the currently loaded network is not the embedded network (some net set
|
||||||
|
through the UCI setoption) then the filename parameter is required and the
|
||||||
|
network is saved into that file.
|
||||||
|
|
||||||
|
* #### flip
|
||||||
|
Flips the side to move.
|
||||||
|
|
||||||
|
### Generating Training Data
|
||||||
|
|
||||||
|
To generate training data from the classic eval, use the generate_training_data command with the setting "Use NNUE" set to "false". The given example is generation in its simplest form. There are more commands.
|
||||||
|
|
||||||
|
```
|
||||||
|
uci
|
||||||
|
setoption name PruneAtShallowDepth value false
|
||||||
|
setoption name Use NNUE value false
|
||||||
|
setoption name Threads value X
|
||||||
|
setoption name Hash value Y
|
||||||
|
setoption name SyzygyPath value path
|
||||||
|
isready
|
||||||
|
generate_training_data depth A count B keep_draws 1 eval_limit 32000
|
||||||
|
```
|
||||||
|
|
||||||
|
- `A` is the searched depth per move, or how far the engine looks forward. This value is an integer.
|
||||||
|
- `B` is the amount of positions generated. This value is also an integer.
|
||||||
|
|
||||||
|
Specify how many threads and how much memory you would like to use with the `x` and `y` values. The option SyzygyPath is not necessary, but if you would like to use it, you must first have Syzygy endgame tablebases on your computer, which you can find [here](http://oics.olympuschess.com/tracker/index.php). You will need to have a torrent client to download these tablebases, as that is probably the fastest way to obtain them. The `path` is the path to the folder containing those tablebases. It does not have to be surrounded in quotes.
|
||||||
|
|
||||||
|
This will create a file named "training_data.binpack" in the same folder as the binary containing the generated training data. Once generation is done, you can rename the file to something like "1billiondepth12.binpack" to remember the depth and quantity of the positions and move it to a folder named "trainingdata" in the same directory as the binaries.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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 `generate_training_data` command.
|
||||||
|
- `.plain` - a human readable training data format. This one is not supported directly by the `generate_training_data` command. 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 `generate_training_data`. It is supported directly by `generate_training_data` command. It is currently the default for the `generate_training_data` command. A more in depth description can be found [here](docs/binpack.md)
|
||||||
|
|
||||||
|
### Conversion between formats.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## A note on classical evaluation versus NNUE evaluation
|
||||||
|
|
||||||
Both approaches assign a value to a position that is used in alpha-beta (PVS) search
|
Both approaches assign a value to a position that is used in alpha-beta (PVS) search
|
||||||
to find the best move. The classical evaluation computes this value as a function
|
to find the best move. The classical evaluation computes this value as a function
|
||||||
of various chess concepts, handcrafted by experts, tested and tuned using fishtest.
|
of various chess concepts, handcrafted by experts, tested and tuned using fishtest.
|
||||||
The NNUE evaluation computes this value with a neural network based on basic
|
The NNUE evaluation computes this value with a neural network based on basic
|
||||||
inputs (e.g. piece positions only). The network is optimized and trained
|
inputs (e.g. piece positions only). The network is optimized and trained
|
||||||
on the evalutions of millions of positions at moderate search depth.
|
on the evaluations of millions of positions at moderate search depth.
|
||||||
|
|
||||||
The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward.
|
The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward.
|
||||||
It can be evaluated efficiently on CPUs, and exploits the fact that only parts
|
It can be evaluated efficiently on CPUs, and exploits the fact that only parts
|
||||||
of the neural network need to be updated after a typical chess move.
|
of the neural network need to be updated after a typical chess move.
|
||||||
[The nodchip repository](https://github.com/nodchip/Stockfish) provides additional
|
[The nodchip repository](https://github.com/nodchip/Stockfish) provided the first
|
||||||
tools to train and develop the NNUE networks.
|
version of the needed tools to train and develop the NNUE networks. Today, more
|
||||||
|
advanced training tools are available in
|
||||||
|
[the nnue-pytorch repository](https://github.com/glinscott/nnue-pytorch/),
|
||||||
|
while data generation tools are available in
|
||||||
|
[a dedicated branch](https://github.com/official-stockfish/Stockfish/tree/tools).
|
||||||
|
|
||||||
On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation
|
On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation
|
||||||
results in stronger playing strength, even if the nodes per second computed by the engine
|
results in much stronger playing strength, even if the nodes per second computed by
|
||||||
is somewhat lower (roughly 60% of nps is typical).
|
the engine is somewhat lower (roughly 80% of nps is typical).
|
||||||
|
|
||||||
Note that the NNUE evaluation depends on the Stockfish binary and the network parameter
|
Notes:
|
||||||
file (see EvalFile). Not every parameter file is compatible with a given Stockfish binary.
|
|
||||||
The default value of the EvalFile UCI option is the name of a network that is guaranteed
|
|
||||||
to be compatible with that binary.
|
|
||||||
|
|
||||||
## What to expect from Syzygybases?
|
1) the NNUE evaluation depends on the Stockfish binary and the network parameter file
|
||||||
|
(see the EvalFile UCI option). Not every parameter file is compatible with a given
|
||||||
|
Stockfish binary, but the default value of the EvalFile UCI option is the name of a
|
||||||
|
network that is guaranteed to be compatible with that binary.
|
||||||
|
|
||||||
|
2) to use the NNUE evaluation, the additional data file with neural network parameters
|
||||||
|
needs to be available. Normally, this file is already embedded in the binary or it can
|
||||||
|
be downloaded. The filename for the default (recommended) net can be found as the default
|
||||||
|
value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue`
|
||||||
|
(for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from
|
||||||
|
```
|
||||||
|
https://tests.stockfishchess.org/api/nn/[filename]
|
||||||
|
```
|
||||||
|
replacing `[filename]` as needed.
|
||||||
|
|
||||||
|
## What to expect from the Syzygy tablebases?
|
||||||
|
|
||||||
If the engine is searching a position that is not in the tablebases (e.g.
|
If the engine is searching a position that is not in the tablebases (e.g.
|
||||||
a position with 8 pieces), it will access the tablebases during the search.
|
a position with 8 pieces), it will access the tablebases during the search.
|
||||||
If the engine reports a very large score (typically 153.xx), this means
|
If the engine reports a very large score (typically 153.xx), this means
|
||||||
that it has found a winning line into a tablebase position.
|
it has found a winning line into a tablebase position.
|
||||||
|
|
||||||
If the engine is given a position to search that is in the tablebases, it
|
If the engine is given a position to search that is in the tablebases, it
|
||||||
will use the tablebases at the beginning of the search to preselect all
|
will use the tablebases at the beginning of the search to preselect all
|
||||||
@@ -175,14 +255,14 @@ good moves, i.e. all moves that preserve the win or preserve the draw while
|
|||||||
taking into account the 50-move rule.
|
taking into account the 50-move rule.
|
||||||
It will then perform a search only on those moves. **The engine will not move
|
It will then perform a search only on those moves. **The engine will not move
|
||||||
immediately**, unless there is only a single good move. **The engine likely
|
immediately**, unless there is only a single good move. **The engine likely
|
||||||
will not report a mate score even if the position is known to be won.**
|
will not report a mate score, even if the position is known to be won.**
|
||||||
|
|
||||||
It is therefore clear that this behaviour is not identical to what one might
|
It is therefore clear that this behaviour is not identical to what one might
|
||||||
be used to with Nalimov tablebases. There are technical reasons for this
|
be used to with Nalimov tablebases. There are technical reasons for this
|
||||||
difference, the main technical reason being that Nalimov tablebases use the
|
difference, the main technical reason being that Nalimov tablebases use the
|
||||||
DTM metric (distance-to-mate), while Syzygybases use a variation of the
|
DTM metric (distance-to-mate), while the Syzygy tablebases use a variation of the
|
||||||
DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move
|
DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move
|
||||||
counter). This special metric is one of the reasons that Syzygybases are
|
counter). This special metric is one of the reasons that the Syzygy tablebases are
|
||||||
more compact than Nalimov tablebases, while still storing all information
|
more compact than Nalimov tablebases, while still storing all information
|
||||||
needed for optimal play and in addition being able to take into account
|
needed for optimal play and in addition being able to take into account
|
||||||
the 50-move rule.
|
the 50-move rule.
|
||||||
@@ -191,8 +271,8 @@ the 50-move rule.
|
|||||||
|
|
||||||
Stockfish supports large pages on Linux and Windows. Large pages make
|
Stockfish supports large pages on Linux and Windows. Large pages make
|
||||||
the hash access more efficient, improving the engine speed, especially
|
the hash access more efficient, improving the engine speed, especially
|
||||||
on large hash sizes. Typical increases are 5..10% in terms of nps, but
|
on large hash sizes. Typical increases are 5..10% in terms of nodes per
|
||||||
speed increases up to 30% have been measured. The support is
|
second, but speed increases up to 30% have been measured. The support is
|
||||||
automatic. Stockfish attempts to use large pages when available and
|
automatic. Stockfish attempts to use large pages when available and
|
||||||
will fall back to regular memory allocation when this is not the case.
|
will fall back to regular memory allocation when this is not the case.
|
||||||
|
|
||||||
@@ -200,17 +280,17 @@ will fall back to regular memory allocation when this is not the case.
|
|||||||
|
|
||||||
Large page support on Linux is obtained by the Linux kernel
|
Large page support on Linux is obtained by the Linux kernel
|
||||||
transparent huge pages functionality. Typically, transparent huge pages
|
transparent huge pages functionality. Typically, transparent huge pages
|
||||||
are already enabled and no configuration is needed.
|
are already enabled, and no configuration is needed.
|
||||||
|
|
||||||
### Support on Windows
|
### Support on Windows
|
||||||
|
|
||||||
The use of large pages requires "Lock Pages in Memory" privilege. See
|
The use of large pages requires "Lock Pages in Memory" privilege. See
|
||||||
[Enable the Lock Pages in Memory Option (Windows)](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows)
|
[Enable the Lock Pages in Memory Option (Windows)](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows)
|
||||||
on how to enable this privilege. Logout/login may be needed
|
on how to enable this privilege, then run [RAMMap](https://docs.microsoft.com/en-us/sysinternals/downloads/rammap)
|
||||||
afterwards. Due to memory fragmentation, it may not always be
|
to double-check that large pages are used. We suggest that you reboot
|
||||||
possible to allocate large pages even when enabled. A reboot
|
your computer after you have enabled large pages, because long Windows
|
||||||
might alleviate this problem. To determine whether large pages
|
sessions suffer from memory fragmentation, which may prevent Stockfish
|
||||||
are in use, see the engine log.
|
from getting large pages: a fresh session is better in this regard.
|
||||||
|
|
||||||
## Compiling Stockfish yourself from the sources
|
## Compiling Stockfish yourself from the sources
|
||||||
|
|
||||||
@@ -225,26 +305,26 @@ targets with corresponding descriptions.
|
|||||||
```
|
```
|
||||||
cd src
|
cd src
|
||||||
make help
|
make help
|
||||||
|
make net
|
||||||
make build ARCH=x86-64-modern
|
make build ARCH=x86-64-modern
|
||||||
```
|
```
|
||||||
|
|
||||||
When not using the Makefile to compile (for instance with Microsoft MSVC) you
|
When not using the Makefile to compile (for instance, with Microsoft MSVC) you
|
||||||
need to manually set/unset some switches in the compiler command line; see
|
need to manually set/unset some switches in the compiler command line; see
|
||||||
file *types.h* for a quick reference.
|
file *types.h* for a quick reference.
|
||||||
|
|
||||||
When reporting an issue or a bug, please tell us which version and
|
When reporting an issue or a bug, please tell us which Stockfish version
|
||||||
compiler you used to create your executable. These informations can
|
and which compiler you used to create your executable. This information
|
||||||
be found by typing the following commands in a console:
|
can be found by typing the following command in a console:
|
||||||
|
|
||||||
```
|
```
|
||||||
./stockfish
|
./stockfish compiler
|
||||||
compiler
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Understanding the code base and participating in the project
|
## Understanding the code base and participating in the project
|
||||||
|
|
||||||
Stockfish's improvement over the last couple of years has been a great
|
Stockfish's improvement over the last decade has been a great community
|
||||||
community effort. There are a few ways to help contribute to its growth.
|
effort. There are a few ways to help contribute to its growth.
|
||||||
|
|
||||||
### Donating hardware
|
### Donating hardware
|
||||||
|
|
||||||
@@ -265,8 +345,9 @@ generic rather than being focused on Stockfish's precise implementation.
|
|||||||
Nevertheless, a helpful resource.
|
Nevertheless, a helpful resource.
|
||||||
|
|
||||||
* The latest source can always be found on [GitHub](https://github.com/official-stockfish/Stockfish).
|
* The latest source can always be found on [GitHub](https://github.com/official-stockfish/Stockfish).
|
||||||
Discussions about Stockfish take place in the [FishCooking](https://groups.google.com/forum/#!forum/fishcooking)
|
Discussions about Stockfish take place these days mainly in the [FishCooking](https://groups.google.com/forum/#!forum/fishcooking)
|
||||||
group and engine testing is done on [Fishtest](https://tests.stockfishchess.org/tests).
|
group and on the [Stockfish Discord channel](https://discord.gg/nv8gDtt).
|
||||||
|
The engine testing is done on [Fishtest](https://tests.stockfishchess.org/tests).
|
||||||
If you want to help improve Stockfish, please read this [guideline](https://github.com/glinscott/fishtest/wiki/Creating-my-first-test)
|
If you want to help improve Stockfish, please read this [guideline](https://github.com/glinscott/fishtest/wiki/Creating-my-first-test)
|
||||||
first, where the basics of Stockfish development are explained.
|
first, where the basics of Stockfish development are explained.
|
||||||
|
|
||||||
@@ -274,16 +355,17 @@ first, where the basics of Stockfish development are explained.
|
|||||||
## Terms of use
|
## Terms of use
|
||||||
|
|
||||||
Stockfish is free, and distributed under the **GNU General Public License version 3**
|
Stockfish is free, and distributed under the **GNU General Public License version 3**
|
||||||
(GPL v3). Essentially, this means that you are free to do almost exactly
|
(GPL v3). Essentially, this means you are free to do almost exactly
|
||||||
what you want with the program, including distributing it among your
|
what you want with the program, including distributing it among your
|
||||||
friends, making it available for download from your web site, selling
|
friends, making it available for download from your website, selling
|
||||||
it (either by itself or as part of some bigger software package), or
|
it (either by itself or as part of some bigger software package), or
|
||||||
using it as the starting point for a software project of your own.
|
using it as the starting point for a software project of your own.
|
||||||
|
|
||||||
The only real limitation is that whenever you distribute Stockfish in
|
The only real limitation is that whenever you distribute Stockfish in
|
||||||
some way, you must always include the full source code, or a pointer
|
some way, you MUST always include the license and the full source code
|
||||||
to where the source code can be found. If you make any changes to the
|
(or a pointer to where the source code can be found) to generate the
|
||||||
source code, these changes must also be made available under the GPL.
|
exact binary you are distributing. If you make any changes to the
|
||||||
|
source code, these changes must also be made available under the GPL v3.
|
||||||
|
|
||||||
For full details, read the copy of the GPL v3 found in the file named
|
For full details, read the copy of the GPL v3 found in the file named
|
||||||
*Copying.txt*.
|
[*Copying.txt*](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt).
|
||||||
|
|||||||
+233
-152
@@ -1,154 +1,235 @@
|
|||||||
Contributors with >10,000 CPU hours as of January 7, 2020
|
Contributors to Fishtest with >10,000 CPU hours, as of 2022-04-14.
|
||||||
Thank you!
|
Thank you!
|
||||||
|
|
||||||
Username CPU Hours Games played
|
Username CPU Hours Games played
|
||||||
--------------------------------------------------
|
------------------------------------------------------------------
|
||||||
noobpwnftw 9305707 695548021
|
noobpwnftw 31714850 2267266129
|
||||||
mlang 780050 61648867
|
mlang 2954099 198421098
|
||||||
dew 621626 43921547
|
technologov 2324150 102449398
|
||||||
mibere 524702 42238645
|
dew 1670874 99276012
|
||||||
crunchy 354587 27344275
|
grandphish2 1134273 68070459
|
||||||
cw 354495 27274181
|
okrout 901194 77738874
|
||||||
fastgm 332801 22804359
|
TueRens 821388 50207666
|
||||||
JojoM 295750 20437451
|
tvijlbrief 795993 51894442
|
||||||
CSU_Dynasty 262015 21828122
|
pemo 744463 32486677
|
||||||
Fisherman 232181 18939229
|
JojoM 724378 43660674
|
||||||
ctoks 218866 17622052
|
mibere 703840 46867607
|
||||||
glinscott 201989 13780820
|
linrock 626939 17408017
|
||||||
tvijlbrief 201204 15337115
|
gvreuls 534079 34352532
|
||||||
velislav 188630 14348485
|
cw 507221 34006775
|
||||||
gvreuls 187164 15149976
|
fastgm 489749 29344518
|
||||||
bking_US 180289 11876016
|
crunchy 427035 27344275
|
||||||
nordlandia 172076 13467830
|
CSU_Dynasty 424643 28525220
|
||||||
leszek 157152 11443978
|
ctoks 415771 27364603
|
||||||
Thanar 148021 12365359
|
oz 369200 27017658
|
||||||
spams 141975 10319326
|
bcross 342642 23671289
|
||||||
drabel 138073 11121749
|
Fisherman 327231 21829379
|
||||||
vdv 137850 9394330
|
velislav 325670 20911076
|
||||||
mgrabiak 133578 10454324
|
leszek 321295 19874113
|
||||||
TueRens 132485 10878471
|
Dantist 274747 16910258
|
||||||
bcross 129683 11557084
|
mgrabiak 237604 15418700
|
||||||
marrco 126078 9356740
|
robal 217959 13840386
|
||||||
sqrt2 125830 9724586
|
glinscott 217799 13780820
|
||||||
robal 122873 9593418
|
nordlandia 211692 13484886
|
||||||
vdbergh 120766 8926915
|
drabel 201967 13798360
|
||||||
malala 115926 8002293
|
bking_US 198894 11876016
|
||||||
CoffeeOne 114241 5004100
|
mhoram 194862 12261809
|
||||||
dsmith 113189 7570238
|
Thanar 179852 12365359
|
||||||
BrunoBanani 104644 7436849
|
vdv 175544 9904472
|
||||||
Data 92328 8220352
|
spams 157128 10319326
|
||||||
mhoram 89333 6695109
|
rpngn 154081 9652139
|
||||||
davar 87924 7009424
|
marrco 150300 9402229
|
||||||
xoto 81094 6869316
|
sqrt2 147963 9724586
|
||||||
ElbertoOne 80899 7023771
|
vdbergh 137430 8955097
|
||||||
grandphish2 78067 6160199
|
CoffeeOne 137100 5024116
|
||||||
brabos 77212 6186135
|
malala 136182 8002293
|
||||||
psk 75733 5984901
|
xoto 133759 9159372
|
||||||
BRAVONE 73875 5054681
|
davar 125240 8117121
|
||||||
sunu 70771 5597972
|
dsmith 122059 7570238
|
||||||
sterni1971 70605 5590573
|
amicic 119659 7937885
|
||||||
MaZePallas 66886 5188978
|
Data 113305 8220352
|
||||||
Vizvezdenec 63708 4967313
|
BrunoBanani 112960 7436849
|
||||||
nssy 63462 5259388
|
CypressChess 108321 7759588
|
||||||
jromang 61634 4940891
|
DesolatedDodo 106811 6776980
|
||||||
teddybaer 61231 5407666
|
MaZePallas 102823 6633619
|
||||||
Pking_cda 60099 5293873
|
sterni1971 100532 5880772
|
||||||
solarlight 57469 5028306
|
sunu 100167 7040199
|
||||||
dv8silencer 56913 3883992
|
ElbertoOne 99028 7023771
|
||||||
tinker 54936 4086118
|
skiminki 98123 6478402
|
||||||
renouve 49732 3501516
|
brabos 92118 6186135
|
||||||
Freja 49543 3733019
|
cuistot 90358 5351004
|
||||||
robnjr 46972 4053117
|
psk 89957 5984901
|
||||||
rap 46563 3219146
|
racerschmacer 85712 6119648
|
||||||
Bobo1239 46036 3817196
|
Vizvezdenec 83761 5344740
|
||||||
ttruscott 45304 3649765
|
zeryl 83680 5250995
|
||||||
racerschmacer 44881 3975413
|
sschnee 83003 4840890
|
||||||
finfish 44764 3370515
|
0x3C33 82614 5271253
|
||||||
eva42 41783 3599691
|
BRAVONE 81239 5054681
|
||||||
biffhero 40263 3111352
|
nssy 76497 5259388
|
||||||
bigpen0r 39817 3291647
|
teddybaer 75125 5407666
|
||||||
mhunt 38871 2691355
|
jromang 74796 5175825
|
||||||
ronaldjerum 38820 3240695
|
Pking_cda 73776 5293873
|
||||||
Antihistamine 38785 2761312
|
Calis007 72477 4088576
|
||||||
pb00067 38038 3086320
|
solarlight 70517 5028306
|
||||||
speedycpu 37591 3003273
|
dv8silencer 70287 3883992
|
||||||
rkl 37207 3289580
|
Bobo1239 68515 4652287
|
||||||
VoyagerOne 37050 3441673
|
manap 66273 4121774
|
||||||
jbwiebe 35320 2805433
|
yurikvelo 65716 4457300
|
||||||
cuistot 34191 2146279
|
tinker 64333 4268790
|
||||||
homyur 33927 2850481
|
Wolfgang 62644 3817410
|
||||||
manap 32873 2327384
|
qurashee 61208 3429862
|
||||||
gri 32538 2515779
|
robnjr 57262 4053117
|
||||||
oryx 31267 2899051
|
Freja 56938 3733019
|
||||||
EthanOConnor 30959 2090311
|
ttruscott 56010 3680085
|
||||||
SC 30832 2730764
|
rkl 55132 4164467
|
||||||
csnodgrass 29505 2688994
|
renouve 53811 3501516
|
||||||
jmdana 29458 2205261
|
megaman7de 52434 3243016
|
||||||
strelock 28219 2067805
|
MaxKlaxxMiner 51977 3153032
|
||||||
jkiiski 27832 1904470
|
finfish 51360 3370515
|
||||||
Pyafue 27533 1902349
|
eva42 51272 3599691
|
||||||
Garf 27515 2747562
|
eastorwest 51058 3451555
|
||||||
eastorwest 27421 2317535
|
rap 49985 3219146
|
||||||
slakovv 26903 2021889
|
pb00067 49727 3298270
|
||||||
Prcuvu 24835 2170122
|
Spprtr 48920 3161711
|
||||||
anst 24714 2190091
|
bigpen0r 47667 3336927
|
||||||
hyperbolic.tom 24319 2017394
|
ronaldjerum 47654 3240695
|
||||||
Patrick_G 23687 1801617
|
biffhero 46564 3111352
|
||||||
Sharaf_DG 22896 1786697
|
Fifis 45843 3088497
|
||||||
nabildanial 22195 1519409
|
VoyagerOne 45476 3452465
|
||||||
chriswk 21931 1868317
|
speedycpu 43842 3003273
|
||||||
achambord 21665 1767323
|
jbwiebe 43305 2805433
|
||||||
Zirie 20887 1472937
|
Antihistamine 41788 2761312
|
||||||
team-oh 20217 1636708
|
mhunt 41735 2691355
|
||||||
Isidor 20096 1680691
|
homyur 39893 2850481
|
||||||
ncfish1 19931 1520927
|
gri 39871 2515779
|
||||||
nesoneg 19875 1463031
|
armo9494 39064 2832326
|
||||||
Spprtr 19853 1548165
|
oryx 38867 2976992
|
||||||
JanErik 19849 1703875
|
SC 37299 2731694
|
||||||
agg177 19478 1395014
|
Garf 37213 2986270
|
||||||
SFTUser 19231 1567999
|
tolkki963 37059 2154330
|
||||||
xor12 19017 1680165
|
csnodgrass 36207 2688994
|
||||||
sg4032 18431 1641865
|
jmdana 36157 2210661
|
||||||
rstoesser 18118 1293588
|
strelock 34716 2074055
|
||||||
MazeOfGalious 17917 1629593
|
DMBK 34010 2482916
|
||||||
j3corre 17743 941444
|
EthanOConnor 33370 2090311
|
||||||
cisco2015 17725 1690126
|
slakovv 32915 2021889
|
||||||
ianh2105 17706 1632562
|
gopeto 30993 2028106
|
||||||
dex 17678 1467203
|
manapbk 30987 1810399
|
||||||
jundery 17194 1115855
|
Prcuvu 30377 2170122
|
||||||
iisiraider 17019 1101015
|
anst 30301 2190091
|
||||||
horst.prack 17012 1465656
|
jkiiski 30136 1904470
|
||||||
Adrian.Schmidt123 16563 1281436
|
hyperbolic.tom 29840 2017394
|
||||||
purplefishies 16342 1092533
|
chuckstablers 29659 2093438
|
||||||
wei 16274 1745989
|
Pyafue 29650 1902349
|
||||||
ville 16144 1384026
|
ncfish1 29105 1704011
|
||||||
eudhan 15712 1283717
|
belzedar94 27935 1789106
|
||||||
OuaisBla 15581 972000
|
OuaisBla 27636 1578800
|
||||||
DragonLord 15559 1162790
|
chriswk 26902 1868317
|
||||||
dju 14716 875569
|
achambord 26582 1767323
|
||||||
chris 14479 1487385
|
Patrick_G 26276 1801617
|
||||||
0xB00B1ES 14079 1001120
|
yorkman 26193 1992080
|
||||||
OssumOpossum 13776 1007129
|
SFTUser 25182 1675689
|
||||||
enedene 13460 905279
|
nabildanial 24942 1519409
|
||||||
bpfliegel 13346 884523
|
Sharaf_DG 24765 1786697
|
||||||
Ente 13198 1156722
|
rodneyc 24275 1410450
|
||||||
IgorLeMasson 13087 1147232
|
agg177 23890 1395014
|
||||||
jpulman 13000 870599
|
JanErik 23408 1703875
|
||||||
ako027ako 12775 1173203
|
Isidor 23388 1680691
|
||||||
Nikolay.IT 12352 1068349
|
Norabor 23339 1602636
|
||||||
Andrew Grant 12327 895539
|
Ente 23270 1651432
|
||||||
joster 12008 950160
|
cisco2015 22897 1762669
|
||||||
AdrianSA 11996 804972
|
MarcusTullius 22688 1274821
|
||||||
Nesa92 11455 1111993
|
Zirie 22542 1472937
|
||||||
fatmurphy 11345 853210
|
team-oh 22272 1636708
|
||||||
Dark_wizzie 11108 1007152
|
MazeOfGalious 21978 1629593
|
||||||
modolief 10869 896470
|
sg4032 21947 1643265
|
||||||
mschmidt 10757 803401
|
ianh2105 21725 1632562
|
||||||
infinity 10594 727027
|
xor12 21628 1680365
|
||||||
mabichito 10524 749391
|
dex 21612 1467203
|
||||||
Thomas A. Anderson 10474 732094
|
nesoneg 21494 1463031
|
||||||
thijsk 10431 719357
|
Roady 21323 1433822
|
||||||
Flopzee 10339 894821
|
sphinx 21211 1384728
|
||||||
crocogoat 10104 1013854
|
user213718 21196 1397710
|
||||||
SapphireBrand 10104 969604
|
spcc 21065 1311338
|
||||||
stocky 10017 699440
|
jjoshua2 21001 1423089
|
||||||
|
horst.prack 20878 1465656
|
||||||
|
0xB00B1ES 20590 1208666
|
||||||
|
j3corre 20405 941444
|
||||||
|
kdave 20364 1389254
|
||||||
|
Adrian.Schmidt123 20316 1281436
|
||||||
|
Ulysses 20217 1351500
|
||||||
|
markkulix 19976 1115258
|
||||||
|
wei 19973 1745989
|
||||||
|
rstoesser 19569 1293588
|
||||||
|
eudhan 19274 1283717
|
||||||
|
fishtester 18995 1238686
|
||||||
|
vulcan 18871 1729392
|
||||||
|
jundery 18445 1115855
|
||||||
|
iisiraider 18247 1101015
|
||||||
|
ville 17883 1384026
|
||||||
|
chris 17698 1487385
|
||||||
|
purplefishies 17595 1092533
|
||||||
|
dju 17353 978595
|
||||||
|
Wencey 17125 805964
|
||||||
|
DragonLord 17014 1162790
|
||||||
|
thirdlife 16996 447356
|
||||||
|
IgorLeMasson 16064 1147232
|
||||||
|
ako027ako 15671 1173203
|
||||||
|
AndreasKrug 15550 1194497
|
||||||
|
Nikolay.IT 15154 1068349
|
||||||
|
Andrew Grant 15114 895539
|
||||||
|
scuzzi 14928 953313
|
||||||
|
OssumOpossum 14857 1007129
|
||||||
|
Karby 14808 867120
|
||||||
|
jsys14 14652 855642
|
||||||
|
enedene 14476 905279
|
||||||
|
bpfliegel 14298 884523
|
||||||
|
mpx86 14019 759568
|
||||||
|
jpulman 13982 870599
|
||||||
|
crocogoat 13803 1117422
|
||||||
|
joster 13794 950160
|
||||||
|
Nesa92 13786 1114691
|
||||||
|
mbeier 13650 1044928
|
||||||
|
Hjax 13535 915487
|
||||||
|
Dark_wizzie 13422 1007152
|
||||||
|
Jopo12321 13367 678852
|
||||||
|
Rudolphous 13244 883140
|
||||||
|
Machariel 13010 863104
|
||||||
|
mabichito 12903 749391
|
||||||
|
thijsk 12886 722107
|
||||||
|
AdrianSA 12860 804972
|
||||||
|
infinigon 12807 937332
|
||||||
|
Flopzee 12698 894821
|
||||||
|
fatmurphy 12547 853210
|
||||||
|
SapphireBrand 12416 969604
|
||||||
|
modolief 12386 896470
|
||||||
|
Farseer 12249 694108
|
||||||
|
pgontarz 12151 848794
|
||||||
|
pirt 12008 923149
|
||||||
|
stocky 11954 699440
|
||||||
|
mschmidt 11941 803401
|
||||||
|
dbernier 11609 818636
|
||||||
|
Maxim 11543 836024
|
||||||
|
infinity 11470 727027
|
||||||
|
aga 11409 695071
|
||||||
|
torbjo 11395 729145
|
||||||
|
Thomas A. Anderson 11372 732094
|
||||||
|
savage84 11358 670860
|
||||||
|
FormazChar 11349 850327
|
||||||
|
d64 11263 789184
|
||||||
|
MooTheCow 11237 720174
|
||||||
|
snicolet 11106 869170
|
||||||
|
ali-al-zhrani 11098 768494
|
||||||
|
whelanh 11067 235676
|
||||||
|
Jackfish 10978 720078
|
||||||
|
deflectooor 10886 520116
|
||||||
|
basepi 10637 744851
|
||||||
|
Cubox 10621 826448
|
||||||
|
michaelrpg 10509 739239
|
||||||
|
OIVAS7572 10420 995586
|
||||||
|
dzjp 10343 732529
|
||||||
|
Garruk 10334 704065
|
||||||
|
ols 10259 570669
|
||||||
|
lbraesch 10252 647825
|
||||||
|
qoo_charly_cai 10212 620407
|
||||||
|
Naven94 10069 503192
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
version: 1.0.{build}
|
|
||||||
clone_depth: 50
|
|
||||||
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
- nnue-player-wip
|
|
||||||
|
|
||||||
# Operating system (build VM template)
|
|
||||||
os: Visual Studio 2019
|
|
||||||
|
|
||||||
# Build platform, i.e. x86, x64, AnyCPU. This setting is optional.
|
|
||||||
platform:
|
|
||||||
- x86
|
|
||||||
- x64
|
|
||||||
|
|
||||||
# build Configuration, i.e. Debug, Release, etc.
|
|
||||||
configuration:
|
|
||||||
- Debug
|
|
||||||
- Release
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
# The build fail immediately once one of the job fails
|
|
||||||
fast_finish: true
|
|
||||||
|
|
||||||
# Scripts that are called at very beginning, before repo cloning
|
|
||||||
init:
|
|
||||||
- cmake --version
|
|
||||||
- msbuild /version
|
|
||||||
|
|
||||||
before_build:
|
|
||||||
- ps: |
|
|
||||||
# Get sources
|
|
||||||
$src = get-childitem -Path *.cpp -Recurse | select -ExpandProperty FullName
|
|
||||||
$src = $src -join ' '
|
|
||||||
$src = $src.Replace("\", "/")
|
|
||||||
|
|
||||||
# Build CMakeLists.txt
|
|
||||||
$t = 'cmake_minimum_required(VERSION 3.17)',
|
|
||||||
'project(Stockfish)',
|
|
||||||
'set(CMAKE_CXX_STANDARD 17)',
|
|
||||||
'set(CMAKE_CXX_STANDARD_REQUIRED ON)',
|
|
||||||
'set (CMAKE_CXX_EXTENSIONS OFF)',
|
|
||||||
'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)',
|
|
||||||
'set(source_files', $src, ')',
|
|
||||||
'add_executable(stockfish ${source_files})'
|
|
||||||
|
|
||||||
# Write CMakeLists.txt withouth BOM
|
|
||||||
$MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt'
|
|
||||||
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
|
|
||||||
[System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding)
|
|
||||||
|
|
||||||
# Obtain bench reference from git log
|
|
||||||
$b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1
|
|
||||||
$bench = $b -match '\D+(\d+)' | % { $matches[1] }
|
|
||||||
Write-Host "Reference bench:" $bench
|
|
||||||
$g = "Visual Studio 16 2019"
|
|
||||||
If (${env:PLATFORM} -eq 'x64') { $a = "x64" }
|
|
||||||
If (${env:PLATFORM} -eq 'x86') { $a = "Win32" }
|
|
||||||
cmake -G "${g}" -A ${a} .
|
|
||||||
Write-Host "Generated files for: " $g $a
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- cmake --build . --config %CONFIGURATION% -- /verbosity:minimal
|
|
||||||
|
|
||||||
before_test:
|
|
||||||
- cd src/%CONFIGURATION%
|
|
||||||
- stockfish bench 2> out.txt >NUL
|
|
||||||
- ps: |
|
|
||||||
# Verify bench number
|
|
||||||
$s = (gc "./out.txt" | out-string)
|
|
||||||
$r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] })
|
|
||||||
Write-Host "Engine bench:" $r
|
|
||||||
Write-Host "Reference bench:" $bench
|
|
||||||
If ($r -ne $bench) { exit 1 }
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Binpack
|
||||||
|
|
||||||
|
Binpack is a binary training data storage format designed to take advantage of position chains differing by a single move. Therefore it is very good at compactly storing data generated from real games (as opposed to random positions for example sourced from an opening book).
|
||||||
|
|
||||||
|
It is currently implemented through a single header library in `extra/nnue_data_binpack_format.h`.
|
||||||
|
|
||||||
|
Below follows a rough description of the format in a BNF-like notation.
|
||||||
|
|
||||||
|
```
|
||||||
|
[[nodiscard]] std::uint16_t signedToUnsigned(std::int16_t a) {
|
||||||
|
std::uint16_t r;
|
||||||
|
std::memcpy(&r, &a, sizeof(std::uint16_t));
|
||||||
|
if (r & 0x8000) r ^= 0x7FFF; // flip value bits if negative
|
||||||
|
r = (r << 1) | (r >> 15); // store sign bit at bit 0
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
file := <block>*
|
||||||
|
block := BINP<chain>*
|
||||||
|
chain := <stem><movetext>
|
||||||
|
stem := <pos><move><score><ply_and_result><rule50> (32 bytes)
|
||||||
|
pos := https://github.com/Sopel97/nnue_data_compress/blob/master/src/chess/Position.h#L1166 (24 bytes)
|
||||||
|
move := https://github.com/Sopel97/nnue_data_compress/blob/master/src/chess/Chess.h#L1044 (2 bytes)
|
||||||
|
score := signedToUnsigned(score) (2 bytes, big endian)
|
||||||
|
ply_and_result := ply bitwise_or (signedToUnsigned(result) << 14) (2 bytes, big endian)
|
||||||
|
rule50 := rule_50_counter (2 bytes, big endian)
|
||||||
|
// this is a small defect from old version,
|
||||||
|
I didn't want to break backwards compatibility. Effectively means that there's
|
||||||
|
one byte left for something else in the future because rule50 always fits in one byte.
|
||||||
|
|
||||||
|
movetext := <count><move_and_score>*
|
||||||
|
count := number of plies in the movetext (2 bytes, big endian). Can be 0.
|
||||||
|
move_and_score := <encoded_move><encoded_score> (~2 bytes)
|
||||||
|
encoded_move := oof this one is complicated to explain.
|
||||||
|
https://github.com/Sopel97/nnue_data_compress/blob/master/src/compress_file.cpp#L827.
|
||||||
|
https://github.com/Sopel97/chess_pos_db/blob/master/docs/bcgn/variable_length.md
|
||||||
|
|
||||||
|
encoded_score := https://en.wikipedia.org/wiki/Variable-width_encoding
|
||||||
|
with block size of 4 bits + 1 bit for extension bit.
|
||||||
|
Encoded value is signedToUnsigned(-prev_score - current_score)
|
||||||
|
(scores are always seen from the perspective of side to move in <pos>, that's why the '-' before prev_score)
|
||||||
|
```
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Convert
|
||||||
|
|
||||||
|
`convert` allows conversion of training data between any of `.plain`, `.bin`, and `.binpack`.
|
||||||
|
|
||||||
|
As all commands in stockfish `convert` can be invoked either from command line (as `stockfish.exe convert ...`) or in the interactive prompt.
|
||||||
|
|
||||||
|
The syntax of this command is as follows:
|
||||||
|
```
|
||||||
|
convert from_path to_path [append] [validate]
|
||||||
|
```
|
||||||
|
|
||||||
|
`from_path` is the path to the file to convert from. The type of the data is deduced based on its extension (one of `.plain`, `.bin`, `.binpack`).
|
||||||
|
`to_path` is the path to an output file. The type of the data is deduced from its extension. If the file does not exist it is created.
|
||||||
|
|
||||||
|
`append` and `validate` can come in any order and are optional.
|
||||||
|
If `append` not specified then the output file will be truncated prior to any writes. If `append` is specified then the converted training data will be appended to the end of the output file.
|
||||||
|
|
||||||
|
If `validate` is specified then the conversion will stop on the first illegal move found and a diagnostic will be shown.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# generate_training_data
|
||||||
|
|
||||||
|
`generate_training_data` command allows generation of training data from self-play in a manner that suits training better than traditional games. It introduces random moves to diversify openings, and fixed depth evaluation.
|
||||||
|
|
||||||
|
As all commands in stockfish `generate_training_data` can be invoked either from command line (as `stockfish.exe generate_training_data ...`, but this is not recommended because it's not possible to specify UCI options before `generate_training_data` executes) or in the interactive prompt.
|
||||||
|
|
||||||
|
It is recommended to set the `PruneAtShallowDepth` UCI option to `false` as it will increase the quality of fixed depth searches.
|
||||||
|
|
||||||
|
It is recommended to keep the `EnableTranspositionTable` UCI option at the default `true` value as it will make the generation process faster without noticably harming the uniformity of the data.
|
||||||
|
|
||||||
|
`generate_training_data` takes named parameters in the form of `generate_training_data param_1_name param_1_value param_2_name param_2_value ...`.
|
||||||
|
|
||||||
|
Currently the following options are available:
|
||||||
|
|
||||||
|
`set_recommended_uci_options` - this is a modifier not a parameter, no value follows it. If specified then some UCI options are set to recommended values.
|
||||||
|
|
||||||
|
`depth` - sets minimum and maximum depth of evaluation of each position. Default: 3.
|
||||||
|
|
||||||
|
`mindepth` - minimum depth of evaluation of each position. If not specified then the same as `depth`.
|
||||||
|
|
||||||
|
`maxdepth` - minimum depth of evaluation of each position. If not specified then the same as `depth`.
|
||||||
|
|
||||||
|
`nodes` - the number of nodes to use for evaluation of each position. This number is multiplied by the number of PVs of the current search. This does NOT override the `depth` and `depth2` options. If specified then whichever of depth or nodes limit is reached first applies.
|
||||||
|
|
||||||
|
`count` - the number of training data entries to generate. 1 entry == 1 position. If both `count` and `max_time_*` are specified the data generation process will end when any of conditions is fullfilled. Default: 8000000000 (8B).
|
||||||
|
|
||||||
|
`max_time_seconds`, `max_time_minutes`, `max_time_hours` - specifies the maximum runtime for the data generation. The data generation will NOT be interrupted while a self-play game is in progress. If both `count` and `max_time_*` are specified the data generation process will end when any of conditions is fullfilled. Default: \~250 years.
|
||||||
|
|
||||||
|
`output_file_name` - the name of the file to output to. If the extension is not present or doesn't match the selected training data format the right extension will be appened. Default: generated_kifu
|
||||||
|
|
||||||
|
`eval_limit` - evaluations with higher absolute value than this will not be written and will terminate a self-play game. Should not exceed 10000 which is VALUE_KNOWN_WIN, but is only hardcapped at mate in 2 (\~30000). Default: 3000
|
||||||
|
|
||||||
|
`random_move_min_ply` - the minimal ply at which a random move may be executed instead of a move chosen by search. Default: 1.
|
||||||
|
|
||||||
|
`random_move_max_ply` - the maximal ply at which a random move may be executed instead of a move chosen by search. Default: 24.
|
||||||
|
|
||||||
|
`random_move_count` - maximum number of random moves in a single self-play game. Default: 5.
|
||||||
|
|
||||||
|
`random_move_like_apery` - either 0 or 1. If 1 then random king moves will be followed by a random king move from the opponent whenever possible with 50% probability. Default: 0.
|
||||||
|
|
||||||
|
`random_multi_pv` - the number of PVs used for determining the random move. If not specified then a truly random move will be chosen. If specified then a multiPV search will be performed the random move will be one of the moves chosen by the search.
|
||||||
|
|
||||||
|
`random_multi_pv_diff` - Makes the multiPV random move selection consider only moves that are at most `random_multi_pv_diff` worse than the next best move. Default: 30000 (all multiPV moves).
|
||||||
|
|
||||||
|
`random_multi_pv_depth` - the depth to use for multiPV search for random move. Default: `depth2`.
|
||||||
|
|
||||||
|
`random_multi_pv_nodes` - the maximum number of nodes for a multiPV search for random move. Default: `nodes`.
|
||||||
|
|
||||||
|
`write_min_ply` - minimum ply for which the training data entry will be emitted. Default: 16.
|
||||||
|
|
||||||
|
`write_max_ply` - maximum ply for which the training data entry will be emitted. Default: 400.
|
||||||
|
|
||||||
|
`book` - a path to an opening book to use for the starting positions. Currently only .epd format is supported. If not specified then the starting position is always the standard chess starting position.
|
||||||
|
|
||||||
|
`save_every` - the number of training data entries per file. If not specified then there will be always one file. If specified there may be more than one file generated (each having at most `save_every` training data entries) and each file will have a unique number attached.
|
||||||
|
|
||||||
|
`random_file_name` - if specified then the output filename will be chosen randomly. Overrides `output_file_name`.
|
||||||
|
|
||||||
|
`keep_draws` - either 0 or 1. If 1 then training data from drawn games will be emitted too. Default: 1.
|
||||||
|
|
||||||
|
`adjudicate_draws_by_score` - either 0 or 1. If 1 then drawn games will be adjudicated when the score remains 0 for at least 8 plies after ply 80. Default: 1.
|
||||||
|
|
||||||
|
`adjudicate_draws_by_insufficient_mating_material` - either 0 or 1. If 1 then position with insufficient material will be adjudicated as draws. Default: 1.
|
||||||
|
|
||||||
|
`data_format` - format of the training data to use. Either `bin` or `binpack`. Default: `binpack`.
|
||||||
|
|
||||||
|
`seed` - seed for the PRNG. Can be either a number or a string. If it's a string then its hash will be used. If not specified then the current time will be used.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# generate_training_data_nonpv
|
||||||
|
|
||||||
|
`generate_training_data_nonpv` command allows generation of training data from self-play in a manner that suits training better than traditional games. It plays fixed nodes self play games for exploration and records [some of] the evaluated positions. Then rescores them with fixed depth search.
|
||||||
|
|
||||||
|
As all commands in stockfish `generate_training_data_nonpv` can be invoked either from command line (as `stockfish.exe generate_training_data_nonpv ...`, but this is not recommended because it's not possible to specify UCI options before `generate_training_data_nonpv` executes) or in the interactive prompt.
|
||||||
|
|
||||||
|
It is recommended to set the `PruneAtShallowDepth` UCI option to `false` as it will increase the quality of fixed depth searches.
|
||||||
|
|
||||||
|
It is recommended to keep the `EnableTranspositionTable` UCI option at the default `true` value as it will make the generation process faster without noticably harming the uniformity of the data.
|
||||||
|
|
||||||
|
`generate_training_data_nonpv` takes named parameters in the form of `generate_training_data_nonpv param_1_name param_1_value param_2_name param_2_value ...`.
|
||||||
|
|
||||||
|
Currently the following options are available:
|
||||||
|
|
||||||
|
`depth` - the search depth to use for rescoring. Default: 3.
|
||||||
|
|
||||||
|
`count` - the number of training data entries to generate. 1 entry == 1 position. Default: 1000000 (1M).
|
||||||
|
|
||||||
|
`exploration_min_nodes` - the min number of nodes to use for exploraton during selfplay. Default: 5000.
|
||||||
|
|
||||||
|
`exploration_max_nodes` - the max number of nodes to use for exploraton during selfplay. The number of nodes is chosen from a uniform distribution between min and max. Default: 15000.
|
||||||
|
|
||||||
|
`exploration_save_rate` - the ratio of positions seen during exploration self play games that are saved for later rescoring. Default: 0.01 (meaning 1 in 100 positions seen during search get saved for rescoring).
|
||||||
|
|
||||||
|
`output_file` - the name of the file to output to. If the extension is not present or doesn't match the selected training data format the right extension will be appened. Default: generated_gensfen_nonpv
|
||||||
|
|
||||||
|
`eval_limit` - evaluations with higher absolute value than this will not be written and will terminate a self-play game. Should not exceed 10000 which is VALUE_KNOWN_WIN, but is only hardcapped at mate in 2 (\~30000). Default: 4000
|
||||||
|
|
||||||
|
`exploration_eval_limit` - same as `eval_limit` but used during exploration with a value from fixed depth search.
|
||||||
|
|
||||||
|
`exploration_min_pieces` - the min number of pieces in the self play games to start the fixed depth search. Note that even if there's N pieces on the board the fixed nodes search usually reaches positions with less pieces and they are saved too. Default: 8.
|
||||||
|
|
||||||
|
`exploration_max_ply` the max ply for the exploration self play. Default: 200.
|
||||||
|
|
||||||
|
`smart_fen_skipping` - this is a flag option. When specified some position that are not good candidates for teaching are removed from the output. This includes positions where the best move is a capture or promotion, and position where a king is in check.
|
||||||
|
|
||||||
|
`book` - a path to an opening book to use for the starting positions. Currently only .epd format is supported. If not specified then the starting position is always the standard chess starting position.
|
||||||
|
|
||||||
|
`data_format` - format of the training data to use. Either `bin` or `binpack`. Default: `binpack`.
|
||||||
|
|
||||||
|
`seed` - seed for the PRNG. Can be either a number or a string. If it's a string then its hash will be used. If not specified then the current time will be used.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Stats
|
||||||
|
|
||||||
|
`gather_statistics` command allows gathering various statistics from a .bin or a .binpack file. The syntax is `gather_statistics (GROUP)* input_file FILENAME`. There can be many groups specified. Any statistic gatherer that belongs to at least one of the specified groups will be used.
|
||||||
|
|
||||||
|
Simplest usage: `stockfish.exe gather_statistics all input_file a.binpack`
|
||||||
|
|
||||||
|
Any name that doesn't designate an argument name or is not an argument will be interpreted as a group name.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
`input_file` - the path to the .bin or .binpack input file to read
|
||||||
|
|
||||||
|
`output_file` - optional path to the output file to write the results too. Results are always written on the console, so if this is specified the results will be written in both places.
|
||||||
|
|
||||||
|
`max_count` - the maximum number of positions to process. Default: no limit.
|
||||||
|
|
||||||
|
## Groups
|
||||||
|
|
||||||
|
`all` - a special group designating all statistics gatherers available.
|
||||||
|
|
||||||
|
`position_count` - the total number of positions in the file.
|
||||||
|
|
||||||
|
`king`, `king_square_count` - the number of times a king was on each square. Output is layed out as a chessboard, with the 8th rank being the topmost. Separate values for white and black kings.
|
||||||
|
|
||||||
|
`move`, `move_from_count` - same as `king_square_count` but for from_sq(move)
|
||||||
|
|
||||||
|
`move`, `move_to_count` - same as `king_square_count` but for to_sq(move)
|
||||||
|
|
||||||
|
`move`, `move_type` - the number of moves with each type. Includes normal, captures, castling, promotions, enpassant. The groups are not disjoint.
|
||||||
|
|
||||||
|
`move`, `moved_piece_type` - the number of times a piece of each type was moved
|
||||||
|
|
||||||
|
`piece_count` - the histogram of the number of pieces on the board
|
||||||
|
|
||||||
|
`ply_discontinuities` - the number of times the ply jumped by a value different than 1 between two consecutive positions. Usually the number of games.
|
||||||
|
|
||||||
|
`material_imbalance` - the histogram of imbalances, with values computed using "simple eval", that is pawn=1, bishop=knight=3, rook=5, queen=9
|
||||||
|
|
||||||
|
`results` - the distribution of game results
|
||||||
|
|
||||||
|
`endgames_6man` - distribution of endgame configurations for <=6 pieces (including kings)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Transform
|
||||||
|
|
||||||
|
`transform` command exposes subcommands that perform some specific transformation over data. The call syntax is `transform <subcommand>`. Currently implemented subcommands are listed and described below.
|
||||||
|
|
||||||
|
## `nudged_static`
|
||||||
|
|
||||||
|
`transform nudged_static` takes named parameters in the form of `transform nudged_static param_1_name param_1_value param_2_name param_2_value ...` and flag parameters which don't require values.
|
||||||
|
|
||||||
|
This command goes through positions in the input files and replaces the scores with new ones - generated from static eval - but slightly adjusted based on the scores in the original input file.
|
||||||
|
|
||||||
|
Currently the following options are available:
|
||||||
|
|
||||||
|
`input_file` - path to the input file. Supports bin and binpack formats. Default: in.binpack.
|
||||||
|
|
||||||
|
`output_file` - path to the output file. Supports bin and binpack formats. Default: out.binpack.
|
||||||
|
|
||||||
|
`absolute` - states that the adjustment should be bounded by an absolute value. After this token follows the maximum absolute adjustment. Values are always adjusted towards scores in the input file. This is the default mode. Default maximum adjustement: 5.
|
||||||
|
|
||||||
|
`relative` - states that the adjustment should be bounded by a value relative in magnitude to the static eval value. After this token follows the maximum relative change - a floating point value greater than 0. For example a value of 0.1 only allows changing the static eval by at most 10% towards the score from the input file.
|
||||||
|
|
||||||
|
`interpolate` states that the output score should be a value interpolated between static eval and the score from the input file. After this token follows the interpolation constant `t`. `t` of 0 means that only static eval is used. `t` of 1 means that only score from the input file is used. `t` of 0.5 means that the static eval and input score are averaged. It accepts values outside of range `<0, 1>`, but the usefulness is questionable.
|
||||||
|
|
||||||
|
## `rescore`
|
||||||
|
|
||||||
|
`transform rescore` takes named parameters in the form of `transform rescore param_1_name param_1_value param_2_name param_2_value ...` and flag parameters which don't require values.
|
||||||
|
|
||||||
|
This tool respects the UCI option `Threads` and uses all available threads.
|
||||||
|
|
||||||
|
This command takes a path to the input file that is either a .epd file which contains one FEN per line or a .bin or .binpack file and outputs a .bin or .binpack file with these positions rescored with specified depth search.
|
||||||
|
|
||||||
|
Currently the following options are available:
|
||||||
|
|
||||||
|
`input_file` - path to the input file. Default: in.binpack.
|
||||||
|
|
||||||
|
`output_file` - path to the output .bin or .binpack file. The file is opened in append mode. Default: out.binpack.
|
||||||
|
|
||||||
|
`depth` - the search depth to use for rescoring. Default: 3.
|
||||||
|
|
||||||
|
`keep_moves` - whether to keep moves from the input file if available. Allows to keep compression in .binpack. Default: 1.
|
||||||
|
|
||||||
|
`research_count` - number of additional searches of depth N done on the same position before using the eval. Default: 0.
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# validate_training_data
|
||||||
|
|
||||||
|
`validate_training_data` allows validation of training data of types `.plain`, `.bin`, and `.binpack`.
|
||||||
|
|
||||||
|
As all commands in stockfish `validate_training_data` can be invoked either from command line (as `stockfish.exe validate_training_data ...`) or in the interactive prompt.
|
||||||
|
|
||||||
|
The syntax of this command is as follows:
|
||||||
|
```
|
||||||
|
validate_training_data in_path
|
||||||
|
```
|
||||||
|
|
||||||
|
`in_path` is the path to the file to validate. The type of the data is deduced based on its extension (one of `.plain`, `.bin`, `.binpack`).
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# `pgn_to_plain`
|
||||||
|
This script converts pgn files into text file to apply `learn convert_bin` command. You need to import [python-chess](https://pypi.org/project/python-chess/) to use this script.
|
||||||
|
|
||||||
|
|
||||||
|
pip install python-chess
|
||||||
|
|
||||||
|
|
||||||
|
# Example of Qhapaq's finetune using `pgn_to_plain`
|
||||||
|
|
||||||
|
## Download data
|
||||||
|
You can download data from [here](http://rebel13.nl/index.html)
|
||||||
|
|
||||||
|
## Convert pgn files
|
||||||
|
|
||||||
|
**Important : convert text will be superheavy (approx 200 byte / position)**
|
||||||
|
|
||||||
|
python pgn_to_plain.py --pgn "pgn/*.pgn" --start_ply 1 --output converted_pgn.txt
|
||||||
|
|
||||||
|
|
||||||
|
`--pgn` option supports wildcard. When you use pgn files with elo >= 3300, You will get 1.7 GB text file.
|
||||||
|
|
||||||
|
|
||||||
|
## Convert into training data
|
||||||
|
|
||||||
|
|
||||||
|
### Example build command
|
||||||
|
|
||||||
|
make nnue-learn ARCH=x86-64
|
||||||
|
|
||||||
|
See `src/Makefile` for detail.
|
||||||
|
|
||||||
|
|
||||||
|
### Convert
|
||||||
|
|
||||||
|
./stockfish
|
||||||
|
learn convert_bin converted_pgn.txt output_file_name pgn_bin.bin
|
||||||
|
learn shuffle pgn_bin.bin
|
||||||
|
|
||||||
|
You also need to prepare validation data for training like following.
|
||||||
|
|
||||||
|
python pgn_to_plain.py --pgn "pgn/ccrl-40-15-3400.pgn" --start_ply 1 --output ccrl-40-15-3400.txt
|
||||||
|
./stockfish
|
||||||
|
learn convert_bin ccrl-40-15-3400.txt ccrl-40-15-3400_plain.bin
|
||||||
|
|
||||||
|
|
||||||
|
### Learn
|
||||||
|
|
||||||
|
./stockfish
|
||||||
|
setoption name Threads value 8
|
||||||
|
learn shuffled_sfen.bin newbob_decay 0.5 validation_set_file_name ccrl-40-15-3400_plain.bin nn_batch_size 50000 batchsize 1000000 eval_save_interval 8000000 eta 0.05 lambda 0.0 eval_limit 3000 mirror_percentage 0 use_draw_in_training 1
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
ENTRY_SIZE = 40
|
||||||
|
NUM_ENTRIES_IN_CHUNK = 1024*1024
|
||||||
|
|
||||||
|
def copy(infile, outfile, count, times):
|
||||||
|
if times > 1:
|
||||||
|
outfile.write(infile.read(count*ENTRY_SIZE)*times)
|
||||||
|
else:
|
||||||
|
offset = 0
|
||||||
|
while offset < count:
|
||||||
|
to_read = NUM_ENTRIES_IN_CHUNK if offset + NUM_ENTRIES_IN_CHUNK <= count else count - offset
|
||||||
|
|
||||||
|
outfile.write(infile.read(to_read*ENTRY_SIZE))
|
||||||
|
|
||||||
|
offset += NUM_ENTRIES_IN_CHUNK
|
||||||
|
|
||||||
|
def work():
|
||||||
|
filename = sys.argv[1]
|
||||||
|
offset = int(sys.argv[2])
|
||||||
|
count = int(sys.argv[3])
|
||||||
|
times = int(sys.argv[4]) if len(sys.argv) >= 5 else 1
|
||||||
|
|
||||||
|
with open(filename, 'rb') as infile:
|
||||||
|
infile.seek(offset * ENTRY_SIZE)
|
||||||
|
filename_parts = filename.split('.')
|
||||||
|
out_path = '.'.join(filename_parts[:-1]) + '_' + str(offset) + '_' + str(count) + '_' + str(times) + '.' + filename_parts[-1]
|
||||||
|
with open(out_path, 'wb') as outfile:
|
||||||
|
copy(infile, outfile, count, times)
|
||||||
|
|
||||||
|
def show_help():
|
||||||
|
print('Usage: python extract_bin.py filename offset count [times]')
|
||||||
|
print('filename - the path to the .bin file to process')
|
||||||
|
print('offset - the number of sfens to skip')
|
||||||
|
print('count - the number of sfens to extract')
|
||||||
|
print('times - the number of times to repeat the extracted sfens. Default = 1')
|
||||||
|
print('The result is saved in a new file named `filename.stem`_`offset`_`count`_`times`.bin')
|
||||||
|
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
show_help()
|
||||||
|
else:
|
||||||
|
work()
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def copy_next_chunk(in_file, out_file):
|
||||||
|
chunk_header = in_file.read(8)
|
||||||
|
assert chunk_header[0:4] == b"BINP"
|
||||||
|
size = struct.unpack("<I", chunk_header[4:])[0]
|
||||||
|
|
||||||
|
out_file.write(chunk_header)
|
||||||
|
data = in_file.read(size)
|
||||||
|
out_file.write(data)
|
||||||
|
|
||||||
|
return size + 8
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print("Usage: python interleave_binpacks.py infile1 ... infileN outfile")
|
||||||
|
print(" The output binpack, will contain all data from the input files.")
|
||||||
|
print(" Data is read sequentially from the input, randomly alternating between files.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# open last arg as output file name
|
||||||
|
out_filename = sys.argv[-1]
|
||||||
|
print("outfile: ", out_filename)
|
||||||
|
|
||||||
|
if Path(out_filename).exists():
|
||||||
|
print(
|
||||||
|
"Output path {} already exists. Please specify a path to a file that does not exist.".format(
|
||||||
|
out_filename
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
out_file = open(out_filename, "wb")
|
||||||
|
|
||||||
|
# open other args as input file names, and get their sizes
|
||||||
|
in_filenames = []
|
||||||
|
for i in range(1, len(sys.argv) - 1):
|
||||||
|
in_filenames.append(sys.argv[i])
|
||||||
|
print("infiles: ", in_filenames)
|
||||||
|
|
||||||
|
in_files = []
|
||||||
|
in_files_remaining = []
|
||||||
|
for in_filename in in_filenames:
|
||||||
|
in_file = open(in_filename, "rb")
|
||||||
|
in_files.append(in_file)
|
||||||
|
file_size = os.path.getsize(in_filename)
|
||||||
|
in_files_remaining.append(file_size)
|
||||||
|
|
||||||
|
# randomly pick a file, with a probability related to their sizes.
|
||||||
|
# copy from the front and keep track of remaining sizes
|
||||||
|
total_remaining = sum(in_files_remaining)
|
||||||
|
print("Merging {} bytes ".format(total_remaining))
|
||||||
|
|
||||||
|
total_size = 0
|
||||||
|
report_every = 100
|
||||||
|
prev_mib = -report_every
|
||||||
|
|
||||||
|
while total_remaining > 0:
|
||||||
|
where = random.randrange(total_remaining)
|
||||||
|
i = 0
|
||||||
|
while where >= in_files_remaining[i]:
|
||||||
|
where -= in_files_remaining[i]
|
||||||
|
i += 1
|
||||||
|
size = copy_next_chunk(in_files[i], out_file)
|
||||||
|
in_files_remaining[i] -= size
|
||||||
|
total_remaining -= size
|
||||||
|
total_size += size
|
||||||
|
mib = total_size // 1024 // 1024
|
||||||
|
if mib // 100 != prev_mib // 100:
|
||||||
|
print("Copied {} MiB".format(mib))
|
||||||
|
prev_mib = mib
|
||||||
|
|
||||||
|
out_file.close()
|
||||||
|
for in_file in in_files:
|
||||||
|
in_file.close()
|
||||||
|
|
||||||
|
print("Merged {} bytes".format(total_size))
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import chess.pgn
|
||||||
|
import argparse
|
||||||
|
import glob
|
||||||
|
import re
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# todo close in c++ tools using pgn-extract
|
||||||
|
# https://www.cs.kent.ac.uk/people/staff/djb/pgn-extract/help.html#-w
|
||||||
|
|
||||||
|
commentRe = re.compile("([+-]*M*[0-9.]*)/([0-9]*)")
|
||||||
|
mateRe = re.compile("([+-])M([0-9]*)")
|
||||||
|
flip_black = False
|
||||||
|
|
||||||
|
def parse_result(result_str:str, board:chess.Board) -> int:
|
||||||
|
if result_str == "1/2-1/2":
|
||||||
|
return 0
|
||||||
|
if result_str == "0-1":
|
||||||
|
if board.turn == chess.WHITE:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
elif result_str == "1-0":
|
||||||
|
if board.turn == chess.WHITE:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
print("illegal result", result_str)
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
def game_sanity_check(game: chess.pgn.Game) -> bool:
|
||||||
|
if not game.headers["Result"] in ["1/2-1/2", "0-1", "1-0"]:
|
||||||
|
print("invalid result", game.headers["Result"])
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def parse_comment_for_score(comment_str: str, board: chess.Board) -> int:
|
||||||
|
global commentRe
|
||||||
|
global mateRe
|
||||||
|
global flip_black
|
||||||
|
|
||||||
|
try:
|
||||||
|
m = commentRe.search(comment_str)
|
||||||
|
if m:
|
||||||
|
score = m.group(1)
|
||||||
|
# depth = int(m.group(2))
|
||||||
|
m = mateRe.search(score)
|
||||||
|
if m:
|
||||||
|
if m.group(1) == "+":
|
||||||
|
score = 32000 - int(m.group(2))
|
||||||
|
else:
|
||||||
|
score = -32000 + int(m.group(2))
|
||||||
|
else:
|
||||||
|
score = int(float(score) * 208) # pawn to SF PawnValueEg
|
||||||
|
|
||||||
|
if flip_black and board.turn == chess.BLACK:
|
||||||
|
score = -score
|
||||||
|
else:
|
||||||
|
score = 0
|
||||||
|
except:
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
return score
|
||||||
|
|
||||||
|
def parse_game(game: chess.pgn.Game, writer, start_play: int=1)->None:
|
||||||
|
board: chess.Board = game.board()
|
||||||
|
if not game_sanity_check(game):
|
||||||
|
return
|
||||||
|
|
||||||
|
result: str = game.headers["Result"]
|
||||||
|
ply = 0
|
||||||
|
for node in game.mainline():
|
||||||
|
move = node.move
|
||||||
|
if ply >= start_play:
|
||||||
|
comment: str = node.comment
|
||||||
|
writer.write("fen " + board.fen() + "\n")
|
||||||
|
writer.write("move " + str(move) + "\n")
|
||||||
|
writer.write("score " + str(parse_comment_for_score(comment, board)) + "\n")
|
||||||
|
writer.write("ply " + str(ply)+"\n")
|
||||||
|
writer.write("result " + str(parse_result(result, board)) +"\n")
|
||||||
|
writer.write("e\n")
|
||||||
|
ply += 1
|
||||||
|
board.push(move)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--pgn", type=str, required=True)
|
||||||
|
parser.add_argument("--start_ply", type=int, default=1)
|
||||||
|
parser.add_argument("--output", type=str, default="plain.txt")
|
||||||
|
parser.add_argument("--flip_black_score", action='store_true', dest='flip_black_score', help="Flip black score. Default: False")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
global flip_black
|
||||||
|
flip_black = args.flip_black_score
|
||||||
|
|
||||||
|
pgn_files: List[str] = glob.glob(args.pgn)
|
||||||
|
pgn_files = sorted(pgn_files, key=lambda x:float(re.findall("-(\d+).pgn",x)[0] if re.findall("-(\d+).pgn",x) else 0.0))
|
||||||
|
f = open(args.output, 'w')
|
||||||
|
for pgn_file in pgn_files:
|
||||||
|
print("parse", pgn_file)
|
||||||
|
pgn_loader = open(pgn_file)
|
||||||
|
while True:
|
||||||
|
game = chess.pgn.read_game(pgn_loader)
|
||||||
|
if game is None:
|
||||||
|
break
|
||||||
|
parse_game(game, f, args.start_ply)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def index_binpack(file):
|
||||||
|
print('Indexing...')
|
||||||
|
index = []
|
||||||
|
offset = 0
|
||||||
|
report_every = 100
|
||||||
|
prev_mib = -report_every
|
||||||
|
while file.peek():
|
||||||
|
chunk_header = file.read(8)
|
||||||
|
assert chunk_header[0:4] == b'BINP'
|
||||||
|
size = struct.unpack('<I', chunk_header[4:])[0]
|
||||||
|
file.seek(size, os.SEEK_CUR)
|
||||||
|
index.append((offset, size + 8))
|
||||||
|
offset += size + 8
|
||||||
|
|
||||||
|
mib = offset // 1024 // 1024
|
||||||
|
if mib // 100 != prev_mib // 100:
|
||||||
|
print('Indexed {} MiB'.format(mib))
|
||||||
|
prev_mib = mib
|
||||||
|
|
||||||
|
return index
|
||||||
|
|
||||||
|
def copy_binpack_indexed(in_file, index, out_files):
|
||||||
|
print('Copying...')
|
||||||
|
total_size = 0
|
||||||
|
report_every = 100
|
||||||
|
prev_mib = -report_every
|
||||||
|
nextfile = 0
|
||||||
|
for offset, size in index:
|
||||||
|
in_file.seek(offset, os.SEEK_SET)
|
||||||
|
data = in_file.read(size)
|
||||||
|
assert len(data) == size
|
||||||
|
out_files[nextfile].write(data)
|
||||||
|
nextfile = (nextfile + 1) % len(out_files)
|
||||||
|
|
||||||
|
total_size += size
|
||||||
|
mib = total_size // 1024 // 1024
|
||||||
|
if mib // 100 != prev_mib // 100:
|
||||||
|
print('Copied {} MiB'.format(mib))
|
||||||
|
prev_mib = mib
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print('Usage: python shuffle_binpack.py infile outfile [split_count]')
|
||||||
|
return
|
||||||
|
|
||||||
|
in_filename = sys.argv[1]
|
||||||
|
|
||||||
|
if len(sys.argv) > 3:
|
||||||
|
# split the infile in split_count pieces, creating new outfile names based on the provided name
|
||||||
|
basefile = sys.argv[2]
|
||||||
|
split_count = int(sys.argv[3])
|
||||||
|
base=os.path.splitext(basefile)[0]
|
||||||
|
ext=os.path.splitext(basefile)[1]
|
||||||
|
out_filenames = []
|
||||||
|
for i in range(split_count):
|
||||||
|
out_filenames.append(base+"_{}".format(i)+ext)
|
||||||
|
else:
|
||||||
|
out_filenames = [sys.argv[2]]
|
||||||
|
|
||||||
|
for out_filename in out_filenames:
|
||||||
|
if (Path(out_filename).exists()):
|
||||||
|
print('Output path {} already exists. Please specify a path to a file that does not exist.'.format(out_filename))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(out_filenames)
|
||||||
|
|
||||||
|
in_file = open(in_filename, 'rb')
|
||||||
|
index = index_binpack(in_file)
|
||||||
|
|
||||||
|
print('Shuffling...')
|
||||||
|
random.shuffle(index)
|
||||||
|
|
||||||
|
out_files = []
|
||||||
|
for out_filename in out_filenames:
|
||||||
|
out_files.append(open(out_filename, 'wb'))
|
||||||
|
|
||||||
|
copy_binpack_indexed(in_file, index, out_files)
|
||||||
|
|
||||||
|
in_file.close()
|
||||||
|
for out_file in out_files:
|
||||||
|
out_file.close()
|
||||||
|
|
||||||
|
main()
|
||||||
+501
-241
File diff suppressed because it is too large
Load Diff
+19
-3
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -87,16 +87,20 @@ const vector<string> Defaults = {
|
|||||||
// Chess 960
|
// Chess 960
|
||||||
"setoption name UCI_Chess960 value true",
|
"setoption name UCI_Chess960 value true",
|
||||||
"bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
|
"bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
|
||||||
|
"nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
|
||||||
"setoption name UCI_Chess960 value false"
|
"setoption name UCI_Chess960 value false"
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
/// setup_bench() builds a list of UCI commands to be run by bench. There
|
/// 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
|
/// 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
|
/// should be used, the limit value spent for each position, a file name
|
||||||
/// where to look for positions in FEN format and the type of the limit:
|
/// where to look for positions in FEN format, the type of the limit:
|
||||||
/// depth, perft, nodes and movetime (in millisecs).
|
/// depth, perft, nodes and movetime (in millisecs), and evaluation type
|
||||||
|
/// mixed (default), classical, NNUE.
|
||||||
///
|
///
|
||||||
/// bench -> search default positions up to depth 13
|
/// bench -> search default positions up to depth 13
|
||||||
/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB)
|
/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB)
|
||||||
@@ -115,6 +119,7 @@ vector<string> setup_bench(const Position& current, istream& is) {
|
|||||||
string limit = (is >> token) ? token : "13";
|
string limit = (is >> token) ? token : "13";
|
||||||
string fenFile = (is >> token) ? token : "default";
|
string fenFile = (is >> token) ? token : "default";
|
||||||
string limitType = (is >> token) ? token : "depth";
|
string limitType = (is >> token) ? token : "depth";
|
||||||
|
string evalType = (is >> token) ? token : "mixed";
|
||||||
|
|
||||||
go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
|
go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
|
||||||
|
|
||||||
@@ -146,14 +151,25 @@ vector<string> setup_bench(const Position& current, istream& is) {
|
|||||||
list.emplace_back("setoption name Hash value " + ttSize);
|
list.emplace_back("setoption name Hash value " + ttSize);
|
||||||
list.emplace_back("ucinewgame");
|
list.emplace_back("ucinewgame");
|
||||||
|
|
||||||
|
size_t posCounter = 0;
|
||||||
|
|
||||||
for (const string& fen : fens)
|
for (const string& fen : fens)
|
||||||
if (fen.find("setoption") != string::npos)
|
if (fen.find("setoption") != string::npos)
|
||||||
list.emplace_back(fen);
|
list.emplace_back(fen);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0))
|
||||||
|
list.emplace_back("setoption name Use NNUE value false");
|
||||||
|
else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0))
|
||||||
|
list.emplace_back("setoption name Use NNUE value true");
|
||||||
list.emplace_back("position fen " + fen);
|
list.emplace_back("position fen " + fen);
|
||||||
list.emplace_back(go);
|
list.emplace_back(go);
|
||||||
|
++posCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list.emplace_back("setoption name Use NNUE value true");
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+7
-5
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// There are 24 possible pawn squares: files A to D and ranks from 2 to 7.
|
// There are 24 possible pawn squares: files A to D and ranks from 2 to 7.
|
||||||
@@ -66,7 +68,6 @@ namespace {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) {
|
bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) {
|
||||||
|
|
||||||
assert(file_of(wpsq) <= FILE_D);
|
assert(file_of(wpsq) <= FILE_D);
|
||||||
@@ -96,7 +97,6 @@ void Bitbases::init() {
|
|||||||
KPKBitbase.set(idx);
|
KPKBitbase.set(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
KPKPosition::KPKPosition(unsigned idx) {
|
KPKPosition::KPKPosition(unsigned idx) {
|
||||||
@@ -150,8 +150,8 @@ namespace {
|
|||||||
Bitboard b = attacks_bb<KING>(ksq[stm]);
|
Bitboard b = attacks_bb<KING>(ksq[stm]);
|
||||||
|
|
||||||
while (b)
|
while (b)
|
||||||
r |= stm == WHITE ? db[index(BLACK, ksq[BLACK] , pop_lsb(&b), psq)]
|
r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)]
|
||||||
: db[index(WHITE, pop_lsb(&b), ksq[WHITE], psq)];
|
: db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)];
|
||||||
|
|
||||||
if (stm == WHITE)
|
if (stm == WHITE)
|
||||||
{
|
{
|
||||||
@@ -168,3 +168,5 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+25
-6
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,11 +22,14 @@
|
|||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
uint8_t PopCnt16[1 << 16];
|
uint8_t PopCnt16[1 << 16];
|
||||||
uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
||||||
|
|
||||||
Bitboard SquareBB[SQUARE_NB];
|
Bitboard SquareBB[SQUARE_NB];
|
||||||
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||||
|
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||||
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
||||||
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
|
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
|
||||||
|
|
||||||
@@ -39,13 +42,22 @@ namespace {
|
|||||||
Bitboard BishopTable[0x1480]; // To store bishop attacks
|
Bitboard BishopTable[0x1480]; // To store bishop attacks
|
||||||
|
|
||||||
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
|
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// safe_destination() returns the bitboard of target square for the given step
|
||||||
|
/// from the given square. If the step is off the board, returns empty bitboard.
|
||||||
|
|
||||||
|
inline Bitboard safe_destination(Square s, int step) {
|
||||||
|
Square to = Square(s + step);
|
||||||
|
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
|
/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
|
||||||
/// to be printed to standard output. Useful for debugging.
|
/// 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";
|
std::string s = "+---+---+---+---+---+---+---+---+\n";
|
||||||
|
|
||||||
@@ -96,12 +108,17 @@ void Bitboards::init() {
|
|||||||
|
|
||||||
for (PieceType pt : { BISHOP, ROOK })
|
for (PieceType pt : { BISHOP, ROOK })
|
||||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||||
|
{
|
||||||
if (PseudoAttacks[pt][s1] & 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 {
|
namespace {
|
||||||
|
|
||||||
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
|
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
|
||||||
@@ -110,10 +127,10 @@ namespace {
|
|||||||
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
|
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
|
||||||
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
|
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
|
||||||
|
|
||||||
for(Direction d : (pt == ROOK ? RookDirections : BishopDirections))
|
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
|
||||||
{
|
{
|
||||||
Square s = sq;
|
Square s = sq;
|
||||||
while(safe_destination(s, d) && !(occupied & s))
|
while (safe_destination(s, d) && !(occupied & s))
|
||||||
attacks |= (s += d);
|
attacks |= (s += d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,3 +218,5 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+34
-26
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,19 +23,21 @@
|
|||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace Bitbases {
|
namespace Bitbases {
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
bool probe(Square wksq, Square wpsq, Square bksq, Color us);
|
bool probe(Square wksq, Square wpsq, Square bksq, Color us);
|
||||||
|
|
||||||
}
|
} // namespace Stockfish::Bitbases
|
||||||
|
|
||||||
namespace Bitboards {
|
namespace Bitboards {
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
const std::string pretty(Bitboard b);
|
std::string pretty(Bitboard b);
|
||||||
|
|
||||||
}
|
} // namespace Stockfish::Bitboards
|
||||||
|
|
||||||
constexpr Bitboard AllSquares = ~Bitboard(0);
|
constexpr Bitboard AllSquares = ~Bitboard(0);
|
||||||
constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL;
|
constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL;
|
||||||
@@ -73,6 +75,7 @@ extern uint8_t PopCnt16[1 << 16];
|
|||||||
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
||||||
|
|
||||||
extern Bitboard SquareBB[SQUARE_NB];
|
extern Bitboard SquareBB[SQUARE_NB];
|
||||||
|
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||||
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||||
extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
||||||
extern Bitboard PawnAttacks[COLOR_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) {
|
inline Bitboard line_bb(Square s1, Square s2) {
|
||||||
|
|
||||||
assert(is_ok(s1) && is_ok(s2));
|
assert(is_ok(s1) && is_ok(s2));
|
||||||
|
|
||||||
return LineBB[s1][s2];
|
return LineBB[s1][s2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// between_bb() returns a bitboard representing squares that are linearly
|
/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open
|
||||||
/// between the two given squares (excluding the given squares). If the given
|
/// segment between the squares s1 and s2 (excluding s1 but including s2). If the
|
||||||
/// squares are not on a same file/rank/diagonal, we return 0. For instance,
|
/// 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 and E6.
|
/// 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) {
|
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
|
/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in
|
||||||
/// in front of the given one, from the point of view of the given color. For instance,
|
/// 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.
|
/// 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) {
|
constexpr Bitboard forward_ranks_bb(Color c, Square s) {
|
||||||
@@ -279,16 +288,6 @@ inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
|
|||||||
inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); }
|
inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); }
|
||||||
|
|
||||||
|
|
||||||
/// safe_destination() returns the bitboard of target square for the given step
|
|
||||||
/// from the given square. If the step is off the board, returns empty bitboard.
|
|
||||||
|
|
||||||
inline Bitboard safe_destination(Square s, int step)
|
|
||||||
{
|
|
||||||
Square to = Square(s + step);
|
|
||||||
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// attacks_bb(Square) returns the pseudo attacks of the give piece type
|
/// attacks_bb(Square) returns the pseudo attacks of the give piece type
|
||||||
/// assuming an empty board.
|
/// assuming an empty board.
|
||||||
|
|
||||||
@@ -422,13 +421,20 @@ inline Square msb(Bitboard b) {
|
|||||||
|
|
||||||
#endif
|
#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
|
/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
|
||||||
|
|
||||||
inline Square pop_lsb(Bitboard* b) {
|
inline Square pop_lsb(Bitboard& b) {
|
||||||
assert(*b);
|
assert(b);
|
||||||
const Square s = lsb(*b);
|
const Square s = lsb(b);
|
||||||
*b &= *b - 1;
|
b &= b - 1;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,4 +446,6 @@ inline Square frontmost_sq(Color c, Bitboard b) {
|
|||||||
return c == WHITE ? msb(b) : lsb(b);
|
return c == WHITE ? msb(b) : lsb(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef BITBOARD_H_INCLUDED
|
#endif // #ifndef BITBOARD_H_INCLUDED
|
||||||
|
|||||||
+9
-5
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
#include "endgame.h"
|
#include "endgame.h"
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Used to drive the king towards the edge of the board
|
// Used to drive the king towards the edge of the board
|
||||||
@@ -553,8 +555,8 @@ ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, RookValueMg, 2));
|
assert(verify_material(pos, strongSide, RookValueMg, 2));
|
||||||
assert(verify_material(pos, weakSide, RookValueMg, 1));
|
assert(verify_material(pos, weakSide, RookValueMg, 1));
|
||||||
|
|
||||||
Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
|
Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN));
|
||||||
Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
|
Square strongPawn2 = msb(pos.pieces(strongSide, PAWN));
|
||||||
Square weakKing = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
|
|
||||||
// Does the stronger side have a passed pawn?
|
// Does the stronger side have a passed pawn?
|
||||||
@@ -638,8 +640,8 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
|
|||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
|
|
||||||
Square weakKing = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
|
Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN));
|
||||||
Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
|
Square strongPawn2 = msb(pos.pieces(strongSide, PAWN));
|
||||||
Square blockSq1, blockSq2;
|
Square blockSq1, blockSq2;
|
||||||
|
|
||||||
if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2))
|
if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2))
|
||||||
@@ -741,3 +743,5 @@ ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
|
|||||||
// it's probably at least a draw even with the pawn.
|
// it's probably at least a draw even with the pawn.
|
||||||
return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
|
return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+4
-1
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
/// EndgameCode lists all supported endgame functions by corresponding codes
|
/// EndgameCode lists all supported endgame functions by corresponding codes
|
||||||
|
|
||||||
@@ -120,4 +121,6 @@ namespace Endgames {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef ENDGAME_H_INCLUDED
|
#endif // #ifndef ENDGAME_H_INCLUDED
|
||||||
|
|||||||
+390
-168
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,46 +20,146 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring> // For std::memset
|
#include <cstring> // For std::memset
|
||||||
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <streambuf>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "nnue/evaluate_nnue.h"
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "evaluate.h"
|
#include "evaluate.h"
|
||||||
#include "material.h"
|
#include "material.h"
|
||||||
|
#include "misc.h"
|
||||||
#include "pawns.h"
|
#include "pawns.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
#include "timeman.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
|
#include "incbin/incbin.h"
|
||||||
|
|
||||||
|
// Macro to embed the default efficiently updatable neural network (NNUE) file
|
||||||
|
// data in the engine binary (using incbin.h, by Dale Weiler).
|
||||||
|
// This macro invocation will declare the following three variables
|
||||||
|
// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data
|
||||||
|
// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
|
||||||
|
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
|
||||||
|
// Note that this does not work in Microsoft Visual Studio.
|
||||||
|
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
|
||||||
|
INCBIN(EmbeddedNNUE, EvalFileDefaultName);
|
||||||
|
#else
|
||||||
|
const unsigned char gEmbeddedNNUEData[1] = {0x0};
|
||||||
|
const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
|
||||||
|
const unsigned int gEmbeddedNNUESize = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace Eval {
|
namespace Eval {
|
||||||
|
|
||||||
bool useNNUE;
|
namespace NNUE {
|
||||||
std::string eval_file_loaded="None";
|
string currentEvalFileName = "None";
|
||||||
|
UseNNUEMode useNNUE;
|
||||||
|
|
||||||
void init_NNUE() {
|
static UseNNUEMode nnue_mode_from_option(const UCI::Option& mode)
|
||||||
|
{
|
||||||
|
if (mode == "false")
|
||||||
|
return UseNNUEMode::False;
|
||||||
|
else if (mode == "true")
|
||||||
|
return UseNNUEMode::True;
|
||||||
|
else if (mode == "pure")
|
||||||
|
return UseNNUEMode::Pure;
|
||||||
|
|
||||||
useNNUE = Options["Use NNUE"];
|
return UseNNUEMode::False;
|
||||||
std::string eval_file = std::string(Options["EvalFile"]);
|
}
|
||||||
if (useNNUE && eval_file_loaded != eval_file)
|
|
||||||
if (Eval::NNUE::load_eval_file(eval_file))
|
|
||||||
eval_file_loaded = eval_file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void verify_NNUE() {
|
/// NNUE::init() tries to load a NNUE network at startup time, or when the engine
|
||||||
|
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
|
||||||
|
/// The name of the NNUE network is always retrieved from the EvalFile option.
|
||||||
|
/// We search the given network in three locations: internally (the default
|
||||||
|
/// network may be embedded in the binary), in the active working directory and
|
||||||
|
/// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
|
||||||
|
/// variable to have the engine search in a special directory in their distro.
|
||||||
|
|
||||||
std::string eval_file = std::string(Options["EvalFile"]);
|
void NNUE::init() {
|
||||||
if (useNNUE && eval_file_loaded != eval_file)
|
|
||||||
|
useNNUE = nnue_mode_from_option(Options["Use NNUE"]);
|
||||||
|
if (useNNUE == UseNNUEMode::False)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string eval_file = string(Options["EvalFile"]);
|
||||||
|
if (eval_file.empty())
|
||||||
|
eval_file = EvalFileDefaultName;
|
||||||
|
|
||||||
|
#if defined(DEFAULT_NNUE_DIRECTORY)
|
||||||
|
#define stringify2(x) #x
|
||||||
|
#define stringify(x) stringify2(x)
|
||||||
|
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
|
||||||
|
#else
|
||||||
|
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (string directory : dirs)
|
||||||
|
if (currentEvalFileName != eval_file)
|
||||||
|
{
|
||||||
|
if (directory != "<internal>")
|
||||||
|
{
|
||||||
|
ifstream stream(directory + eval_file, ios::binary);
|
||||||
|
if (load_eval(eval_file, stream))
|
||||||
|
currentEvalFileName = eval_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directory == "<internal>" && eval_file == EvalFileDefaultName)
|
||||||
|
{
|
||||||
|
// C++ way to prepare a buffer for a memory stream
|
||||||
|
class MemoryBuffer : public basic_streambuf<char> {
|
||||||
|
public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); }
|
||||||
|
};
|
||||||
|
|
||||||
|
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
|
||||||
|
size_t(gEmbeddedNNUESize));
|
||||||
|
(void) gEmbeddedNNUEEnd; // Silence warning on unused variable
|
||||||
|
|
||||||
|
istream stream(&buffer);
|
||||||
|
if (load_eval(eval_file, stream))
|
||||||
|
currentEvalFileName = eval_file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NNUE::verify() verifies that the last net used was loaded successfully
|
||||||
|
void NNUE::verify() {
|
||||||
|
|
||||||
|
string eval_file = string(Options["EvalFile"]);
|
||||||
|
if (eval_file.empty())
|
||||||
|
eval_file = EvalFileDefaultName;
|
||||||
|
|
||||||
|
if (useNNUE != UseNNUEMode::False && currentEvalFileName != eval_file)
|
||||||
{
|
{
|
||||||
std::cerr << "Use of NNUE evaluation, but the file " << eval_file << " was not loaded successfully. "
|
|
||||||
<< "These network evaluation parameters must be available, compatible with this version of the code. "
|
string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available.";
|
||||||
<< "The UCI option EvalFile might need to specify the full path, including the directory/folder name, to the file." << std::endl;
|
string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully.";
|
||||||
std::exit(EXIT_FAILURE);
|
string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
|
||||||
|
string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName);
|
||||||
|
string msg5 = "The engine will be terminated now.";
|
||||||
|
|
||||||
|
sync_cout << "info string ERROR: " << msg1 << sync_endl;
|
||||||
|
sync_cout << "info string ERROR: " << msg2 << sync_endl;
|
||||||
|
sync_cout << "info string ERROR: " << msg3 << sync_endl;
|
||||||
|
sync_cout << "info string ERROR: " << msg4 << sync_endl;
|
||||||
|
sync_cout << "info string ERROR: " << msg5 << sync_endl;
|
||||||
|
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useNNUE)
|
if (useNNUE != UseNNUEMode::False)
|
||||||
sync_cout << "info string NNUE evaluation using " << eval_file << " enabled." << sync_endl;
|
sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl;
|
||||||
else
|
else
|
||||||
sync_cout << "info string classical evaluation enabled." << sync_endl;
|
sync_cout << "info string classical evaluation enabled" << sync_endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +197,7 @@ namespace Trace {
|
|||||||
else
|
else
|
||||||
os << scores[t][WHITE] << " | " << scores[t][BLACK];
|
os << scores[t][WHITE] << " | " << scores[t][BLACK];
|
||||||
|
|
||||||
os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n";
|
os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n";
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,17 +207,17 @@ using namespace Trace;
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Threshold for lazy and space evaluation
|
// Threshold for lazy and space evaluation
|
||||||
constexpr Value LazyThreshold1 = Value(1400);
|
constexpr Value LazyThreshold1 = Value(3631);
|
||||||
constexpr Value LazyThreshold2 = Value(1300);
|
constexpr Value LazyThreshold2 = Value(2084);
|
||||||
constexpr Value SpaceThreshold = Value(12222);
|
constexpr Value SpaceThreshold = Value(11551);
|
||||||
|
|
||||||
// KingAttackWeights[PieceType] contains king attack weights by piece type
|
// KingAttackWeights[PieceType] contains king attack weights by piece type
|
||||||
constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
|
constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 76, 46, 45, 14 };
|
||||||
|
|
||||||
// SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
|
// SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
|
||||||
// higher if multiple safe checks are possible for that piece type.
|
// higher if multiple safe checks are possible for that piece type.
|
||||||
constexpr int SafeCheck[][2] = {
|
constexpr int SafeCheck[][2] = {
|
||||||
{}, {}, {792, 1283}, {645, 967}, {1084, 1897}, {772, 1119}
|
{}, {}, {805, 1292}, {650, 984}, {1071, 1886}, {730, 1128}
|
||||||
};
|
};
|
||||||
|
|
||||||
#define S(mg, eg) make_score(mg, eg)
|
#define S(mg, eg) make_score(mg, eg)
|
||||||
@@ -125,73 +225,76 @@ namespace {
|
|||||||
// MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game,
|
// MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game,
|
||||||
// indexed by piece type and number of attacked squares in the mobility area.
|
// indexed by piece type and number of attacked squares in the mobility area.
|
||||||
constexpr Score MobilityBonus[][32] = {
|
constexpr Score MobilityBonus[][32] = {
|
||||||
{ S(-62,-81), S(-53,-56), S(-12,-31), S( -4,-16), S( 3, 5), S( 13, 11), // Knight
|
{ S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight
|
||||||
S( 22, 17), S( 28, 20), S( 33, 25) },
|
S( 21, 16), S( 28, 21), S( 37, 26) },
|
||||||
{ S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishop
|
{ S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop
|
||||||
S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86),
|
S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87),
|
||||||
S( 91, 88), S( 98, 97) },
|
S( 91, 88), S( 96, 98) },
|
||||||
{ S(-60,-78), S(-20,-17), S( 2, 23), S( 3, 39), S( 3, 70), S( 11, 99), // Rook
|
{ S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook
|
||||||
S( 22,103), S( 31,121), S( 40,134), S( 40,139), S( 41,158), S( 48,164),
|
S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160),
|
||||||
S( 57,168), S( 57,169), S( 62,172) },
|
S( 57,165), S( 58,170), S( 67,175) },
|
||||||
{ S(-30,-48), S(-12,-30), S( -8, -7), S( -9, 19), S( 20, 40), S( 23, 55), // Queen
|
{ S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen
|
||||||
S( 23, 59), S( 35, 75), S( 38, 78), S( 53, 96), S( 64, 96), S( 65,100),
|
S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101),
|
||||||
S( 65,121), S( 66,127), S( 67,131), S( 67,133), S( 72,136), S( 72,141),
|
S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140),
|
||||||
S( 77,147), S( 79,150), S( 93,151), S(108,168), S(108,168), S(108,171),
|
S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171),
|
||||||
S(110,182), S(114,182), S(114,192), S(116,219) }
|
S(112,178), S(114,185), S(114,187), S(119,221) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// BishopPawns[distance from edge] contains a file-dependent penalty for pawns on
|
||||||
|
// squares of the same color as our bishop.
|
||||||
|
constexpr Score BishopPawns[int(FILE_NB) / 2] = {
|
||||||
|
S(3, 8), S(3, 9), S(2, 7), S(3, 7)
|
||||||
};
|
};
|
||||||
|
|
||||||
// KingProtector[knight/bishop] contains penalty for each distance unit to own king
|
// KingProtector[knight/bishop] contains penalty for each distance unit to own king
|
||||||
constexpr Score KingProtector[] = { S(8, 9), S(6, 9) };
|
constexpr Score KingProtector[] = { S(9, 9), S(7, 9) };
|
||||||
|
|
||||||
// Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a
|
// 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.
|
// pawn protected square on rank 4 to 6 which is also safe from a pawn attack.
|
||||||
constexpr Score Outpost[] = { S(56, 36), S(30, 23) };
|
constexpr Score Outpost[] = { S(54, 34), S(31, 25) };
|
||||||
|
|
||||||
// PassedRank[Rank] contains a bonus according to the rank of a passed pawn
|
// PassedRank[Rank] contains a bonus according to the rank of a passed pawn
|
||||||
constexpr Score PassedRank[RANK_NB] = {
|
constexpr Score PassedRank[RANK_NB] = {
|
||||||
S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260)
|
S(0, 0), S(2, 38), S(15, 36), S(22, 50), S(64, 81), S(166, 184), S(284, 269)
|
||||||
};
|
};
|
||||||
|
|
||||||
// RookOnFile[semiopen/open] contains bonuses for each rook when there is
|
constexpr Score RookOnClosedFile = S(10, 5);
|
||||||
// no (friendly) pawn on the rook file.
|
constexpr Score RookOnOpenFile[] = { S(18, 8), S(49, 26) };
|
||||||
constexpr Score RookOnFile[] = { S(19, 7), S(48, 29) };
|
|
||||||
|
|
||||||
// ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
|
// ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
|
||||||
// which piece type attacks which one. Attacks on lesser pieces which are
|
// which piece type attacks which one. Attacks on lesser pieces which are
|
||||||
// pawn-defended are not considered.
|
// pawn-defended are not considered.
|
||||||
constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
|
constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
|
||||||
S(0, 0), S(5, 32), S(57, 41), S(77, 56), S(88, 119), S(79, 161)
|
S(0, 0), S(6, 37), S(64, 50), S(82, 57), S(103, 130), S(81, 163)
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
|
constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
|
||||||
S(0, 0), S(3, 46), S(37, 68), S(42, 60), S(0, 38), S(58, 41)
|
S(0, 0), S(3, 44), S(36, 71), S(44, 59), S(0, 39), S(60, 39)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr Value CorneredBishop = Value(50);
|
||||||
|
|
||||||
// Assorted bonuses and penalties
|
// Assorted bonuses and penalties
|
||||||
constexpr Score BadOutpost = S( -7, 36);
|
constexpr Score UncontestedOutpost = S( 0, 10);
|
||||||
constexpr Score BishopOnKingRing = S( 24, 0);
|
constexpr Score BishopOnKingRing = S( 24, 0);
|
||||||
constexpr Score BishopPawns = S( 3, 7);
|
|
||||||
constexpr Score BishopXRayPawns = S( 4, 5);
|
constexpr Score BishopXRayPawns = S( 4, 5);
|
||||||
constexpr Score CorneredBishop = S( 50, 50);
|
|
||||||
constexpr Score FlankAttacks = S( 8, 0);
|
constexpr Score FlankAttacks = S( 8, 0);
|
||||||
constexpr Score Hanging = S( 69, 36);
|
constexpr Score Hanging = S( 72, 40);
|
||||||
constexpr Score KnightOnQueen = S( 16, 11);
|
constexpr Score KnightOnQueen = S( 16, 11);
|
||||||
constexpr Score LongDiagonalBishop = S( 45, 0);
|
constexpr Score LongDiagonalBishop = S( 45, 0);
|
||||||
constexpr Score MinorBehindPawn = S( 18, 3);
|
constexpr Score MinorBehindPawn = S( 18, 3);
|
||||||
constexpr Score PassedFile = S( 11, 8);
|
constexpr Score PassedFile = S( 13, 8);
|
||||||
constexpr Score PawnlessFlank = S( 17, 95);
|
constexpr Score PawnlessFlank = S( 19, 97);
|
||||||
constexpr Score QueenInfiltration = S( -2, 14);
|
constexpr Score ReachableOutpost = S( 33, 19);
|
||||||
constexpr Score ReachableOutpost = S( 31, 22);
|
constexpr Score RestrictedPiece = S( 6, 7);
|
||||||
constexpr Score RestrictedPiece = S( 7, 7);
|
|
||||||
constexpr Score RookOnKingRing = S( 16, 0);
|
constexpr Score RookOnKingRing = S( 16, 0);
|
||||||
constexpr Score RookOnQueenFile = S( 6, 11);
|
constexpr Score SliderOnQueen = S( 62, 21);
|
||||||
constexpr Score SliderOnQueen = S( 60, 18);
|
constexpr Score ThreatByKing = S( 24, 87);
|
||||||
constexpr Score ThreatByKing = S( 24, 89);
|
|
||||||
constexpr Score ThreatByPawnPush = S( 48, 39);
|
constexpr Score ThreatByPawnPush = S( 48, 39);
|
||||||
constexpr Score ThreatBySafePawn = S(173, 94);
|
constexpr Score ThreatBySafePawn = S(167, 99);
|
||||||
constexpr Score TrappedRook = S( 55, 13);
|
constexpr Score TrappedRook = S( 55, 13);
|
||||||
constexpr Score WeakQueenProtection = S( 14, 0);
|
constexpr Score WeakQueenProtection = S( 14, 0);
|
||||||
constexpr Score WeakQueen = S( 56, 15);
|
constexpr Score WeakQueen = S( 57, 19);
|
||||||
|
|
||||||
|
|
||||||
#undef S
|
#undef S
|
||||||
@@ -282,8 +385,8 @@ namespace {
|
|||||||
attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
|
attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
|
||||||
|
|
||||||
// Init our king safety tables
|
// Init our king safety tables
|
||||||
Square s = make_square(Utility::clamp(file_of(ksq), FILE_B, FILE_G),
|
Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G),
|
||||||
Utility::clamp(rank_of(ksq), RANK_2, RANK_7));
|
std::clamp(rank_of(ksq), RANK_2, RANK_7));
|
||||||
kingRing[Us] = attacks_bb<KING>(s) | s;
|
kingRing[Us] = attacks_bb<KING>(s) | s;
|
||||||
|
|
||||||
kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
|
kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
|
||||||
@@ -303,15 +406,16 @@ namespace {
|
|||||||
constexpr Direction Down = -pawn_push(Us);
|
constexpr Direction Down = -pawn_push(Us);
|
||||||
constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
|
constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
|
||||||
: Rank5BB | Rank4BB | Rank3BB);
|
: Rank5BB | Rank4BB | Rank3BB);
|
||||||
const Square* pl = pos.squares<Pt>(Us);
|
Bitboard b1 = pos.pieces(Us, Pt);
|
||||||
|
|
||||||
Bitboard b, bb;
|
Bitboard b, bb;
|
||||||
Score score = SCORE_ZERO;
|
Score score = SCORE_ZERO;
|
||||||
|
|
||||||
attackedBy[Us][Pt] = 0;
|
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
|
// Find attacked squares, including x-ray attacks for bishops and rooks
|
||||||
b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
|
b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
|
||||||
: Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
|
: Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
|
||||||
@@ -338,21 +442,21 @@ namespace {
|
|||||||
score += BishopOnKingRing;
|
score += BishopOnKingRing;
|
||||||
|
|
||||||
int mob = popcount(b & mobilityArea[Us]);
|
int mob = popcount(b & mobilityArea[Us]);
|
||||||
|
|
||||||
mobility[Us] += MobilityBonus[Pt - 2][mob];
|
mobility[Us] += MobilityBonus[Pt - 2][mob];
|
||||||
|
|
||||||
if (Pt == BISHOP || Pt == KNIGHT)
|
if (Pt == BISHOP || Pt == KNIGHT)
|
||||||
{
|
{
|
||||||
// Bonus if the piece is on an outpost square or can reach one
|
// 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] & ~pe->pawn_attacks_span(Them);
|
bb = OutpostRanks & (attackedBy[Us][PAWN] | shift<Down>(pos.pieces(PAWN)))
|
||||||
|
& ~pe->pawn_attacks_span(Them);
|
||||||
Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN);
|
Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN);
|
||||||
|
|
||||||
if ( Pt == KNIGHT
|
if ( Pt == KNIGHT
|
||||||
&& bb & s & ~CenterFiles // on a side outpost
|
&& bb & s & ~CenterFiles // on a side outpost
|
||||||
&& !(b & targets) // no relevant attacks
|
&& !(b & targets) // no relevant attacks
|
||||||
&& (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide))))
|
&& (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide))))
|
||||||
score += BadOutpost;
|
score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide));
|
||||||
else if (bb & s)
|
else if (bb & s)
|
||||||
score += Outpost[Pt == BISHOP];
|
score += Outpost[Pt == BISHOP];
|
||||||
else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
|
else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
|
||||||
@@ -365,14 +469,14 @@ namespace {
|
|||||||
// Penalty if the piece is far from the king
|
// Penalty if the piece is far from the king
|
||||||
score -= KingProtector[Pt == BISHOP] * distance(pos.square<KING>(Us), s);
|
score -= KingProtector[Pt == BISHOP] * distance(pos.square<KING>(Us), s);
|
||||||
|
|
||||||
if (Pt == BISHOP)
|
if constexpr (Pt == BISHOP)
|
||||||
{
|
{
|
||||||
// Penalty according to the number of our pawns on the same color square as the
|
// 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
|
// bishop, bigger when the center files are blocked with pawns and smaller
|
||||||
// when the bishop is outside the pawn chain.
|
// when the bishop is outside the pawn chain.
|
||||||
Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
|
Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
|
||||||
|
|
||||||
score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s)
|
score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s)
|
||||||
* (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles));
|
* (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles));
|
||||||
|
|
||||||
// Penalty for all enemy pawns x-rayed
|
// Penalty for all enemy pawns x-rayed
|
||||||
@@ -390,45 +494,48 @@ namespace {
|
|||||||
{
|
{
|
||||||
Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST);
|
Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST);
|
||||||
if (pos.piece_on(s + d) == make_piece(Us, PAWN))
|
if (pos.piece_on(s + d) == make_piece(Us, PAWN))
|
||||||
score -= !pos.empty(s + d + pawn_push(Us)) ? CorneredBishop * 4
|
score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop)
|
||||||
: pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2
|
: 3 * make_score(CorneredBishop, CorneredBishop);
|
||||||
: CorneredBishop;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Pt == ROOK)
|
if constexpr (Pt == ROOK)
|
||||||
{
|
{
|
||||||
// Bonus for rook on the same file as a queen
|
// Bonuses for rook on a (semi-)open or closed file
|
||||||
if (file_bb(s) & pos.pieces(QUEEN))
|
|
||||||
score += RookOnQueenFile;
|
|
||||||
|
|
||||||
// Bonus for rook on an open or semi-open file
|
|
||||||
if (pos.is_on_semiopen_file(Us, s))
|
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<KING>(Us));
|
score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)];
|
||||||
if ((kf < FILE_E) == (file_of(s) < kf))
|
}
|
||||||
score -= TrappedRook * (1 + !pos.castling_rights(Us));
|
else
|
||||||
|
{
|
||||||
|
// If our pawn on this file is blocked, increase penalty
|
||||||
|
if ( pos.pieces(Us, PAWN)
|
||||||
|
& shift<Down>(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<KING>(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
|
// Penalty if any relative pin or discovered attack against the queen
|
||||||
Bitboard queenPinners;
|
Bitboard queenPinners;
|
||||||
if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
|
if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
|
||||||
score -= WeakQueen;
|
score -= WeakQueen;
|
||||||
|
|
||||||
// Bonus for queen on weak square in enemy camp
|
|
||||||
if (relative_rank(Us, s) > RANK_4 && (~pe->pawn_attacks_span(Them) & s))
|
|
||||||
score += QueenInfiltration;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (T)
|
if constexpr (T)
|
||||||
Trace::add(Pt, Us, score);
|
Trace::add(Pt, Us, score);
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@@ -504,18 +611,18 @@ namespace {
|
|||||||
int kingFlankAttack = popcount(b1) + popcount(b2);
|
int kingFlankAttack = popcount(b1) + popcount(b2);
|
||||||
int kingFlankDefense = popcount(b3);
|
int kingFlankDefense = popcount(b3);
|
||||||
|
|
||||||
kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them]
|
kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo)
|
||||||
+ 185 * popcount(kingRing[Us] & weak)
|
+ 183 * popcount(kingRing[Us] & weak) // (~15 Elo)
|
||||||
+ 148 * popcount(unsafeChecks)
|
+ 148 * popcount(unsafeChecks) // (~4 Elo)
|
||||||
+ 98 * popcount(pos.blockers_for_king(Us))
|
+ 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo)
|
||||||
+ 69 * kingAttacksCount[Them]
|
+ 69 * kingAttacksCount[Them] // (~0.5 Elo)
|
||||||
+ 3 * kingFlankAttack * kingFlankAttack / 8
|
+ 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo)
|
||||||
+ mg_value(mobility[Them] - mobility[Us])
|
+ mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo)
|
||||||
- 873 * !pos.count<QUEEN>(Them)
|
- 873 * !pos.count<QUEEN>(Them) // (~24 Elo)
|
||||||
- 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING])
|
- 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo)
|
||||||
- 6 * mg_value(score) / 8
|
- 6 * mg_value(score) / 8 // (~8 Elo)
|
||||||
- 4 * kingFlankDefense
|
- 4 * kingFlankDefense // (~5 Elo)
|
||||||
+ 37;
|
+ 37; // (~0.5 Elo)
|
||||||
|
|
||||||
// Transform the kingDanger units into a Score, and subtract it from the evaluation
|
// Transform the kingDanger units into a Score, and subtract it from the evaluation
|
||||||
if (kingDanger > 100)
|
if (kingDanger > 100)
|
||||||
@@ -528,7 +635,7 @@ namespace {
|
|||||||
// Penalty if king flank is under attack, potentially moving toward the king
|
// Penalty if king flank is under attack, potentially moving toward the king
|
||||||
score -= FlankAttacks * kingFlankAttack;
|
score -= FlankAttacks * kingFlankAttack;
|
||||||
|
|
||||||
if (T)
|
if constexpr (T)
|
||||||
Trace::add(KING, Us, score);
|
Trace::add(KING, Us, score);
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@@ -567,11 +674,11 @@ namespace {
|
|||||||
{
|
{
|
||||||
b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]);
|
b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]);
|
||||||
while (b)
|
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];
|
b = weak & attackedBy[Us][ROOK];
|
||||||
while (b)
|
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])
|
if (weak & attackedBy[Us][KING])
|
||||||
score += ThreatByKing;
|
score += ThreatByKing;
|
||||||
@@ -629,7 +736,7 @@ namespace {
|
|||||||
score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance);
|
score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (T)
|
if constexpr (T)
|
||||||
Trace::add(THREAT, Us, score);
|
Trace::add(THREAT, Us, score);
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@@ -669,7 +776,7 @@ namespace {
|
|||||||
|
|
||||||
while (b)
|
while (b)
|
||||||
{
|
{
|
||||||
Square s = pop_lsb(&b);
|
Square s = pop_lsb(b);
|
||||||
|
|
||||||
assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
|
assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
|
||||||
|
|
||||||
@@ -683,8 +790,8 @@ namespace {
|
|||||||
Square blockSq = s + Up;
|
Square blockSq = s + Up;
|
||||||
|
|
||||||
// Adjust bonus based on the king's proximity
|
// Adjust bonus based on the king's proximity
|
||||||
bonus += make_score(0, ( (king_proximity(Them, blockSq) * 19) / 4
|
bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4
|
||||||
- king_proximity(Us, blockSq) * 2) * w);
|
- king_proximity(Us, blockSq) * 2) * w);
|
||||||
|
|
||||||
// If blockSq is not the queening square then consider also a second push
|
// If blockSq is not the queening square then consider also a second push
|
||||||
if (r != RANK_7)
|
if (r != RANK_7)
|
||||||
@@ -699,14 +806,16 @@ namespace {
|
|||||||
bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN);
|
bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN);
|
||||||
|
|
||||||
if (!(pos.pieces(Them) & bb))
|
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
|
// 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.
|
// and even smaller bonus if it is attacked but block square is not.
|
||||||
int k = !unsafeSquares ? 35 :
|
int k = !unsafeSquares ? 36 :
|
||||||
!(unsafeSquares & squaresToQueen) ? 20 :
|
!(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 :
|
||||||
!(unsafeSquares & blockSq) ? 9 :
|
!(unsafeSquares & squaresToQueen) ? 17 :
|
||||||
|
!(unsafeSquares & blockSq) ? 7 :
|
||||||
0 ;
|
0 ;
|
||||||
|
|
||||||
// Assign a larger bonus if the block square is defended
|
// Assign a larger bonus if the block square is defended
|
||||||
@@ -720,7 +829,7 @@ namespace {
|
|||||||
score += bonus - PassedFile * edge_distance(file_of(s));
|
score += bonus - PassedFile * edge_distance(file_of(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (T)
|
if constexpr (T)
|
||||||
Trace::add(PASSED, Us, score);
|
Trace::add(PASSED, Us, score);
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@@ -728,7 +837,7 @@ namespace {
|
|||||||
|
|
||||||
|
|
||||||
// Evaluation::space() computes a space evaluation for a given side, aiming to improve game
|
// Evaluation::space() computes a space evaluation for a given side, aiming to improve game
|
||||||
// play in the opening. It is based on the number of safe squares on the 4 central files
|
// play in the opening. It is based on the number of safe squares on the four central files
|
||||||
// on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
|
// on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
|
||||||
// Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
|
// Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
|
||||||
|
|
||||||
@@ -755,11 +864,13 @@ namespace {
|
|||||||
behind |= shift<Down>(behind);
|
behind |= shift<Down>(behind);
|
||||||
behind |= shift<Down+Down>(behind);
|
behind |= shift<Down+Down>(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 bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
|
||||||
int weight = pos.count<ALL_PIECES>(Us) - 3 + std::min(pe->blocked_count(), 9);
|
int weight = pos.count<ALL_PIECES>(Us) - 3 + std::min(pe->blocked_count(), 9);
|
||||||
Score score = make_score(bonus * weight * weight / 16, 0);
|
Score score = make_score(bonus * weight * weight / 16, 0);
|
||||||
|
|
||||||
if (T)
|
if constexpr (T)
|
||||||
Trace::add(SPACE, Us, score);
|
Trace::add(SPACE, Us, score);
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@@ -774,7 +885,7 @@ namespace {
|
|||||||
Value Evaluation<T>::winnable(Score score) const {
|
Value Evaluation<T>::winnable(Score score) const {
|
||||||
|
|
||||||
int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
|
int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
|
||||||
- distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
|
+ int(rank_of(pos.square<KING>(WHITE)) - rank_of(pos.square<KING>(BLACK)));
|
||||||
|
|
||||||
bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
|
bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
|
||||||
&& (pos.pieces(PAWN) & KingSide);
|
&& (pos.pieces(PAWN) & KingSide);
|
||||||
@@ -801,7 +912,7 @@ namespace {
|
|||||||
// Now apply the bonus: note that we find the attacking side by extracting the
|
// Now apply the bonus: note that we find the attacking side by extracting the
|
||||||
// sign of the midgame or endgame values, and that we carefully cap the bonus
|
// sign of the midgame or endgame values, and that we carefully cap the bonus
|
||||||
// so that the midgame and endgame scores do not change sign after the bonus.
|
// so that the midgame and endgame scores do not change sign after the bonus.
|
||||||
int u = ((mg > 0) - (mg < 0)) * Utility::clamp(complexity + 50, -abs(mg), 0);
|
int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0);
|
||||||
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
|
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
|
||||||
|
|
||||||
mg += u;
|
mg += u;
|
||||||
@@ -811,28 +922,42 @@ namespace {
|
|||||||
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
|
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
|
||||||
int sf = me->scale_factor(pos, strongSide);
|
int sf = me->scale_factor(pos, strongSide);
|
||||||
|
|
||||||
// If scale factor is not already specific, scale down via general heuristics
|
// If scale factor is not already specific, scale up/down via general heuristics
|
||||||
if (sf == SCALE_FACTOR_NORMAL)
|
if (sf == SCALE_FACTOR_NORMAL)
|
||||||
{
|
{
|
||||||
if (pos.opposite_bishops())
|
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
|
if ( pos.non_pawn_material(WHITE) == BishopValueMg
|
||||||
&& pos.non_pawn_material(BLACK) == BishopValueMg)
|
&& pos.non_pawn_material(BLACK) == BishopValueMg)
|
||||||
sf = 18 + 4 * popcount(pe->passed_pawns(strongSide));
|
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
|
else
|
||||||
sf = 22 + 3 * pos.count<ALL_PIECES>(strongSide);
|
sf = 22 + 3 * pos.count<ALL_PIECES>(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
|
else if ( pos.non_pawn_material(WHITE) == RookValueMg
|
||||||
&& pos.non_pawn_material(BLACK) == RookValueMg
|
&& pos.non_pawn_material(BLACK) == RookValueMg
|
||||||
&& pos.count<PAWN>(strongSide) - pos.count<PAWN>(~strongSide) <= 1
|
&& pos.count<PAWN>(strongSide) - pos.count<PAWN>(~strongSide) <= 1
|
||||||
&& bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN))
|
&& bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN))
|
||||||
&& (attacks_bb<KING>(pos.square<KING>(~strongSide)) & pos.pieces(~strongSide, PAWN)))
|
&& (attacks_bb<KING>(pos.square<KING>(~strongSide)) & pos.pieces(~strongSide, PAWN)))
|
||||||
sf = 36;
|
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<QUEEN>() == 1)
|
else if (pos.count<QUEEN>() == 1)
|
||||||
sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
|
sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
|
||||||
: pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE));
|
: pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(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
|
else
|
||||||
sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide));
|
sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide)) - 4 * !pawnsOnBothFlanks;
|
||||||
|
|
||||||
|
// Reduce scale factor in case of pawns being on a single flank
|
||||||
|
sf -= 4 * !pawnsOnBothFlanks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate between the middlegame and (scaled by 'sf') endgame score
|
// Interpolate between the middlegame and (scaled by 'sf') endgame score
|
||||||
@@ -840,7 +965,7 @@ namespace {
|
|||||||
+ eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL;
|
+ eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL;
|
||||||
v /= PHASE_MIDGAME;
|
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(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));
|
Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL));
|
||||||
@@ -870,7 +995,7 @@ namespace {
|
|||||||
// Initialize score by reading the incrementally updated scores included in
|
// Initialize score by reading the incrementally updated scores included in
|
||||||
// the position object (material + piece square tables) and the material
|
// the position object (material + piece square tables) and the material
|
||||||
// imbalance. Score is computed internally from the white point of view.
|
// imbalance. Score is computed internally from the white point of view.
|
||||||
Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->contempt;
|
Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->trend;
|
||||||
|
|
||||||
// Probe the pawn hash table
|
// Probe the pawn hash table
|
||||||
pe = Pawns::probe(pos);
|
pe = Pawns::probe(pos);
|
||||||
@@ -878,7 +1003,9 @@ namespace {
|
|||||||
|
|
||||||
// Early exit if score is high
|
// Early exit if score is high
|
||||||
auto lazy_skip = [&](Value lazyThreshold) {
|
auto lazy_skip = [&](Value lazyThreshold) {
|
||||||
return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64;
|
return abs(mg_value(score) + eg_value(score)) > lazyThreshold
|
||||||
|
+ std::abs(pos.this_thread()->bestValue) * 5 / 4
|
||||||
|
+ pos.non_pawn_material() / 32;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (lazy_skip(LazyThreshold1))
|
if (lazy_skip(LazyThreshold1))
|
||||||
@@ -912,7 +1039,7 @@ make_v:
|
|||||||
Value v = winnable(score);
|
Value v = winnable(score);
|
||||||
|
|
||||||
// In case of tracing add all remaining individual evaluation terms
|
// In case of tracing add all remaining individual evaluation terms
|
||||||
if (T)
|
if constexpr (T)
|
||||||
{
|
{
|
||||||
Trace::add(MATERIAL, pos.psq_score());
|
Trace::add(MATERIAL, pos.psq_score());
|
||||||
Trace::add(IMBALANCE, me->imbalance());
|
Trace::add(IMBALANCE, me->imbalance());
|
||||||
@@ -924,15 +1051,44 @@ make_v:
|
|||||||
v = (v / 16) * 16;
|
v = (v / 16) * 16;
|
||||||
|
|
||||||
// Side to move point of view
|
// Side to move point of view
|
||||||
v = (pos.side_to_move() == WHITE ? v : -v) + Tempo;
|
v = (pos.side_to_move() == WHITE ? v : -v);
|
||||||
|
|
||||||
// Damp down the evaluation linearly when shuffling
|
|
||||||
v = v * (100 - pos.rule50_count()) / 100;
|
|
||||||
|
|
||||||
return 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 -= CorneredBishop;
|
||||||
|
|
||||||
|
if ( pos.piece_on(SQ_H1) == W_BISHOP
|
||||||
|
&& pos.piece_on(SQ_G2) == W_PAWN)
|
||||||
|
correction -= CorneredBishop;
|
||||||
|
|
||||||
|
if ( pos.piece_on(SQ_A8) == B_BISHOP
|
||||||
|
&& pos.piece_on(SQ_B7) == B_PAWN)
|
||||||
|
correction += CorneredBishop;
|
||||||
|
|
||||||
|
if ( pos.piece_on(SQ_H8) == B_BISHOP
|
||||||
|
&& pos.piece_on(SQ_G7) == B_PAWN)
|
||||||
|
correction += CorneredBishop;
|
||||||
|
|
||||||
|
return pos.side_to_move() == WHITE ? Value(3 * correction)
|
||||||
|
: -Value(3 * correction);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Eval
|
||||||
|
|
||||||
|
|
||||||
/// evaluate() is the evaluator for the outer world. It returns a static
|
/// evaluate() is the evaluator for the outer world. It returns a static
|
||||||
@@ -940,10 +1096,56 @@ make_v:
|
|||||||
|
|
||||||
Value Eval::evaluate(const Position& pos) {
|
Value Eval::evaluate(const Position& pos) {
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
pos.this_thread()->on_eval();
|
||||||
return NNUE::evaluate(pos);
|
|
||||||
else
|
Value v;
|
||||||
return Evaluation<NO_TRACE>(pos).value();
|
|
||||||
|
if (NNUE::useNNUE == NNUE::UseNNUEMode::Pure) {
|
||||||
|
v = NNUE::evaluate(pos);
|
||||||
|
|
||||||
|
// Guarantee evaluation does not hit the tablebase range
|
||||||
|
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical,
|
||||||
|
// but we switch to NNUE during long shuffling or with high material on the board.
|
||||||
|
bool useClassical = (pos.this_thread()->depth > 9 || pos.count<ALL_PIECES>() > 7) &&
|
||||||
|
abs(eg_value(pos.psq_score())) * 5 > (856 + pos.non_pawn_material() / 64) * (10 + pos.rule50_count());
|
||||||
|
|
||||||
|
// Deciding between classical and NNUE eval (~10 Elo): for high PSQ imbalance we use classical,
|
||||||
|
// but we switch to NNUE during long shuffling or with high material on the board.
|
||||||
|
if (NNUE::useNNUE == NNUE::UseNNUEMode::False || useClassical)
|
||||||
|
{
|
||||||
|
v = Evaluation<NO_TRACE>(pos).value(); // classical
|
||||||
|
useClassical = abs(v) >= 297;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If result of a classical evaluation is much lower than threshold fall back to NNUE
|
||||||
|
if (NNUE::useNNUE != NNUE::UseNNUEMode::False && !useClassical)
|
||||||
|
{
|
||||||
|
Value nnue = NNUE::evaluate(pos, true); // NNUE
|
||||||
|
int scale = 1080 + 110 * pos.non_pawn_material() / 5120;
|
||||||
|
Color stm = pos.side_to_move();
|
||||||
|
Value optimism = pos.this_thread()->optimism[stm];
|
||||||
|
Value psq = (stm == WHITE ? 1 : -1) * eg_value(pos.psq_score());
|
||||||
|
int complexity = (278 * abs(nnue - psq)) / 256;
|
||||||
|
|
||||||
|
optimism = optimism * (251 + complexity) / 256;
|
||||||
|
v = (nnue * scale + optimism * (scale - 852)) / 1024;
|
||||||
|
|
||||||
|
if (pos.is_chess960())
|
||||||
|
v += fix_FRC(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Damp down the evaluation linearly when shuffling
|
||||||
|
v = v * (195 - pos.rule50_count()) / 211;
|
||||||
|
|
||||||
|
// Guarantee evaluation does not hit the tablebase range
|
||||||
|
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||||
|
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// trace() is like evaluate(), but instead of returning a value, it returns
|
/// trace() is like evaluate(), but instead of returning a value, it returns
|
||||||
@@ -951,7 +1153,7 @@ Value Eval::evaluate(const Position& pos) {
|
|||||||
/// descriptions and values of each evaluation term. Useful for debugging.
|
/// descriptions and values of each evaluation term. Useful for debugging.
|
||||||
/// Trace scores are from white's point of view
|
/// Trace scores are from white's point of view
|
||||||
|
|
||||||
std::string Eval::trace(const Position& pos) {
|
std::string Eval::trace(Position& pos) {
|
||||||
|
|
||||||
if (pos.checkers())
|
if (pos.checkers())
|
||||||
return "Final evaluation: none (in check)";
|
return "Final evaluation: none (in check)";
|
||||||
@@ -961,42 +1163,62 @@ std::string Eval::trace(const Position& pos) {
|
|||||||
|
|
||||||
Value v;
|
Value v;
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
std::memset(scores, 0, sizeof(scores));
|
||||||
{
|
|
||||||
v = NNUE::evaluate(pos);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::memset(scores, 0, sizeof(scores));
|
|
||||||
|
|
||||||
pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt
|
// Reset any global variable used in eval
|
||||||
|
pos.this_thread()->depth = 0;
|
||||||
|
pos.this_thread()->trend = SCORE_ZERO;
|
||||||
|
pos.this_thread()->bestValue = VALUE_ZERO;
|
||||||
|
pos.this_thread()->optimism[WHITE] = VALUE_ZERO;
|
||||||
|
pos.this_thread()->optimism[BLACK] = VALUE_ZERO;
|
||||||
|
|
||||||
v = Evaluation<TRACE>(pos).value();
|
v = Evaluation<TRACE>(pos).value();
|
||||||
|
|
||||||
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
|
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
|
||||||
<< " Term | White | Black | Total \n"
|
<< " Contributing terms for the classical eval:\n"
|
||||||
<< " | MG EG | MG EG | MG EG \n"
|
<< "+------------+-------------+-------------+-------------+\n"
|
||||||
<< " ------------+-------------+-------------+------------\n"
|
<< "| Term | White | Black | Total |\n"
|
||||||
<< " Material | " << Term(MATERIAL)
|
<< "| | MG EG | MG EG | MG EG |\n"
|
||||||
<< " Imbalance | " << Term(IMBALANCE)
|
<< "+------------+-------------+-------------+-------------+\n"
|
||||||
<< " Pawns | " << Term(PAWN)
|
<< "| Material | " << Term(MATERIAL)
|
||||||
<< " Knights | " << Term(KNIGHT)
|
<< "| Imbalance | " << Term(IMBALANCE)
|
||||||
<< " Bishops | " << Term(BISHOP)
|
<< "| Pawns | " << Term(PAWN)
|
||||||
<< " Rooks | " << Term(ROOK)
|
<< "| Knights | " << Term(KNIGHT)
|
||||||
<< " Queens | " << Term(QUEEN)
|
<< "| Bishops | " << Term(BISHOP)
|
||||||
<< " Mobility | " << Term(MOBILITY)
|
<< "| Rooks | " << Term(ROOK)
|
||||||
<< " King safety | " << Term(KING)
|
<< "| Queens | " << Term(QUEEN)
|
||||||
<< " Threats | " << Term(THREAT)
|
<< "| Mobility | " << Term(MOBILITY)
|
||||||
<< " Passed | " << Term(PASSED)
|
<< "|King safety | " << Term(KING)
|
||||||
<< " Space | " << Term(SPACE)
|
<< "| Threats | " << Term(THREAT)
|
||||||
<< " Winnable | " << Term(WINNABLE)
|
<< "| Passed | " << Term(PASSED)
|
||||||
<< " ------------+-------------+-------------+------------\n"
|
<< "| Space | " << Term(SPACE)
|
||||||
<< " Total | " << Term(TOTAL);
|
<< "| Winnable | " << Term(WINNABLE)
|
||||||
}
|
<< "+------------+-------------+-------------+-------------+\n"
|
||||||
|
<< "| Total | " << Term(TOTAL)
|
||||||
|
<< "+------------+-------------+-------------+-------------+\n";
|
||||||
|
|
||||||
|
if (NNUE::useNNUE != NNUE::UseNNUEMode::False)
|
||||||
|
ss << '\n' << NNUE::trace(pos) << '\n';
|
||||||
|
|
||||||
|
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
|
||||||
|
|
||||||
v = pos.side_to_move() == WHITE ? v : -v;
|
v = pos.side_to_move() == WHITE ? v : -v;
|
||||||
|
ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n";
|
||||||
|
if (NNUE::useNNUE != NNUE::UseNNUEMode::False)
|
||||||
|
{
|
||||||
|
v = NNUE::evaluate(pos, false);
|
||||||
|
v = pos.side_to_move() == WHITE ? v : -v;
|
||||||
|
ss << "NNUE evaluation " << to_cp(v) << " (white side)\n";
|
||||||
|
}
|
||||||
|
|
||||||
ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n";
|
v = evaluate(pos);
|
||||||
|
v = pos.side_to_move() == WHITE ? v : -v;
|
||||||
|
ss << "Final evaluation " << to_cp(v) << " (white side)";
|
||||||
|
if (NNUE::useNNUE != NNUE::UseNNUEMode::False)
|
||||||
|
ss << " [with scaled NNUE, hybrid, ...]";
|
||||||
|
ss << "\n";
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+29
-10
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,30 +20,49 @@
|
|||||||
#define EVALUATE_H_INCLUDED
|
#define EVALUATE_H_INCLUDED
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
class Position;
|
class Position;
|
||||||
|
|
||||||
namespace Eval {
|
namespace Eval {
|
||||||
|
|
||||||
std::string trace(const Position& pos);
|
std::string trace(Position& pos);
|
||||||
Value evaluate(const Position& pos);
|
Value evaluate(const Position& pos);
|
||||||
|
|
||||||
extern bool useNNUE;
|
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
|
||||||
extern std::string eval_file_loaded;
|
// for the build process (profile-build and fishtest) to work. Do not change the
|
||||||
void init_NNUE();
|
// name of the macro, as it is used in the Makefile.
|
||||||
void verify_NNUE();
|
#define EvalFileDefaultName "nn-3c0aa92af1da.nnue"
|
||||||
|
|
||||||
namespace NNUE {
|
namespace NNUE {
|
||||||
|
enum struct UseNNUEMode
|
||||||
|
{
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
Pure
|
||||||
|
};
|
||||||
|
|
||||||
Value evaluate(const Position& pos);
|
extern UseNNUEMode useNNUE;
|
||||||
Value compute_eval(const Position& pos);
|
extern std::string currentEvalFileName;
|
||||||
void update_eval(const Position& pos);
|
|
||||||
bool load_eval_file(const std::string& evalFile);
|
std::string trace(Position& pos);
|
||||||
|
Value evaluate(const Position& pos, bool adjusted = false);
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void verify();
|
||||||
|
|
||||||
|
bool load_eval(std::string name, std::istream& stream);
|
||||||
|
bool save_eval(std::ostream& stream);
|
||||||
|
bool save_eval(const std::optional<std::string>& filename);
|
||||||
|
|
||||||
} // namespace NNUE
|
} // namespace NNUE
|
||||||
|
|
||||||
} // namespace Eval
|
} // namespace Eval
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef EVALUATE_H_INCLUDED
|
#endif // #ifndef EVALUATE_H_INCLUDED
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
The file "incbin.h" is free and unencumbered software released into
|
||||||
|
the public domain by Dale Weiler, see:
|
||||||
|
<https://github.com/graphitemaster/incbin>
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
||||||
Executable
+368
@@ -0,0 +1,368 @@
|
|||||||
|
/**
|
||||||
|
* @file incbin.h
|
||||||
|
* @author Dale Weiler
|
||||||
|
* @brief Utility for including binary files
|
||||||
|
*
|
||||||
|
* Facilities for including binary files into the current translation unit and
|
||||||
|
* making use from them externally in other translation units.
|
||||||
|
*/
|
||||||
|
#ifndef INCBIN_HDR
|
||||||
|
#define INCBIN_HDR
|
||||||
|
#include <limits.h>
|
||||||
|
#if defined(__AVX512BW__) || \
|
||||||
|
defined(__AVX512CD__) || \
|
||||||
|
defined(__AVX512DQ__) || \
|
||||||
|
defined(__AVX512ER__) || \
|
||||||
|
defined(__AVX512PF__) || \
|
||||||
|
defined(__AVX512VL__) || \
|
||||||
|
defined(__AVX512F__)
|
||||||
|
# define INCBIN_ALIGNMENT_INDEX 6
|
||||||
|
#elif defined(__AVX__) || \
|
||||||
|
defined(__AVX2__)
|
||||||
|
# define INCBIN_ALIGNMENT_INDEX 5
|
||||||
|
#elif defined(__SSE__) || \
|
||||||
|
defined(__SSE2__) || \
|
||||||
|
defined(__SSE3__) || \
|
||||||
|
defined(__SSSE3__) || \
|
||||||
|
defined(__SSE4_1__) || \
|
||||||
|
defined(__SSE4_2__) || \
|
||||||
|
defined(__neon__)
|
||||||
|
# define INCBIN_ALIGNMENT_INDEX 4
|
||||||
|
#elif ULONG_MAX != 0xffffffffu
|
||||||
|
# define INCBIN_ALIGNMENT_INDEX 3
|
||||||
|
# else
|
||||||
|
# define INCBIN_ALIGNMENT_INDEX 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */
|
||||||
|
#define INCBIN_ALIGN_SHIFT_0 1
|
||||||
|
#define INCBIN_ALIGN_SHIFT_1 2
|
||||||
|
#define INCBIN_ALIGN_SHIFT_2 4
|
||||||
|
#define INCBIN_ALIGN_SHIFT_3 8
|
||||||
|
#define INCBIN_ALIGN_SHIFT_4 16
|
||||||
|
#define INCBIN_ALIGN_SHIFT_5 32
|
||||||
|
#define INCBIN_ALIGN_SHIFT_6 64
|
||||||
|
|
||||||
|
/* Actual alignment value */
|
||||||
|
#define INCBIN_ALIGNMENT \
|
||||||
|
INCBIN_CONCATENATE( \
|
||||||
|
INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \
|
||||||
|
INCBIN_ALIGNMENT_INDEX)
|
||||||
|
|
||||||
|
/* Stringize */
|
||||||
|
#define INCBIN_STR(X) \
|
||||||
|
#X
|
||||||
|
#define INCBIN_STRINGIZE(X) \
|
||||||
|
INCBIN_STR(X)
|
||||||
|
/* Concatenate */
|
||||||
|
#define INCBIN_CAT(X, Y) \
|
||||||
|
X ## Y
|
||||||
|
#define INCBIN_CONCATENATE(X, Y) \
|
||||||
|
INCBIN_CAT(X, Y)
|
||||||
|
/* Deferred macro expansion */
|
||||||
|
#define INCBIN_EVAL(X) \
|
||||||
|
X
|
||||||
|
#define INCBIN_INVOKE(N, ...) \
|
||||||
|
INCBIN_EVAL(N(__VA_ARGS__))
|
||||||
|
|
||||||
|
/* Green Hills uses a different directive for including binary data */
|
||||||
|
#if defined(__ghs__)
|
||||||
|
# if (__ghs_asm == 2)
|
||||||
|
# define INCBIN_MACRO ".file"
|
||||||
|
/* Or consider the ".myrawdata" entry in the ld file */
|
||||||
|
# else
|
||||||
|
# define INCBIN_MACRO "\tINCBIN"
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# define INCBIN_MACRO ".incbin"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
# define INCBIN_ALIGN \
|
||||||
|
__attribute__((aligned(INCBIN_ALIGNMENT)))
|
||||||
|
#else
|
||||||
|
# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__arm__) || /* GNU C and RealView */ \
|
||||||
|
defined(__arm) || /* Diab */ \
|
||||||
|
defined(_ARM) /* ImageCraft */
|
||||||
|
# define INCBIN_ARM
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
/* Utilize .balign where supported */
|
||||||
|
# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
|
||||||
|
# define INCBIN_ALIGN_BYTE ".balign 1\n"
|
||||||
|
#elif defined(INCBIN_ARM)
|
||||||
|
/*
|
||||||
|
* On arm assemblers, the alignment value is calculated as (1 << n) where `n' is
|
||||||
|
* the shift count. This is the value passed to `.align'
|
||||||
|
*/
|
||||||
|
# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n"
|
||||||
|
# define INCBIN_ALIGN_BYTE ".align 0\n"
|
||||||
|
#else
|
||||||
|
/* We assume other inline assembler's treat `.align' as `.balign' */
|
||||||
|
# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
|
||||||
|
# define INCBIN_ALIGN_BYTE ".align 1\n"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* INCBIN_CONST is used by incbin.c generated files */
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
# define INCBIN_EXTERNAL extern "C"
|
||||||
|
# define INCBIN_CONST extern const
|
||||||
|
#else
|
||||||
|
# define INCBIN_EXTERNAL extern
|
||||||
|
# define INCBIN_CONST const
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Optionally override the linker section into which data is emitted.
|
||||||
|
*
|
||||||
|
* @warning If you use this facility, you'll have to deal with platform-specific linker output
|
||||||
|
* section naming on your own
|
||||||
|
*
|
||||||
|
* Overriding the default linker output section, e.g for esp8266/Arduino:
|
||||||
|
* @code
|
||||||
|
* #define INCBIN_OUTPUT_SECTION ".irom.text"
|
||||||
|
* #include "incbin.h"
|
||||||
|
* INCBIN(Foo, "foo.txt");
|
||||||
|
* // Data is emitted into program memory that never gets copied to RAM
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
#if !defined(INCBIN_OUTPUT_SECTION)
|
||||||
|
# if defined(__APPLE__)
|
||||||
|
# define INCBIN_OUTPUT_SECTION ".const_data"
|
||||||
|
# else
|
||||||
|
# define INCBIN_OUTPUT_SECTION ".rodata"
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
/* The directives are different for Apple branded compilers */
|
||||||
|
# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n"
|
||||||
|
# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
|
||||||
|
# define INCBIN_INT ".long "
|
||||||
|
# define INCBIN_MANGLE "_"
|
||||||
|
# define INCBIN_BYTE ".byte "
|
||||||
|
# define INCBIN_TYPE(...)
|
||||||
|
#else
|
||||||
|
# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n"
|
||||||
|
# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
|
||||||
|
# if defined(__ghs__)
|
||||||
|
# define INCBIN_INT ".word "
|
||||||
|
# else
|
||||||
|
# define INCBIN_INT ".int "
|
||||||
|
# endif
|
||||||
|
# if defined(__USER_LABEL_PREFIX__)
|
||||||
|
# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__)
|
||||||
|
# else
|
||||||
|
# define INCBIN_MANGLE ""
|
||||||
|
# endif
|
||||||
|
# if defined(INCBIN_ARM)
|
||||||
|
/* On arm assemblers, `@' is used as a line comment token */
|
||||||
|
# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n"
|
||||||
|
# elif defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
/* Mingw doesn't support this directive either */
|
||||||
|
# define INCBIN_TYPE(NAME)
|
||||||
|
# else
|
||||||
|
/* It's safe to use `@' on other architectures */
|
||||||
|
# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n"
|
||||||
|
# endif
|
||||||
|
# define INCBIN_BYTE ".byte "
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* List of style types used for symbol names */
|
||||||
|
#define INCBIN_STYLE_CAMEL 0
|
||||||
|
#define INCBIN_STYLE_SNAKE 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Specify the prefix to use for symbol names.
|
||||||
|
*
|
||||||
|
* By default this is `g', producing symbols of the form:
|
||||||
|
* @code
|
||||||
|
* #include "incbin.h"
|
||||||
|
* INCBIN(Foo, "foo.txt");
|
||||||
|
*
|
||||||
|
* // Now you have the following symbols:
|
||||||
|
* // const unsigned char gFooData[];
|
||||||
|
* // const unsigned char *const gFooEnd;
|
||||||
|
* // const unsigned int gFooSize;
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* If however you specify a prefix before including: e.g:
|
||||||
|
* @code
|
||||||
|
* #define INCBIN_PREFIX incbin
|
||||||
|
* #include "incbin.h"
|
||||||
|
* INCBIN(Foo, "foo.txt");
|
||||||
|
*
|
||||||
|
* // Now you have the following symbols instead:
|
||||||
|
* // const unsigned char incbinFooData[];
|
||||||
|
* // const unsigned char *const incbinFooEnd;
|
||||||
|
* // const unsigned int incbinFooSize;
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
#if !defined(INCBIN_PREFIX)
|
||||||
|
# define INCBIN_PREFIX g
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Specify the style used for symbol names.
|
||||||
|
*
|
||||||
|
* Possible options are
|
||||||
|
* - INCBIN_STYLE_CAMEL "CamelCase"
|
||||||
|
* - INCBIN_STYLE_SNAKE "snake_case"
|
||||||
|
*
|
||||||
|
* Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form:
|
||||||
|
* @code
|
||||||
|
* #include "incbin.h"
|
||||||
|
* INCBIN(Foo, "foo.txt");
|
||||||
|
*
|
||||||
|
* // Now you have the following symbols:
|
||||||
|
* // const unsigned char <prefix>FooData[];
|
||||||
|
* // const unsigned char *const <prefix>FooEnd;
|
||||||
|
* // const unsigned int <prefix>FooSize;
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* If however you specify a style before including: e.g:
|
||||||
|
* @code
|
||||||
|
* #define INCBIN_STYLE INCBIN_STYLE_SNAKE
|
||||||
|
* #include "incbin.h"
|
||||||
|
* INCBIN(foo, "foo.txt");
|
||||||
|
*
|
||||||
|
* // Now you have the following symbols:
|
||||||
|
* // const unsigned char <prefix>foo_data[];
|
||||||
|
* // const unsigned char *const <prefix>foo_end;
|
||||||
|
* // const unsigned int <prefix>foo_size;
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
#if !defined(INCBIN_STYLE)
|
||||||
|
# define INCBIN_STYLE INCBIN_STYLE_CAMEL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Style lookup tables */
|
||||||
|
#define INCBIN_STYLE_0_DATA Data
|
||||||
|
#define INCBIN_STYLE_0_END End
|
||||||
|
#define INCBIN_STYLE_0_SIZE Size
|
||||||
|
#define INCBIN_STYLE_1_DATA _data
|
||||||
|
#define INCBIN_STYLE_1_END _end
|
||||||
|
#define INCBIN_STYLE_1_SIZE _size
|
||||||
|
|
||||||
|
/* Style lookup: returning identifier */
|
||||||
|
#define INCBIN_STYLE_IDENT(TYPE) \
|
||||||
|
INCBIN_CONCATENATE( \
|
||||||
|
INCBIN_STYLE_, \
|
||||||
|
INCBIN_CONCATENATE( \
|
||||||
|
INCBIN_EVAL(INCBIN_STYLE), \
|
||||||
|
INCBIN_CONCATENATE(_, TYPE)))
|
||||||
|
|
||||||
|
/* Style lookup: returning string literal */
|
||||||
|
#define INCBIN_STYLE_STRING(TYPE) \
|
||||||
|
INCBIN_STRINGIZE( \
|
||||||
|
INCBIN_STYLE_IDENT(TYPE)) \
|
||||||
|
|
||||||
|
/* Generate the global labels by indirectly invoking the macro with our style
|
||||||
|
* type and concatenating the name against them. */
|
||||||
|
#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
|
||||||
|
INCBIN_INVOKE( \
|
||||||
|
INCBIN_GLOBAL, \
|
||||||
|
INCBIN_CONCATENATE( \
|
||||||
|
NAME, \
|
||||||
|
INCBIN_INVOKE( \
|
||||||
|
INCBIN_STYLE_IDENT, \
|
||||||
|
TYPE))) \
|
||||||
|
INCBIN_INVOKE( \
|
||||||
|
INCBIN_TYPE, \
|
||||||
|
INCBIN_CONCATENATE( \
|
||||||
|
NAME, \
|
||||||
|
INCBIN_INVOKE( \
|
||||||
|
INCBIN_STYLE_IDENT, \
|
||||||
|
TYPE)))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Externally reference binary data included in another translation unit.
|
||||||
|
*
|
||||||
|
* Produces three external symbols that reference the binary data included in
|
||||||
|
* another translation unit.
|
||||||
|
*
|
||||||
|
* The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
|
||||||
|
* "Data", as well as "End" and "Size" after. An example is provided below.
|
||||||
|
*
|
||||||
|
* @param NAME The name given for the binary data
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* INCBIN_EXTERN(Foo);
|
||||||
|
*
|
||||||
|
* // Now you have the following symbols:
|
||||||
|
* // extern const unsigned char <prefix>FooData[];
|
||||||
|
* // extern const unsigned char *const <prefix>FooEnd;
|
||||||
|
* // extern const unsigned int <prefix>FooSize;
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
#define INCBIN_EXTERN(NAME) \
|
||||||
|
INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \
|
||||||
|
INCBIN_CONCATENATE( \
|
||||||
|
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
||||||
|
INCBIN_STYLE_IDENT(DATA))[]; \
|
||||||
|
INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \
|
||||||
|
INCBIN_CONCATENATE( \
|
||||||
|
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
||||||
|
INCBIN_STYLE_IDENT(END)); \
|
||||||
|
INCBIN_EXTERNAL const unsigned int \
|
||||||
|
INCBIN_CONCATENATE( \
|
||||||
|
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
||||||
|
INCBIN_STYLE_IDENT(SIZE))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Include a binary file into the current translation unit.
|
||||||
|
*
|
||||||
|
* Includes a binary file into the current translation unit, producing three symbols
|
||||||
|
* for objects that encode the data and size respectively.
|
||||||
|
*
|
||||||
|
* The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
|
||||||
|
* "Data", as well as "End" and "Size" after. An example is provided below.
|
||||||
|
*
|
||||||
|
* @param NAME The name to associate with this binary data (as an identifier.)
|
||||||
|
* @param FILENAME The file to include (as a string literal.)
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* INCBIN(Icon, "icon.png");
|
||||||
|
*
|
||||||
|
* // Now you have the following symbols:
|
||||||
|
* // const unsigned char <prefix>IconData[];
|
||||||
|
* // const unsigned char *const <prefix>IconEnd;
|
||||||
|
* // const unsigned int <prefix>IconSize;
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @warning This must be used in global scope
|
||||||
|
* @warning The identifiers may be different if INCBIN_STYLE is not default
|
||||||
|
*
|
||||||
|
* To externally reference the data included by this in another translation unit
|
||||||
|
* please @see INCBIN_EXTERN.
|
||||||
|
*/
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define INCBIN(NAME, FILENAME) \
|
||||||
|
INCBIN_EXTERN(NAME)
|
||||||
|
#else
|
||||||
|
#define INCBIN(NAME, FILENAME) \
|
||||||
|
__asm__(INCBIN_SECTION \
|
||||||
|
INCBIN_GLOBAL_LABELS(NAME, DATA) \
|
||||||
|
INCBIN_ALIGN_HOST \
|
||||||
|
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \
|
||||||
|
INCBIN_MACRO " \"" FILENAME "\"\n" \
|
||||||
|
INCBIN_GLOBAL_LABELS(NAME, END) \
|
||||||
|
INCBIN_ALIGN_BYTE \
|
||||||
|
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \
|
||||||
|
INCBIN_BYTE "1\n" \
|
||||||
|
INCBIN_GLOBAL_LABELS(NAME, SIZE) \
|
||||||
|
INCBIN_ALIGN_HOST \
|
||||||
|
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \
|
||||||
|
INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \
|
||||||
|
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \
|
||||||
|
INCBIN_ALIGN_HOST \
|
||||||
|
".text\n" \
|
||||||
|
); \
|
||||||
|
INCBIN_EXTERN(NAME)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
+8
-6
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,23 +18,25 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "nnue/evaluate_nnue.h"
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "endgame.h"
|
#include "endgame.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
|
#include "psqt.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
|
#include "syzygy/tbprobe.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
#include "syzygy/tbprobe.h"
|
|
||||||
|
|
||||||
namespace PSQT {
|
using namespace Stockfish;
|
||||||
void init();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
std::cout << engine_info() << std::endl;
|
std::cout << engine_info() << std::endl;
|
||||||
|
|
||||||
|
CommandLine::init(argc, argv);
|
||||||
UCI::init(Options);
|
UCI::init(Options);
|
||||||
Tune::init();
|
Tune::init();
|
||||||
PSQT::init();
|
PSQT::init();
|
||||||
@@ -44,7 +46,7 @@ int main(int argc, char* argv[]) {
|
|||||||
Endgames::init();
|
Endgames::init();
|
||||||
Threads.set(size_t(Options["Threads"]));
|
Threads.set(size_t(Options["Threads"]));
|
||||||
Search::clear(); // After threads are up
|
Search::clear(); // After threads are up
|
||||||
Eval::init_NNUE();
|
Eval::NNUE::init();
|
||||||
|
|
||||||
UCI::loop(argc, argv);
|
UCI::loop(argc, argv);
|
||||||
|
|
||||||
|
|||||||
+33
-24
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,32 +24,39 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
#define S(mg, eg) make_score(mg, eg)
|
||||||
|
|
||||||
// Polynomial material imbalance parameters
|
// Polynomial material imbalance parameters
|
||||||
|
|
||||||
constexpr int QuadraticOurs[][PIECE_TYPE_NB] = {
|
// One Score parameter for each pair (our piece, another of our pieces)
|
||||||
// OUR PIECES
|
constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = {
|
||||||
// pair pawn knight bishop rook queen
|
// OUR PIECE 2
|
||||||
{1438 }, // Bishop pair
|
// bishop pair pawn knight bishop rook queen
|
||||||
{ 40, 38 }, // Pawn
|
{S(1419, 1455) }, // Bishop pair
|
||||||
{ 32, 255, -62 }, // Knight OUR PIECES
|
{S( 101, 28), S( 37, 39) }, // Pawn
|
||||||
{ 0, 104, 4, 0 }, // Bishop
|
{S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1
|
||||||
{ -26, -2, 47, 105, -208 }, // Rook
|
{S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop
|
||||||
{-189, 24, 117, 133, -134, -6 } // Queen
|
{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] = {
|
// One Score parameter for each pair (our piece, their piece)
|
||||||
// THEIR PIECES
|
constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = {
|
||||||
// pair pawn knight bishop rook queen
|
// THEIR PIECE
|
||||||
{ }, // Bishop pair
|
// bishop pair pawn knight bishop rook queen
|
||||||
{ 36, }, // Pawn
|
{ }, // Bishop pair
|
||||||
{ 9, 63, }, // Knight OUR PIECES
|
{S( 33, 30) }, // Pawn
|
||||||
{ 59, 65, 42, }, // Bishop
|
{S( 46, 18), S(106, 84) }, // Knight OUR PIECE
|
||||||
{ 46, 39, 24, -24, }, // Rook
|
{S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop
|
||||||
{ 97, 100, -42, 137, 268, } // Queen
|
{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
|
// Endgame evaluation and scaling functions are accessed directly and not through
|
||||||
// the function maps because they correspond to more than one material hash key.
|
// the function maps because they correspond to more than one material hash key.
|
||||||
Endgame<KXK> EvaluateKXK[] = { Endgame<KXK>(WHITE), Endgame<KXK>(BLACK) };
|
Endgame<KXK> EvaluateKXK[] = { Endgame<KXK>(WHITE), Endgame<KXK>(BLACK) };
|
||||||
@@ -67,7 +74,7 @@ namespace {
|
|||||||
|
|
||||||
bool is_KBPsK(const Position& pos, Color us) {
|
bool is_KBPsK(const Position& pos, Color us) {
|
||||||
return pos.non_pawn_material(us) == BishopValueMg
|
return pos.non_pawn_material(us) == BishopValueMg
|
||||||
&& pos.count<PAWN >(us) >= 1;
|
&& pos.count<PAWN>(us) >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_KQKRPs(const Position& pos, Color us) {
|
bool is_KQKRPs(const Position& pos, Color us) {
|
||||||
@@ -82,11 +89,11 @@ namespace {
|
|||||||
/// piece type for both colors.
|
/// piece type for both colors.
|
||||||
|
|
||||||
template<Color Us>
|
template<Color Us>
|
||||||
int imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
|
Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
|
||||||
|
|
||||||
constexpr Color Them = ~Us;
|
constexpr Color Them = ~Us;
|
||||||
|
|
||||||
int bonus = 0;
|
Score bonus = SCORE_ZERO;
|
||||||
|
|
||||||
// Second-degree polynomial material imbalance, by Tord Romstad
|
// Second-degree polynomial material imbalance, by Tord Romstad
|
||||||
for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
|
for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
|
||||||
@@ -130,7 +137,7 @@ Entry* probe(const Position& pos) {
|
|||||||
|
|
||||||
Value npm_w = pos.non_pawn_material(WHITE);
|
Value npm_w = pos.non_pawn_material(WHITE);
|
||||||
Value npm_b = pos.non_pawn_material(BLACK);
|
Value npm_b = pos.non_pawn_material(BLACK);
|
||||||
Value npm = Utility::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
|
Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
|
||||||
|
|
||||||
// Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
|
// Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
|
||||||
e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
|
e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
|
||||||
@@ -213,8 +220,10 @@ Entry* probe(const Position& pos) {
|
|||||||
{ pos.count<BISHOP>(BLACK) > 1, pos.count<PAWN>(BLACK), pos.count<KNIGHT>(BLACK),
|
{ pos.count<BISHOP>(BLACK) > 1, pos.count<PAWN>(BLACK), pos.count<KNIGHT>(BLACK),
|
||||||
pos.count<BISHOP>(BLACK) , pos.count<ROOK>(BLACK), pos.count<QUEEN >(BLACK) } };
|
pos.count<BISHOP>(BLACK) , pos.count<ROOK>(BLACK), pos.count<QUEEN >(BLACK) } };
|
||||||
|
|
||||||
e->value = int16_t((imbalance<WHITE>(pieceCount) - imbalance<BLACK>(pieceCount)) / 16);
|
e->score = (imbalance<WHITE>(pieceCount) - imbalance<BLACK>(pieceCount)) / 16;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Material
|
} // namespace Material
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace Material {
|
namespace Stockfish::Material {
|
||||||
|
|
||||||
/// Material::Entry contains various information about a material configuration.
|
/// Material::Entry contains various information about a material configuration.
|
||||||
/// It contains a material imbalance evaluation, a function pointer to a special
|
/// It contains a material imbalance evaluation, a function pointer to a special
|
||||||
@@ -37,8 +37,8 @@ namespace Material {
|
|||||||
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
|
|
||||||
Score imbalance() const { return make_score(value, value); }
|
Score imbalance() const { return score; }
|
||||||
Phase game_phase() const { return gamePhase; }
|
Phase game_phase() const { return (Phase)gamePhase; }
|
||||||
bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
|
bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
|
||||||
Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
|
Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
|
||||||
|
|
||||||
@@ -57,15 +57,15 @@ struct Entry {
|
|||||||
const EndgameBase<Value>* evaluationFunction;
|
const EndgameBase<Value>* evaluationFunction;
|
||||||
const EndgameBase<ScaleFactor>* scalingFunction[COLOR_NB]; // Could be one for each
|
const EndgameBase<ScaleFactor>* scalingFunction[COLOR_NB]; // Could be one for each
|
||||||
// side (e.g. KPKP, KBPsK)
|
// side (e.g. KPKP, KBPsK)
|
||||||
int16_t value;
|
Score score;
|
||||||
|
int16_t gamePhase;
|
||||||
uint8_t factor[COLOR_NB];
|
uint8_t factor[COLOR_NB];
|
||||||
Phase gamePhase;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef HashTable<Entry, 8192> Table;
|
typedef HashTable<Entry, 8192> Table;
|
||||||
|
|
||||||
Entry* probe(const Position& pos);
|
Entry* probe(const Position& pos);
|
||||||
|
|
||||||
} // namespace Material
|
} // namespace Stockfish::Material
|
||||||
|
|
||||||
#endif // #ifndef MATERIAL_H_INCLUDED
|
#endif // #ifndef MATERIAL_H_INCLUDED
|
||||||
|
|||||||
+204
-77
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -36,6 +36,8 @@ typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP,
|
|||||||
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
|
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
|
||||||
typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY);
|
typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY);
|
||||||
typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
||||||
|
typedef bool(*fun4_t)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
|
||||||
|
typedef WORD(*fun5_t)();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -51,11 +53,20 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__)
|
||||||
|
#define POSIXALIGNEDALLOC
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
|
SynchronizedRegionLogger sync_region_cout(std::cout);
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// Version number. If Version is left empty, then compile date in the format
|
/// Version number. If Version is left empty, then compile date in the format
|
||||||
@@ -103,7 +114,14 @@ public:
|
|||||||
|
|
||||||
static Logger l;
|
static Logger l;
|
||||||
|
|
||||||
if (!fname.empty() && !l.file.is_open())
|
if (l.file.is_open())
|
||||||
|
{
|
||||||
|
cout.rdbuf(l.out.buf);
|
||||||
|
cin.rdbuf(l.in.buf);
|
||||||
|
l.file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fname.empty())
|
||||||
{
|
{
|
||||||
l.file.open(fname, ifstream::out);
|
l.file.open(fname, ifstream::out);
|
||||||
|
|
||||||
@@ -116,23 +134,18 @@ public:
|
|||||||
cin.rdbuf(&l.in);
|
cin.rdbuf(&l.in);
|
||||||
cout.rdbuf(&l.out);
|
cout.rdbuf(&l.out);
|
||||||
}
|
}
|
||||||
else if (fname.empty() && l.file.is_open())
|
|
||||||
{
|
|
||||||
cout.rdbuf(l.out.buf);
|
|
||||||
cin.rdbuf(l.in.buf);
|
|
||||||
l.file.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// engine_info() returns the full name of the current Stockfish version. This
|
/// engine_info() returns the full name of the current Stockfish version. This
|
||||||
/// will be either "Stockfish <Tag> DD-MM-YY" (where DD-MM-YY is the date when
|
/// will be either "Stockfish <Tag> DD-MM-YY" (where DD-MM-YY is the date when
|
||||||
/// the program was compiled) or "Stockfish <Version>", depending on whether
|
/// the program was compiled) or "Stockfish <Version>", depending on whether
|
||||||
/// Version is empty.
|
/// 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");
|
const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
|
||||||
string month, day, year;
|
string month, day, year;
|
||||||
@@ -155,7 +168,7 @@ const string engine_info(bool to_uci) {
|
|||||||
|
|
||||||
/// compiler_info() returns a string trying to describe the compiler we use
|
/// compiler_info() returns a string trying to describe the compiler we use
|
||||||
|
|
||||||
const std::string compiler_info() {
|
std::string compiler_info() {
|
||||||
|
|
||||||
#define stringify2(x) #x
|
#define stringify2(x) #x
|
||||||
#define stringify(x) stringify2(x)
|
#define stringify(x) stringify2(x)
|
||||||
@@ -184,6 +197,18 @@ const std::string compiler_info() {
|
|||||||
compiler += "(version ";
|
compiler += "(version ";
|
||||||
compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
|
compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
|
||||||
compiler += ")";
|
compiler += ")";
|
||||||
|
#elif defined(__e2k__) && defined(__LCC__)
|
||||||
|
#define dot_ver2(n) \
|
||||||
|
compiler += (char)'.'; \
|
||||||
|
compiler += (char)('0' + (n) / 10); \
|
||||||
|
compiler += (char)('0' + (n) % 10);
|
||||||
|
|
||||||
|
compiler += "MCST LCC ";
|
||||||
|
compiler += "(version ";
|
||||||
|
compiler += std::to_string(__LCC__ / 100);
|
||||||
|
dot_ver2(__LCC__ % 100)
|
||||||
|
dot_ver2(__LCC_MINOR__)
|
||||||
|
compiler += ")";
|
||||||
#elif __GNUC__
|
#elif __GNUC__
|
||||||
compiler += "g++ (GNUC) ";
|
compiler += "g++ (GNUC) ";
|
||||||
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
||||||
@@ -214,26 +239,33 @@ const std::string compiler_info() {
|
|||||||
|
|
||||||
compiler += "\nCompilation settings include: ";
|
compiler += "\nCompilation settings include: ";
|
||||||
compiler += (Is64Bit ? " 64bit" : " 32bit");
|
compiler += (Is64Bit ? " 64bit" : " 32bit");
|
||||||
|
#if defined(USE_VNNI)
|
||||||
|
compiler += " VNNI";
|
||||||
|
#endif
|
||||||
#if defined(USE_AVX512)
|
#if defined(USE_AVX512)
|
||||||
compiler += " AVX512";
|
compiler += " AVX512";
|
||||||
#endif
|
#endif
|
||||||
|
compiler += (HasPext ? " BMI2" : "");
|
||||||
#if defined(USE_AVX2)
|
#if defined(USE_AVX2)
|
||||||
compiler += " AVX2";
|
compiler += " AVX2";
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_SSE42)
|
|
||||||
compiler += " SSE42";
|
|
||||||
#endif
|
|
||||||
#if defined(USE_SSE41)
|
#if defined(USE_SSE41)
|
||||||
compiler += " SSE41";
|
compiler += " SSE41";
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_SSSE3)
|
#if defined(USE_SSSE3)
|
||||||
compiler += " SSSE3";
|
compiler += " SSSE3";
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_SSE3)
|
#if defined(USE_SSE2)
|
||||||
compiler += " SSE3";
|
compiler += " SSE2";
|
||||||
#endif
|
#endif
|
||||||
compiler += (HasPext ? " BMI2" : "");
|
compiler += (HasPopCnt ? " POPCNT" : "");
|
||||||
compiler += (HasPopCnt ? " POPCNT" : "");
|
#if defined(USE_MMX)
|
||||||
|
compiler += " MMX";
|
||||||
|
#endif
|
||||||
|
#if defined(USE_NEON)
|
||||||
|
compiler += " NEON";
|
||||||
|
#endif
|
||||||
|
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
compiler += " DEBUG";
|
compiler += " DEBUG";
|
||||||
#endif
|
#endif
|
||||||
@@ -316,13 +348,16 @@ void prefetch(void* addr) {
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Wrappers for systems where the c++17 implementation doesn't guarantee the availability of aligned_alloc.
|
|
||||||
/// Memory allocated with std_aligned_alloc must be freed with std_aligned_free.
|
/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation
|
||||||
///
|
/// does not guarantee the availability of aligned_alloc(). Memory allocated with
|
||||||
|
/// std_aligned_alloc() must be freed with std_aligned_free().
|
||||||
|
|
||||||
void* std_aligned_alloc(size_t alignment, size_t size) {
|
void* std_aligned_alloc(size_t alignment, size_t size) {
|
||||||
#if defined(__APPLE__)
|
|
||||||
return aligned_alloc(alignment, size);
|
#if defined(POSIXALIGNEDALLOC)
|
||||||
|
void *mem;
|
||||||
|
return posix_memalign(&mem, alignment, size) ? nullptr : mem;
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
return _mm_malloc(size, alignment);
|
return _mm_malloc(size, alignment);
|
||||||
#else
|
#else
|
||||||
@@ -331,7 +366,8 @@ void* std_aligned_alloc(size_t alignment, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void std_aligned_free(void* ptr) {
|
void std_aligned_free(void* ptr) {
|
||||||
#if defined(__APPLE__)
|
|
||||||
|
#if defined(POSIXALIGNEDALLOC)
|
||||||
free(ptr);
|
free(ptr);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
_mm_free(ptr);
|
_mm_free(ptr);
|
||||||
@@ -340,25 +376,16 @@ void std_aligned_free(void* ptr) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// aligned_ttmem_alloc() will return suitably aligned memory, and if possible use large pages.
|
/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages.
|
||||||
/// The returned pointer is the aligned one, while the mem argument is the one that needs
|
|
||||||
/// to be passed to free. With c++17 some of this functionality could be simplified.
|
|
||||||
|
|
||||||
#if defined(__linux__) && !defined(__ANDROID__)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
static void* aligned_large_pages_alloc_windows(size_t allocSize) {
|
||||||
|
|
||||||
constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page sizes
|
#if !defined(_WIN64)
|
||||||
size_t size = ((allocSize + alignment - 1) / alignment) * alignment; // multiple of alignment
|
(void)allocSize; // suppress unused-parameter compiler warning
|
||||||
if (posix_memalign(&mem, alignment, size))
|
return nullptr;
|
||||||
mem = nullptr;
|
#else
|
||||||
madvise(mem, allocSize, MADV_HUGEPAGE);
|
|
||||||
return mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(_WIN64)
|
|
||||||
|
|
||||||
static void* aligned_ttmem_alloc_large_pages(size_t allocSize) {
|
|
||||||
|
|
||||||
HANDLE hProcessToken { };
|
HANDLE hProcessToken { };
|
||||||
LUID luid { };
|
LUID luid { };
|
||||||
@@ -401,25 +428,14 @@ static void* aligned_ttmem_alloc_large_pages(size_t allocSize) {
|
|||||||
CloseHandle(hProcessToken);
|
CloseHandle(hProcessToken);
|
||||||
|
|
||||||
return mem;
|
return mem;
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
void* aligned_large_pages_alloc(size_t allocSize) {
|
||||||
|
|
||||||
static bool firstCall = true;
|
|
||||||
|
|
||||||
// Try to allocate large pages
|
// Try to allocate large pages
|
||||||
mem = aligned_ttmem_alloc_large_pages(allocSize);
|
void* mem = aligned_large_pages_alloc_windows(allocSize);
|
||||||
|
|
||||||
// Suppress info strings on the first call. The first call occurs before 'uci'
|
|
||||||
// is received and in that case this output confuses some GUIs.
|
|
||||||
if (!firstCall)
|
|
||||||
{
|
|
||||||
if (mem)
|
|
||||||
sync_cout << "info string Hash table allocation: Windows large pages used." << sync_endl;
|
|
||||||
else
|
|
||||||
sync_cout << "info string Hash table allocation: Windows large pages not used." << sync_endl;
|
|
||||||
}
|
|
||||||
firstCall = false;
|
|
||||||
|
|
||||||
// Fall back to regular, page aligned, allocation if necessary
|
// Fall back to regular, page aligned, allocation if necessary
|
||||||
if (!mem)
|
if (!mem)
|
||||||
@@ -430,37 +446,46 @@ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
void* aligned_large_pages_alloc(size_t allocSize) {
|
||||||
|
|
||||||
constexpr size_t alignment = 64; // assumed cache line size
|
#if defined(__linux__)
|
||||||
size_t size = allocSize + alignment - 1; // allocate some extra space
|
constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size
|
||||||
mem = malloc(size);
|
#else
|
||||||
void* ret = reinterpret_cast<void*>((uintptr_t(mem) + alignment - 1) & ~uintptr_t(alignment - 1));
|
constexpr size_t alignment = 4096; // assumed small page size
|
||||||
return ret;
|
#endif
|
||||||
|
|
||||||
|
// round up to multiples of alignment
|
||||||
|
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
|
||||||
|
void *mem = std_aligned_alloc(alignment, size);
|
||||||
|
#if defined(MADV_HUGEPAGE)
|
||||||
|
madvise(mem, size, MADV_HUGEPAGE);
|
||||||
|
#endif
|
||||||
|
return mem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/// aligned_ttmem_free() will free the previously allocated ttmem
|
/// aligned_large_pages_free() will free the previously allocated ttmem
|
||||||
|
|
||||||
#if defined(_WIN64)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
void aligned_ttmem_free(void* mem) {
|
void aligned_large_pages_free(void* mem) {
|
||||||
|
|
||||||
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
|
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
|
||||||
{
|
{
|
||||||
DWORD err = GetLastError();
|
DWORD err = GetLastError();
|
||||||
std::cerr << "Failed to free transposition table. Error code: 0x" <<
|
std::cerr << "Failed to free large page memory. Error code: 0x"
|
||||||
std::hex << err << std::dec << std::endl;
|
<< std::hex << err
|
||||||
|
<< std::dec << std::endl;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
void aligned_ttmem_free(void *mem) {
|
void aligned_large_pages_free(void *mem) {
|
||||||
free(mem);
|
std_aligned_free(mem);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -474,11 +499,11 @@ void bindThisThread(size_t) {}
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/// best_group() retrieves logical processor information using Windows specific
|
/// best_node() retrieves logical processor information using Windows specific
|
||||||
/// API and returns the best group id for the thread with index idx. Original
|
/// API and returns the best node id for the thread with index idx. Original
|
||||||
/// code from Texel by Peter Österlund.
|
/// code from Texel by Peter Österlund.
|
||||||
|
|
||||||
int best_group(size_t idx) {
|
int best_node(size_t idx) {
|
||||||
|
|
||||||
int threads = 0;
|
int threads = 0;
|
||||||
int nodes = 0;
|
int nodes = 0;
|
||||||
@@ -492,7 +517,8 @@ int best_group(size_t idx) {
|
|||||||
if (!fun1)
|
if (!fun1)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
// First call to get returnLength. We expect it to fail due to null buffer
|
// First call to GetLogicalProcessorInformationEx() to get returnLength.
|
||||||
|
// We expect the call to fail due to null buffer.
|
||||||
if (fun1(RelationAll, nullptr, &returnLength))
|
if (fun1(RelationAll, nullptr, &returnLength))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@@ -500,7 +526,7 @@ int best_group(size_t idx) {
|
|||||||
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr;
|
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr;
|
||||||
ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength);
|
ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength);
|
||||||
|
|
||||||
// Second call, now we expect to succeed
|
// Second call to GetLogicalProcessorInformationEx(), now we expect to succeed
|
||||||
if (!fun1(RelationAll, buffer, &returnLength))
|
if (!fun1(RelationAll, buffer, &returnLength))
|
||||||
{
|
{
|
||||||
free(buffer);
|
free(buffer);
|
||||||
@@ -550,24 +576,125 @@ int best_group(size_t idx) {
|
|||||||
void bindThisThread(size_t idx) {
|
void bindThisThread(size_t idx) {
|
||||||
|
|
||||||
// Use only local variables to be thread-safe
|
// Use only local variables to be thread-safe
|
||||||
int group = best_group(idx);
|
int node = best_node(idx);
|
||||||
|
|
||||||
if (group == -1)
|
if (node == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Early exit if the needed API are not available at runtime
|
// Early exit if the needed API are not available at runtime
|
||||||
HMODULE k32 = GetModuleHandle("Kernel32.dll");
|
HMODULE k32 = GetModuleHandle("Kernel32.dll");
|
||||||
auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
|
auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
|
||||||
auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
|
auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
|
||||||
|
auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2");
|
||||||
|
auto fun5 = (fun5_t)(void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount");
|
||||||
|
|
||||||
if (!fun2 || !fun3)
|
if (!fun2 || !fun3)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
GROUP_AFFINITY affinity;
|
if (!fun4 || !fun5)
|
||||||
if (fun2(group, &affinity))
|
{
|
||||||
fun3(GetCurrentThread(), &affinity, nullptr);
|
GROUP_AFFINITY affinity;
|
||||||
|
if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx
|
||||||
|
fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If a numa node has more than one processor group, we assume they are
|
||||||
|
// sized equal and we spread threads evenly across the groups.
|
||||||
|
USHORT elements, returnedElements;
|
||||||
|
elements = fun5(); // GetMaximumProcessorGroupCount
|
||||||
|
GROUP_AFFINITY *affinity = (GROUP_AFFINITY*)malloc(elements * sizeof(GROUP_AFFINITY));
|
||||||
|
if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2
|
||||||
|
fun3(GetCurrentThread(), &affinity[idx % returnedElements], nullptr); // SetThreadGroupAffinity
|
||||||
|
free(affinity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace WinProcGroup
|
} // namespace WinProcGroup
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <direct.h>
|
||||||
|
#define GETCWD _getcwd
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#define GETCWD getcwd
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace CommandLine {
|
||||||
|
|
||||||
|
string argv0; // path+name of the executable binary, as given by argv[0]
|
||||||
|
string binaryDirectory; // path of the executable directory
|
||||||
|
string workingDirectory; // path of the working directory
|
||||||
|
|
||||||
|
void init(int argc, char* argv[]) {
|
||||||
|
(void)argc;
|
||||||
|
string pathSeparator;
|
||||||
|
|
||||||
|
// extract the path+name of the executable binary
|
||||||
|
argv0 = argv[0];
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
pathSeparator = "\\";
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
// Under windows argv[0] may not have the extension. Also _get_pgmptr() had
|
||||||
|
// issues in some windows 10 versions, so check returned values carefully.
|
||||||
|
char* pgmptr = nullptr;
|
||||||
|
if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
|
||||||
|
argv0 = pgmptr;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
pathSeparator = "/";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// extract the working directory
|
||||||
|
workingDirectory = "";
|
||||||
|
char buff[40000];
|
||||||
|
char* cwd = GETCWD(buff, 40000);
|
||||||
|
if (cwd)
|
||||||
|
workingDirectory = cwd;
|
||||||
|
|
||||||
|
// extract the binary directory path from argv0
|
||||||
|
binaryDirectory = argv0;
|
||||||
|
size_t pos = binaryDirectory.find_last_of("\\/");
|
||||||
|
if (pos == std::string::npos)
|
||||||
|
binaryDirectory = "." + pathSeparator;
|
||||||
|
else
|
||||||
|
binaryDirectory.resize(pos + 1);
|
||||||
|
|
||||||
|
// pattern replacement: "./" at the start of path is replaced by the working directory
|
||||||
|
if (binaryDirectory.find("." + pathSeparator) == 0)
|
||||||
|
binaryDirectory.replace(0, 1, workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace CommandLine
|
||||||
|
|
||||||
|
// Returns a string that represents the current time. (Used when learning evaluation functions)
|
||||||
|
std::string now_string()
|
||||||
|
{
|
||||||
|
// Using std::ctime(), localtime() gives a warning that MSVC is not secure.
|
||||||
|
// This shouldn't happen in the C++ standard, but...
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
// C4996 : 'ctime' : This function or variable may be unsafe.Consider using ctime_s instead.
|
||||||
|
#pragma warning(disable : 4996)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto tp = std::chrono::system_clock::to_time_t(now);
|
||||||
|
auto result = string(std::ctime(&tp));
|
||||||
|
|
||||||
|
// remove line endings if they are included at the end
|
||||||
|
while (*result.rbegin() == '\n' || (*result.rbegin() == '\r'))
|
||||||
|
result.pop_back();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sleep(int ms)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+582
-11
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,32 +19,56 @@
|
|||||||
#ifndef MISC_H_INCLUDED
|
#ifndef MISC_H_INCLUDED
|
||||||
#define MISC_H_INCLUDED
|
#define MISC_H_INCLUDED
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cctype>
|
||||||
|
#include <sstream>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
const std::string engine_info(bool to_uci = false);
|
namespace Stockfish {
|
||||||
const std::string compiler_info();
|
|
||||||
|
std::string engine_info(bool to_uci = false);
|
||||||
|
std::string compiler_info();
|
||||||
void prefetch(void* addr);
|
void prefetch(void* addr);
|
||||||
void start_logger(const std::string& fname);
|
void start_logger(const std::string& fname);
|
||||||
void* std_aligned_alloc(size_t alignment, size_t size);
|
void* std_aligned_alloc(size_t alignment, size_t size);
|
||||||
void std_aligned_free(void* ptr);
|
void std_aligned_free(void* ptr);
|
||||||
void* aligned_ttmem_alloc(size_t size, void*& mem);
|
void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes
|
||||||
void aligned_ttmem_free(void* mem); // nop if mem == nullptr
|
void aligned_large_pages_free(void* mem); // nop if mem == nullptr
|
||||||
|
|
||||||
void dbg_hit_on(bool b);
|
void dbg_hit_on(bool b);
|
||||||
void dbg_hit_on(bool c, bool b);
|
void dbg_hit_on(bool c, bool b);
|
||||||
void dbg_mean_of(int v);
|
void dbg_mean_of(int v);
|
||||||
void dbg_print();
|
void dbg_print();
|
||||||
|
|
||||||
|
/// Debug macro to write to std::err if NDEBUG flag is set, and do nothing otherwise
|
||||||
|
#if defined(NDEBUG)
|
||||||
|
#define debug 1 && std::cerr
|
||||||
|
#else
|
||||||
|
#define debug 0 && std::cerr
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline void hit_any_key() {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
debug << "Hit any key to continue..." << std::endl << std::flush;
|
||||||
|
system("read"); // on Windows, should be system("pause");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
|
typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
|
||||||
|
|
||||||
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
|
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
|
||||||
|
|
||||||
inline TimePoint now() {
|
inline TimePoint now() {
|
||||||
return std::chrono::duration_cast<std::chrono::milliseconds>
|
return std::chrono::duration_cast<std::chrono::milliseconds>
|
||||||
(std::chrono::steady_clock::now().time_since_epoch()).count();
|
(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||||
@@ -65,15 +89,319 @@ std::ostream& operator<<(std::ostream&, SyncCout);
|
|||||||
#define sync_cout std::cout << IO_LOCK
|
#define sync_cout std::cout << IO_LOCK
|
||||||
#define sync_endl std::endl << IO_UNLOCK
|
#define sync_endl std::endl << IO_UNLOCK
|
||||||
|
|
||||||
namespace Utility {
|
|
||||||
|
|
||||||
/// Clamp a value between lo and hi. Available in c++17.
|
// align_ptr_up() : get the first aligned element of an array.
|
||||||
template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
|
// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes,
|
||||||
return v < lo ? lo : v > hi ? hi : v;
|
// where N is the number of elements in the array.
|
||||||
|
template <uintptr_t Alignment, typename T>
|
||||||
|
T* align_ptr_up(T* ptr)
|
||||||
|
{
|
||||||
|
static_assert(alignof(T) < Alignment);
|
||||||
|
|
||||||
|
const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
|
||||||
|
return reinterpret_cast<T*>(reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// IsLittleEndian : true if and only if the binary is compiled on a little endian machine
|
||||||
|
static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
|
||||||
|
static inline const bool IsLittleEndian = (Le.c[0] == 4);
|
||||||
|
|
||||||
|
|
||||||
|
// RunningAverage : a class to calculate a running average of a series of values.
|
||||||
|
// For efficiency, all computations are done with integers.
|
||||||
|
class RunningAverage {
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Reset the running average to rational value p / q
|
||||||
|
void set(int64_t p, int64_t q)
|
||||||
|
{ average = p * PERIOD * RESOLUTION / q; }
|
||||||
|
|
||||||
|
// Update average with value v
|
||||||
|
void update(int64_t v)
|
||||||
|
{ average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; }
|
||||||
|
|
||||||
|
// Test if average is strictly greater than rational a / b
|
||||||
|
bool is_greater(int64_t a, int64_t b) const
|
||||||
|
{ return b * average > a * (PERIOD * RESOLUTION); }
|
||||||
|
|
||||||
|
int64_t value() const
|
||||||
|
{ return average / (PERIOD * RESOLUTION); }
|
||||||
|
|
||||||
|
private :
|
||||||
|
static constexpr int64_t PERIOD = 4096;
|
||||||
|
static constexpr int64_t RESOLUTION = 1024;
|
||||||
|
int64_t average;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, std::size_t MaxSize>
|
||||||
|
class ValueList {
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::size_t size() const { return size_; }
|
||||||
|
void resize(std::size_t newSize) { size_ = newSize; }
|
||||||
|
void push_back(const T& value) { values_[size_++] = value; }
|
||||||
|
T& operator[](std::size_t index) { return values_[index]; }
|
||||||
|
T* begin() { return values_; }
|
||||||
|
T* end() { return values_ + size_; }
|
||||||
|
const T& operator[](std::size_t index) const { return values_[index]; }
|
||||||
|
const T* begin() const { return values_; }
|
||||||
|
const T* end() const { return values_ + size_; }
|
||||||
|
|
||||||
|
void swap(ValueList& other) {
|
||||||
|
const std::size_t maxSize = std::max(size_, other.size_);
|
||||||
|
for (std::size_t i = 0; i < maxSize; ++i) {
|
||||||
|
std::swap(values_[i], other.values_[i]);
|
||||||
|
}
|
||||||
|
std::swap(size_, other.size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T values_[MaxSize];
|
||||||
|
std::size_t size_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This logger allows printing many parts in a region atomically
|
||||||
|
// but doesn't block the threads trying to append to other regions.
|
||||||
|
// Instead if some region tries to pring while other region holds
|
||||||
|
// the lock the messages are queued to be printed as soon as the
|
||||||
|
// current region releases the lock.
|
||||||
|
struct SynchronizedRegionLogger
|
||||||
|
{
|
||||||
|
using RegionId = std::uint64_t;
|
||||||
|
|
||||||
|
struct Region
|
||||||
|
{
|
||||||
|
friend struct SynchronizedRegionLogger;
|
||||||
|
|
||||||
|
Region() :
|
||||||
|
logger(nullptr), region_id(0), is_held(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Region(const Region&) = delete;
|
||||||
|
Region& operator=(const Region&) = delete;
|
||||||
|
|
||||||
|
Region(Region&& other) :
|
||||||
|
logger(other.logger), region_id(other.region_id), is_held(other.is_held)
|
||||||
|
{
|
||||||
|
other.logger = nullptr;
|
||||||
|
other.is_held = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Region& operator=(Region&& other) {
|
||||||
|
if (is_held && logger != nullptr)
|
||||||
|
{
|
||||||
|
logger->release_region(region_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = other.logger;
|
||||||
|
region_id = other.region_id;
|
||||||
|
is_held = other.is_held;
|
||||||
|
|
||||||
|
other.is_held = false;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Region() { unlock(); }
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
if (is_held) {
|
||||||
|
is_held = false;
|
||||||
|
|
||||||
|
if (logger != nullptr)
|
||||||
|
logger->release_region(region_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Region& operator << (std::ostream&(*pManip)(std::ostream&)) {
|
||||||
|
if (logger != nullptr)
|
||||||
|
logger->write(region_id, pManip);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Region& operator << (const T& value) {
|
||||||
|
if (logger != nullptr)
|
||||||
|
logger->write(region_id, value);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SynchronizedRegionLogger* logger;
|
||||||
|
RegionId region_id;
|
||||||
|
bool is_held;
|
||||||
|
|
||||||
|
Region(SynchronizedRegionLogger& log, RegionId id) :
|
||||||
|
logger(&log), region_id(id), is_held(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct RegionBookkeeping
|
||||||
|
{
|
||||||
|
RegionBookkeeping(RegionId rid) : id(rid), is_held(true) {}
|
||||||
|
|
||||||
|
std::vector<std::string> pending_parts;
|
||||||
|
RegionId id;
|
||||||
|
bool is_held;
|
||||||
|
};
|
||||||
|
|
||||||
|
RegionId init_next_region()
|
||||||
|
{
|
||||||
|
static RegionId next_id = 0;
|
||||||
|
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
|
||||||
|
const auto id = next_id++;
|
||||||
|
regions.emplace_back(id);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(RegionId id, std::ostream&(*pManip)(std::ostream&)) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
|
||||||
|
if (regions.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (id == regions.front().id) {
|
||||||
|
// We can just directly print to the output because
|
||||||
|
// we are at the front of the region queue.
|
||||||
|
out << *pManip;
|
||||||
|
} else {
|
||||||
|
// We have to schedule the print until previous regions are
|
||||||
|
// processed
|
||||||
|
auto* region = find_region_nolock(id);
|
||||||
|
if (region == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << *pManip;
|
||||||
|
region->pending_parts.emplace_back(std::move(ss).str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void write(RegionId id, const T& value) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
|
||||||
|
if (regions.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (id == regions.front().id) {
|
||||||
|
// We can just directly print to the output because
|
||||||
|
// we are at the front of the region queue.
|
||||||
|
out << value;
|
||||||
|
} else {
|
||||||
|
// We have to schedule the print until previous regions are
|
||||||
|
// processed
|
||||||
|
auto* region = find_region_nolock(id);
|
||||||
|
if (region == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << value;
|
||||||
|
region->pending_parts.emplace_back(std::move(ss).str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& out;
|
||||||
|
|
||||||
|
std::deque<RegionBookkeeping> regions;
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
RegionBookkeeping* find_region_nolock(RegionId id) {
|
||||||
|
// Linear search because the amount of concurrent regions should be small.
|
||||||
|
auto it = std::find_if(
|
||||||
|
regions.begin(),
|
||||||
|
regions.end(),
|
||||||
|
[id](const RegionBookkeeping& r) { return r.id == id; });
|
||||||
|
|
||||||
|
if (it == regions.end())
|
||||||
|
return nullptr;
|
||||||
|
else
|
||||||
|
return &*it;
|
||||||
|
}
|
||||||
|
|
||||||
|
void release_region(RegionId id) {
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
|
||||||
|
auto* region = find_region_nolock(id);
|
||||||
|
if (region == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
region->is_held = false;
|
||||||
|
|
||||||
|
process_backlog_nolock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_backlog_nolock()
|
||||||
|
{
|
||||||
|
while(!regions.empty()) {
|
||||||
|
auto& region = regions.front();
|
||||||
|
|
||||||
|
for(auto& part : region.pending_parts) {
|
||||||
|
out << part;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the region is still held then we don't
|
||||||
|
// want to start printing stuff from the next region.
|
||||||
|
if (region.is_held)
|
||||||
|
break;
|
||||||
|
|
||||||
|
regions.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
SynchronizedRegionLogger(std::ostream& s) :
|
||||||
|
out(s)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Region new_region() {
|
||||||
|
const auto id = init_next_region();
|
||||||
|
return Region(*this, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SynchronizedRegionLogger sync_region_cout;
|
||||||
|
|
||||||
|
/// sigmoid(t, x0, y0, C, P, Q) implements a sigmoid-like function using only integers,
|
||||||
|
/// with the following properties:
|
||||||
|
///
|
||||||
|
/// - sigmoid is centered in (x0, y0)
|
||||||
|
/// - sigmoid has amplitude [-P/Q , P/Q] instead of [-1 , +1]
|
||||||
|
/// - limit is (y0 - P/Q) when t tends to -infinity
|
||||||
|
/// - limit is (y0 + P/Q) when t tends to +infinity
|
||||||
|
/// - the slope can be adjusted using C > 0, smaller C giving a steeper sigmoid
|
||||||
|
/// - the slope of the sigmoid when t = x0 is P/(Q*C)
|
||||||
|
/// - sigmoid is increasing with t when P > 0 and Q > 0
|
||||||
|
/// - to get a decreasing sigmoid, change sign of P
|
||||||
|
/// - mean value of the sigmoid is y0
|
||||||
|
///
|
||||||
|
/// Use <https://www.desmos.com/calculator/jhh83sqq92> to draw the sigmoid
|
||||||
|
|
||||||
|
inline int64_t sigmoid(int64_t t, int64_t x0,
|
||||||
|
int64_t y0,
|
||||||
|
int64_t C,
|
||||||
|
int64_t P,
|
||||||
|
int64_t Q)
|
||||||
|
{
|
||||||
|
assert(C > 0);
|
||||||
|
assert(Q != 0);
|
||||||
|
return y0 + P * (t-x0) / (Q * (std::abs(t-x0) + C)) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// xorshift64star Pseudo-Random Number Generator
|
/// xorshift64star Pseudo-Random Number Generator
|
||||||
/// This class is based on original code written and dedicated
|
/// This class is based on original code written and dedicated
|
||||||
/// to the public domain by Sebastiano Vigna (2014).
|
/// to the public domain by Sebastiano Vigna (2014).
|
||||||
@@ -89,6 +417,19 @@ template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi)
|
|||||||
/// For further analysis see
|
/// For further analysis see
|
||||||
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
|
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
|
||||||
|
|
||||||
|
static uint64_t string_hash(const std::string& str)
|
||||||
|
{
|
||||||
|
uint64_t h = 525201411107845655ull;
|
||||||
|
|
||||||
|
for (auto c : str) {
|
||||||
|
h ^= static_cast<uint64_t>(c);
|
||||||
|
h *= 0x5bd1e9955bd1e995ull;
|
||||||
|
h ^= h >> 47;
|
||||||
|
}
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
class PRNG {
|
class PRNG {
|
||||||
|
|
||||||
uint64_t s;
|
uint64_t s;
|
||||||
@@ -100,7 +441,9 @@ class PRNG {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
PRNG() { set_seed_from_time(); }
|
||||||
PRNG(uint64_t seed) : s(seed) { assert(seed); }
|
PRNG(uint64_t seed) : s(seed) { assert(seed); }
|
||||||
|
PRNG(const std::string& seed) { set_seed(seed); }
|
||||||
|
|
||||||
template<typename T> T rand() { return T(rand64()); }
|
template<typename T> T rand() { return T(rand64()); }
|
||||||
|
|
||||||
@@ -108,8 +451,54 @@ public:
|
|||||||
/// Output values only have 1/8th of their bits set on average.
|
/// Output values only have 1/8th of their bits set on average.
|
||||||
template<typename T> T sparse_rand()
|
template<typename T> T sparse_rand()
|
||||||
{ return T(rand64() & rand64() & rand64()); }
|
{ return T(rand64() & rand64() & rand64()); }
|
||||||
|
// Returns a random number from 0 to n-1. (Not uniform distribution, but this is enough in reality)
|
||||||
|
uint64_t rand(uint64_t n) { return rand<uint64_t>() % n; }
|
||||||
|
|
||||||
|
// Return the random seed used internally.
|
||||||
|
uint64_t get_seed() const { return s; }
|
||||||
|
|
||||||
|
void set_seed(uint64_t seed) { s = seed; }
|
||||||
|
|
||||||
|
uint64_t next_random_seed()
|
||||||
|
{
|
||||||
|
uint64_t seed = 0;
|
||||||
|
for(int i = 0; i < 64; ++i)
|
||||||
|
{
|
||||||
|
const auto off = rand64() % 64;
|
||||||
|
seed |= (rand64() & (uint64_t(1) << off)) >> off;
|
||||||
|
seed <<= 1;
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_seed_from_time()
|
||||||
|
{
|
||||||
|
set_seed(std::chrono::system_clock::now().time_since_epoch().count());
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_seed(const std::string& str)
|
||||||
|
{
|
||||||
|
if (str.empty())
|
||||||
|
{
|
||||||
|
set_seed_from_time();
|
||||||
|
}
|
||||||
|
else if (std::all_of(str.begin(), str.end(), [](char c) { return std::isdigit(c);} )) {
|
||||||
|
set_seed(std::stoull(str));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
set_seed(string_hash(str));
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Display a random seed. (For debugging)
|
||||||
|
inline std::ostream& operator<<(std::ostream& os, PRNG& prng)
|
||||||
|
{
|
||||||
|
os << "PRNG::seed = " << std::hex << prng.get_seed() << std::dec;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
||||||
#if defined(__GNUC__) && defined(IS_64BIT)
|
#if defined(__GNUC__) && defined(IS_64BIT)
|
||||||
__extension__ typedef unsigned __int128 uint128;
|
__extension__ typedef unsigned __int128 uint128;
|
||||||
@@ -124,6 +513,74 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This bitset can be accessed concurrently, provided
|
||||||
|
// the concurrent accesses are performed on distinct
|
||||||
|
// instances of underlying type. That means the cuncurrent
|
||||||
|
// accesses need to be spaced by at least
|
||||||
|
// bits_per_bucket bits.
|
||||||
|
// But at least best_concurrent_access_stride bits
|
||||||
|
// is recommended to prevent false sharing.
|
||||||
|
template <uint64_t N>
|
||||||
|
struct LargeBitset
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
constexpr static uint64_t cache_line_size = 64;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using UnderlyingType = uint64_t;
|
||||||
|
|
||||||
|
constexpr static uint64_t num_bits = N;
|
||||||
|
constexpr static uint64_t bits_per_bucket = 8 * sizeof(uint64_t);
|
||||||
|
constexpr static uint64_t num_buckets = (num_bits + bits_per_bucket - 1) / bits_per_bucket;
|
||||||
|
constexpr static uint64_t best_concurrent_access_stride = 8 * cache_line_size;
|
||||||
|
|
||||||
|
LargeBitset()
|
||||||
|
{
|
||||||
|
std::fill(std::begin(bits), std::end(bits), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(uint64_t idx)
|
||||||
|
{
|
||||||
|
const uint64_t bucket = idx / bits_per_bucket;
|
||||||
|
const uint64_t bit = uint64_t(1) << (idx % bits_per_bucket);
|
||||||
|
bits[bucket] |= bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test(uint64_t idx) const
|
||||||
|
{
|
||||||
|
const uint64_t bucket = idx / bits_per_bucket;
|
||||||
|
const uint64_t bit = uint64_t(1) << (idx % bits_per_bucket);
|
||||||
|
return bits[bucket] & bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t count() const
|
||||||
|
{
|
||||||
|
uint64_t c = 0;
|
||||||
|
uint64_t i = 0;
|
||||||
|
|
||||||
|
for (; i < num_buckets - 3; i += 4)
|
||||||
|
{
|
||||||
|
uint64_t c0 = popcount(bits[i+0]);
|
||||||
|
uint64_t c1 = popcount(bits[i+1]);
|
||||||
|
uint64_t c2 = popcount(bits[i+2]);
|
||||||
|
uint64_t c3 = popcount(bits[i+3]);
|
||||||
|
c0 += c1;
|
||||||
|
c2 += c3;
|
||||||
|
c += c0 + c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < num_buckets; ++i)
|
||||||
|
{
|
||||||
|
c += popcount(bits[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
alignas(cache_line_size) UnderlyingType bits[num_buckets];
|
||||||
|
};
|
||||||
|
|
||||||
/// Under Windows it is not possible for a process to run on more than one
|
/// Under Windows it is not possible for a process to run on more than one
|
||||||
/// logical processor group. This usually means to be limited to use max 64
|
/// logical processor group. This usually means to be limited to use max 64
|
||||||
/// cores. To overcome this, some special platform specific API should be
|
/// cores. To overcome this, some special platform specific API should be
|
||||||
@@ -134,4 +591,118 @@ namespace WinProcGroup {
|
|||||||
void bindThisThread(size_t idx);
|
void bindThisThread(size_t idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a string that represents the current time. (Used for log output when learning evaluation function)
|
||||||
|
std::string now_string();
|
||||||
|
void sleep(int ms);
|
||||||
|
|
||||||
|
namespace Algo {
|
||||||
|
// Fisher-Yates
|
||||||
|
template <typename Rng, typename T>
|
||||||
|
void shuffle(std::vector<T>& buf, Rng&& prng)
|
||||||
|
{
|
||||||
|
const auto size = buf.size();
|
||||||
|
for (uint64_t i = 0; i < size; ++i)
|
||||||
|
std::swap(buf[i], buf[prng.rand(size - i) + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// split the string
|
||||||
|
inline std::vector<std::string> split(const std::string& input, char delimiter) {
|
||||||
|
std::istringstream stream(input);
|
||||||
|
std::string field;
|
||||||
|
std::vector<std::string> fields;
|
||||||
|
|
||||||
|
while (std::getline(stream, field, delimiter)) {
|
||||||
|
fields.push_back(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Path
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
// Something like Path class in C#. File name manipulation.
|
||||||
|
// Match with the C# method name.
|
||||||
|
struct Path
|
||||||
|
{
|
||||||
|
// Combine the path name and file name and return it.
|
||||||
|
// If the folder name is not an empty string, append it if there is no'/' or'\\' at the end.
|
||||||
|
static std::string combine(const std::string& folder, const std::string& filename)
|
||||||
|
{
|
||||||
|
if (folder.length() >= 1 && *folder.rbegin() != '/' && *folder.rbegin() != '\\')
|
||||||
|
return folder + "/" + filename;
|
||||||
|
|
||||||
|
return folder + filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file name part (excluding the folder name) from the full path expression.
|
||||||
|
static std::string get_file_name(const std::string& path)
|
||||||
|
{
|
||||||
|
// I don't know which "\" or "/" is used.
|
||||||
|
auto path_index1 = path.find_last_of("\\") + 1;
|
||||||
|
auto path_index2 = path.find_last_of("/") + 1;
|
||||||
|
auto path_index = std::max(path_index1, path_index2);
|
||||||
|
|
||||||
|
return path.substr(path_index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// It is ignored when new even though alignas is specified & because it is ignored when the STL container allocates memory,
|
||||||
|
// A custom allocator used for that.
|
||||||
|
template <typename T>
|
||||||
|
class AlignedAllocator {
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
AlignedAllocator() {}
|
||||||
|
AlignedAllocator(const AlignedAllocator&) {}
|
||||||
|
AlignedAllocator(AlignedAllocator&&) {}
|
||||||
|
|
||||||
|
template <typename U> AlignedAllocator(const AlignedAllocator<U>&) {}
|
||||||
|
|
||||||
|
T* allocate(std::size_t n) { return (T*)std_aligned_alloc(alignof(T), n * sizeof(T)); }
|
||||||
|
void deallocate(T* p, std::size_t ) { std_aligned_free(p); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class CacheLineAlignedAllocator {
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
constexpr static uint64_t cache_line_size = 64;
|
||||||
|
|
||||||
|
CacheLineAlignedAllocator() {}
|
||||||
|
CacheLineAlignedAllocator(const CacheLineAlignedAllocator&) {}
|
||||||
|
CacheLineAlignedAllocator(CacheLineAlignedAllocator&&) {}
|
||||||
|
|
||||||
|
template <typename U> CacheLineAlignedAllocator(const CacheLineAlignedAllocator<U>&) {}
|
||||||
|
|
||||||
|
T* allocate(std::size_t n) { return (T*)std_aligned_alloc(cache_line_size, n * sizeof(T)); }
|
||||||
|
void deallocate(T* p, std::size_t) { std_aligned_free(p); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Dependency Wrapper
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
namespace Dependency
|
||||||
|
{
|
||||||
|
// In the Linux environment, if you getline() the text file is'\r\n'
|
||||||
|
// Since'\r' remains at the end, write a wrapper to remove this'\r'.
|
||||||
|
// So when calling getline() on fstream,
|
||||||
|
// just write getline() instead of std::getline() and use this function.
|
||||||
|
extern bool getline(std::ifstream& fs, std::string& s);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace CommandLine {
|
||||||
|
void init(int argc, char* argv[]);
|
||||||
|
|
||||||
|
extern std::string binaryDirectory; // path of the executable directory
|
||||||
|
extern std::string workingDirectory; // path of the working directory
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef MISC_H_INCLUDED
|
#endif // #ifndef MISC_H_INCLUDED
|
||||||
|
|||||||
+72
-163
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,24 +21,21 @@
|
|||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template<GenType Type, Direction D>
|
template<GenType Type, Direction D>
|
||||||
ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) {
|
ExtMove* make_promotions(ExtMove* moveList, Square to) {
|
||||||
|
|
||||||
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
||||||
{
|
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
|
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
|
||||||
if (attacks_bb<KNIGHT>(to) & ksq)
|
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
|
if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
|
||||||
{
|
{
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
|
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
|
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
|
||||||
if (!(attacks_bb<KNIGHT>(to) & ksq))
|
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return moveList;
|
return moveList;
|
||||||
@@ -55,20 +52,16 @@ namespace {
|
|||||||
constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
|
constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
|
||||||
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
|
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
|
||||||
|
|
||||||
const Square ksq = pos.square<KING>(Them);
|
const Bitboard emptySquares = ~pos.pieces();
|
||||||
Bitboard emptySquares;
|
const Bitboard enemies = Type == EVASIONS ? pos.checkers()
|
||||||
|
: pos.pieces(Them);
|
||||||
|
|
||||||
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
|
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
|
||||||
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
|
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
|
||||||
|
|
||||||
Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target:
|
|
||||||
Type == CAPTURES ? target : pos.pieces(Them));
|
|
||||||
|
|
||||||
// Single and double pawn pushes, no promotions
|
// Single and double pawn pushes, no promotions
|
||||||
if (Type != CAPTURES)
|
if (Type != CAPTURES)
|
||||||
{
|
{
|
||||||
emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces());
|
|
||||||
|
|
||||||
Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
|
Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
|
||||||
Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
|
Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
|
||||||
|
|
||||||
@@ -80,33 +73,24 @@ namespace {
|
|||||||
|
|
||||||
if (Type == QUIET_CHECKS)
|
if (Type == QUIET_CHECKS)
|
||||||
{
|
{
|
||||||
b1 &= pawn_attacks_bb(Them, ksq);
|
// To make a quiet check, you either make a direct check by pushing a pawn
|
||||||
b2 &= pawn_attacks_bb(Them, ksq);
|
// or push a blocker pawn that is not on the same file as the enemy king.
|
||||||
|
// Discovered check promotion has been already generated amongst the captures.
|
||||||
// Add pawn pushes which give discovered check. This is possible only
|
Square ksq = pos.square<KING>(Them);
|
||||||
// if the pawn is not on the same file as the enemy king, because we
|
Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq);
|
||||||
// don't generate captures. Note that a possible discovery check
|
b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns);
|
||||||
// promotion has been already generated amongst the captures.
|
b2 &= pawn_attacks_bb(Them, ksq) | shift<Up+Up>(dcCandidatePawns);
|
||||||
Bitboard dcCandidateQuiets = pos.blockers_for_king(Them) & pawnsNotOn7;
|
|
||||||
if (dcCandidateQuiets)
|
|
||||||
{
|
|
||||||
Bitboard dc1 = shift<Up>(dcCandidateQuiets) & emptySquares & ~file_bb(ksq);
|
|
||||||
Bitboard dc2 = shift<Up>(dc1 & TRank3BB) & emptySquares;
|
|
||||||
|
|
||||||
b1 |= dc1;
|
|
||||||
b2 |= dc2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (b1)
|
while (b1)
|
||||||
{
|
{
|
||||||
Square to = pop_lsb(&b1);
|
Square to = pop_lsb(b1);
|
||||||
*moveList++ = make_move(to - Up, to);
|
*moveList++ = make_move(to - Up, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (b2)
|
while (b2)
|
||||||
{
|
{
|
||||||
Square to = pop_lsb(&b2);
|
Square to = pop_lsb(b2);
|
||||||
*moveList++ = make_move(to - Up - Up, to);
|
*moveList++ = make_move(to - Up - Up, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,27 +98,24 @@ namespace {
|
|||||||
// Promotions and underpromotions
|
// Promotions and underpromotions
|
||||||
if (pawnsOn7)
|
if (pawnsOn7)
|
||||||
{
|
{
|
||||||
if (Type == CAPTURES)
|
|
||||||
emptySquares = ~pos.pieces();
|
|
||||||
|
|
||||||
if (Type == EVASIONS)
|
|
||||||
emptySquares &= target;
|
|
||||||
|
|
||||||
Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
|
Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
|
||||||
Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
|
Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
|
||||||
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
|
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
|
||||||
|
|
||||||
|
if (Type == EVASIONS)
|
||||||
|
b3 &= target;
|
||||||
|
|
||||||
while (b1)
|
while (b1)
|
||||||
moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(&b1), ksq);
|
moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(b1));
|
||||||
|
|
||||||
while (b2)
|
while (b2)
|
||||||
moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(&b2), ksq);
|
moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(b2));
|
||||||
|
|
||||||
while (b3)
|
while (b3)
|
||||||
moveList = make_promotions<Type, Up >(moveList, pop_lsb(&b3), ksq);
|
moveList = make_promotions<Type, Up >(moveList, pop_lsb(b3));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard and en-passant captures
|
// Standard and en passant captures
|
||||||
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
||||||
{
|
{
|
||||||
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
|
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
|
||||||
@@ -142,13 +123,13 @@ namespace {
|
|||||||
|
|
||||||
while (b1)
|
while (b1)
|
||||||
{
|
{
|
||||||
Square to = pop_lsb(&b1);
|
Square to = pop_lsb(b1);
|
||||||
*moveList++ = make_move(to - UpRight, to);
|
*moveList++ = make_move(to - UpRight, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (b2)
|
while (b2)
|
||||||
{
|
{
|
||||||
Square to = pop_lsb(&b2);
|
Square to = pop_lsb(b2);
|
||||||
*moveList++ = make_move(to - UpLeft, to);
|
*moveList++ = make_move(to - UpLeft, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,10 +137,8 @@ namespace {
|
|||||||
{
|
{
|
||||||
assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6));
|
assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6));
|
||||||
|
|
||||||
// An en passant capture can be an evasion only if the checking piece
|
// An en passant capture cannot resolve a discovered check
|
||||||
// is the double pushed pawn and so is in the target. Otherwise this
|
if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
|
||||||
// is a discovery check and we are forced to do otherwise.
|
|
||||||
if (Type == EVASIONS && !(target & (pos.ep_square() - Up)))
|
|
||||||
return moveList;
|
return moveList;
|
||||||
|
|
||||||
b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
|
b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
|
||||||
@@ -167,7 +146,7 @@ namespace {
|
|||||||
assert(b1);
|
assert(b1);
|
||||||
|
|
||||||
while (b1)
|
while (b1)
|
||||||
*moveList++ = make<ENPASSANT>(pop_lsb(&b1), pos.ep_square());
|
*moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,27 +159,19 @@ namespace {
|
|||||||
|
|
||||||
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
|
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
|
||||||
|
|
||||||
const Square* pl = pos.squares<Pt>(Us);
|
Bitboard bb = pos.pieces(Us, Pt);
|
||||||
|
|
||||||
for (Square from = *pl; from != SQ_NONE; from = *++pl)
|
while (bb)
|
||||||
{
|
{
|
||||||
if (Checks)
|
Square from = pop_lsb(bb);
|
||||||
{
|
|
||||||
if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN)
|
|
||||||
&& !(attacks_bb<Pt>(from) & target & pos.check_squares(Pt)))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (pos.blockers_for_king(~Us) & from)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
|
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
|
||||||
|
|
||||||
if (Checks)
|
// To check, you either move freely a blocker or make a direct check.
|
||||||
|
if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from)))
|
||||||
b &= pos.check_squares(Pt);
|
b &= pos.check_squares(Pt);
|
||||||
|
|
||||||
while (b)
|
while (b)
|
||||||
*moveList++ = make_move(from, pop_lsb(&b));
|
*moveList++ = make_move(from, pop_lsb(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
return moveList;
|
return moveList;
|
||||||
@@ -209,46 +180,39 @@ namespace {
|
|||||||
|
|
||||||
template<Color Us, GenType Type>
|
template<Color Us, GenType Type>
|
||||||
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
|
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
|
||||||
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations
|
|
||||||
|
static_assert(Type != LEGAL, "Unsupported type in generate_all()");
|
||||||
|
|
||||||
|
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations
|
||||||
|
const Square ksq = pos.square<KING>(Us);
|
||||||
Bitboard target;
|
Bitboard target;
|
||||||
|
|
||||||
switch (Type)
|
// Skip generating non-king moves when in double check
|
||||||
|
if (Type != EVASIONS || !more_than_one(pos.checkers()))
|
||||||
{
|
{
|
||||||
case CAPTURES:
|
target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
|
||||||
target = pos.pieces(~Us);
|
: Type == NON_EVASIONS ? ~pos.pieces( Us)
|
||||||
break;
|
: Type == CAPTURES ? pos.pieces(~Us)
|
||||||
case QUIETS:
|
: ~pos.pieces( ); // QUIETS || QUIET_CHECKS
|
||||||
case QUIET_CHECKS:
|
|
||||||
target = ~pos.pieces();
|
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
|
||||||
break;
|
moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
|
||||||
case EVASIONS:
|
moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
|
||||||
{
|
moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
|
||||||
Square checksq = lsb(pos.checkers());
|
moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
|
||||||
target = between_bb(pos.square<KING>(Us), checksq) | checksq;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case NON_EVASIONS:
|
|
||||||
target = ~pos.pieces(Us);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
static_assert(true, "Unsupported type in generate_all()");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
|
if (!Checks || pos.blockers_for_king(~Us) & ksq)
|
||||||
moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
|
|
||||||
moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
|
|
||||||
moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
|
|
||||||
moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
|
|
||||||
|
|
||||||
if (Type != QUIET_CHECKS && Type != EVASIONS)
|
|
||||||
{
|
{
|
||||||
Square ksq = pos.square<KING>(Us);
|
Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
|
||||||
Bitboard b = attacks_bb<KING>(ksq) & target;
|
if (Checks)
|
||||||
while (b)
|
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
|
||||||
*moveList++ = make_move(ksq, pop_lsb(&b));
|
|
||||||
|
|
||||||
if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING))
|
while (b)
|
||||||
for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
|
*moveList++ = make_move(ksq, pop_lsb(b));
|
||||||
|
|
||||||
|
if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING))
|
||||||
|
for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
|
||||||
if (!pos.castling_impeded(cr) && pos.can_castle(cr))
|
if (!pos.castling_impeded(cr) && pos.can_castle(cr))
|
||||||
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
|
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
|
||||||
}
|
}
|
||||||
@@ -259,8 +223,10 @@ namespace {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// <CAPTURES> Generates all pseudo-legal captures plus queen and checking knight promotions
|
/// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
|
||||||
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions(except checking knight)
|
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
|
||||||
|
/// <EVASIONS> Generates all pseudo-legal check evasions when the side to move is in check
|
||||||
|
/// <QUIET_CHECKS> Generates all pseudo-legal non-captures giving check, except castling and promotions
|
||||||
/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
|
/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
|
||||||
///
|
///
|
||||||
/// Returns a pointer to the end of the move list.
|
/// Returns a pointer to the end of the move list.
|
||||||
@@ -268,8 +234,8 @@ namespace {
|
|||||||
template<GenType Type>
|
template<GenType Type>
|
||||||
ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
||||||
|
|
||||||
static_assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS, "Unsupported type in generate()");
|
static_assert(Type != LEGAL, "Unsupported type in generate()");
|
||||||
assert(!pos.checkers());
|
assert((Type == EVASIONS) == (bool)pos.checkers());
|
||||||
|
|
||||||
Color us = pos.side_to_move();
|
Color us = pos.side_to_move();
|
||||||
|
|
||||||
@@ -280,70 +246,11 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
|||||||
// Explicit template instantiations
|
// Explicit template instantiations
|
||||||
template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
|
template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
|
||||||
template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
|
template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
|
||||||
|
template ExtMove* generate<EVASIONS>(const Position&, ExtMove*);
|
||||||
|
template ExtMove* generate<QUIET_CHECKS>(const Position&, ExtMove*);
|
||||||
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
|
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
|
||||||
|
|
||||||
|
|
||||||
/// generate<QUIET_CHECKS> generates all pseudo-legal non-captures.
|
|
||||||
/// Returns a pointer to the end of the move list.
|
|
||||||
template<>
|
|
||||||
ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
|
|
||||||
|
|
||||||
assert(!pos.checkers());
|
|
||||||
|
|
||||||
Color us = pos.side_to_move();
|
|
||||||
Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us) & ~pos.pieces(PAWN);
|
|
||||||
|
|
||||||
while (dc)
|
|
||||||
{
|
|
||||||
Square from = pop_lsb(&dc);
|
|
||||||
PieceType pt = type_of(pos.piece_on(from));
|
|
||||||
|
|
||||||
Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces();
|
|
||||||
|
|
||||||
if (pt == KING)
|
|
||||||
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~us));
|
|
||||||
|
|
||||||
while (b)
|
|
||||||
*moveList++ = make_move(from, pop_lsb(&b));
|
|
||||||
}
|
|
||||||
|
|
||||||
return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList)
|
|
||||||
: generate_all<BLACK, QUIET_CHECKS>(pos, moveList);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// generate<EVASIONS> generates all pseudo-legal check evasions when the side
|
|
||||||
/// to move is in check. Returns a pointer to the end of the move list.
|
|
||||||
template<>
|
|
||||||
ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
|
|
||||||
|
|
||||||
assert(pos.checkers());
|
|
||||||
|
|
||||||
Color us = pos.side_to_move();
|
|
||||||
Square ksq = pos.square<KING>(us);
|
|
||||||
Bitboard sliderAttacks = 0;
|
|
||||||
Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN);
|
|
||||||
|
|
||||||
// Find all the squares attacked by slider checkers. We will remove them from
|
|
||||||
// the king evasions in order to skip known illegal moves, which avoids any
|
|
||||||
// useless legality checks later on.
|
|
||||||
while (sliders)
|
|
||||||
sliderAttacks |= line_bb(ksq, pop_lsb(&sliders)) & ~pos.checkers();
|
|
||||||
|
|
||||||
// Generate evasions for king, capture and non capture moves
|
|
||||||
Bitboard b = attacks_bb<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
|
|
||||||
while (b)
|
|
||||||
*moveList++ = make_move(ksq, pop_lsb(&b));
|
|
||||||
|
|
||||||
if (more_than_one(pos.checkers()))
|
|
||||||
return moveList; // Double check, only a king move can save the day
|
|
||||||
|
|
||||||
// Generate blocking evasions or captures of the checking piece
|
|
||||||
return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList)
|
|
||||||
: generate_all<BLACK, EVASIONS>(pos, moveList);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// generate<LEGAL> generates all the legal moves in the given position
|
/// generate<LEGAL> generates all the legal moves in the given position
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
@@ -357,7 +264,7 @@ ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
|
|||||||
moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
|
moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
|
||||||
: generate<NON_EVASIONS>(pos, moveList);
|
: generate<NON_EVASIONS>(pos, moveList);
|
||||||
while (cur != 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))
|
&& !pos.legal(*cur))
|
||||||
*cur = (--moveList)->move;
|
*cur = (--moveList)->move;
|
||||||
else
|
else
|
||||||
@@ -365,3 +272,5 @@ ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
|
|||||||
|
|
||||||
return moveList;
|
return moveList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+8
-1
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
class Position;
|
class Position;
|
||||||
|
|
||||||
enum GenType {
|
enum GenType {
|
||||||
@@ -66,8 +68,13 @@ struct MoveList {
|
|||||||
return std::find(begin(), end(), move) != end();
|
return std::find(begin(), end(), move) != end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns the i th element
|
||||||
|
const ExtMove at(size_t i) const { assert(0 <= i && i < size()); return begin()[i]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ExtMove moveList[MAX_MOVES], *last;
|
ExtMove moveList[MAX_MOVES], *last;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef MOVEGEN_H_INCLUDED
|
#endif // #ifndef MOVEGEN_H_INCLUDED
|
||||||
|
|||||||
+77
-35
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,8 +18,11 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "bitboard.h"
|
||||||
#include "movepick.h"
|
#include "movepick.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
enum Stages {
|
enum Stages {
|
||||||
@@ -54,11 +57,14 @@ namespace {
|
|||||||
/// ordering is at the current node.
|
/// ordering is at the current node.
|
||||||
|
|
||||||
/// MovePicker constructor for the main search
|
/// MovePicker constructor for the main search
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const LowPlyHistory* lp,
|
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
||||||
const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, const Move* killers, int pl)
|
const CapturePieceToHistory* cph,
|
||||||
: pos(p), mainHistory(mh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch),
|
const PieceToHistory** ch,
|
||||||
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) {
|
Move cm,
|
||||||
|
const Move* killers)
|
||||||
|
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
|
||||||
|
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d)
|
||||||
|
{
|
||||||
assert(d > 0);
|
assert(d > 0);
|
||||||
|
|
||||||
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
|
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
|
||||||
@@ -67,21 +73,24 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist
|
|||||||
|
|
||||||
/// MovePicker constructor for quiescence search
|
/// MovePicker constructor for quiescence search
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
||||||
const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs)
|
const CapturePieceToHistory* cph,
|
||||||
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) {
|
const PieceToHistory** ch,
|
||||||
|
Square rs)
|
||||||
|
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d)
|
||||||
|
{
|
||||||
assert(d <= 0);
|
assert(d <= 0);
|
||||||
|
|
||||||
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) +
|
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) +
|
||||||
!(ttm && (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
|
!( ttm
|
||||||
&& pos.pseudo_legal(ttm));
|
&& (pos.checkers() || depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
|
||||||
|
&& pos.pseudo_legal(ttm));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker constructor for ProbCut: we generate captures with SEE greater
|
/// MovePicker constructor for ProbCut: we generate captures with SEE greater
|
||||||
/// than or equal to the given threshold.
|
/// than or equal to the given threshold.
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
|
MovePicker::MovePicker(const Position& p, Move ttm, Value th, Depth d, const CapturePieceToHistory* cph)
|
||||||
: pos(p), captureHistory(cph), ttMove(ttm), threshold(th) {
|
: pos(p), captureHistory(cph), ttMove(ttm), threshold(th), depth(d)
|
||||||
|
{
|
||||||
assert(!pos.checkers());
|
assert(!pos.checkers());
|
||||||
|
|
||||||
stage = PROBCUT_TT + !(ttm && pos.capture(ttm)
|
stage = PROBCUT_TT + !(ttm && pos.capture(ttm)
|
||||||
@@ -97,18 +106,48 @@ void MovePicker::score() {
|
|||||||
|
|
||||||
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
|
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
|
||||||
|
|
||||||
for (auto& m : *this)
|
Bitboard threatened, threatenedByPawn, threatenedByMinor, threatenedByRook;
|
||||||
if (Type == CAPTURES)
|
if constexpr (Type == QUIETS)
|
||||||
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)))];
|
Color us = pos.side_to_move();
|
||||||
|
// squares threatened by pawns
|
||||||
|
threatenedByPawn = pos.attacks_by<PAWN>(~us);
|
||||||
|
// squares threatened by minors or pawns
|
||||||
|
threatenedByMinor = pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
|
||||||
|
// squares threatened by rooks, minors or pawns
|
||||||
|
threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
|
||||||
|
|
||||||
else if (Type == QUIETS)
|
// pieces threatened by pieces of lesser material value
|
||||||
|
threatened = (pos.pieces(us, QUEEN) & threatenedByRook)
|
||||||
|
| (pos.pieces(us, ROOK) & threatenedByMinor)
|
||||||
|
| (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Silence unused variable warnings
|
||||||
|
(void) threatened;
|
||||||
|
(void) threatenedByPawn;
|
||||||
|
(void) threatenedByMinor;
|
||||||
|
(void) threatenedByRook;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& m : *this)
|
||||||
|
if constexpr (Type == CAPTURES)
|
||||||
|
m.value = 6 * int(PieceValue[MG][pos.piece_on(to_sq(m))])
|
||||||
|
+ (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
|
||||||
|
|
||||||
|
else if constexpr (Type == QUIETS)
|
||||||
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
|
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
|
||||||
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
||||||
+ 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
|
+ (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
|
||||||
+ 2 * (*continuationHistory[3])[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)]
|
+ (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)]
|
||||||
+ (ply < MAX_LPH ? std::min(4, depth / 3) * (*lowPlyHistory)[ply][from_to(m)] : 0);
|
+ (threatened & from_sq(m) ?
|
||||||
|
(type_of(pos.moved_piece(m)) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000
|
||||||
|
: type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000
|
||||||
|
: !(to_sq(m) & threatenedByPawn) ? 15000
|
||||||
|
: 0)
|
||||||
|
: 0);
|
||||||
|
|
||||||
else // Type == EVASIONS
|
else // Type == EVASIONS
|
||||||
{
|
{
|
||||||
@@ -116,8 +155,8 @@ void MovePicker::score() {
|
|||||||
m.value = PieceValue[MG][pos.piece_on(to_sq(m))]
|
m.value = PieceValue[MG][pos.piece_on(to_sq(m))]
|
||||||
- Value(type_of(pos.moved_piece(m)));
|
- Value(type_of(pos.moved_piece(m)));
|
||||||
else
|
else
|
||||||
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
|
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
|
||||||
+ (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
||||||
- (1 << 28);
|
- (1 << 28);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +180,7 @@ Move MovePicker::select(Pred filter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker::next_move() is the most important method of the MovePicker class. It
|
/// 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.
|
/// moves left, picking the move with the highest score from a list of generated moves.
|
||||||
Move MovePicker::next_move(bool skipQuiets) {
|
Move MovePicker::next_move(bool skipQuiets) {
|
||||||
|
|
||||||
@@ -162,11 +201,12 @@ top:
|
|||||||
endMoves = generate<CAPTURES>(pos, cur);
|
endMoves = generate<CAPTURES>(pos, cur);
|
||||||
|
|
||||||
score<CAPTURES>();
|
score<CAPTURES>();
|
||||||
|
partial_insertion_sort(cur, endMoves, -3000 * depth);
|
||||||
++stage;
|
++stage;
|
||||||
goto top;
|
goto top;
|
||||||
|
|
||||||
case GOOD_CAPTURE:
|
case GOOD_CAPTURE:
|
||||||
if (select<Best>([&](){
|
if (select<Next>([&](){
|
||||||
return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ?
|
return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ?
|
||||||
// Move losing capture to endBadCaptures to be tried later
|
// Move losing capture to endBadCaptures to be tried later
|
||||||
true : (*endBadCaptures++ = *cur, false); }))
|
true : (*endBadCaptures++ = *cur, false); }))
|
||||||
@@ -182,7 +222,7 @@ top:
|
|||||||
--endMoves;
|
--endMoves;
|
||||||
|
|
||||||
++stage;
|
++stage;
|
||||||
/* fallthrough */
|
[[fallthrough]];
|
||||||
|
|
||||||
case REFUTATION:
|
case REFUTATION:
|
||||||
if (select<Next>([&](){ return *cur != MOVE_NONE
|
if (select<Next>([&](){ return *cur != MOVE_NONE
|
||||||
@@ -190,7 +230,7 @@ top:
|
|||||||
&& pos.pseudo_legal(*cur); }))
|
&& pos.pseudo_legal(*cur); }))
|
||||||
return *(cur - 1);
|
return *(cur - 1);
|
||||||
++stage;
|
++stage;
|
||||||
/* fallthrough */
|
[[fallthrough]];
|
||||||
|
|
||||||
case QUIET_INIT:
|
case QUIET_INIT:
|
||||||
if (!skipQuiets)
|
if (!skipQuiets)
|
||||||
@@ -203,7 +243,7 @@ top:
|
|||||||
}
|
}
|
||||||
|
|
||||||
++stage;
|
++stage;
|
||||||
/* fallthrough */
|
[[fallthrough]];
|
||||||
|
|
||||||
case QUIET:
|
case QUIET:
|
||||||
if ( !skipQuiets
|
if ( !skipQuiets
|
||||||
@@ -217,7 +257,7 @@ top:
|
|||||||
endMoves = endBadCaptures;
|
endMoves = endBadCaptures;
|
||||||
|
|
||||||
++stage;
|
++stage;
|
||||||
/* fallthrough */
|
[[fallthrough]];
|
||||||
|
|
||||||
case BAD_CAPTURE:
|
case BAD_CAPTURE:
|
||||||
return select<Next>([](){ return true; });
|
return select<Next>([](){ return true; });
|
||||||
@@ -228,16 +268,16 @@ top:
|
|||||||
|
|
||||||
score<EVASIONS>();
|
score<EVASIONS>();
|
||||||
++stage;
|
++stage;
|
||||||
/* fallthrough */
|
[[fallthrough]];
|
||||||
|
|
||||||
case EVASION:
|
case EVASION:
|
||||||
return select<Best>([](){ return true; });
|
return select<Best>([](){ return true; });
|
||||||
|
|
||||||
case PROBCUT:
|
case PROBCUT:
|
||||||
return select<Best>([&](){ return pos.see_ge(*cur, threshold); });
|
return select<Next>([&](){ return pos.see_ge(*cur, threshold); });
|
||||||
|
|
||||||
case QCAPTURE:
|
case QCAPTURE:
|
||||||
if (select<Best>([&](){ return depth > DEPTH_QS_RECAPTURES
|
if (select<Next>([&](){ return depth > DEPTH_QS_RECAPTURES
|
||||||
|| to_sq(*cur) == recaptureSquare; }))
|
|| to_sq(*cur) == recaptureSquare; }))
|
||||||
return *(cur - 1);
|
return *(cur - 1);
|
||||||
|
|
||||||
@@ -246,14 +286,14 @@ top:
|
|||||||
return MOVE_NONE;
|
return MOVE_NONE;
|
||||||
|
|
||||||
++stage;
|
++stage;
|
||||||
/* fallthrough */
|
[[fallthrough]];
|
||||||
|
|
||||||
case QCHECK_INIT:
|
case QCHECK_INIT:
|
||||||
cur = moves;
|
cur = moves;
|
||||||
endMoves = generate<QUIET_CHECKS>(pos, cur);
|
endMoves = generate<QUIET_CHECKS>(pos, cur);
|
||||||
|
|
||||||
++stage;
|
++stage;
|
||||||
/* fallthrough */
|
[[fallthrough]];
|
||||||
|
|
||||||
case QCHECK:
|
case QCHECK:
|
||||||
return select<Next>([](){ return true; });
|
return select<Next>([](){ return true; });
|
||||||
@@ -262,3 +302,5 @@ top:
|
|||||||
assert(false);
|
assert(false);
|
||||||
return MOVE_NONE; // Silence warning
|
return MOVE_NONE; // Silence warning
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+17
-23
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
/// StatsEntry stores the stat table value. It is usually a number but could
|
/// 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
|
/// 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
|
/// to directly call history update operator<<() on the entry so to use stats
|
||||||
@@ -84,13 +86,7 @@ enum StatsType { NoCaptures, Captures };
|
|||||||
/// unsuccessful during the current search, and is used for reduction and move
|
/// unsuccessful during the current search, and is used for reduction and move
|
||||||
/// ordering decisions. It uses 2 tables (one for each color) indexed by
|
/// 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
|
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
|
||||||
typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
|
typedef Stats<int16_t, 14365, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> 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 search and filled during iterative deepening
|
|
||||||
constexpr int MAX_LPH = 4;
|
|
||||||
typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory;
|
|
||||||
|
|
||||||
/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
|
/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
|
||||||
/// move, see www.chessprogramming.org/Countermove_Heuristic
|
/// move, see www.chessprogramming.org/Countermove_Heuristic
|
||||||
@@ -108,12 +104,12 @@ typedef Stats<int16_t, 29952, PIECE_NB, SQUARE_NB> PieceToHistory;
|
|||||||
typedef Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHistory;
|
typedef Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB> 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
|
/// 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,
|
/// 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
|
/// when MOVE_NONE is returned. In order to improve the efficiency of the
|
||||||
/// beta algorithm, MovePicker attempts to return the moves which are most likely
|
/// alpha-beta algorithm, MovePicker attempts to return the moves which are most
|
||||||
/// to get a cut-off first.
|
/// likely to get a cut-off first.
|
||||||
class MovePicker {
|
class MovePicker {
|
||||||
|
|
||||||
enum PickType { Next, Best };
|
enum PickType { Next, Best };
|
||||||
@@ -121,18 +117,16 @@ class MovePicker {
|
|||||||
public:
|
public:
|
||||||
MovePicker(const MovePicker&) = delete;
|
MovePicker(const MovePicker&) = delete;
|
||||||
MovePicker& operator=(const MovePicker&) = delete;
|
MovePicker& operator=(const MovePicker&) = delete;
|
||||||
MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
|
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
||||||
|
const CapturePieceToHistory*,
|
||||||
|
const PieceToHistory**,
|
||||||
|
Move,
|
||||||
|
const Move*);
|
||||||
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
||||||
const CapturePieceToHistory*,
|
const CapturePieceToHistory*,
|
||||||
const PieceToHistory**,
|
const PieceToHistory**,
|
||||||
Square);
|
Square);
|
||||||
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
MovePicker(const Position&, Move, Value, Depth, const CapturePieceToHistory*);
|
||||||
const LowPlyHistory*,
|
|
||||||
const CapturePieceToHistory*,
|
|
||||||
const PieceToHistory**,
|
|
||||||
Move,
|
|
||||||
const Move*,
|
|
||||||
int);
|
|
||||||
Move next_move(bool skipQuiets = false);
|
Move next_move(bool skipQuiets = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -143,7 +137,6 @@ private:
|
|||||||
|
|
||||||
const Position& pos;
|
const Position& pos;
|
||||||
const ButterflyHistory* mainHistory;
|
const ButterflyHistory* mainHistory;
|
||||||
const LowPlyHistory* lowPlyHistory;
|
|
||||||
const CapturePieceToHistory* captureHistory;
|
const CapturePieceToHistory* captureHistory;
|
||||||
const PieceToHistory** continuationHistory;
|
const PieceToHistory** continuationHistory;
|
||||||
Move ttMove;
|
Move ttMove;
|
||||||
@@ -152,8 +145,9 @@ private:
|
|||||||
Square recaptureSquare;
|
Square recaptureSquare;
|
||||||
Value threshold;
|
Value threshold;
|
||||||
Depth depth;
|
Depth depth;
|
||||||
int ply;
|
|
||||||
ExtMove moves[MAX_MOVES];
|
ExtMove moves[MAX_MOVES];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef MOVEPICK_H_INCLUDED
|
#endif // #ifndef MOVEPICK_H_INCLUDED
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Definition of input features and network structure used in NNUE evaluation function
|
|
||||||
|
|
||||||
#ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED
|
|
||||||
#define NNUE_HALFKP_256X2_32_32_H_INCLUDED
|
|
||||||
|
|
||||||
#include "../features/feature_set.h"
|
|
||||||
#include "../features/half_kp.h"
|
|
||||||
|
|
||||||
#include "../layers/input_slice.h"
|
|
||||||
#include "../layers/affine_transform.h"
|
|
||||||
#include "../layers/clipped_relu.h"
|
|
||||||
|
|
||||||
namespace Eval::NNUE {
|
|
||||||
|
|
||||||
// Input features used in evaluation function
|
|
||||||
using RawFeatures = Features::FeatureSet<
|
|
||||||
Features::HalfKP<Features::Side::kFriend>>;
|
|
||||||
|
|
||||||
// Number of input feature dimensions after conversion
|
|
||||||
constexpr IndexType kTransformedFeatureDimensions = 256;
|
|
||||||
|
|
||||||
namespace Layers {
|
|
||||||
|
|
||||||
// Define network structure
|
|
||||||
using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
|
|
||||||
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
|
|
||||||
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
|
|
||||||
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
|
|
||||||
|
|
||||||
} // namespace Layers
|
|
||||||
|
|
||||||
using Network = Layers::OutputLayer;
|
|
||||||
|
|
||||||
} // namespace Eval::NNUE
|
|
||||||
|
|
||||||
#endif // #ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED
|
|
||||||
+324
-99
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,161 +18,386 @@
|
|||||||
|
|
||||||
// Code for calculating NNUE evaluation function
|
// Code for calculating NNUE evaluation function
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#include "../evaluate.h"
|
#include "../evaluate.h"
|
||||||
#include "../position.h"
|
#include "../position.h"
|
||||||
#include "../misc.h"
|
#include "../misc.h"
|
||||||
#include "../uci.h"
|
#include "../uci.h"
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
#include "evaluate_nnue.h"
|
#include "evaluate_nnue.h"
|
||||||
|
|
||||||
ExtPieceSquare kpp_board_index[PIECE_NB] = {
|
namespace Stockfish::Eval::NNUE {
|
||||||
// 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 Eval::NNUE {
|
|
||||||
|
|
||||||
// Input feature converter
|
// Input feature converter
|
||||||
AlignedPtr<FeatureTransformer> feature_transformer;
|
LargePagePtr<FeatureTransformer> featureTransformer;
|
||||||
|
|
||||||
// Evaluation function
|
// Evaluation function
|
||||||
AlignedPtr<Network> network;
|
AlignedPtr<Network> network[LayerStacks];
|
||||||
|
|
||||||
// Evaluation function file name
|
// Evaluation function file name
|
||||||
std::string fileName;
|
std::string fileName;
|
||||||
|
std::string netDescription;
|
||||||
|
|
||||||
namespace Detail {
|
namespace Detail {
|
||||||
|
|
||||||
// Initialize the evaluation function parameters
|
// Initialize the evaluation function parameters
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void Initialize(AlignedPtr<T>& pointer) {
|
void initialize(AlignedPtr<T>& pointer) {
|
||||||
|
|
||||||
pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
|
pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
|
||||||
std::memset(pointer.get(), 0, sizeof(T));
|
std::memset(pointer.get(), 0, sizeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void initialize(LargePagePtr<T>& pointer) {
|
||||||
|
|
||||||
|
static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
|
||||||
|
pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
|
||||||
|
std::memset(pointer.get(), 0, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
// Read evaluation function parameters
|
// Read evaluation function parameters
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool ReadParameters(std::istream& stream, const AlignedPtr<T>& pointer) {
|
bool read_parameters(std::istream& stream, T& reference) {
|
||||||
|
|
||||||
std::uint32_t header;
|
std::uint32_t header;
|
||||||
stream.read(reinterpret_cast<char*>(&header), sizeof(header));
|
header = read_little_endian<std::uint32_t>(stream);
|
||||||
if (!stream || header != T::GetHashValue()) return false;
|
if (!stream || header != T::get_hash_value()) return false;
|
||||||
return pointer->ReadParameters(stream);
|
return reference.read_parameters(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write evaluation function parameters
|
||||||
|
template <typename T>
|
||||||
|
bool write_parameters(std::ostream& stream, const T& reference) {
|
||||||
|
|
||||||
|
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
|
||||||
|
return reference.write_parameters(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Detail
|
} // namespace Detail
|
||||||
|
|
||||||
// Initialize the evaluation function parameters
|
// Initialize the evaluation function parameters
|
||||||
void Initialize() {
|
void initialize() {
|
||||||
|
|
||||||
Detail::Initialize(feature_transformer);
|
Detail::initialize(featureTransformer);
|
||||||
Detail::Initialize(network);
|
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||||
|
Detail::initialize(network[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read network header
|
// Read network header
|
||||||
bool ReadHeader(std::istream& stream,
|
bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc)
|
||||||
std::uint32_t* hash_value, std::string* architecture) {
|
{
|
||||||
|
|
||||||
std::uint32_t version, size;
|
std::uint32_t version, size;
|
||||||
stream.read(reinterpret_cast<char*>(&version), sizeof(version));
|
|
||||||
stream.read(reinterpret_cast<char*>(hash_value), sizeof(*hash_value));
|
version = read_little_endian<std::uint32_t>(stream);
|
||||||
stream.read(reinterpret_cast<char*>(&size), sizeof(size));
|
*hashValue = read_little_endian<std::uint32_t>(stream);
|
||||||
if (!stream || version != kVersion) return false;
|
size = read_little_endian<std::uint32_t>(stream);
|
||||||
architecture->resize(size);
|
if (!stream || version != Version) return false;
|
||||||
stream.read(&(*architecture)[0], size);
|
desc->resize(size);
|
||||||
|
stream.read(&(*desc)[0], size);
|
||||||
|
return !stream.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write network header
|
||||||
|
bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc)
|
||||||
|
{
|
||||||
|
write_little_endian<std::uint32_t>(stream, Version);
|
||||||
|
write_little_endian<std::uint32_t>(stream, hashValue);
|
||||||
|
write_little_endian<std::uint32_t>(stream, (std::uint32_t)desc.size());
|
||||||
|
stream.write(&desc[0], desc.size());
|
||||||
return !stream.fail();
|
return !stream.fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read network parameters
|
// Read network parameters
|
||||||
bool ReadParameters(std::istream& stream) {
|
bool read_parameters(std::istream& stream) {
|
||||||
|
|
||||||
std::uint32_t hash_value;
|
std::uint32_t hashValue;
|
||||||
std::string architecture;
|
if (!read_header(stream, &hashValue, &netDescription)) return false;
|
||||||
if (!ReadHeader(stream, &hash_value, &architecture)) return false;
|
if (hashValue != HashValue) return false;
|
||||||
if (hash_value != kHashValue) return false;
|
if (!Detail::read_parameters(stream, *featureTransformer)) return false;
|
||||||
if (!Detail::ReadParameters(stream, feature_transformer)) return false;
|
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||||
if (!Detail::ReadParameters(stream, network)) return false;
|
if (!Detail::read_parameters(stream, *(network[i]))) return false;
|
||||||
return stream && stream.peek() == std::ios::traits_type::eof();
|
return stream && stream.peek() == std::ios::traits_type::eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proceed with the difference calculation if possible
|
// Write network parameters
|
||||||
static void UpdateAccumulatorIfPossible(const Position& pos) {
|
bool write_parameters(std::ostream& stream) {
|
||||||
|
|
||||||
feature_transformer->UpdateAccumulatorIfPossible(pos);
|
if (!write_header(stream, HashValue, netDescription)) return false;
|
||||||
}
|
if (!Detail::write_parameters(stream, *featureTransformer)) return false;
|
||||||
|
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||||
// Calculate the evaluation value
|
if (!Detail::write_parameters(stream, *(network[i]))) return false;
|
||||||
static Value ComputeScore(const Position& pos, bool refresh) {
|
return (bool)stream;
|
||||||
|
|
||||||
auto& accumulator = pos.state()->accumulator;
|
|
||||||
if (!refresh && accumulator.computed_score) {
|
|
||||||
return accumulator.score;
|
|
||||||
}
|
|
||||||
|
|
||||||
alignas(kCacheLineSize) TransformedFeatureType
|
|
||||||
transformed_features[FeatureTransformer::kBufferSize];
|
|
||||||
feature_transformer->Transform(pos, transformed_features, refresh);
|
|
||||||
alignas(kCacheLineSize) char buffer[Network::kBufferSize];
|
|
||||||
const auto output = network->Propagate(transformed_features, buffer);
|
|
||||||
|
|
||||||
auto score = static_cast<Value>(output[0] / FV_SCALE);
|
|
||||||
|
|
||||||
accumulator.score = score;
|
|
||||||
accumulator.computed_score = true;
|
|
||||||
return accumulator.score;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the evaluation function file
|
|
||||||
bool load_eval_file(const std::string& evalFile) {
|
|
||||||
|
|
||||||
Initialize();
|
|
||||||
fileName = evalFile;
|
|
||||||
|
|
||||||
std::ifstream stream(evalFile, std::ios::binary);
|
|
||||||
|
|
||||||
const bool result = ReadParameters(stream);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluation function. Perform differential calculation.
|
// Evaluation function. Perform differential calculation.
|
||||||
Value evaluate(const Position& pos) {
|
Value evaluate(const Position& pos, bool adjusted) {
|
||||||
Value v = ComputeScore(pos, false);
|
|
||||||
v = Utility::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
|
||||||
|
|
||||||
return v;
|
// We manually align the arrays on the stack because with gcc < 9.3
|
||||||
|
// overaligning stack variables with alignas() doesn't work correctly.
|
||||||
|
|
||||||
|
constexpr uint64_t alignment = CacheLineSize;
|
||||||
|
int delta = 10 - pos.non_pawn_material() / 1515;
|
||||||
|
|
||||||
|
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
|
||||||
|
TransformedFeatureType transformedFeaturesUnaligned[
|
||||||
|
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
|
||||||
|
|
||||||
|
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
||||||
|
#else
|
||||||
|
alignas(alignment)
|
||||||
|
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ASSERT_ALIGNED(transformedFeatures, alignment);
|
||||||
|
|
||||||
|
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
||||||
|
const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket);
|
||||||
|
const auto positional = network[bucket]->propagate(transformedFeatures);
|
||||||
|
|
||||||
|
// Give more value to positional evaluation when adjusted flag is set
|
||||||
|
if (adjusted)
|
||||||
|
return static_cast<Value>(((128 - delta) * psqt + (128 + delta) * positional) / 128 / OutputScale);
|
||||||
|
else
|
||||||
|
return static_cast<Value>((psqt + positional) / OutputScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluation function. Perform full calculation.
|
struct NnueEvalTrace {
|
||||||
Value compute_eval(const Position& pos) {
|
static_assert(LayerStacks == PSQTBuckets);
|
||||||
return ComputeScore(pos, true);
|
|
||||||
|
Value psqt[LayerStacks];
|
||||||
|
Value positional[LayerStacks];
|
||||||
|
std::size_t correctBucket;
|
||||||
|
};
|
||||||
|
|
||||||
|
static NnueEvalTrace trace_evaluate(const Position& pos) {
|
||||||
|
|
||||||
|
// We manually align the arrays on the stack because with gcc < 9.3
|
||||||
|
// overaligning stack variables with alignas() doesn't work correctly.
|
||||||
|
|
||||||
|
constexpr uint64_t alignment = CacheLineSize;
|
||||||
|
|
||||||
|
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
|
||||||
|
TransformedFeatureType transformedFeaturesUnaligned[
|
||||||
|
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
|
||||||
|
|
||||||
|
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
||||||
|
#else
|
||||||
|
alignas(alignment)
|
||||||
|
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ASSERT_ALIGNED(transformedFeatures, alignment);
|
||||||
|
|
||||||
|
NnueEvalTrace t{};
|
||||||
|
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
||||||
|
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) {
|
||||||
|
const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket);
|
||||||
|
const auto positional = network[bucket]->propagate(transformedFeatures);
|
||||||
|
|
||||||
|
t.psqt[bucket] = static_cast<Value>( materialist / OutputScale );
|
||||||
|
t.positional[bucket] = static_cast<Value>( positional / OutputScale );
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proceed with the difference calculation if possible
|
static const std::string PieceToChar(" PNBRQK pnbrqk");
|
||||||
void update_eval(const Position& pos) {
|
|
||||||
UpdateAccumulatorIfPossible(pos);
|
|
||||||
|
// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer.
|
||||||
|
// The buffer must have capacity for at least 5 chars.
|
||||||
|
static void format_cp_compact(Value v, char* buffer) {
|
||||||
|
|
||||||
|
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
|
||||||
|
|
||||||
|
int cp = std::abs(100 * v / PawnValueEg);
|
||||||
|
if (cp >= 10000)
|
||||||
|
{
|
||||||
|
buffer[1] = '0' + cp / 10000; cp %= 10000;
|
||||||
|
buffer[2] = '0' + cp / 1000; cp %= 1000;
|
||||||
|
buffer[3] = '0' + cp / 100;
|
||||||
|
buffer[4] = ' ';
|
||||||
|
}
|
||||||
|
else if (cp >= 1000)
|
||||||
|
{
|
||||||
|
buffer[1] = '0' + cp / 1000; cp %= 1000;
|
||||||
|
buffer[2] = '0' + cp / 100; cp %= 100;
|
||||||
|
buffer[3] = '.';
|
||||||
|
buffer[4] = '0' + cp / 10;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer[1] = '0' + cp / 100; cp %= 100;
|
||||||
|
buffer[2] = '.';
|
||||||
|
buffer[3] = '0' + cp / 10; cp %= 10;
|
||||||
|
buffer[4] = '0' + cp / 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Eval::NNUE
|
|
||||||
|
// format_cp_aligned_dot() converts a Value into (centi)pawns and writes it in a buffer,
|
||||||
|
// always keeping two decimals. The buffer must have capacity for at least 7 chars.
|
||||||
|
static void format_cp_aligned_dot(Value v, char* buffer) {
|
||||||
|
|
||||||
|
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
|
||||||
|
|
||||||
|
double cp = 1.0 * std::abs(int(v)) / PawnValueEg;
|
||||||
|
sprintf(&buffer[1], "%6.2f", cp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// trace() returns a string with the value of each piece on a board,
|
||||||
|
// and a table for (PSQT, Layers) values bucket by bucket.
|
||||||
|
|
||||||
|
std::string trace(Position& pos) {
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
|
||||||
|
char board[3*8+1][8*8+2];
|
||||||
|
std::memset(board, ' ', sizeof(board));
|
||||||
|
for (int row = 0; row < 3*8+1; ++row)
|
||||||
|
board[row][8*8+1] = '\0';
|
||||||
|
|
||||||
|
// A lambda to output one box of the board
|
||||||
|
auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) {
|
||||||
|
|
||||||
|
const int x = ((int)file) * 8;
|
||||||
|
const int y = (7 - (int)rank) * 3;
|
||||||
|
for (int i = 1; i < 8; ++i)
|
||||||
|
board[y][x+i] = board[y+3][x+i] = '-';
|
||||||
|
for (int i = 1; i < 3; ++i)
|
||||||
|
board[y+i][x] = board[y+i][x+8] = '|';
|
||||||
|
board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+';
|
||||||
|
if (pc != NO_PIECE)
|
||||||
|
board[y+1][x+4] = PieceToChar[pc];
|
||||||
|
if (value != VALUE_NONE)
|
||||||
|
format_cp_compact(value, &board[y+2][x+2]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// We estimate the value of each piece by doing a differential evaluation from
|
||||||
|
// the current base eval, simulating the removal of the piece from its square.
|
||||||
|
Value base = evaluate(pos);
|
||||||
|
base = pos.side_to_move() == WHITE ? base : -base;
|
||||||
|
|
||||||
|
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||||
|
for (Rank r = RANK_1; r <= RANK_8; ++r)
|
||||||
|
{
|
||||||
|
Square sq = make_square(f, r);
|
||||||
|
Piece pc = pos.piece_on(sq);
|
||||||
|
Value v = VALUE_NONE;
|
||||||
|
|
||||||
|
if (pc != NO_PIECE && type_of(pc) != KING)
|
||||||
|
{
|
||||||
|
auto st = pos.state();
|
||||||
|
|
||||||
|
pos.remove_piece(sq);
|
||||||
|
st->accumulator.computed[WHITE] = false;
|
||||||
|
st->accumulator.computed[BLACK] = false;
|
||||||
|
|
||||||
|
Value eval = evaluate(pos);
|
||||||
|
eval = pos.side_to_move() == WHITE ? eval : -eval;
|
||||||
|
v = base - eval;
|
||||||
|
|
||||||
|
pos.put_piece(pc, sq);
|
||||||
|
st->accumulator.computed[WHITE] = false;
|
||||||
|
st->accumulator.computed[BLACK] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSquare(f, r, pc, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << " NNUE derived piece values:\n";
|
||||||
|
for (int row = 0; row < 3*8+1; ++row)
|
||||||
|
ss << board[row] << '\n';
|
||||||
|
ss << '\n';
|
||||||
|
|
||||||
|
auto t = trace_evaluate(pos);
|
||||||
|
|
||||||
|
ss << " NNUE network contributions "
|
||||||
|
<< (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl
|
||||||
|
<< "+------------+------------+------------+------------+\n"
|
||||||
|
<< "| Bucket | Material | Positional | Total |\n"
|
||||||
|
<< "| | (PSQT) | (Layers) | |\n"
|
||||||
|
<< "+------------+------------+------------+------------+\n";
|
||||||
|
|
||||||
|
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)
|
||||||
|
{
|
||||||
|
char buffer[3][8];
|
||||||
|
std::memset(buffer, '\0', sizeof(buffer));
|
||||||
|
|
||||||
|
format_cp_aligned_dot(t.psqt[bucket], buffer[0]);
|
||||||
|
format_cp_aligned_dot(t.positional[bucket], buffer[1]);
|
||||||
|
format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]);
|
||||||
|
|
||||||
|
ss << "| " << bucket << " "
|
||||||
|
<< " | " << buffer[0] << " "
|
||||||
|
<< " | " << buffer[1] << " "
|
||||||
|
<< " | " << buffer[2] << " "
|
||||||
|
<< " |";
|
||||||
|
if (bucket == t.correctBucket)
|
||||||
|
ss << " <-- this bucket is used";
|
||||||
|
ss << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << "+------------+------------+------------+------------+\n";
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Load eval, from a file stream or a memory stream
|
||||||
|
bool load_eval(std::string name, std::istream& stream) {
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
fileName = name;
|
||||||
|
return read_parameters(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save eval, to a file stream or a memory stream
|
||||||
|
bool save_eval(std::ostream& stream) {
|
||||||
|
|
||||||
|
if (fileName.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return write_parameters(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save eval, to a file given by its name
|
||||||
|
bool save_eval(const std::optional<std::string>& filename) {
|
||||||
|
|
||||||
|
std::string actualFilename;
|
||||||
|
std::string msg;
|
||||||
|
|
||||||
|
if (filename.has_value())
|
||||||
|
actualFilename = filename.value();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (currentEvalFileName != EvalFileDefaultName)
|
||||||
|
{
|
||||||
|
msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified";
|
||||||
|
|
||||||
|
sync_cout << msg << sync_endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
actualFilename = EvalFileDefaultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream stream(actualFilename, std::ios_base::binary);
|
||||||
|
bool saved = save_eval(stream);
|
||||||
|
|
||||||
|
msg = saved ? "Network saved successfully to " + actualFilename
|
||||||
|
: "Failed to export a net";
|
||||||
|
|
||||||
|
sync_cout << msg << sync_endl;
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace Eval::NNUE {
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
// Hash value of evaluation function structure
|
// Hash value of evaluation function structure
|
||||||
constexpr std::uint32_t kHashValue =
|
constexpr std::uint32_t HashValue =
|
||||||
FeatureTransformer::GetHashValue() ^ Network::GetHashValue();
|
FeatureTransformer::get_hash_value() ^ Network::get_hash_value();
|
||||||
|
|
||||||
// Deleter for automating release of memory area
|
// Deleter for automating release of memory area
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -40,9 +40,20 @@ namespace Eval::NNUE {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct LargePageDeleter {
|
||||||
|
void operator()(T* ptr) const {
|
||||||
|
ptr->~T();
|
||||||
|
aligned_large_pages_free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
|
using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
|
||||||
|
|
||||||
} // namespace Eval::NNUE
|
template <typename T>
|
||||||
|
using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
|
||||||
#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
|
#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// A class template that represents the input feature set of the NNUE evaluation function
|
|
||||||
|
|
||||||
#ifndef NNUE_FEATURE_SET_H_INCLUDED
|
|
||||||
#define NNUE_FEATURE_SET_H_INCLUDED
|
|
||||||
|
|
||||||
#include "features_common.h"
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
namespace Eval::NNUE::Features {
|
|
||||||
|
|
||||||
// Class template that represents a list of values
|
|
||||||
template <typename T, T... Values>
|
|
||||||
struct CompileTimeList;
|
|
||||||
|
|
||||||
template <typename T, T First, T... Remaining>
|
|
||||||
struct CompileTimeList<T, First, Remaining...> {
|
|
||||||
static constexpr bool Contains(T value) {
|
|
||||||
return value == First || CompileTimeList<T, Remaining...>::Contains(value);
|
|
||||||
}
|
|
||||||
static constexpr std::array<T, sizeof...(Remaining) + 1>
|
|
||||||
kValues = {{First, Remaining...}};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Base class of feature set
|
|
||||||
template <typename Derived>
|
|
||||||
class FeatureSetBase {
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Get a list of indices for active features
|
|
||||||
template <typename IndexListType>
|
|
||||||
static void AppendActiveIndices(
|
|
||||||
const Position& pos, TriggerEvent trigger, IndexListType active[2]) {
|
|
||||||
|
|
||||||
for (Color perspective : { WHITE, BLACK }) {
|
|
||||||
Derived::CollectActiveIndices(
|
|
||||||
pos, trigger, perspective, &active[perspective]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of indices for recently changed features
|
|
||||||
template <typename PositionType, typename IndexListType>
|
|
||||||
static void AppendChangedIndices(
|
|
||||||
const PositionType& pos, TriggerEvent trigger,
|
|
||||||
IndexListType removed[2], IndexListType added[2], bool reset[2]) {
|
|
||||||
|
|
||||||
const auto& dp = pos.state()->dirtyPiece;
|
|
||||||
if (dp.dirty_num == 0) return;
|
|
||||||
|
|
||||||
for (Color perspective : { WHITE, BLACK }) {
|
|
||||||
reset[perspective] = false;
|
|
||||||
switch (trigger) {
|
|
||||||
case TriggerEvent::kFriendKingMoved:
|
|
||||||
reset[perspective] =
|
|
||||||
dp.pieceId[0] == PIECE_ID_KING + perspective;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (reset[perspective]) {
|
|
||||||
Derived::CollectActiveIndices(
|
|
||||||
pos, trigger, perspective, &added[perspective]);
|
|
||||||
} else {
|
|
||||||
Derived::CollectChangedIndices(
|
|
||||||
pos, trigger, perspective,
|
|
||||||
&removed[perspective], &added[perspective]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Class template that represents the feature set
|
|
||||||
template <typename FeatureType>
|
|
||||||
class FeatureSet<FeatureType> : public FeatureSetBase<FeatureSet<FeatureType>> {
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Hash value embedded in the evaluation file
|
|
||||||
static constexpr std::uint32_t kHashValue = FeatureType::kHashValue;
|
|
||||||
// Number of feature dimensions
|
|
||||||
static constexpr IndexType kDimensions = FeatureType::kDimensions;
|
|
||||||
// Maximum number of simultaneously active features
|
|
||||||
static constexpr IndexType kMaxActiveDimensions =
|
|
||||||
FeatureType::kMaxActiveDimensions;
|
|
||||||
// Trigger for full calculation instead of difference calculation
|
|
||||||
using SortedTriggerSet =
|
|
||||||
CompileTimeList<TriggerEvent, FeatureType::kRefreshTrigger>;
|
|
||||||
static constexpr auto kRefreshTriggers = SortedTriggerSet::kValues;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Get a list of indices for active features
|
|
||||||
static void CollectActiveIndices(
|
|
||||||
const Position& pos, const TriggerEvent trigger, const Color perspective,
|
|
||||||
IndexList* const active) {
|
|
||||||
if (FeatureType::kRefreshTrigger == trigger) {
|
|
||||||
FeatureType::AppendActiveIndices(pos, perspective, active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of indices for recently changed features
|
|
||||||
static void CollectChangedIndices(
|
|
||||||
const Position& pos, const TriggerEvent trigger, const Color perspective,
|
|
||||||
IndexList* const removed, IndexList* const added) {
|
|
||||||
|
|
||||||
if (FeatureType::kRefreshTrigger == trigger) {
|
|
||||||
FeatureType::AppendChangedIndices(pos, perspective, removed, added);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the base class and the class template that recursively uses itself a friend
|
|
||||||
friend class FeatureSetBase<FeatureSet>;
|
|
||||||
template <typename... FeatureTypes>
|
|
||||||
friend class FeatureSet;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Eval::NNUE::Features
|
|
||||||
|
|
||||||
#endif // #ifndef NNUE_FEATURE_SET_H_INCLUDED
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Definition of input features HalfKAv2_hm of NNUE evaluation function
|
||||||
|
|
||||||
|
#include "half_ka_v2_hm.h"
|
||||||
|
|
||||||
|
#include "../../position.h"
|
||||||
|
|
||||||
|
namespace Stockfish::Eval::NNUE::Features {
|
||||||
|
|
||||||
|
// Orient a square according to perspective (rotates by 180 for black)
|
||||||
|
inline Square HalfKAv2_hm::orient(Color perspective, Square s, Square ksq) {
|
||||||
|
return Square(int(s) ^ (bool(perspective) * SQ_A8) ^ ((file_of(ksq) < FILE_E) * SQ_H1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index of a feature for a given king position and another piece on some square
|
||||||
|
inline IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) {
|
||||||
|
Square o_ksq = orient(perspective, ksq, ksq);
|
||||||
|
return IndexType(orient(perspective, s, ksq) + PieceSquareIndex[perspective][pc] + PS_NB * KingBuckets[o_ksq]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of indices for active features
|
||||||
|
void HalfKAv2_hm::append_active_indices(
|
||||||
|
const Position& pos,
|
||||||
|
Color perspective,
|
||||||
|
IndexList& active
|
||||||
|
) {
|
||||||
|
Square ksq = pos.square<KING>(perspective);
|
||||||
|
Bitboard bb = pos.pieces();
|
||||||
|
while (bb)
|
||||||
|
{
|
||||||
|
Square s = pop_lsb(bb);
|
||||||
|
active.push_back(make_index(perspective, s, pos.piece_on(s), ksq));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// append_changed_indices() : get a list of indices for recently changed features
|
||||||
|
|
||||||
|
void HalfKAv2_hm::append_changed_indices(
|
||||||
|
Square ksq,
|
||||||
|
const DirtyPiece& dp,
|
||||||
|
Color perspective,
|
||||||
|
IndexList& removed,
|
||||||
|
IndexList& added
|
||||||
|
) {
|
||||||
|
for (int i = 0; i < dp.dirty_num; ++i) {
|
||||||
|
if (dp.from[i] != SQ_NONE)
|
||||||
|
removed.push_back(make_index(perspective, dp.from[i], dp.piece[i], ksq));
|
||||||
|
if (dp.to[i] != SQ_NONE)
|
||||||
|
added.push_back(make_index(perspective, dp.to[i], dp.piece[i], ksq));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int HalfKAv2_hm::update_cost(const StateInfo* st) {
|
||||||
|
return st->dirtyPiece.dirty_num;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HalfKAv2_hm::refresh_cost(const Position& pos) {
|
||||||
|
return pos.count<ALL_PIECES>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
|
||||||
|
return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE::Features
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Definition of input features HalfKP of NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
||||||
|
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
|
#include "../../evaluate.h"
|
||||||
|
#include "../../misc.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
struct StateInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Stockfish::Eval::NNUE::Features {
|
||||||
|
|
||||||
|
// Feature HalfKAv2_hm: Combination of the position of own king
|
||||||
|
// and the position of pieces. Position mirrored such that king always on e..h files.
|
||||||
|
class HalfKAv2_hm {
|
||||||
|
|
||||||
|
// unique number for each piece type on each square
|
||||||
|
enum {
|
||||||
|
PS_NONE = 0,
|
||||||
|
PS_W_PAWN = 0,
|
||||||
|
PS_B_PAWN = 1 * SQUARE_NB,
|
||||||
|
PS_W_KNIGHT = 2 * SQUARE_NB,
|
||||||
|
PS_B_KNIGHT = 3 * SQUARE_NB,
|
||||||
|
PS_W_BISHOP = 4 * SQUARE_NB,
|
||||||
|
PS_B_BISHOP = 5 * SQUARE_NB,
|
||||||
|
PS_W_ROOK = 6 * SQUARE_NB,
|
||||||
|
PS_B_ROOK = 7 * SQUARE_NB,
|
||||||
|
PS_W_QUEEN = 8 * SQUARE_NB,
|
||||||
|
PS_B_QUEEN = 9 * SQUARE_NB,
|
||||||
|
PS_KING = 10 * SQUARE_NB,
|
||||||
|
PS_NB = 11 * SQUARE_NB
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
|
||||||
|
// convention: W - us, B - them
|
||||||
|
// viewed from other side, W and B are reversed
|
||||||
|
{ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,
|
||||||
|
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE },
|
||||||
|
{ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
|
||||||
|
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Orient a square according to perspective (rotates by 180 for black)
|
||||||
|
static Square orient(Color perspective, Square s, Square ksq);
|
||||||
|
|
||||||
|
// 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 ksq);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Feature name
|
||||||
|
static constexpr const char* Name = "HalfKAv2_hm(Friend)";
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t HashValue = 0x7f234cb8u;
|
||||||
|
|
||||||
|
// Number of feature dimensions
|
||||||
|
static constexpr IndexType Dimensions =
|
||||||
|
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
|
||||||
|
|
||||||
|
static constexpr int KingBuckets[64] = {
|
||||||
|
-1, -1, -1, -1, 31, 30, 29, 28,
|
||||||
|
-1, -1, -1, -1, 27, 26, 25, 24,
|
||||||
|
-1, -1, -1, -1, 23, 22, 21, 20,
|
||||||
|
-1, -1, -1, -1, 19, 18, 17, 16,
|
||||||
|
-1, -1, -1, -1, 15, 14, 13, 12,
|
||||||
|
-1, -1, -1, -1, 11, 10, 9, 8,
|
||||||
|
-1, -1, -1, -1, 7, 6, 5, 4,
|
||||||
|
-1, -1, -1, -1, 3, 2, 1, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maximum number of simultaneously active features.
|
||||||
|
static constexpr IndexType MaxActiveDimensions = 32;
|
||||||
|
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
Square ksq,
|
||||||
|
const DirtyPiece& dp,
|
||||||
|
Color perspective,
|
||||||
|
IndexList& removed,
|
||||||
|
IndexList& added
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns the cost of updating one perspective, the most costly one.
|
||||||
|
// Assumes no refresh needed.
|
||||||
|
static int update_cost(const StateInfo* st);
|
||||||
|
static int refresh_cost(const Position& pos);
|
||||||
|
|
||||||
|
// Returns whether the change stored in this StateInfo means that
|
||||||
|
// a full accumulator refresh is required.
|
||||||
|
static bool requires_refresh(const StateInfo* st, Color perspective);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE::Features
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Definition of input features HalfKP of NNUE evaluation function
|
|
||||||
|
|
||||||
#include "half_kp.h"
|
|
||||||
#include "index_list.h"
|
|
||||||
|
|
||||||
namespace Eval::NNUE::Features {
|
|
||||||
|
|
||||||
// Find the index of the feature quantity from the king position and PieceSquare
|
|
||||||
template <Side AssociatedKing>
|
|
||||||
inline IndexType HalfKP<AssociatedKing>::MakeIndex(Square sq_k, PieceSquare p) {
|
|
||||||
return static_cast<IndexType>(PS_END) * static_cast<IndexType>(sq_k) + p;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get pieces information
|
|
||||||
template <Side AssociatedKing>
|
|
||||||
inline void HalfKP<AssociatedKing>::GetPieces(
|
|
||||||
const Position& pos, Color perspective,
|
|
||||||
PieceSquare** pieces, Square* sq_target_k) {
|
|
||||||
|
|
||||||
*pieces = (perspective == BLACK) ?
|
|
||||||
pos.eval_list()->piece_list_fb() :
|
|
||||||
pos.eval_list()->piece_list_fw();
|
|
||||||
const PieceId target = (AssociatedKing == Side::kFriend) ?
|
|
||||||
static_cast<PieceId>(PIECE_ID_KING + perspective) :
|
|
||||||
static_cast<PieceId>(PIECE_ID_KING + ~perspective);
|
|
||||||
*sq_target_k = static_cast<Square>(((*pieces)[target] - PS_W_KING) % SQUARE_NB);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of indices for active features
|
|
||||||
template <Side AssociatedKing>
|
|
||||||
void HalfKP<AssociatedKing>::AppendActiveIndices(
|
|
||||||
const Position& pos, Color perspective, IndexList* active) {
|
|
||||||
|
|
||||||
// Do nothing if array size is small to avoid compiler warning
|
|
||||||
if (RawFeatures::kMaxActiveDimensions < kMaxActiveDimensions) return;
|
|
||||||
|
|
||||||
PieceSquare* pieces;
|
|
||||||
Square sq_target_k;
|
|
||||||
GetPieces(pos, perspective, &pieces, &sq_target_k);
|
|
||||||
for (PieceId i = PIECE_ID_ZERO; i < PIECE_ID_KING; ++i) {
|
|
||||||
if (pieces[i] != PS_NONE) {
|
|
||||||
active->push_back(MakeIndex(sq_target_k, pieces[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of indices for recently changed features
|
|
||||||
template <Side AssociatedKing>
|
|
||||||
void HalfKP<AssociatedKing>::AppendChangedIndices(
|
|
||||||
const Position& pos, Color perspective,
|
|
||||||
IndexList* removed, IndexList* added) {
|
|
||||||
|
|
||||||
PieceSquare* pieces;
|
|
||||||
Square sq_target_k;
|
|
||||||
GetPieces(pos, perspective, &pieces, &sq_target_k);
|
|
||||||
const auto& dp = pos.state()->dirtyPiece;
|
|
||||||
for (int i = 0; i < dp.dirty_num; ++i) {
|
|
||||||
if (dp.pieceId[i] >= PIECE_ID_KING) continue;
|
|
||||||
const auto old_p = static_cast<PieceSquare>(
|
|
||||||
dp.old_piece[i].from[perspective]);
|
|
||||||
if (old_p != PS_NONE) {
|
|
||||||
removed->push_back(MakeIndex(sq_target_k, old_p));
|
|
||||||
}
|
|
||||||
const auto new_p = static_cast<PieceSquare>(
|
|
||||||
dp.new_piece[i].from[perspective]);
|
|
||||||
if (new_p != PS_NONE) {
|
|
||||||
added->push_back(MakeIndex(sq_target_k, new_p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template class HalfKP<Side::kFriend>;
|
|
||||||
|
|
||||||
} // namespace Eval::NNUE::Features
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Definition of input features HalfKP of NNUE evaluation function
|
|
||||||
|
|
||||||
#ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED
|
|
||||||
#define NNUE_FEATURES_HALF_KP_H_INCLUDED
|
|
||||||
|
|
||||||
#include "../../evaluate.h"
|
|
||||||
#include "features_common.h"
|
|
||||||
|
|
||||||
namespace Eval::NNUE::Features {
|
|
||||||
|
|
||||||
// Feature HalfKP: Combination of the position of own king
|
|
||||||
// and the position of pieces other than kings
|
|
||||||
template <Side AssociatedKing>
|
|
||||||
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<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_END);
|
|
||||||
// Maximum number of simultaneously active features
|
|
||||||
static constexpr IndexType kMaxActiveDimensions = PIECE_ID_KING;
|
|
||||||
// Trigger for full calculation instead of difference calculation
|
|
||||||
static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kFriendKingMoved;
|
|
||||||
|
|
||||||
// Get a list of indices for active features
|
|
||||||
static void AppendActiveIndices(const Position& pos, Color perspective,
|
|
||||||
IndexList* active);
|
|
||||||
|
|
||||||
// Get a list of indices for recently changed features
|
|
||||||
static void AppendChangedIndices(const Position& pos, Color perspective,
|
|
||||||
IndexList* removed, IndexList* added);
|
|
||||||
|
|
||||||
// Index of a feature for a given king position and another piece on some square
|
|
||||||
static IndexType MakeIndex(Square sq_k, PieceSquare p);
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Get pieces information
|
|
||||||
static void GetPieces(const Position& pos, Color perspective,
|
|
||||||
PieceSquare** pieces, Square* sq_target_k);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Eval::NNUE::Features
|
|
||||||
|
|
||||||
#endif // #ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Definition of index list of input features
|
|
||||||
|
|
||||||
#ifndef NNUE_FEATURES_INDEX_LIST_H_INCLUDED
|
|
||||||
#define NNUE_FEATURES_INDEX_LIST_H_INCLUDED
|
|
||||||
|
|
||||||
#include "../../position.h"
|
|
||||||
#include "../nnue_architecture.h"
|
|
||||||
|
|
||||||
namespace Eval::NNUE::Features {
|
|
||||||
|
|
||||||
// Class template used for feature index list
|
|
||||||
template <typename T, std::size_t MaxSize>
|
|
||||||
class ValueList {
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::size_t size() const { return size_; }
|
|
||||||
void resize(std::size_t size) { size_ = size; }
|
|
||||||
void push_back(const T& value) { values_[size_++] = value; }
|
|
||||||
T& operator[](std::size_t index) { return values_[index]; }
|
|
||||||
T* begin() { return values_; }
|
|
||||||
T* end() { return values_ + size_; }
|
|
||||||
const T& operator[](std::size_t index) const { return values_[index]; }
|
|
||||||
const T* begin() const { return values_; }
|
|
||||||
const T* end() const { return values_ + size_; }
|
|
||||||
|
|
||||||
void swap(ValueList& other) {
|
|
||||||
const std::size_t max_size = std::max(size_, other.size_);
|
|
||||||
for (std::size_t i = 0; i < max_size; ++i) {
|
|
||||||
std::swap(values_[i], other.values_[i]);
|
|
||||||
}
|
|
||||||
std::swap(size_, other.size_);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
T values_[MaxSize];
|
|
||||||
std::size_t size_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Type of feature index list
|
|
||||||
class IndexList
|
|
||||||
: public ValueList<IndexType, RawFeatures::kMaxActiveDimensions> {
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Eval::NNUE::Features
|
|
||||||
|
|
||||||
#endif // NNUE_FEATURES_INDEX_LIST_H_INCLUDED
|
|
||||||
+479
-155
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,180 +22,341 @@
|
|||||||
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <type_traits>
|
||||||
#include "../nnue_common.h"
|
#include "../nnue_common.h"
|
||||||
|
#include "../../simd.h"
|
||||||
|
|
||||||
namespace Eval::NNUE::Layers {
|
/*
|
||||||
|
This file contains the definition for a fully connected layer (aka affine transform).
|
||||||
|
Two approaches are employed, depending on the sizes of the transform.
|
||||||
|
|
||||||
// Affine transformation layer
|
Approach 1:
|
||||||
template <typename PreviousLayer, IndexType OutputDimensions>
|
- used when the PaddedInputDimensions >= 128
|
||||||
class AffineTransform {
|
- uses AVX512 if possible
|
||||||
public:
|
- processes inputs in batches of 2*InputSimdWidth
|
||||||
// Input/output type
|
- so in batches of 128 for AVX512
|
||||||
using InputType = typename PreviousLayer::OutputType;
|
- the weight blocks of size InputSimdWidth are transposed such that
|
||||||
using OutputType = std::int32_t;
|
access is sequential
|
||||||
static_assert(std::is_same<InputType, std::uint8_t>::value, "");
|
- N columns of the weight matrix are processed a time, where N
|
||||||
|
depends on the architecture (the amount of registers)
|
||||||
|
- accumulate + hadd is used
|
||||||
|
|
||||||
// Number of input/output dimensions
|
Approach 2:
|
||||||
static constexpr IndexType kInputDimensions =
|
- used when the PaddedInputDimensions < 128
|
||||||
PreviousLayer::kOutputDimensions;
|
- does not use AVX512
|
||||||
static constexpr IndexType kOutputDimensions = OutputDimensions;
|
- expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32.
|
||||||
static constexpr IndexType kPaddedInputDimensions =
|
- that's why AVX512 is hard to implement
|
||||||
CeilToMultiple<IndexType>(kInputDimensions, kMaxSimdWidth);
|
- expected use-case is small layers
|
||||||
|
- not optimized as well as the approach 1
|
||||||
|
- inputs are processed in chunks of 4, weights are respectively transposed
|
||||||
|
- accumulation happens directly to int32s
|
||||||
|
*/
|
||||||
|
|
||||||
// Size of forward propagation buffer used in this layer
|
namespace Stockfish::Eval::NNUE::Layers {
|
||||||
static constexpr std::size_t kSelfBufferSize =
|
|
||||||
CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize);
|
|
||||||
|
|
||||||
// Size of the forward propagation buffer used from the input layer to this layer
|
// Fallback implementation for older/other architectures.
|
||||||
static constexpr std::size_t kBufferSize =
|
// Identical for both approaches. Requires the input to be padded to at least 16 values.
|
||||||
PreviousLayer::kBufferSize + kSelfBufferSize;
|
#if !defined(USE_SSSE3)
|
||||||
|
template <IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
|
||||||
|
static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input)
|
||||||
|
{
|
||||||
|
# if defined(USE_SSE2)
|
||||||
|
// At least a multiple of 16, with SSE2.
|
||||||
|
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
|
||||||
|
const __m128i Zeros = _mm_setzero_si128();
|
||||||
|
const auto inputVector = reinterpret_cast<const __m128i*>(input);
|
||||||
|
|
||||||
// Hash value embedded in the evaluation file
|
# elif defined(USE_MMX)
|
||||||
static constexpr std::uint32_t GetHashValue() {
|
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 8;
|
||||||
std::uint32_t hash_value = 0xCC03DAE4u;
|
const __m64 Zeros = _mm_setzero_si64();
|
||||||
hash_value += kOutputDimensions;
|
const auto inputVector = reinterpret_cast<const __m64*>(input);
|
||||||
hash_value ^= PreviousLayer::GetHashValue() >> 1;
|
|
||||||
hash_value ^= PreviousLayer::GetHashValue() << 31;
|
# elif defined(USE_NEON)
|
||||||
return hash_value;
|
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
|
||||||
|
const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
|
||||||
|
# endif
|
||||||
|
|
||||||
|
for (IndexType i = 0; i < OutputDimensions; ++i) {
|
||||||
|
const IndexType offset = i * PaddedInputDimensions;
|
||||||
|
|
||||||
|
# if defined(USE_SSE2)
|
||||||
|
__m128i sumLo = _mm_cvtsi32_si128(biases[i]);
|
||||||
|
__m128i sumHi = Zeros;
|
||||||
|
const auto row = reinterpret_cast<const __m128i*>(&weights[offset]);
|
||||||
|
for (IndexType j = 0; j < NumChunks; ++j) {
|
||||||
|
__m128i row_j = _mm_load_si128(&row[j]);
|
||||||
|
__m128i input_j = _mm_load_si128(&inputVector[j]);
|
||||||
|
__m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
|
||||||
|
__m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8);
|
||||||
|
__m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros);
|
||||||
|
__m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros);
|
||||||
|
__m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo);
|
||||||
|
__m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi);
|
||||||
|
sumLo = _mm_add_epi32(sumLo, productLo);
|
||||||
|
sumHi = _mm_add_epi32(sumHi, productHi);
|
||||||
|
}
|
||||||
|
__m128i sum = _mm_add_epi32(sumLo, sumHi);
|
||||||
|
__m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2));
|
||||||
|
sum = _mm_add_epi32(sum, sumHigh_64);
|
||||||
|
__m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2));
|
||||||
|
sum = _mm_add_epi32(sum, sum_second_32);
|
||||||
|
output[i] = _mm_cvtsi128_si32(sum);
|
||||||
|
|
||||||
|
# elif defined(USE_MMX)
|
||||||
|
__m64 sumLo = _mm_cvtsi32_si64(biases[i]);
|
||||||
|
__m64 sumHi = Zeros;
|
||||||
|
const auto row = reinterpret_cast<const __m64*>(&weights[offset]);
|
||||||
|
for (IndexType j = 0; j < NumChunks; ++j) {
|
||||||
|
__m64 row_j = row[j];
|
||||||
|
__m64 input_j = inputVector[j];
|
||||||
|
__m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8);
|
||||||
|
__m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8);
|
||||||
|
__m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros);
|
||||||
|
__m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros);
|
||||||
|
__m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo);
|
||||||
|
__m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi);
|
||||||
|
sumLo = _mm_add_pi32(sumLo, productLo);
|
||||||
|
sumHi = _mm_add_pi32(sumHi, productHi);
|
||||||
|
}
|
||||||
|
__m64 sum = _mm_add_pi32(sumLo, sumHi);
|
||||||
|
sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum));
|
||||||
|
output[i] = _mm_cvtsi64_si32(sum);
|
||||||
|
|
||||||
|
# elif defined(USE_NEON)
|
||||||
|
int32x4_t sum = {biases[i]};
|
||||||
|
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
|
||||||
|
for (IndexType j = 0; j < NumChunks; ++j) {
|
||||||
|
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
|
||||||
|
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
|
||||||
|
sum = vpadalq_s16(sum, product);
|
||||||
|
}
|
||||||
|
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
|
||||||
|
|
||||||
|
# else
|
||||||
|
std::int32_t sum = biases[i];
|
||||||
|
for (IndexType j = 0; j < InputDimensions; ++j) {
|
||||||
|
sum += weights[offset + j] * input[j];
|
||||||
|
}
|
||||||
|
output[i] = sum;
|
||||||
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read network parameters
|
# if defined(USE_MMX)
|
||||||
bool ReadParameters(std::istream& stream) {
|
_mm_empty();
|
||||||
if (!previous_layer_.ReadParameters(stream)) return false;
|
# endif
|
||||||
stream.read(reinterpret_cast<char*>(biases_),
|
}
|
||||||
kOutputDimensions * sizeof(BiasType));
|
#endif
|
||||||
stream.read(reinterpret_cast<char*>(weights_),
|
|
||||||
kOutputDimensions * kPaddedInputDimensions *
|
template <IndexType InDims, IndexType OutDims, typename Enabled = void>
|
||||||
sizeof(WeightType));
|
class AffineTransform;
|
||||||
|
|
||||||
|
// A specialization for large inputs.
|
||||||
|
template <IndexType InDims, IndexType OutDims>
|
||||||
|
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) >= 2*64)>> {
|
||||||
|
public:
|
||||||
|
// Input/output type
|
||||||
|
using InputType = std::uint8_t;
|
||||||
|
using OutputType = std::int32_t;
|
||||||
|
|
||||||
|
// Number of input/output dimensions
|
||||||
|
static constexpr IndexType InputDimensions = InDims;
|
||||||
|
static constexpr IndexType OutputDimensions = OutDims;
|
||||||
|
|
||||||
|
static constexpr IndexType PaddedInputDimensions =
|
||||||
|
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
|
||||||
|
static constexpr IndexType PaddedOutputDimensions =
|
||||||
|
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
|
||||||
|
|
||||||
|
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||||
|
|
||||||
|
static_assert(PaddedInputDimensions >= 128, "Something went wrong. This specialization should not have been chosen.");
|
||||||
|
|
||||||
|
#if defined (USE_AVX512)
|
||||||
|
static constexpr const IndexType InputSimdWidth = 64;
|
||||||
|
static constexpr const IndexType MaxNumOutputRegs = 16;
|
||||||
|
#elif defined (USE_AVX2)
|
||||||
|
static constexpr const IndexType InputSimdWidth = 32;
|
||||||
|
static constexpr const IndexType MaxNumOutputRegs = 8;
|
||||||
|
#elif defined (USE_SSSE3)
|
||||||
|
static constexpr const IndexType InputSimdWidth = 16;
|
||||||
|
static constexpr const IndexType MaxNumOutputRegs = 8;
|
||||||
|
#elif defined (USE_NEON)
|
||||||
|
static constexpr const IndexType InputSimdWidth = 8;
|
||||||
|
static constexpr const IndexType MaxNumOutputRegs = 8;
|
||||||
|
#else
|
||||||
|
// The fallback implementation will not have permuted weights.
|
||||||
|
// We define these to avoid a lot of ifdefs later.
|
||||||
|
static constexpr const IndexType InputSimdWidth = 1;
|
||||||
|
static constexpr const IndexType MaxNumOutputRegs = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs].
|
||||||
|
// A small block is a region of size [InputSimdWidth, 1]
|
||||||
|
|
||||||
|
static constexpr const IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions);
|
||||||
|
static constexpr const IndexType SmallBlockSize = InputSimdWidth;
|
||||||
|
static constexpr const IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions;
|
||||||
|
static constexpr const IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize;
|
||||||
|
static constexpr const IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize;
|
||||||
|
static constexpr const IndexType NumBigBlocks = OutputDimensions / NumOutputRegs;
|
||||||
|
|
||||||
|
static_assert(OutputDimensions % NumOutputRegs == 0);
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||||
|
std::uint32_t hashValue = 0xCC03DAE4u;
|
||||||
|
hashValue += OutputDimensions;
|
||||||
|
hashValue ^= prevHash >> 1;
|
||||||
|
hashValue ^= prevHash << 31;
|
||||||
|
return hashValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Transposes the small blocks within a block.
|
||||||
|
Effectively means that weights can be traversed sequentially during inference.
|
||||||
|
*/
|
||||||
|
static IndexType get_weight_index(IndexType i)
|
||||||
|
{
|
||||||
|
const IndexType smallBlock = (i / SmallBlockSize) % NumSmallBlocksInBigBlock;
|
||||||
|
const IndexType smallBlockCol = smallBlock / NumSmallBlocksPerOutput;
|
||||||
|
const IndexType smallBlockRow = smallBlock % NumSmallBlocksPerOutput;
|
||||||
|
const IndexType bigBlock = i / BigBlockSize;
|
||||||
|
const IndexType rest = i % SmallBlockSize;
|
||||||
|
|
||||||
|
const IndexType idx =
|
||||||
|
bigBlock * BigBlockSize
|
||||||
|
+ smallBlockRow * SmallBlockSize * NumOutputRegs
|
||||||
|
+ smallBlockCol * SmallBlockSize
|
||||||
|
+ rest;
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool read_parameters(std::istream& stream) {
|
||||||
|
for (IndexType i = 0; i < OutputDimensions; ++i)
|
||||||
|
biases[i] = read_little_endian<BiasType>(stream);
|
||||||
|
|
||||||
|
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
|
||||||
|
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
|
||||||
|
|
||||||
|
return !stream.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write network parameters
|
||||||
|
bool write_parameters(std::ostream& stream) const {
|
||||||
|
for (IndexType i = 0; i < OutputDimensions; ++i)
|
||||||
|
write_little_endian<BiasType>(stream, biases[i]);
|
||||||
|
|
||||||
|
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
|
||||||
|
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
|
||||||
|
|
||||||
return !stream.fail();
|
return !stream.fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward propagation
|
// Forward propagation
|
||||||
const OutputType* Propagate(
|
const OutputType* propagate(
|
||||||
const TransformedFeatureType* transformed_features, char* buffer) const {
|
const InputType* input, OutputType* output) const {
|
||||||
const auto input = previous_layer_.Propagate(
|
|
||||||
transformed_features, buffer + kSelfBufferSize);
|
|
||||||
const auto output = reinterpret_cast<OutputType*>(buffer);
|
|
||||||
|
|
||||||
#if defined(USE_AVX512)
|
#if defined (USE_AVX512)
|
||||||
constexpr IndexType kNumChunks = kPaddedInputDimensions / (kSimdWidth * 2);
|
using acc_vec_t = __m512i;
|
||||||
const __m512i kOnes = _mm512_set1_epi16(1);
|
using bias_vec_t = __m128i;
|
||||||
const auto input_vector = reinterpret_cast<const __m512i*>(input);
|
using weight_vec_t = __m512i;
|
||||||
|
using in_vec_t = __m512i;
|
||||||
|
#define vec_zero _mm512_setzero_si512()
|
||||||
|
#define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2
|
||||||
|
#define vec_hadd Simd::m512_hadd
|
||||||
|
#define vec_haddx4 Simd::m512_haddx4
|
||||||
|
#elif defined (USE_AVX2)
|
||||||
|
using acc_vec_t = __m256i;
|
||||||
|
using bias_vec_t = __m128i;
|
||||||
|
using weight_vec_t = __m256i;
|
||||||
|
using in_vec_t = __m256i;
|
||||||
|
#define vec_zero _mm256_setzero_si256()
|
||||||
|
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
|
||||||
|
#define vec_hadd Simd::m256_hadd
|
||||||
|
#define vec_haddx4 Simd::m256_haddx4
|
||||||
|
#elif defined (USE_SSSE3)
|
||||||
|
using acc_vec_t = __m128i;
|
||||||
|
using bias_vec_t = __m128i;
|
||||||
|
using weight_vec_t = __m128i;
|
||||||
|
using in_vec_t = __m128i;
|
||||||
|
#define vec_zero _mm_setzero_si128()
|
||||||
|
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
|
||||||
|
#define vec_hadd Simd::m128_hadd
|
||||||
|
#define vec_haddx4 Simd::m128_haddx4
|
||||||
|
#elif defined (USE_NEON)
|
||||||
|
using acc_vec_t = int32x4_t;
|
||||||
|
using bias_vec_t = int32x4_t;
|
||||||
|
using weight_vec_t = int8x8_t;
|
||||||
|
using in_vec_t = int8x8_t;
|
||||||
|
#define vec_zero {0}
|
||||||
|
#define vec_add_dpbusd_32x2 Simd::neon_m128_add_dpbusd_epi32x2
|
||||||
|
#define vec_hadd Simd::neon_m128_hadd
|
||||||
|
#define vec_haddx4 Simd::neon_m128_haddx4
|
||||||
|
#endif
|
||||||
|
|
||||||
#elif defined(USE_AVX2)
|
#if defined (USE_SSSE3) || defined (USE_NEON)
|
||||||
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
|
const in_vec_t* invec = reinterpret_cast<const in_vec_t*>(input);
|
||||||
const __m256i kOnes = _mm256_set1_epi16(1);
|
|
||||||
const auto input_vector = reinterpret_cast<const __m256i*>(input);
|
|
||||||
|
|
||||||
#elif defined(USE_SSSE3)
|
// Perform accumulation to registers for each big block
|
||||||
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
|
for (IndexType bigBlock = 0; bigBlock < NumBigBlocks; ++bigBlock)
|
||||||
const __m128i kOnes = _mm_set1_epi16(1);
|
{
|
||||||
const auto input_vector = reinterpret_cast<const __m128i*>(input);
|
acc_vec_t acc[NumOutputRegs] = { vec_zero };
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
// Each big block has NumOutputRegs small blocks in each "row", one per register.
|
||||||
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
|
// We process two small blocks at a time to save on one addition without VNNI.
|
||||||
const auto input_vector = reinterpret_cast<const int8x8_t*>(input);
|
for (IndexType smallBlock = 0; smallBlock < NumSmallBlocksPerOutput; smallBlock += 2)
|
||||||
#endif
|
|
||||||
|
|
||||||
for (IndexType i = 0; i < kOutputDimensions; ++i) {
|
|
||||||
const IndexType offset = i * kPaddedInputDimensions;
|
|
||||||
|
|
||||||
#if defined(USE_AVX512)
|
|
||||||
__m512i sum = _mm512_setzero_si512();
|
|
||||||
const auto row = reinterpret_cast<const __m512i*>(&weights_[offset]);
|
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
|
||||||
__m512i product = _mm512_maddubs_epi16(_mm512_loadu_si512(&input_vector[j]), _mm512_load_si512(&row[j]));
|
|
||||||
#else
|
|
||||||
__m512i product = _mm512_maddubs_epi16(_mm512_load_si512(&input_vector[j]), _mm512_load_si512(&row[j]));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
product = _mm512_madd_epi16(product, kOnes);
|
|
||||||
sum = _mm512_add_epi32(sum, product);
|
|
||||||
}
|
|
||||||
output[i] = _mm512_reduce_add_epi32(sum) + biases_[i];
|
|
||||||
|
|
||||||
// Note: Changing kMaxSimdWidth from 32 to 64 breaks loading existing networks.
|
|
||||||
// As a result kPaddedInputDimensions may not be an even multiple of 64(512bit)
|
|
||||||
// and we have to do one more 256bit chunk.
|
|
||||||
if (kPaddedInputDimensions != kNumChunks * kSimdWidth * 2)
|
|
||||||
{
|
{
|
||||||
const auto iv_256 = reinterpret_cast<const __m256i*>(input);
|
const weight_vec_t* weightvec =
|
||||||
const auto row_256 = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
reinterpret_cast<const weight_vec_t*>(
|
||||||
int j = kNumChunks * 2;
|
weights
|
||||||
|
+ bigBlock * BigBlockSize
|
||||||
|
+ smallBlock * SmallBlockSize * NumOutputRegs);
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__) // See HACK comment below in AVX2.
|
const in_vec_t in0 = invec[smallBlock + 0];
|
||||||
__m256i sum256 = _mm256_maddubs_epi16(_mm256_loadu_si256(&iv_256[j]), _mm256_load_si256(&row_256[j]));
|
const in_vec_t in1 = invec[smallBlock + 1];
|
||||||
#else
|
|
||||||
__m256i sum256 = _mm256_maddubs_epi16(_mm256_load_si256(&iv_256[j]), _mm256_load_si256(&row_256[j]));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
sum256 = _mm256_madd_epi16(sum256, _mm256_set1_epi16(1));
|
for (IndexType k = 0; k < NumOutputRegs; ++k)
|
||||||
sum256 = _mm256_hadd_epi32(sum256, sum256);
|
vec_add_dpbusd_32x2(acc[k], in0, weightvec[k], in1, weightvec[k + NumOutputRegs]);
|
||||||
sum256 = _mm256_hadd_epi32(sum256, sum256);
|
|
||||||
const __m128i lo = _mm256_extracti128_si256(sum256, 0);
|
|
||||||
const __m128i hi = _mm256_extracti128_si256(sum256, 1);
|
|
||||||
output[i] += _mm_cvtsi128_si32(lo) + _mm_cvtsi128_si32(hi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(USE_AVX2)
|
// Horizontally add all accumulators.
|
||||||
__m256i sum = _mm256_setzero_si256();
|
if constexpr (NumOutputRegs % 4 == 0)
|
||||||
const auto row = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
{
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
bias_vec_t* outputvec = reinterpret_cast<bias_vec_t*>(output);
|
||||||
__m256i product = _mm256_maddubs_epi16(
|
const bias_vec_t* biasvec = reinterpret_cast<const bias_vec_t*>(biases);
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
for (IndexType k = 0; k < NumOutputRegs; k += 4)
|
||||||
// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Because the binary
|
{
|
||||||
// compiled with g++ in MSYS2 crashes here because the output memory is not aligned
|
const IndexType idx = (bigBlock * NumOutputRegs + k) / 4;
|
||||||
// even though alignas is specified.
|
outputvec[idx] = vec_haddx4(acc[k+0], acc[k+1], acc[k+2], acc[k+3], biasvec[idx]);
|
||||||
_mm256_loadu_si256
|
}
|
||||||
#else
|
|
||||||
_mm256_load_si256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
(&input_vector[j]), _mm256_load_si256(&row[j]));
|
|
||||||
product = _mm256_madd_epi16(product, kOnes);
|
|
||||||
sum = _mm256_add_epi32(sum, product);
|
|
||||||
}
|
}
|
||||||
sum = _mm256_hadd_epi32(sum, sum);
|
else
|
||||||
sum = _mm256_hadd_epi32(sum, sum);
|
{
|
||||||
const __m128i lo = _mm256_extracti128_si256(sum, 0);
|
for (IndexType k = 0; k < NumOutputRegs; ++k)
|
||||||
const __m128i hi = _mm256_extracti128_si256(sum, 1);
|
{
|
||||||
output[i] = _mm_cvtsi128_si32(lo) + _mm_cvtsi128_si32(hi) + biases_[i];
|
const IndexType idx = (bigBlock * NumOutputRegs + k);
|
||||||
|
output[idx] = vec_hadd(acc[k], biases[idx]);
|
||||||
#elif defined(USE_SSSE3)
|
}
|
||||||
__m128i sum = _mm_cvtsi32_si128(biases_[i]);
|
|
||||||
const auto row = reinterpret_cast<const __m128i*>(&weights_[offset]);
|
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
|
||||||
__m128i product = _mm_maddubs_epi16(
|
|
||||||
_mm_load_si128(&input_vector[j]), _mm_load_si128(&row[j]));
|
|
||||||
product = _mm_madd_epi16(product, kOnes);
|
|
||||||
sum = _mm_add_epi32(sum, product);
|
|
||||||
}
|
}
|
||||||
sum = _mm_hadd_epi32(sum, sum);
|
|
||||||
sum = _mm_hadd_epi32(sum, sum);
|
|
||||||
output[i] = _mm_cvtsi128_si32(sum);
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
|
||||||
int32x4_t sum = {biases_[i]};
|
|
||||||
const auto row = reinterpret_cast<const int8x8_t*>(&weights_[offset]);
|
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
|
||||||
int16x8_t product = vmull_s8(input_vector[j * 2], row[j * 2]);
|
|
||||||
product = vmlal_s8(product, input_vector[j * 2 + 1], row[j * 2 + 1]);
|
|
||||||
sum = vpadalq_s16(sum, product);
|
|
||||||
}
|
|
||||||
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
|
|
||||||
|
|
||||||
#else
|
|
||||||
OutputType sum = biases_[i];
|
|
||||||
for (IndexType j = 0; j < kInputDimensions; ++j) {
|
|
||||||
sum += weights_[offset + j] * input[j];
|
|
||||||
}
|
|
||||||
output[i] = sum;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# undef vec_zero
|
||||||
|
# undef vec_add_dpbusd_32x2
|
||||||
|
# undef vec_hadd
|
||||||
|
# undef vec_haddx4
|
||||||
|
#else
|
||||||
|
// Use old implementation for the other architectures.
|
||||||
|
affine_transform_non_ssse3<
|
||||||
|
InputDimensions,
|
||||||
|
PaddedInputDimensions,
|
||||||
|
OutputDimensions>(output, weights, biases, input);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,13 +364,176 @@ namespace Eval::NNUE::Layers {
|
|||||||
using BiasType = OutputType;
|
using BiasType = OutputType;
|
||||||
using WeightType = std::int8_t;
|
using WeightType = std::int8_t;
|
||||||
|
|
||||||
PreviousLayer previous_layer_;
|
alignas(CacheLineSize) BiasType biases[OutputDimensions];
|
||||||
|
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
|
||||||
alignas(kCacheLineSize) BiasType biases_[kOutputDimensions];
|
|
||||||
alignas(kCacheLineSize)
|
|
||||||
WeightType weights_[kOutputDimensions * kPaddedInputDimensions];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Eval::NNUE::Layers
|
template <IndexType InDims, IndexType OutDims>
|
||||||
|
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) < 2*64)>> {
|
||||||
|
public:
|
||||||
|
// Input/output type
|
||||||
|
// Input/output type
|
||||||
|
using InputType = std::uint8_t;
|
||||||
|
using OutputType = std::int32_t;
|
||||||
|
|
||||||
|
// Number of input/output dimensions
|
||||||
|
static constexpr IndexType InputDimensions = InDims;
|
||||||
|
static constexpr IndexType OutputDimensions = OutDims;
|
||||||
|
|
||||||
|
static constexpr IndexType PaddedInputDimensions =
|
||||||
|
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
|
||||||
|
static constexpr IndexType PaddedOutputDimensions =
|
||||||
|
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
|
||||||
|
|
||||||
|
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||||
|
|
||||||
|
static_assert(PaddedInputDimensions < 128, "Something went wrong. This specialization should not have been chosen.");
|
||||||
|
|
||||||
|
#if defined (USE_SSSE3)
|
||||||
|
static constexpr const IndexType OutputSimdWidth = SimdWidth / 4;
|
||||||
|
static constexpr const IndexType InputSimdWidth = SimdWidth;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||||
|
std::uint32_t hashValue = 0xCC03DAE4u;
|
||||||
|
hashValue += OutputDimensions;
|
||||||
|
hashValue ^= prevHash >> 1;
|
||||||
|
hashValue ^= prevHash << 31;
|
||||||
|
return hashValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IndexType get_weight_index_scrambled(IndexType i)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 +
|
||||||
|
i / PaddedInputDimensions * 4 +
|
||||||
|
i % 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IndexType get_weight_index(IndexType i)
|
||||||
|
{
|
||||||
|
#if defined (USE_SSSE3)
|
||||||
|
return get_weight_index_scrambled(i);
|
||||||
|
#else
|
||||||
|
return i;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool read_parameters(std::istream& stream) {
|
||||||
|
for (IndexType i = 0; i < OutputDimensions; ++i)
|
||||||
|
biases[i] = read_little_endian<BiasType>(stream);
|
||||||
|
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
|
||||||
|
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
|
||||||
|
|
||||||
|
return !stream.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write network parameters
|
||||||
|
bool write_parameters(std::ostream& stream) const {
|
||||||
|
for (IndexType i = 0; i < OutputDimensions; ++i)
|
||||||
|
write_little_endian<BiasType>(stream, biases[i]);
|
||||||
|
|
||||||
|
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
|
||||||
|
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
|
||||||
|
|
||||||
|
return !stream.fail();
|
||||||
|
}
|
||||||
|
// Forward propagation
|
||||||
|
const OutputType* propagate(
|
||||||
|
const InputType* input, OutputType* output) const {
|
||||||
|
|
||||||
|
#if defined (USE_AVX2)
|
||||||
|
using vec_t = __m256i;
|
||||||
|
#define vec_setzero _mm256_setzero_si256
|
||||||
|
#define vec_set_32 _mm256_set1_epi32
|
||||||
|
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
||||||
|
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
|
||||||
|
#define vec_add_dpbusd_32x4 Simd::m256_add_dpbusd_epi32x4
|
||||||
|
#define vec_hadd Simd::m256_hadd
|
||||||
|
#define vec_haddx4 Simd::m256_haddx4
|
||||||
|
#elif defined (USE_SSSE3)
|
||||||
|
using vec_t = __m128i;
|
||||||
|
#define vec_setzero _mm_setzero_si128
|
||||||
|
#define vec_set_32 _mm_set1_epi32
|
||||||
|
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
||||||
|
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
|
||||||
|
#define vec_add_dpbusd_32x4 Simd::m128_add_dpbusd_epi32x4
|
||||||
|
#define vec_hadd Simd::m128_hadd
|
||||||
|
#define vec_haddx4 Simd::m128_haddx4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined (USE_SSSE3)
|
||||||
|
const auto inputVector = reinterpret_cast<const vec_t*>(input);
|
||||||
|
|
||||||
|
static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1);
|
||||||
|
|
||||||
|
if constexpr (OutputDimensions % OutputSimdWidth == 0)
|
||||||
|
{
|
||||||
|
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
|
||||||
|
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
|
||||||
|
|
||||||
|
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
|
||||||
|
const vec_t* biasvec = reinterpret_cast<const vec_t*>(biases);
|
||||||
|
vec_t acc[NumRegs];
|
||||||
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
|
acc[k] = biasvec[k];
|
||||||
|
|
||||||
|
for (IndexType i = 0; i < NumChunks; i += 2)
|
||||||
|
{
|
||||||
|
const vec_t in0 = vec_set_32(input32[i + 0]);
|
||||||
|
const vec_t in1 = vec_set_32(input32[i + 1]);
|
||||||
|
const auto col0 = reinterpret_cast<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
|
||||||
|
const auto col1 = reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
|
||||||
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
|
vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec_t* outptr = reinterpret_cast<vec_t*>(output);
|
||||||
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
|
outptr[k] = acc[k];
|
||||||
|
}
|
||||||
|
else if constexpr (OutputDimensions == 1)
|
||||||
|
{
|
||||||
|
constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth;
|
||||||
|
vec_t sum0 = vec_setzero();
|
||||||
|
const auto row0 = reinterpret_cast<const vec_t*>(&weights[0]);
|
||||||
|
|
||||||
|
for (int j = 0; j < (int)NumChunks; ++j)
|
||||||
|
{
|
||||||
|
const vec_t in = inputVector[j];
|
||||||
|
vec_add_dpbusd_32(sum0, in, row0[j]);
|
||||||
|
}
|
||||||
|
output[0] = vec_hadd(sum0, biases[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
# undef vec_setzero
|
||||||
|
# undef vec_set_32
|
||||||
|
# undef vec_add_dpbusd_32
|
||||||
|
# undef vec_add_dpbusd_32x2
|
||||||
|
# undef vec_add_dpbusd_32x4
|
||||||
|
# undef vec_hadd
|
||||||
|
# undef vec_haddx4
|
||||||
|
#else
|
||||||
|
// Use old implementation for the other architectures.
|
||||||
|
affine_transform_non_ssse3<
|
||||||
|
InputDimensions,
|
||||||
|
PaddedInputDimensions,
|
||||||
|
OutputDimensions>(output, weights, biases, input);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using BiasType = OutputType;
|
||||||
|
using WeightType = std::int8_t;
|
||||||
|
|
||||||
|
alignas(CacheLineSize) BiasType biases[OutputDimensions];
|
||||||
|
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE::Layers
|
||||||
|
|
||||||
#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
||||||
|
|||||||
+95
-101
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,164 +23,158 @@
|
|||||||
|
|
||||||
#include "../nnue_common.h"
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
namespace Eval::NNUE::Layers {
|
namespace Stockfish::Eval::NNUE::Layers {
|
||||||
|
|
||||||
// Clipped ReLU
|
// Clipped ReLU
|
||||||
template <typename PreviousLayer>
|
template <IndexType InDims>
|
||||||
class ClippedReLU {
|
class ClippedReLU {
|
||||||
public:
|
public:
|
||||||
// Input/output type
|
// Input/output type
|
||||||
using InputType = typename PreviousLayer::OutputType;
|
using InputType = std::int32_t;
|
||||||
using OutputType = std::uint8_t;
|
using OutputType = std::uint8_t;
|
||||||
static_assert(std::is_same<InputType, std::int32_t>::value, "");
|
|
||||||
|
|
||||||
// Number of input/output dimensions
|
// Number of input/output dimensions
|
||||||
static constexpr IndexType kInputDimensions =
|
static constexpr IndexType InputDimensions = InDims;
|
||||||
PreviousLayer::kOutputDimensions;
|
static constexpr IndexType OutputDimensions = InputDimensions;
|
||||||
static constexpr IndexType kOutputDimensions = kInputDimensions;
|
static constexpr IndexType PaddedOutputDimensions =
|
||||||
|
ceil_to_multiple<IndexType>(OutputDimensions, 32);
|
||||||
|
|
||||||
// Size of forward propagation buffer used in this layer
|
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||||
static constexpr std::size_t kSelfBufferSize =
|
|
||||||
CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize);
|
|
||||||
|
|
||||||
// Size of the forward propagation buffer used from the input layer to this layer
|
|
||||||
static constexpr std::size_t kBufferSize =
|
|
||||||
PreviousLayer::kBufferSize + kSelfBufferSize;
|
|
||||||
|
|
||||||
// Hash value embedded in the evaluation file
|
// Hash value embedded in the evaluation file
|
||||||
static constexpr std::uint32_t GetHashValue() {
|
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||||
std::uint32_t hash_value = 0x538D24C7u;
|
std::uint32_t hashValue = 0x538D24C7u;
|
||||||
hash_value += PreviousLayer::GetHashValue();
|
hashValue += prevHash;
|
||||||
return hash_value;
|
return hashValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read network parameters
|
// Read network parameters
|
||||||
bool ReadParameters(std::istream& stream) {
|
bool read_parameters(std::istream&) {
|
||||||
return previous_layer_.ReadParameters(stream);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write network parameters
|
||||||
|
bool write_parameters(std::ostream&) const {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward propagation
|
// Forward propagation
|
||||||
const OutputType* Propagate(
|
const OutputType* propagate(
|
||||||
const TransformedFeatureType* transformed_features, char* buffer) const {
|
const InputType* input, OutputType* output) const {
|
||||||
const auto input = previous_layer_.Propagate(
|
|
||||||
transformed_features, buffer + kSelfBufferSize);
|
|
||||||
const auto output = reinterpret_cast<OutputType*>(buffer);
|
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
#if defined(USE_AVX2)
|
||||||
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
|
if constexpr (InputDimensions % SimdWidth == 0) {
|
||||||
const __m256i kZero = _mm256_setzero_si256();
|
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||||
const __m256i kOffsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
const __m256i Zero = _mm256_setzero_si256();
|
||||||
const auto in = reinterpret_cast<const __m256i*>(input);
|
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
||||||
const auto out = reinterpret_cast<__m256i*>(output);
|
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||||
for (IndexType i = 0; i < kNumChunks; ++i) {
|
const auto out = reinterpret_cast<__m256i*>(output);
|
||||||
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
|
for (IndexType i = 0; i < NumChunks; ++i) {
|
||||||
|
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
_mm256_load_si256(&in[i * 4 + 0]),
|
||||||
// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Because the binary
|
_mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits);
|
||||||
// compiled with g++ in MSYS2 crashes here because the output memory is not aligned
|
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
|
||||||
// even though alignas is specified.
|
_mm256_load_si256(&in[i * 4 + 2]),
|
||||||
_mm256_loadu_si256
|
_mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits);
|
||||||
#else
|
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
|
||||||
_mm256_load_si256
|
_mm256_packs_epi16(words0, words1), Zero), Offsets));
|
||||||
#endif
|
}
|
||||||
|
} else {
|
||||||
(&in[i * 4 + 0]),
|
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
||||||
|
const __m128i Zero = _mm_setzero_si128();
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||||
_mm256_loadu_si256
|
const auto out = reinterpret_cast<__m128i*>(output);
|
||||||
#else
|
for (IndexType i = 0; i < NumChunks; ++i) {
|
||||||
_mm256_load_si256
|
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
|
||||||
#endif
|
_mm_load_si128(&in[i * 4 + 0]),
|
||||||
|
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
|
||||||
(&in[i * 4 + 1])), kWeightScaleBits);
|
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
|
||||||
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
|
_mm_load_si128(&in[i * 4 + 2]),
|
||||||
|
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
||||||
_mm256_loadu_si256
|
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
|
||||||
#else
|
}
|
||||||
_mm256_load_si256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
(&in[i * 4 + 2]),
|
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
|
||||||
_mm256_loadu_si256
|
|
||||||
#else
|
|
||||||
_mm256_load_si256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
(&in[i * 4 + 3])), kWeightScaleBits);
|
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
|
||||||
_mm256_storeu_si256
|
|
||||||
#else
|
|
||||||
_mm256_store_si256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
|
|
||||||
_mm256_packs_epi16(words0, words1), kZero), kOffsets));
|
|
||||||
}
|
}
|
||||||
constexpr IndexType kStart = kNumChunks * kSimdWidth;
|
constexpr IndexType Start =
|
||||||
|
InputDimensions % SimdWidth == 0
|
||||||
|
? InputDimensions / SimdWidth * SimdWidth
|
||||||
|
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
|
||||||
|
|
||||||
#elif defined(USE_SSSE3)
|
#elif defined(USE_SSE2)
|
||||||
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
|
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||||
|
|
||||||
#ifdef USE_SSE41
|
#ifdef USE_SSE41
|
||||||
const __m128i kZero = _mm_setzero_si128();
|
const __m128i Zero = _mm_setzero_si128();
|
||||||
#else
|
#else
|
||||||
const __m128i k0x80s = _mm_set1_epi8(-128);
|
const __m128i k0x80s = _mm_set1_epi8(-128);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||||
const auto out = reinterpret_cast<__m128i*>(output);
|
const auto out = reinterpret_cast<__m128i*>(output);
|
||||||
for (IndexType i = 0; i < kNumChunks; ++i) {
|
for (IndexType i = 0; i < NumChunks; ++i) {
|
||||||
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
|
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
|
||||||
_mm_load_si128(&in[i * 4 + 0]),
|
_mm_load_si128(&in[i * 4 + 0]),
|
||||||
_mm_load_si128(&in[i * 4 + 1])), kWeightScaleBits);
|
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
|
||||||
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
|
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
|
||||||
_mm_load_si128(&in[i * 4 + 2]),
|
_mm_load_si128(&in[i * 4 + 2]),
|
||||||
_mm_load_si128(&in[i * 4 + 3])), kWeightScaleBits);
|
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
|
||||||
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
||||||
_mm_store_si128(&out[i],
|
_mm_store_si128(&out[i],
|
||||||
|
|
||||||
#ifdef USE_SSE41
|
#ifdef USE_SSE41
|
||||||
_mm_max_epi8(packedbytes, kZero)
|
_mm_max_epi8(packedbytes, Zero)
|
||||||
#else
|
#else
|
||||||
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
constexpr IndexType kStart = kNumChunks * kSimdWidth;
|
constexpr IndexType Start = NumChunks * SimdWidth;
|
||||||
|
|
||||||
|
#elif defined(USE_MMX)
|
||||||
|
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||||
|
const __m64 k0x80s = _mm_set1_pi8(-128);
|
||||||
|
const auto in = reinterpret_cast<const __m64*>(input);
|
||||||
|
const auto out = reinterpret_cast<__m64*>(output);
|
||||||
|
for (IndexType i = 0; i < NumChunks; ++i) {
|
||||||
|
const __m64 words0 = _mm_srai_pi16(
|
||||||
|
_mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]),
|
||||||
|
WeightScaleBits);
|
||||||
|
const __m64 words1 = _mm_srai_pi16(
|
||||||
|
_mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]),
|
||||||
|
WeightScaleBits);
|
||||||
|
const __m64 packedbytes = _mm_packs_pi16(words0, words1);
|
||||||
|
out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
|
||||||
|
}
|
||||||
|
_mm_empty();
|
||||||
|
constexpr IndexType Start = NumChunks * SimdWidth;
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
#elif defined(USE_NEON)
|
||||||
constexpr IndexType kNumChunks = kInputDimensions / (kSimdWidth / 2);
|
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
||||||
const int8x8_t kZero = {0};
|
const int8x8_t Zero = {0};
|
||||||
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
||||||
const auto out = reinterpret_cast<int8x8_t*>(output);
|
const auto out = reinterpret_cast<int8x8_t*>(output);
|
||||||
for (IndexType i = 0; i < kNumChunks; ++i) {
|
for (IndexType i = 0; i < NumChunks; ++i) {
|
||||||
int16x8_t shifted;
|
int16x8_t shifted;
|
||||||
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
|
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
|
||||||
pack[0] = vqshrn_n_s32(in[i * 2 + 0], kWeightScaleBits);
|
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);
|
||||||
pack[1] = vqshrn_n_s32(in[i * 2 + 1], kWeightScaleBits);
|
pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits);
|
||||||
out[i] = vmax_s8(vqmovn_s16(shifted), kZero);
|
out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
|
||||||
}
|
}
|
||||||
constexpr IndexType kStart = kNumChunks * (kSimdWidth / 2);
|
constexpr IndexType Start = NumChunks * (SimdWidth / 2);
|
||||||
#else
|
#else
|
||||||
constexpr IndexType kStart = 0;
|
constexpr IndexType Start = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (IndexType i = kStart; i < kInputDimensions; ++i) {
|
for (IndexType i = Start; i < InputDimensions; ++i) {
|
||||||
output[i] = static_cast<OutputType>(
|
output[i] = static_cast<OutputType>(
|
||||||
std::max(0, std::min(127, input[i] >> kWeightScaleBits)));
|
std::max(0, std::min(127, input[i] >> WeightScaleBits)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
PreviousLayer previous_layer_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Eval::NNUE::Layers
|
} // namespace Stockfish::Eval::NNUE::Layers
|
||||||
|
|
||||||
#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Stockfish is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NNUE evaluation function layer InputSlice definition
|
|
||||||
|
|
||||||
#ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
|
|
||||||
#define NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
|
|
||||||
|
|
||||||
#include "../nnue_common.h"
|
|
||||||
|
|
||||||
namespace Eval::NNUE::Layers {
|
|
||||||
|
|
||||||
// Input layer
|
|
||||||
template <IndexType OutputDimensions, IndexType Offset = 0>
|
|
||||||
class InputSlice {
|
|
||||||
public:
|
|
||||||
// Need to maintain alignment
|
|
||||||
static_assert(Offset % kMaxSimdWidth == 0, "");
|
|
||||||
|
|
||||||
// Output type
|
|
||||||
using OutputType = TransformedFeatureType;
|
|
||||||
|
|
||||||
// Output dimensionality
|
|
||||||
static constexpr IndexType kOutputDimensions = OutputDimensions;
|
|
||||||
|
|
||||||
// Size of forward propagation buffer used from the input layer to this layer
|
|
||||||
static constexpr std::size_t kBufferSize = 0;
|
|
||||||
|
|
||||||
// Hash value embedded in the evaluation file
|
|
||||||
static constexpr std::uint32_t GetHashValue() {
|
|
||||||
std::uint32_t hash_value = 0xEC42E90Du;
|
|
||||||
hash_value ^= kOutputDimensions ^ (Offset << 10);
|
|
||||||
return hash_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read network parameters
|
|
||||||
bool ReadParameters(std::istream& /*stream*/) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward propagation
|
|
||||||
const OutputType* Propagate(
|
|
||||||
const TransformedFeatureType* transformed_features,
|
|
||||||
char* /*buffer*/) const {
|
|
||||||
return transformed_features + Offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Layers
|
|
||||||
|
|
||||||
#endif // #ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
|
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Definition of layer ClippedReLU of NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
|
||||||
|
#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
|
namespace Stockfish::Eval::NNUE::Layers {
|
||||||
|
|
||||||
|
// Clipped ReLU
|
||||||
|
template <IndexType InDims>
|
||||||
|
class SqrClippedReLU {
|
||||||
|
public:
|
||||||
|
// Input/output type
|
||||||
|
using InputType = std::int32_t;
|
||||||
|
using OutputType = std::uint8_t;
|
||||||
|
|
||||||
|
// Number of input/output dimensions
|
||||||
|
static constexpr IndexType InputDimensions = InDims;
|
||||||
|
static constexpr IndexType OutputDimensions = InputDimensions;
|
||||||
|
static constexpr IndexType PaddedOutputDimensions =
|
||||||
|
ceil_to_multiple<IndexType>(OutputDimensions, 32);
|
||||||
|
|
||||||
|
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||||
|
std::uint32_t hashValue = 0x538D24C7u;
|
||||||
|
hashValue += prevHash;
|
||||||
|
return hashValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool read_parameters(std::istream&) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write network parameters
|
||||||
|
bool write_parameters(std::ostream&) const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward propagation
|
||||||
|
const OutputType* propagate(
|
||||||
|
const InputType* input, OutputType* output) const {
|
||||||
|
|
||||||
|
#if defined(USE_SSE2)
|
||||||
|
constexpr IndexType NumChunks = InputDimensions / 16;
|
||||||
|
|
||||||
|
#ifdef USE_SSE41
|
||||||
|
const __m128i Zero = _mm_setzero_si128();
|
||||||
|
#else
|
||||||
|
const __m128i k0x80s = _mm_set1_epi8(-128);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static_assert(WeightScaleBits == 6);
|
||||||
|
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||||
|
const auto out = reinterpret_cast<__m128i*>(output);
|
||||||
|
for (IndexType i = 0; i < NumChunks; ++i) {
|
||||||
|
__m128i words0 = _mm_packs_epi32(
|
||||||
|
_mm_load_si128(&in[i * 4 + 0]),
|
||||||
|
_mm_load_si128(&in[i * 4 + 1]));
|
||||||
|
__m128i words1 = _mm_packs_epi32(
|
||||||
|
_mm_load_si128(&in[i * 4 + 2]),
|
||||||
|
_mm_load_si128(&in[i * 4 + 3]));
|
||||||
|
|
||||||
|
// Not sure if
|
||||||
|
words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3);
|
||||||
|
words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3);
|
||||||
|
|
||||||
|
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
||||||
|
|
||||||
|
_mm_store_si128(&out[i],
|
||||||
|
|
||||||
|
#ifdef USE_SSE41
|
||||||
|
_mm_max_epi8(packedbytes, Zero)
|
||||||
|
#else
|
||||||
|
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
constexpr IndexType Start = NumChunks * 16;
|
||||||
|
|
||||||
|
#else
|
||||||
|
constexpr IndexType Start = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (IndexType i = Start; i < InputDimensions; ++i) {
|
||||||
|
output[i] = static_cast<OutputType>(
|
||||||
|
// realy should be /127 but we need to make it fast
|
||||||
|
// needs to be accounted for in the trainer
|
||||||
|
std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE::Layers
|
||||||
|
|
||||||
|
#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,17 +23,15 @@
|
|||||||
|
|
||||||
#include "nnue_architecture.h"
|
#include "nnue_architecture.h"
|
||||||
|
|
||||||
namespace Eval::NNUE {
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
// Class that holds the result of affine transformation of input features
|
// Class that holds the result of affine transformation of input features
|
||||||
struct alignas(32) Accumulator {
|
struct alignas(CacheLineSize) Accumulator {
|
||||||
std::int16_t
|
std::int16_t accumulation[2][TransformedFeatureDimensions];
|
||||||
accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions];
|
std::int32_t psqtAccumulation[2][PSQTBuckets];
|
||||||
Value score;
|
bool computed[2];
|
||||||
bool computed_accumulation;
|
|
||||||
bool computed_score;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Eval::NNUE
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
|
||||||
#endif // NNUE_ACCUMULATOR_H_INCLUDED
|
#endif // NNUE_ACCUMULATOR_H_INCLUDED
|
||||||
|
|||||||
+110
-10
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,18 +21,118 @@
|
|||||||
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
||||||
#define NNUE_ARCHITECTURE_H_INCLUDED
|
#define NNUE_ARCHITECTURE_H_INCLUDED
|
||||||
|
|
||||||
// Defines the network structure
|
#include <memory>
|
||||||
#include "architectures/halfkp_256x2-32-32.h"
|
|
||||||
|
|
||||||
namespace Eval::NNUE {
|
#include "nnue_common.h"
|
||||||
|
|
||||||
static_assert(kTransformedFeatureDimensions % kMaxSimdWidth == 0, "");
|
#include "features/half_ka_v2_hm.h"
|
||||||
static_assert(Network::kOutputDimensions == 1, "");
|
|
||||||
static_assert(std::is_same<Network::OutputType, std::int32_t>::value, "");
|
|
||||||
|
|
||||||
// Trigger for full calculation instead of difference calculation
|
#include "layers/affine_transform.h"
|
||||||
constexpr auto kRefreshTriggers = RawFeatures::kRefreshTriggers;
|
#include "layers/clipped_relu.h"
|
||||||
|
#include "layers/sqr_clipped_relu.h"
|
||||||
|
|
||||||
} // namespace Eval::NNUE
|
#include "../misc.h"
|
||||||
|
|
||||||
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
|
// Input features used in evaluation function
|
||||||
|
using FeatureSet = Features::HalfKAv2_hm;
|
||||||
|
|
||||||
|
// Number of input feature dimensions after conversion
|
||||||
|
constexpr IndexType TransformedFeatureDimensions = 1024;
|
||||||
|
constexpr IndexType PSQTBuckets = 8;
|
||||||
|
constexpr IndexType LayerStacks = 8;
|
||||||
|
|
||||||
|
struct Network
|
||||||
|
{
|
||||||
|
static constexpr int FC_0_OUTPUTS = 15;
|
||||||
|
static constexpr int FC_1_OUTPUTS = 32;
|
||||||
|
|
||||||
|
Layers::AffineTransform<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
|
||||||
|
Layers::SqrClippedReLU<FC_0_OUTPUTS + 1> ac_sqr_0;
|
||||||
|
Layers::ClippedReLU<FC_0_OUTPUTS + 1> ac_0;
|
||||||
|
Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS> fc_1;
|
||||||
|
Layers::ClippedReLU<FC_1_OUTPUTS> ac_1;
|
||||||
|
Layers::AffineTransform<FC_1_OUTPUTS, 1> fc_2;
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t get_hash_value() {
|
||||||
|
// input slice hash
|
||||||
|
std::uint32_t hashValue = 0xEC42E90Du;
|
||||||
|
hashValue ^= TransformedFeatureDimensions * 2;
|
||||||
|
|
||||||
|
hashValue = decltype(fc_0)::get_hash_value(hashValue);
|
||||||
|
hashValue = decltype(ac_0)::get_hash_value(hashValue);
|
||||||
|
hashValue = decltype(fc_1)::get_hash_value(hashValue);
|
||||||
|
hashValue = decltype(ac_1)::get_hash_value(hashValue);
|
||||||
|
hashValue = decltype(fc_2)::get_hash_value(hashValue);
|
||||||
|
|
||||||
|
return hashValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool read_parameters(std::istream& stream) {
|
||||||
|
if (!fc_0.read_parameters(stream)) return false;
|
||||||
|
if (!ac_0.read_parameters(stream)) return false;
|
||||||
|
if (!fc_1.read_parameters(stream)) return false;
|
||||||
|
if (!ac_1.read_parameters(stream)) return false;
|
||||||
|
if (!fc_2.read_parameters(stream)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool write_parameters(std::ostream& stream) const {
|
||||||
|
if (!fc_0.write_parameters(stream)) return false;
|
||||||
|
if (!ac_0.write_parameters(stream)) return false;
|
||||||
|
if (!fc_1.write_parameters(stream)) return false;
|
||||||
|
if (!ac_1.write_parameters(stream)) return false;
|
||||||
|
if (!fc_2.write_parameters(stream)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t propagate(const TransformedFeatureType* transformedFeatures)
|
||||||
|
{
|
||||||
|
struct alignas(CacheLineSize) Buffer
|
||||||
|
{
|
||||||
|
alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out;
|
||||||
|
alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];
|
||||||
|
alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out;
|
||||||
|
alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out;
|
||||||
|
alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out;
|
||||||
|
alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out;
|
||||||
|
|
||||||
|
Buffer()
|
||||||
|
{
|
||||||
|
std::memset(this, 0, sizeof(*this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(__clang__) && (__APPLE__)
|
||||||
|
// workaround for a bug reported with xcode 12
|
||||||
|
static thread_local auto tlsBuffer = std::make_unique<Buffer>();
|
||||||
|
// Access TLS only once, cache result.
|
||||||
|
Buffer& buffer = *tlsBuffer;
|
||||||
|
#else
|
||||||
|
alignas(CacheLineSize) static thread_local Buffer buffer;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fc_0.propagate(transformedFeatures, buffer.fc_0_out);
|
||||||
|
ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out);
|
||||||
|
ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out);
|
||||||
|
std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType));
|
||||||
|
fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out);
|
||||||
|
ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out);
|
||||||
|
fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out);
|
||||||
|
|
||||||
|
// buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in quantized form
|
||||||
|
// but we want 1.0 to be equal to 600*OutputScale
|
||||||
|
std::int32_t fwdOut = int(buffer.fc_0_out[FC_0_OUTPUTS]) * (600*OutputScale) / (127*(1<<WeightScaleBits));
|
||||||
|
std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
|
||||||
|
|
||||||
|
return outputValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
|
||||||
#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
||||||
|
|||||||
+102
-13
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,6 +21,13 @@
|
|||||||
#ifndef NNUE_COMMON_H_INCLUDED
|
#ifndef NNUE_COMMON_H_INCLUDED
|
||||||
#define NNUE_COMMON_H_INCLUDED
|
#define NNUE_COMMON_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../types.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "../misc.h" // for IsLittleEndian
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
#if defined(USE_AVX2)
|
||||||
#include <immintrin.h>
|
#include <immintrin.h>
|
||||||
|
|
||||||
@@ -33,34 +40,40 @@
|
|||||||
#elif defined(USE_SSE2)
|
#elif defined(USE_SSE2)
|
||||||
#include <emmintrin.h>
|
#include <emmintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_MMX)
|
||||||
|
#include <mmintrin.h>
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
#elif defined(USE_NEON)
|
||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Eval::NNUE {
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
// Version of the evaluation file
|
// Version of the evaluation file
|
||||||
constexpr std::uint32_t kVersion = 0x7AF32F16u;
|
constexpr std::uint32_t Version = 0x7AF32F20u;
|
||||||
|
|
||||||
// Constant used in evaluation value calculation
|
// Constant used in evaluation value calculation
|
||||||
constexpr int FV_SCALE = 16;
|
constexpr int OutputScale = 16;
|
||||||
constexpr int kWeightScaleBits = 6;
|
constexpr int WeightScaleBits = 6;
|
||||||
|
|
||||||
// Size of cache line (in bytes)
|
// Size of cache line (in bytes)
|
||||||
constexpr std::size_t kCacheLineSize = 64;
|
constexpr std::size_t CacheLineSize = 64;
|
||||||
|
|
||||||
// SIMD width (in bytes)
|
// SIMD width (in bytes)
|
||||||
#if defined(USE_AVX2)
|
#if defined(USE_AVX2)
|
||||||
constexpr std::size_t kSimdWidth = 32;
|
constexpr std::size_t SimdWidth = 32;
|
||||||
|
|
||||||
#elif defined(USE_SSE2)
|
#elif defined(USE_SSE2)
|
||||||
constexpr std::size_t kSimdWidth = 16;
|
constexpr std::size_t SimdWidth = 16;
|
||||||
|
|
||||||
|
#elif defined(USE_MMX)
|
||||||
|
constexpr std::size_t SimdWidth = 8;
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
#elif defined(USE_NEON)
|
||||||
constexpr std::size_t kSimdWidth = 16;
|
constexpr std::size_t SimdWidth = 16;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
constexpr std::size_t kMaxSimdWidth = 32;
|
constexpr std::size_t MaxSimdWidth = 32;
|
||||||
|
|
||||||
// Type of input feature after conversion
|
// Type of input feature after conversion
|
||||||
using TransformedFeatureType = std::uint8_t;
|
using TransformedFeatureType = std::uint8_t;
|
||||||
@@ -68,10 +81,86 @@ namespace Eval::NNUE {
|
|||||||
|
|
||||||
// Round n up to be a multiple of base
|
// Round n up to be a multiple of base
|
||||||
template <typename IntType>
|
template <typename IntType>
|
||||||
constexpr IntType CeilToMultiple(IntType n, IntType base) {
|
constexpr IntType ceil_to_multiple(IntType n, IntType base) {
|
||||||
return (n + base - 1) / base * base;
|
return (n + base - 1) / base * base;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Eval::NNUE
|
// read_little_endian() is our utility to read an integer (signed or unsigned, any size)
|
||||||
|
// from a stream in little-endian order. We swap the byte order after the read if
|
||||||
|
// necessary to return a result with the byte ordering of the compiling machine.
|
||||||
|
template <typename IntType>
|
||||||
|
inline IntType read_little_endian(std::istream& stream) {
|
||||||
|
IntType result;
|
||||||
|
|
||||||
|
if (IsLittleEndian)
|
||||||
|
stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::uint8_t u[sizeof(IntType)];
|
||||||
|
typename std::make_unsigned<IntType>::type v = 0;
|
||||||
|
|
||||||
|
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
|
||||||
|
for (std::size_t i = 0; i < sizeof(IntType); ++i)
|
||||||
|
v = (v << 8) | u[sizeof(IntType) - i - 1];
|
||||||
|
|
||||||
|
std::memcpy(&result, &v, sizeof(IntType));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write_little_endian() is our utility to write an integer (signed or unsigned, any size)
|
||||||
|
// to a stream in little-endian order. We swap the byte order before the write if
|
||||||
|
// necessary to always write in little endian order, independently of the byte
|
||||||
|
// ordering of the compiling machine.
|
||||||
|
template <typename IntType>
|
||||||
|
inline void write_little_endian(std::ostream& stream, IntType value) {
|
||||||
|
|
||||||
|
if (IsLittleEndian)
|
||||||
|
stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::uint8_t u[sizeof(IntType)];
|
||||||
|
typename std::make_unsigned<IntType>::type v = value;
|
||||||
|
|
||||||
|
std::size_t i = 0;
|
||||||
|
// if constexpr to silence the warning about shift by 8
|
||||||
|
if constexpr (sizeof(IntType) > 1)
|
||||||
|
{
|
||||||
|
for (; i + 1 < sizeof(IntType); ++i)
|
||||||
|
{
|
||||||
|
u[i] = (std::uint8_t)v;
|
||||||
|
v >>= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u[i] = (std::uint8_t)v;
|
||||||
|
|
||||||
|
stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read_little_endian(s, out, N) : read integers in bulk from a little indian stream.
|
||||||
|
// This reads N integers from stream s and put them in array out.
|
||||||
|
template <typename IntType>
|
||||||
|
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
|
||||||
|
if (IsLittleEndian)
|
||||||
|
stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
|
||||||
|
else
|
||||||
|
for (std::size_t i = 0; i < count; ++i)
|
||||||
|
out[i] = read_little_endian<IntType>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write_little_endian(s, values, N) : write integers in bulk to a little indian stream.
|
||||||
|
// This takes N integers from array values and writes them on stream s.
|
||||||
|
template <typename IntType>
|
||||||
|
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
|
||||||
|
if (IsLittleEndian)
|
||||||
|
stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
|
||||||
|
else
|
||||||
|
for (std::size_t i = 0; i < count; ++i)
|
||||||
|
write_little_endian<IntType>(stream, values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
|
||||||
#endif // #ifndef NNUE_COMMON_H_INCLUDED
|
#endif // #ifndef NNUE_COMMON_H_INCLUDED
|
||||||
|
|||||||
+487
-251
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,333 +23,569 @@
|
|||||||
|
|
||||||
#include "nnue_common.h"
|
#include "nnue_common.h"
|
||||||
#include "nnue_architecture.h"
|
#include "nnue_architecture.h"
|
||||||
#include "features/index_list.h"
|
|
||||||
|
#include "../misc.h"
|
||||||
|
#include "../position.h"
|
||||||
|
|
||||||
#include <cstring> // std::memset()
|
#include <cstring> // std::memset()
|
||||||
|
|
||||||
namespace Eval::NNUE {
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
|
using BiasType = std::int16_t;
|
||||||
|
using WeightType = std::int16_t;
|
||||||
|
using PSQTWeightType = std::int32_t;
|
||||||
|
|
||||||
|
// If vector instructions are enabled, we update and refresh the
|
||||||
|
// accumulator tile by tile such that each tile fits in the CPU's
|
||||||
|
// vector registers.
|
||||||
|
#define VECTOR
|
||||||
|
|
||||||
|
static_assert(PSQTBuckets % 8 == 0,
|
||||||
|
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
|
||||||
|
|
||||||
|
#ifdef USE_AVX512
|
||||||
|
typedef __m512i vec_t;
|
||||||
|
typedef __m256i psqt_vec_t;
|
||||||
|
#define vec_load(a) _mm512_load_si512(a)
|
||||||
|
#define vec_store(a,b) _mm512_store_si512(a,b)
|
||||||
|
#define vec_add_16(a,b) _mm512_add_epi16(a,b)
|
||||||
|
#define vec_sub_16(a,b) _mm512_sub_epi16(a,b)
|
||||||
|
#define vec_mul_16(a,b) _mm512_mullo_epi16(a,b)
|
||||||
|
#define vec_zero() _mm512_setzero_epi32()
|
||||||
|
#define vec_set_16(a) _mm512_set1_epi16(a)
|
||||||
|
#define vec_max_16(a,b) _mm512_max_epi16(a,b)
|
||||||
|
#define vec_min_16(a,b) _mm512_min_epi16(a,b)
|
||||||
|
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
|
||||||
|
vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a,7),_mm512_srli_epi16(b,7));
|
||||||
|
return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted);
|
||||||
|
}
|
||||||
|
#define vec_load_psqt(a) _mm256_load_si256(a)
|
||||||
|
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
|
||||||
|
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
|
||||||
|
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
|
||||||
|
#define vec_zero_psqt() _mm256_setzero_si256()
|
||||||
|
#define NumRegistersSIMD 32
|
||||||
|
#define MaxChunkSize 64
|
||||||
|
|
||||||
|
#elif USE_AVX2
|
||||||
|
typedef __m256i vec_t;
|
||||||
|
typedef __m256i psqt_vec_t;
|
||||||
|
#define vec_load(a) _mm256_load_si256(a)
|
||||||
|
#define vec_store(a,b) _mm256_store_si256(a,b)
|
||||||
|
#define vec_add_16(a,b) _mm256_add_epi16(a,b)
|
||||||
|
#define vec_sub_16(a,b) _mm256_sub_epi16(a,b)
|
||||||
|
#define vec_mul_16(a,b) _mm256_mullo_epi16(a,b)
|
||||||
|
#define vec_zero() _mm256_setzero_si256()
|
||||||
|
#define vec_set_16(a) _mm256_set1_epi16(a)
|
||||||
|
#define vec_max_16(a,b) _mm256_max_epi16(a,b)
|
||||||
|
#define vec_min_16(a,b) _mm256_min_epi16(a,b)
|
||||||
|
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
|
||||||
|
vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a,7), _mm256_srli_epi16(b,7));
|
||||||
|
return _mm256_permute4x64_epi64(compacted, 0b11011000);
|
||||||
|
}
|
||||||
|
#define vec_load_psqt(a) _mm256_load_si256(a)
|
||||||
|
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
|
||||||
|
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
|
||||||
|
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
|
||||||
|
#define vec_zero_psqt() _mm256_setzero_si256()
|
||||||
|
#define NumRegistersSIMD 16
|
||||||
|
#define MaxChunkSize 32
|
||||||
|
|
||||||
|
#elif USE_SSE2
|
||||||
|
typedef __m128i vec_t;
|
||||||
|
typedef __m128i psqt_vec_t;
|
||||||
|
#define vec_load(a) (*(a))
|
||||||
|
#define vec_store(a,b) *(a)=(b)
|
||||||
|
#define vec_add_16(a,b) _mm_add_epi16(a,b)
|
||||||
|
#define vec_sub_16(a,b) _mm_sub_epi16(a,b)
|
||||||
|
#define vec_mul_16(a,b) _mm_mullo_epi16(a,b)
|
||||||
|
#define vec_zero() _mm_setzero_si128()
|
||||||
|
#define vec_set_16(a) _mm_set1_epi16(a)
|
||||||
|
#define vec_max_16(a,b) _mm_max_epi16(a,b)
|
||||||
|
#define vec_min_16(a,b) _mm_min_epi16(a,b)
|
||||||
|
#define vec_msb_pack_16(a,b) _mm_packs_epi16(_mm_srli_epi16(a,7),_mm_srli_epi16(b,7))
|
||||||
|
#define vec_load_psqt(a) (*(a))
|
||||||
|
#define vec_store_psqt(a,b) *(a)=(b)
|
||||||
|
#define vec_add_psqt_32(a,b) _mm_add_epi32(a,b)
|
||||||
|
#define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b)
|
||||||
|
#define vec_zero_psqt() _mm_setzero_si128()
|
||||||
|
#define NumRegistersSIMD (Is64Bit ? 16 : 8)
|
||||||
|
#define MaxChunkSize 16
|
||||||
|
|
||||||
|
#elif USE_MMX
|
||||||
|
typedef __m64 vec_t;
|
||||||
|
typedef __m64 psqt_vec_t;
|
||||||
|
#define vec_load(a) (*(a))
|
||||||
|
#define vec_store(a,b) *(a)=(b)
|
||||||
|
#define vec_add_16(a,b) _mm_add_pi16(a,b)
|
||||||
|
#define vec_sub_16(a,b) _mm_sub_pi16(a,b)
|
||||||
|
#define vec_mul_16(a,b) _mm_mullo_pi16(a,b)
|
||||||
|
#define vec_zero() _mm_setzero_si64()
|
||||||
|
#define vec_set_16(a) _mm_set1_pi16(a)
|
||||||
|
inline vec_t vec_max_16(vec_t a,vec_t b){
|
||||||
|
vec_t comparison = _mm_cmpgt_pi16(a,b);
|
||||||
|
return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b));
|
||||||
|
}
|
||||||
|
inline vec_t vec_min_16(vec_t a,vec_t b){
|
||||||
|
vec_t comparison = _mm_cmpgt_pi16(a,b);
|
||||||
|
return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a));
|
||||||
|
}
|
||||||
|
#define vec_msb_pack_16(a,b) _mm_packs_pi16(_mm_srli_pi16(a,7),_mm_srli_pi16(b,7))
|
||||||
|
#define vec_load_psqt(a) (*(a))
|
||||||
|
#define vec_store_psqt(a,b) *(a)=(b)
|
||||||
|
#define vec_add_psqt_32(a,b) _mm_add_pi32(a,b)
|
||||||
|
#define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b)
|
||||||
|
#define vec_zero_psqt() _mm_setzero_si64()
|
||||||
|
#define vec_cleanup() _mm_empty()
|
||||||
|
#define NumRegistersSIMD 8
|
||||||
|
#define MaxChunkSize 8
|
||||||
|
|
||||||
|
#elif USE_NEON
|
||||||
|
typedef int16x8_t vec_t;
|
||||||
|
typedef int32x4_t psqt_vec_t;
|
||||||
|
#define vec_load(a) (*(a))
|
||||||
|
#define vec_store(a,b) *(a)=(b)
|
||||||
|
#define vec_add_16(a,b) vaddq_s16(a,b)
|
||||||
|
#define vec_sub_16(a,b) vsubq_s16(a,b)
|
||||||
|
#define vec_mul_16(a,b) vmulq_s16(a,b)
|
||||||
|
#define vec_zero() vec_t{0}
|
||||||
|
#define vec_set_16(a) vdupq_n_s16(a)
|
||||||
|
#define vec_max_16(a,b) vmaxq_s16(a,b)
|
||||||
|
#define vec_min_16(a,b) vminq_s16(a,b)
|
||||||
|
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
|
||||||
|
const int8x8_t shifta = vshrn_n_s16(a, 7);
|
||||||
|
const int8x8_t shiftb = vshrn_n_s16(b, 7);
|
||||||
|
const int8x16_t compacted = vcombine_s8(shifta,shiftb);
|
||||||
|
return *reinterpret_cast<const vec_t*> (&compacted);
|
||||||
|
}
|
||||||
|
#define vec_load_psqt(a) (*(a))
|
||||||
|
#define vec_store_psqt(a,b) *(a)=(b)
|
||||||
|
#define vec_add_psqt_32(a,b) vaddq_s32(a,b)
|
||||||
|
#define vec_sub_psqt_32(a,b) vsubq_s32(a,b)
|
||||||
|
#define vec_zero_psqt() psqt_vec_t{0}
|
||||||
|
#define NumRegistersSIMD 16
|
||||||
|
#define MaxChunkSize 16
|
||||||
|
|
||||||
|
#else
|
||||||
|
#undef VECTOR
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef VECTOR
|
||||||
|
|
||||||
|
// Compute optimal SIMD register count for feature transformer accumulation.
|
||||||
|
|
||||||
|
// We use __m* types as template arguments, which causes GCC to emit warnings
|
||||||
|
// about losing some attribute information. This is irrelevant to us as we
|
||||||
|
// only take their size, so the following pragma are harmless.
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename SIMDRegisterType,
|
||||||
|
typename LaneType,
|
||||||
|
int NumLanes,
|
||||||
|
int MaxRegisters>
|
||||||
|
static constexpr int BestRegisterCount()
|
||||||
|
{
|
||||||
|
#define RegisterSize sizeof(SIMDRegisterType)
|
||||||
|
#define LaneSize sizeof(LaneType)
|
||||||
|
|
||||||
|
static_assert(RegisterSize >= LaneSize);
|
||||||
|
static_assert(MaxRegisters <= NumRegistersSIMD);
|
||||||
|
static_assert(MaxRegisters > 0);
|
||||||
|
static_assert(NumRegistersSIMD > 0);
|
||||||
|
static_assert(RegisterSize % LaneSize == 0);
|
||||||
|
static_assert((NumLanes * LaneSize) % RegisterSize == 0);
|
||||||
|
|
||||||
|
const int ideal = (NumLanes * LaneSize) / RegisterSize;
|
||||||
|
if (ideal <= MaxRegisters)
|
||||||
|
return ideal;
|
||||||
|
|
||||||
|
// Look for the largest divisor of the ideal register count that is smaller than MaxRegisters
|
||||||
|
for (int divisor = MaxRegisters; divisor > 1; --divisor)
|
||||||
|
if (ideal % divisor == 0)
|
||||||
|
return divisor;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr int NumRegs = BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
|
||||||
|
static constexpr int NumPsqtRegs = BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Input feature converter
|
// Input feature converter
|
||||||
class FeatureTransformer {
|
class FeatureTransformer {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Number of output dimensions for one side
|
// Number of output dimensions for one side
|
||||||
static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions;
|
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
||||||
|
|
||||||
|
#ifdef VECTOR
|
||||||
|
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
|
||||||
|
static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
|
||||||
|
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
|
||||||
|
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Output type
|
// Output type
|
||||||
using OutputType = TransformedFeatureType;
|
using OutputType = TransformedFeatureType;
|
||||||
|
|
||||||
// Number of input/output dimensions
|
// Number of input/output dimensions
|
||||||
static constexpr IndexType kInputDimensions = RawFeatures::kDimensions;
|
static constexpr IndexType InputDimensions = FeatureSet::Dimensions;
|
||||||
static constexpr IndexType kOutputDimensions = kHalfDimensions * 2;
|
static constexpr IndexType OutputDimensions = HalfDimensions;
|
||||||
|
|
||||||
// Size of forward propagation buffer
|
// Size of forward propagation buffer
|
||||||
static constexpr std::size_t kBufferSize =
|
static constexpr std::size_t BufferSize =
|
||||||
kOutputDimensions * sizeof(OutputType);
|
OutputDimensions * sizeof(OutputType);
|
||||||
|
|
||||||
// Hash value embedded in the evaluation file
|
// Hash value embedded in the evaluation file
|
||||||
static constexpr std::uint32_t GetHashValue() {
|
static constexpr std::uint32_t get_hash_value() {
|
||||||
return RawFeatures::kHashValue ^ kOutputDimensions;
|
return FeatureSet::HashValue ^ (OutputDimensions * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read network parameters
|
// Read network parameters
|
||||||
bool ReadParameters(std::istream& stream) {
|
bool read_parameters(std::istream& stream) {
|
||||||
stream.read(reinterpret_cast<char*>(biases_),
|
|
||||||
kHalfDimensions * sizeof(BiasType));
|
read_little_endian<BiasType >(stream, biases , HalfDimensions );
|
||||||
stream.read(reinterpret_cast<char*>(weights_),
|
read_little_endian<WeightType >(stream, weights , HalfDimensions * InputDimensions);
|
||||||
kHalfDimensions * kInputDimensions * sizeof(WeightType));
|
read_little_endian<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
|
||||||
|
|
||||||
return !stream.fail();
|
return !stream.fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proceed with the difference calculation if possible
|
// Write network parameters
|
||||||
bool UpdateAccumulatorIfPossible(const Position& pos) const {
|
bool write_parameters(std::ostream& stream) const {
|
||||||
const auto now = pos.state();
|
|
||||||
if (now->accumulator.computed_accumulation) {
|
write_little_endian<BiasType >(stream, biases , HalfDimensions );
|
||||||
return true;
|
write_little_endian<WeightType >(stream, weights , HalfDimensions * InputDimensions);
|
||||||
}
|
write_little_endian<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
|
||||||
const auto prev = now->previous;
|
|
||||||
if (prev && prev->accumulator.computed_accumulation) {
|
return !stream.fail();
|
||||||
UpdateAccumulator(pos);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert input features
|
// Convert input features
|
||||||
void Transform(const Position& pos, OutputType* output, bool refresh) const {
|
std::int32_t transform(const Position& pos, OutputType* output, int bucket) const {
|
||||||
if (refresh || !UpdateAccumulatorIfPossible(pos)) {
|
update_accumulator(pos, WHITE);
|
||||||
RefreshAccumulator(pos);
|
update_accumulator(pos, BLACK);
|
||||||
}
|
|
||||||
const auto& accumulation = pos.state()->accumulator.accumulation;
|
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
|
|
||||||
constexpr int kControl = 0b11011000;
|
|
||||||
const __m256i kZero = _mm256_setzero_si256();
|
|
||||||
|
|
||||||
#elif defined(USE_SSSE3)
|
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
|
|
||||||
|
|
||||||
#ifdef USE_SSE41
|
|
||||||
const __m128i kZero = _mm_setzero_si128();
|
|
||||||
#else
|
|
||||||
const __m128i k0x80s = _mm_set1_epi8(-128);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
|
||||||
const int8x8_t kZero = {0};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
||||||
for (IndexType p = 0; p < 2; ++p) {
|
const auto& accumulation = pos.state()->accumulator.accumulation;
|
||||||
const IndexType offset = kHalfDimensions * p;
|
const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation;
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
const auto psqt = (
|
||||||
auto out = reinterpret_cast<__m256i*>(&output[offset]);
|
psqtAccumulation[perspectives[0]][bucket]
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
- psqtAccumulation[perspectives[1]][bucket]
|
||||||
__m256i sum0 =
|
) / 2;
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
|
||||||
// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Because the binary
|
|
||||||
// compiled with g++ in MSYS2 crashes here because the output memory is not aligned
|
|
||||||
// even though alignas is specified.
|
|
||||||
_mm256_loadu_si256
|
|
||||||
#else
|
|
||||||
_mm256_load_si256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
(&reinterpret_cast<const __m256i*>(
|
for (IndexType p = 0; p < 2; ++p)
|
||||||
accumulation[perspectives[p]][0])[j * 2 + 0]);
|
{
|
||||||
__m256i sum1 =
|
const IndexType offset = (HalfDimensions / 2) * p;
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
#if defined(VECTOR)
|
||||||
_mm256_loadu_si256
|
|
||||||
#else
|
|
||||||
_mm256_load_si256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
(&reinterpret_cast<const __m256i*>(
|
constexpr IndexType OutputChunkSize = MaxChunkSize;
|
||||||
accumulation[perspectives[p]][0])[j * 2 + 1]);
|
static_assert((HalfDimensions / 2) % OutputChunkSize == 0);
|
||||||
|
constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize;
|
||||||
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
vec_t Zero = vec_zero();
|
||||||
_mm256_storeu_si256
|
vec_t One = vec_set_16(127);
|
||||||
#else
|
|
||||||
_mm256_store_si256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8(
|
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
|
||||||
_mm256_packs_epi16(sum0, sum1), kZero), kControl));
|
const vec_t* in1 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));
|
||||||
}
|
vec_t* out = reinterpret_cast< vec_t*>(output + offset);
|
||||||
|
|
||||||
#elif defined(USE_SSSE3)
|
for (IndexType j = 0; j < NumOutputChunks; j += 1)
|
||||||
auto out = reinterpret_cast<__m128i*>(&output[offset]);
|
{
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero);
|
||||||
__m128i sum0 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
|
const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero);
|
||||||
accumulation[perspectives[p]][0])[j * 2 + 0]);
|
const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero);
|
||||||
__m128i sum1 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
|
const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero);
|
||||||
accumulation[perspectives[p]][0])[j * 2 + 1]);
|
|
||||||
const __m128i packedbytes = _mm_packs_epi16(sum0, sum1);
|
|
||||||
|
|
||||||
_mm_store_si128(&out[j],
|
const vec_t pa = vec_mul_16(sum0a, sum1a);
|
||||||
|
const vec_t pb = vec_mul_16(sum0b, sum1b);
|
||||||
|
|
||||||
#ifdef USE_SSE41
|
out[j] = vec_msb_pack_16(pa, pb);
|
||||||
_mm_max_epi8(packedbytes, kZero)
|
}
|
||||||
#else
|
|
||||||
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
);
|
#else
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
for (IndexType j = 0; j < HalfDimensions / 2; ++j) {
|
||||||
const auto out = reinterpret_cast<int8x8_t*>(&output[offset]);
|
BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
BiasType sum1 = accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
|
||||||
int16x8_t sum = reinterpret_cast<const int16x8_t*>(
|
sum0 = std::max<int>(0, std::min<int>(127, sum0));
|
||||||
accumulation[perspectives[p]][0])[j];
|
sum1 = std::max<int>(0, std::min<int>(127, sum1));
|
||||||
out[j] = vmax_s8(vqmovn_s16(sum), kZero);
|
output[offset + j] = static_cast<OutputType>(sum0 * sum1 / 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
|
||||||
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
|
||||||
BiasType sum = accumulation[static_cast<int>(perspectives[p])][0][j];
|
|
||||||
output[offset + j] = static_cast<OutputType>(
|
|
||||||
std::max<int>(0, std::min<int>(127, sum)));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#if defined(vec_cleanup)
|
||||||
|
vec_cleanup();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return psqt;
|
||||||
|
|
||||||
|
} // end of function transform()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Calculate cumulative value without using difference calculation
|
void update_accumulator(const Position& pos, const Color perspective) const {
|
||||||
void RefreshAccumulator(const Position& pos) const {
|
|
||||||
auto& accumulator = pos.state()->accumulator;
|
|
||||||
IndexType i = 0;
|
|
||||||
Features::IndexList active_indices[2];
|
|
||||||
RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i],
|
|
||||||
active_indices);
|
|
||||||
for (Color perspective : { WHITE, BLACK }) {
|
|
||||||
std::memcpy(accumulator.accumulation[perspective][i], biases_,
|
|
||||||
kHalfDimensions * sizeof(BiasType));
|
|
||||||
for (const auto index : active_indices[perspective]) {
|
|
||||||
const IndexType offset = kHalfDimensions * index;
|
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
// The size must be enough to contain the largest possible update.
|
||||||
auto accumulation = reinterpret_cast<__m256i*>(
|
// That might depend on the feature set and generally relies on the
|
||||||
&accumulator.accumulation[perspective][i][0]);
|
// feature set's update cost calculation to be correct and never
|
||||||
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
// allow updates with more added/removed features than MaxActiveDimensions.
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
|
||||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
|
||||||
_mm256_storeu_si256(&accumulation[j], _mm256_add_epi16(_mm256_loadu_si256(&accumulation[j]), column[j]));
|
|
||||||
#else
|
|
||||||
accumulation[j] = _mm256_add_epi16(accumulation[j], column[j]);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(USE_SSE2)
|
#ifdef VECTOR
|
||||||
auto accumulation = reinterpret_cast<__m128i*>(
|
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
|
||||||
&accumulator.accumulation[perspective][i][0]);
|
// is defined in the VECTOR code below, once in each branch
|
||||||
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
|
vec_t acc[NumRegs];
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
psqt_vec_t psqt[NumPsqtRegs];
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
|
||||||
accumulation[j] = _mm_add_epi16(accumulation[j], column[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
|
||||||
auto accumulation = reinterpret_cast<int16x8_t*>(
|
|
||||||
&accumulator.accumulation[perspective][i][0]);
|
|
||||||
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
|
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
|
||||||
accumulation[j] = vaddq_s16(accumulation[j], column[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
|
||||||
accumulator.accumulation[perspective][i][j] += weights_[offset + j];
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
// Look for a usable accumulator of an earlier position. We keep track
|
||||||
|
// of the estimated gain in terms of features to be added/subtracted.
|
||||||
|
StateInfo *st = pos.state(), *next = nullptr;
|
||||||
|
int gain = FeatureSet::refresh_cost(pos);
|
||||||
|
while (st->previous && !st->accumulator.computed[perspective])
|
||||||
|
{
|
||||||
|
// This governs when a full feature refresh is needed and how many
|
||||||
|
// updates are better than just one full refresh.
|
||||||
|
if ( FeatureSet::requires_refresh(st, perspective)
|
||||||
|
|| (gain -= FeatureSet::update_cost(st) + 1) < 0)
|
||||||
|
break;
|
||||||
|
next = st;
|
||||||
|
st = st->previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulator.computed_accumulation = true;
|
if (st->accumulator.computed[perspective])
|
||||||
accumulator.computed_score = false;
|
{
|
||||||
}
|
if (next == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
// Calculate cumulative value using difference calculation
|
// Update incrementally in two steps. First, we update the "next"
|
||||||
void UpdateAccumulator(const Position& pos) const {
|
// accumulator. Then, we update the current accumulator (pos.state()).
|
||||||
const auto prev_accumulator = pos.state()->previous->accumulator;
|
|
||||||
auto& accumulator = pos.state()->accumulator;
|
|
||||||
IndexType i = 0;
|
|
||||||
Features::IndexList removed_indices[2], added_indices[2];
|
|
||||||
bool reset[2];
|
|
||||||
RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i],
|
|
||||||
removed_indices, added_indices, reset);
|
|
||||||
for (Color perspective : { WHITE, BLACK }) {
|
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
// Gather all features to be updated.
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
const Square ksq = pos.square<KING>(perspective);
|
||||||
auto accumulation = reinterpret_cast<__m256i*>(
|
FeatureSet::IndexList removed[2], added[2];
|
||||||
&accumulator.accumulation[perspective][i][0]);
|
FeatureSet::append_changed_indices(
|
||||||
|
ksq, next->dirtyPiece, perspective, removed[0], added[0]);
|
||||||
|
for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous)
|
||||||
|
FeatureSet::append_changed_indices(
|
||||||
|
ksq, st2->dirtyPiece, perspective, removed[1], added[1]);
|
||||||
|
|
||||||
#elif defined(USE_SSE2)
|
// Mark the accumulators as computed.
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
next->accumulator.computed[perspective] = true;
|
||||||
auto accumulation = reinterpret_cast<__m128i*>(
|
pos.state()->accumulator.computed[perspective] = true;
|
||||||
&accumulator.accumulation[perspective][i][0]);
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
// Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
|
||||||
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
StateInfo *states_to_update[3] =
|
||||||
auto accumulation = reinterpret_cast<int16x8_t*>(
|
{ next, next == pos.state() ? nullptr : pos.state(), nullptr };
|
||||||
&accumulator.accumulation[perspective][i][0]);
|
#ifdef VECTOR
|
||||||
#endif
|
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
||||||
|
{
|
||||||
|
// Load accumulator
|
||||||
|
auto accTile = reinterpret_cast<vec_t*>(
|
||||||
|
&st->accumulator.accumulation[perspective][j * TileHeight]);
|
||||||
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
|
acc[k] = vec_load(&accTile[k]);
|
||||||
|
|
||||||
|
for (IndexType i = 0; states_to_update[i]; ++i)
|
||||||
|
{
|
||||||
|
// Difference calculation for the deactivated features
|
||||||
|
for (const auto index : removed[i])
|
||||||
|
{
|
||||||
|
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
||||||
|
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
||||||
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
|
acc[k] = vec_sub_16(acc[k], column[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference calculation for the activated features
|
||||||
|
for (const auto index : added[i])
|
||||||
|
{
|
||||||
|
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
||||||
|
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
||||||
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
|
acc[k] = vec_add_16(acc[k], column[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store accumulator
|
||||||
|
accTile = reinterpret_cast<vec_t*>(
|
||||||
|
&states_to_update[i]->accumulator.accumulation[perspective][j * TileHeight]);
|
||||||
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
|
vec_store(&accTile[k], acc[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
|
||||||
|
{
|
||||||
|
// Load accumulator
|
||||||
|
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
||||||
|
&st->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
|
||||||
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
|
psqt[k] = vec_load_psqt(&accTilePsqt[k]);
|
||||||
|
|
||||||
|
for (IndexType i = 0; states_to_update[i]; ++i)
|
||||||
|
{
|
||||||
|
// Difference calculation for the deactivated features
|
||||||
|
for (const auto index : removed[i])
|
||||||
|
{
|
||||||
|
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
|
||||||
|
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
||||||
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
|
psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference calculation for the activated features
|
||||||
|
for (const auto index : added[i])
|
||||||
|
{
|
||||||
|
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
|
||||||
|
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
||||||
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
|
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store accumulator
|
||||||
|
accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
||||||
|
&states_to_update[i]->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
|
||||||
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
|
vec_store_psqt(&accTilePsqt[k], psqt[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
for (IndexType i = 0; states_to_update[i]; ++i)
|
||||||
|
{
|
||||||
|
std::memcpy(states_to_update[i]->accumulator.accumulation[perspective],
|
||||||
|
st->accumulator.accumulation[perspective],
|
||||||
|
HalfDimensions * sizeof(BiasType));
|
||||||
|
|
||||||
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
|
states_to_update[i]->accumulator.psqtAccumulation[perspective][k] = st->accumulator.psqtAccumulation[perspective][k];
|
||||||
|
|
||||||
|
st = states_to_update[i];
|
||||||
|
|
||||||
if (reset[perspective]) {
|
|
||||||
std::memcpy(accumulator.accumulation[perspective][i], biases_,
|
|
||||||
kHalfDimensions * sizeof(BiasType));
|
|
||||||
} else {
|
|
||||||
std::memcpy(accumulator.accumulation[perspective][i],
|
|
||||||
prev_accumulator.accumulation[perspective][i],
|
|
||||||
kHalfDimensions * sizeof(BiasType));
|
|
||||||
// Difference calculation for the deactivated features
|
// Difference calculation for the deactivated features
|
||||||
for (const auto index : removed_indices[perspective]) {
|
for (const auto index : removed[i])
|
||||||
const IndexType offset = kHalfDimensions * index;
|
{
|
||||||
|
const IndexType offset = HalfDimensions * index;
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
for (IndexType j = 0; j < HalfDimensions; ++j)
|
||||||
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
st->accumulator.accumulation[perspective][j] -= weights[offset + j];
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
|
||||||
accumulation[j] = _mm256_sub_epi16(accumulation[j], column[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(USE_SSE2)
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
|
st->accumulator.psqtAccumulation[perspective][k] -= psqtWeights[index * PSQTBuckets + k];
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
}
|
||||||
accumulation[j] = _mm_sub_epi16(accumulation[j], column[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
// Difference calculation for the activated features
|
||||||
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
|
for (const auto index : added[i])
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
{
|
||||||
accumulation[j] = vsubq_s16(accumulation[j], column[j]);
|
const IndexType offset = HalfDimensions * index;
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
for (IndexType j = 0; j < HalfDimensions; ++j)
|
||||||
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
st->accumulator.accumulation[perspective][j] += weights[offset + j];
|
||||||
accumulator.accumulation[perspective][i][j] -=
|
|
||||||
weights_[offset + j];
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
|
st->accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{ // Difference calculation for the activated features
|
#endif
|
||||||
for (const auto index : added_indices[perspective]) {
|
}
|
||||||
const IndexType offset = kHalfDimensions * index;
|
else
|
||||||
|
{
|
||||||
|
// Refresh the accumulator
|
||||||
|
auto& accumulator = pos.state()->accumulator;
|
||||||
|
accumulator.computed[perspective] = true;
|
||||||
|
FeatureSet::IndexList active;
|
||||||
|
FeatureSet::append_active_indices(pos, perspective, active);
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
#ifdef VECTOR
|
||||||
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
{
|
||||||
accumulation[j] = _mm256_add_epi16(accumulation[j], column[j]);
|
auto biasesTile = reinterpret_cast<const vec_t*>(
|
||||||
}
|
&biases[j * TileHeight]);
|
||||||
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
|
acc[k] = biasesTile[k];
|
||||||
|
|
||||||
#elif defined(USE_SSE2)
|
for (const auto index : active)
|
||||||
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
|
{
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
||||||
accumulation[j] = _mm_add_epi16(accumulation[j], column[j]);
|
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
for (unsigned k = 0; k < NumRegs; ++k)
|
||||||
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
|
acc[k] = vec_add_16(acc[k], column[k]);
|
||||||
for (IndexType j = 0; j < kNumChunks; ++j) {
|
}
|
||||||
accumulation[j] = vaddq_s16(accumulation[j], column[j]);
|
|
||||||
}
|
auto accTile = reinterpret_cast<vec_t*>(
|
||||||
|
&accumulator.accumulation[perspective][j * TileHeight]);
|
||||||
|
for (unsigned k = 0; k < NumRegs; k++)
|
||||||
|
vec_store(&accTile[k], acc[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
|
psqt[k] = vec_zero_psqt();
|
||||||
|
|
||||||
|
for (const auto index : active)
|
||||||
|
{
|
||||||
|
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
|
||||||
|
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
||||||
|
|
||||||
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
|
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
||||||
|
&accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
|
||||||
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
|
vec_store_psqt(&accTilePsqt[k], psqt[k]);
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
std::memcpy(accumulator.accumulation[perspective], biases,
|
||||||
accumulator.accumulation[perspective][i][j] +=
|
HalfDimensions * sizeof(BiasType));
|
||||||
weights_[offset + j];
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
|
accumulator.psqtAccumulation[perspective][k] = 0;
|
||||||
|
|
||||||
|
for (const auto index : active)
|
||||||
|
{
|
||||||
|
const IndexType offset = HalfDimensions * index;
|
||||||
|
|
||||||
|
for (IndexType j = 0; j < HalfDimensions; ++j)
|
||||||
|
accumulator.accumulation[perspective][j] += weights[offset + j];
|
||||||
|
|
||||||
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
|
accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k];
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
accumulator.computed_accumulation = true;
|
#if defined(USE_MMX)
|
||||||
accumulator.computed_score = false;
|
_mm_empty();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
using BiasType = std::int16_t;
|
alignas(CacheLineSize) BiasType biases[HalfDimensions];
|
||||||
using WeightType = std::int16_t;
|
alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
|
||||||
|
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
|
||||||
alignas(kCacheLineSize) BiasType biases_[kHalfDimensions];
|
|
||||||
alignas(kCacheLineSize)
|
|
||||||
WeightType weights_[kHalfDimensions * kInputDimensions];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Eval::NNUE
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
|
||||||
#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
||||||
|
|||||||
+49
-25
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,35 +24,38 @@
|
|||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
#define V Value
|
#define V Value
|
||||||
#define S(mg, eg) make_score(mg, eg)
|
#define S(mg, eg) make_score(mg, eg)
|
||||||
|
|
||||||
// Pawn penalties
|
// Pawn penalties
|
||||||
constexpr Score Backward = S( 9, 24);
|
constexpr Score Backward = S( 6, 19);
|
||||||
constexpr Score Doubled = S(11, 56);
|
constexpr Score Doubled = S(11, 51);
|
||||||
constexpr Score Isolated = S( 5, 15);
|
constexpr Score DoubledEarly = S(17, 7);
|
||||||
constexpr Score WeakLever = S( 0, 56);
|
constexpr Score Isolated = S( 1, 20);
|
||||||
constexpr Score WeakUnopposed = S(13, 27);
|
constexpr Score WeakLever = S( 2, 57);
|
||||||
|
constexpr Score WeakUnopposed = S(15, 18);
|
||||||
|
|
||||||
// Bonus for blocked pawns at 5th or 6th rank
|
// Bonus for blocked pawns at 5th or 6th rank
|
||||||
constexpr Score BlockedPawn[2] = { S(-11, -4), S(-3, 4) };
|
constexpr Score BlockedPawn[2] = { S(-19, -8), S(-7, 3) };
|
||||||
|
|
||||||
constexpr Score BlockedStorm[RANK_NB] = {
|
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(64, 75), S(-3, 14), S(-12, 19), S(-7, 4), S(-10, 5)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connected pawn bonus
|
// Connected pawn bonus
|
||||||
constexpr int Connected[RANK_NB] = { 0, 7, 8, 12, 29, 48, 86 };
|
constexpr int Connected[RANK_NB] = { 0, 3, 7, 7, 15, 54, 86 };
|
||||||
|
|
||||||
// Strength of pawn shelter for our king by [distance from edge][rank].
|
// 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.
|
// RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king.
|
||||||
constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = {
|
constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = {
|
||||||
{ V( -6), V( 81), V( 93), V( 58), V( 39), V( 18), V( 25) },
|
{ V(-2), V(85), V(95), V(53), V(39), V(23), V(25) },
|
||||||
{ V(-43), V( 61), V( 35), V(-49), V(-29), V(-11), V( -63) },
|
{ V(-55), V(64), V(32), V(-55), V(-30), V(-11), V(-61) },
|
||||||
{ V(-10), V( 75), V( 23), V( -2), V( 32), V( 3), V( -45) },
|
{ V(-11), V(75), V(19), V(-6), V(26), V(9), V(-47) },
|
||||||
{ V(-39), V(-13), V(-29), V(-52), V(-48), V(-67), V(-166) }
|
{ V(-41), V(-11), V(-27), V(-58), V(-42), V(-66), V(-163) }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Danger of enemy pawns moving toward our king by [distance from edge][rank].
|
// Danger of enemy pawns moving toward our king by [distance from edge][rank].
|
||||||
@@ -60,12 +63,18 @@ namespace {
|
|||||||
// is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn
|
// is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn
|
||||||
// on edge, likely blocked by our king.
|
// on edge, likely blocked by our king.
|
||||||
constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = {
|
constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = {
|
||||||
{ V( 85), V(-289), V(-166), V(97), V(50), V( 45), V( 50) },
|
{ V(94), V(-280), V(-170), V(90), V(59), V(47), V(53) },
|
||||||
{ V( 46), V( -25), V( 122), V(45), V(37), V(-10), V( 20) },
|
{ V(43), V(-17), V(128), V(39), V(26), V(-17), V(15) },
|
||||||
{ V( -6), V( 51), V( 168), V(34), V(-2), V(-22), V(-14) },
|
{ V(-9), V(62), V(170), V(34), V(-5), V(-20), V(-11) },
|
||||||
{ V(-15), V( -11), V( 101), V( 4), V(11), V(-15), V(-29) }
|
{ V(-27), V(-19), V(106), V(10), V(2), V(-13), V(-24) }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties
|
||||||
|
// for king when the king is on a semi-open or open file.
|
||||||
|
constexpr Score KingOnFile[2][2] = {{ S(-18,11), S(-6,-3) },
|
||||||
|
{ S( 0, 0), S( 5,-4) }};
|
||||||
|
|
||||||
#undef S
|
#undef S
|
||||||
#undef V
|
#undef V
|
||||||
|
|
||||||
@@ -80,13 +89,14 @@ namespace {
|
|||||||
|
|
||||||
constexpr Color Them = ~Us;
|
constexpr Color Them = ~Us;
|
||||||
constexpr Direction Up = pawn_push(Us);
|
constexpr Direction Up = pawn_push(Us);
|
||||||
|
constexpr Direction Down = -Up;
|
||||||
|
|
||||||
Bitboard neighbours, stoppers, support, phalanx, opposed;
|
Bitboard neighbours, stoppers, support, phalanx, opposed;
|
||||||
Bitboard lever, leverPush, blocked;
|
Bitboard lever, leverPush, blocked;
|
||||||
Square s;
|
Square s;
|
||||||
bool backward, passed, doubled;
|
bool backward, passed, doubled;
|
||||||
Score score = SCORE_ZERO;
|
Score score = SCORE_ZERO;
|
||||||
const Square* pl = pos.squares<PAWN>(Us);
|
Bitboard b = pos.pieces(Us, PAWN);
|
||||||
|
|
||||||
Bitboard ourPawns = pos.pieces( Us, PAWN);
|
Bitboard ourPawns = pos.pieces( Us, PAWN);
|
||||||
Bitboard theirPawns = pos.pieces(Them, PAWN);
|
Bitboard theirPawns = pos.pieces(Them, PAWN);
|
||||||
@@ -99,8 +109,10 @@ namespace {
|
|||||||
e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
|
e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
|
||||||
|
|
||||||
// Loop through all pawns of the current color and score each pawn
|
// 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));
|
assert(pos.piece_on(s) == make_piece(Us, PAWN));
|
||||||
|
|
||||||
Rank r = relative_rank(Us, s);
|
Rank r = relative_rank(Us, s);
|
||||||
@@ -116,6 +128,13 @@ namespace {
|
|||||||
phalanx = neighbours & rank_bb(s);
|
phalanx = neighbours & rank_bb(s);
|
||||||
support = neighbours & rank_bb(s - Up);
|
support = neighbours & rank_bb(s - Up);
|
||||||
|
|
||||||
|
if (doubled)
|
||||||
|
{
|
||||||
|
// Additional doubled penalty if none of their pawns is fixed
|
||||||
|
if (!(ourPawns & shift<Down>(theirPawns | pawn_attacks_bb<Them>(theirPawns))))
|
||||||
|
score -= DoubledEarly;
|
||||||
|
}
|
||||||
|
|
||||||
// A pawn is backward when it is behind all pawns of the same color on
|
// A pawn is backward when it is behind all pawns of the same color on
|
||||||
// the adjacent files and cannot safely advance.
|
// the adjacent files and cannot safely advance.
|
||||||
backward = !(neighbours & forward_ranks_bb(Them, s + Up))
|
backward = !(neighbours & forward_ranks_bb(Them, s + Up))
|
||||||
@@ -147,7 +166,7 @@ namespace {
|
|||||||
if (support | phalanx)
|
if (support | phalanx)
|
||||||
{
|
{
|
||||||
int v = Connected[r] * (2 + bool(phalanx) - bool(opposed))
|
int v = Connected[r] * (2 + bool(phalanx) - bool(opposed))
|
||||||
+ 21 * popcount(support);
|
+ 22 * popcount(support);
|
||||||
|
|
||||||
score += make_score(v, v * (r - 2) / 4);
|
score += make_score(v, v * (r - 2) / 4);
|
||||||
}
|
}
|
||||||
@@ -165,14 +184,14 @@ namespace {
|
|||||||
|
|
||||||
else if (backward)
|
else if (backward)
|
||||||
score -= Backward
|
score -= Backward
|
||||||
+ WeakUnopposed * !opposed;
|
+ WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s);
|
||||||
|
|
||||||
if (!support)
|
if (!support)
|
||||||
score -= Doubled * doubled
|
score -= Doubled * doubled
|
||||||
+ WeakLever * more_than_one(lever);
|
+ WeakLever * more_than_one(lever);
|
||||||
|
|
||||||
if (blocked && r > RANK_4)
|
if (blocked && r >= RANK_5)
|
||||||
score += BlockedPawn[r-4];
|
score += BlockedPawn[r - RANK_5];
|
||||||
}
|
}
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@@ -219,7 +238,7 @@ Score Entry::evaluate_shelter(const Position& pos, Square ksq) const {
|
|||||||
|
|
||||||
Score bonus = make_score(5, 5);
|
Score bonus = make_score(5, 5);
|
||||||
|
|
||||||
File center = Utility::clamp(file_of(ksq), FILE_B, FILE_G);
|
File center = std::clamp(file_of(ksq), FILE_B, FILE_G);
|
||||||
for (File f = File(center - 1); f <= File(center + 1); ++f)
|
for (File f = File(center - 1); f <= File(center + 1); ++f)
|
||||||
{
|
{
|
||||||
b = ourPawns & file_bb(f);
|
b = ourPawns & file_bb(f);
|
||||||
@@ -237,6 +256,9 @@ Score Entry::evaluate_shelter(const Position& pos, Square ksq) const {
|
|||||||
bonus -= make_score(UnblockedStorm[d][theirRank], 0);
|
bonus -= make_score(UnblockedStorm[d][theirRank], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// King On File
|
||||||
|
bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)];
|
||||||
|
|
||||||
return bonus;
|
return bonus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +291,7 @@ Score Entry::do_king_safety(const Position& pos) {
|
|||||||
if (pawns & attacks_bb<KING>(ksq))
|
if (pawns & attacks_bb<KING>(ksq))
|
||||||
minPawnDist = 1;
|
minPawnDist = 1;
|
||||||
else while (pawns)
|
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);
|
return shelter - make_score(0, 16 * minPawnDist);
|
||||||
}
|
}
|
||||||
@@ -279,3 +301,5 @@ template Score Entry::do_king_safety<WHITE>(const Position& pos);
|
|||||||
template Score Entry::do_king_safety<BLACK>(const Position& pos);
|
template Score Entry::do_king_safety<BLACK>(const Position& pos);
|
||||||
|
|
||||||
} // namespace Pawns
|
} // namespace Pawns
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace Pawns {
|
namespace Stockfish::Pawns {
|
||||||
|
|
||||||
/// Pawns::Entry contains various information about a pawn structure. A lookup
|
/// Pawns::Entry contains various information about a pawn structure. A lookup
|
||||||
/// to the pawn hash table (performed by calling the probe function) returns a
|
/// to the pawn hash table (performed by calling the probe function) returns a
|
||||||
@@ -65,6 +65,6 @@ typedef HashTable<Entry, 131072> Table;
|
|||||||
|
|
||||||
Entry* probe(const Position& pos);
|
Entry* probe(const Position& pos);
|
||||||
|
|
||||||
} // namespace Pawns
|
} // namespace Stockfish::Pawns
|
||||||
|
|
||||||
#endif // #ifndef PAWNS_H_INCLUDED
|
#endif // #ifndef PAWNS_H_INCLUDED
|
||||||
|
|||||||
+127
-140
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "nnue/evaluate_nnue.h"
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
@@ -32,8 +34,13 @@
|
|||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
#include "syzygy/tbprobe.h"
|
#include "syzygy/tbprobe.h"
|
||||||
|
|
||||||
|
#include "tools/packed_sfen.h"
|
||||||
|
#include "tools/sfen_packer.h"
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace Zobrist {
|
namespace Zobrist {
|
||||||
|
|
||||||
Key psq[PIECE_NB][SQUARE_NB];
|
Key psq[PIECE_NB][SQUARE_NB];
|
||||||
@@ -71,12 +78,14 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
|||||||
<< std::setfill(' ') << std::dec << "\nCheckers: ";
|
<< std::setfill(' ') << std::dec << "\nCheckers: ";
|
||||||
|
|
||||||
for (Bitboard b = pos.checkers(); b; )
|
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())
|
if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
|
||||||
&& !pos.can_castle(ANY_CASTLING))
|
&& !pos.can_castle(ANY_CASTLING))
|
||||||
{
|
{
|
||||||
StateInfo st;
|
StateInfo st;
|
||||||
|
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
||||||
|
|
||||||
Position p;
|
Position p;
|
||||||
p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread());
|
p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread());
|
||||||
Tablebases::ProbeState s1, s2;
|
Tablebases::ProbeState s1, s2;
|
||||||
@@ -195,12 +204,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
|||||||
|
|
||||||
std::memset(this, 0, sizeof(Position));
|
std::memset(this, 0, sizeof(Position));
|
||||||
std::memset(si, 0, sizeof(StateInfo));
|
std::memset(si, 0, sizeof(StateInfo));
|
||||||
std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
|
|
||||||
st = si;
|
st = si;
|
||||||
|
|
||||||
// Each piece on board gets a unique ID used to track the piece later
|
|
||||||
PieceId piece_id, next_piece_id = PIECE_ID_ZERO;
|
|
||||||
|
|
||||||
ss >> std::noskipws;
|
ss >> std::noskipws;
|
||||||
|
|
||||||
// 1. Piece placement
|
// 1. Piece placement
|
||||||
@@ -212,21 +217,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
|||||||
else if (token == '/')
|
else if (token == '/')
|
||||||
sq += 2 * SOUTH;
|
sq += 2 * SOUTH;
|
||||||
|
|
||||||
else if ((idx = PieceToChar.find(token)) != string::npos)
|
else if ((idx = PieceToChar.find(token)) != string::npos) {
|
||||||
{
|
put_piece(Piece(idx), sq);
|
||||||
auto pc = Piece(idx);
|
|
||||||
put_piece(pc, sq);
|
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
|
||||||
{
|
|
||||||
// Kings get a fixed ID, other pieces get ID in order of placement
|
|
||||||
piece_id =
|
|
||||||
(idx == W_KING) ? PIECE_ID_WKING :
|
|
||||||
(idx == B_KING) ? PIECE_ID_BKING :
|
|
||||||
next_piece_id++;
|
|
||||||
evalList.put_piece(piece_id, sq, pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
++sq;
|
++sq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,7 +310,7 @@ void Position::set_castling_right(Color c, Square rfrom) {
|
|||||||
Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
|
Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
|
||||||
Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
|
Square 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);
|
& ~(kfrom | rfrom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +349,7 @@ void Position::set_state(StateInfo* si) const {
|
|||||||
|
|
||||||
for (Bitboard b = pieces(); b; )
|
for (Bitboard b = pieces(); b; )
|
||||||
{
|
{
|
||||||
Square s = pop_lsb(&b);
|
Square s = pop_lsb(b);
|
||||||
Piece pc = piece_on(s);
|
Piece pc = piece_on(s);
|
||||||
si->key ^= Zobrist::psq[pc][s];
|
si->key ^= Zobrist::psq[pc][s];
|
||||||
|
|
||||||
@@ -408,7 +400,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) {
|
|||||||
/// Position::fen() returns a FEN representation of the position. In case of
|
/// 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.
|
/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function.
|
||||||
|
|
||||||
const string Position::fen() const {
|
string Position::fen() const {
|
||||||
|
|
||||||
int emptyCnt;
|
int emptyCnt;
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
@@ -474,7 +466,7 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners
|
|||||||
|
|
||||||
while (snipers)
|
while (snipers)
|
||||||
{
|
{
|
||||||
Square sniperSq = pop_lsb(&snipers);
|
Square sniperSq = pop_lsb(snipers);
|
||||||
Bitboard b = between_bb(s, sniperSq) & occupancy;
|
Bitboard b = between_bb(s, sniperSq) & occupancy;
|
||||||
|
|
||||||
if (b && !more_than_one(b))
|
if (b && !more_than_one(b))
|
||||||
@@ -518,7 +510,7 @@ bool Position::legal(Move m) const {
|
|||||||
// En passant captures are a tricky special case. Because they are rather
|
// 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
|
// uncommon, we do it simply by testing whether the king is attacked after
|
||||||
// the move is made.
|
// the move is made.
|
||||||
if (type_of(m) == ENPASSANT)
|
if (type_of(m) == EN_PASSANT)
|
||||||
{
|
{
|
||||||
Square ksq = square<KING>(us);
|
Square ksq = square<KING>(us);
|
||||||
Square capsq = to - pawn_push(us);
|
Square capsq = to - pawn_push(us);
|
||||||
@@ -546,22 +538,20 @@ bool Position::legal(Move m) const {
|
|||||||
if (attackers_to(s) & pieces(~us))
|
if (attackers_to(s) & pieces(~us))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// In case of Chess960, verify that when moving the castling rook we do
|
// In case of Chess960, verify if the Rook blocks some checks
|
||||||
// not discover some hidden checker.
|
|
||||||
// For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
|
// For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
|
||||||
return !chess960
|
return !chess960 || !(blockers_for_king(us) & to_sq(m));
|
||||||
|| !(attacks_bb<ROOK>(to, pieces() ^ to_sq(m)) & pieces(~us, ROOK, QUEEN));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the moving piece is a king, check whether the destination square is
|
// If the moving piece is a king, check whether the destination square is
|
||||||
// attacked by the opponent.
|
// attacked by the opponent.
|
||||||
if (type_of(piece_on(from)) == KING)
|
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
|
// 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.
|
// is moving along the ray towards or away from the king.
|
||||||
return !(blockers_for_king(us) & from)
|
return !(blockers_for_king(us) & from)
|
||||||
|| aligned(from, to, square<KING>(us));
|
|| aligned(from, to, square<KING>(us));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -577,8 +567,10 @@ bool Position::pseudo_legal(const Move m) const {
|
|||||||
Piece pc = moved_piece(m);
|
Piece pc = moved_piece(m);
|
||||||
|
|
||||||
// Use a slower but simpler function for uncommon cases
|
// Use a slower but simpler function for uncommon cases
|
||||||
|
// yet we skip the legality check of MoveList<LEGAL>().
|
||||||
if (type_of(m) != NORMAL)
|
if (type_of(m) != NORMAL)
|
||||||
return MoveList<LEGAL>(*this).contains(m);
|
return checkers() ? MoveList< EVASIONS>(*this).contains(m)
|
||||||
|
: MoveList<NON_EVASIONS>(*this).contains(m);
|
||||||
|
|
||||||
// Is not a promotion, so promotion piece must be empty
|
// Is not a promotion, so promotion piece must be empty
|
||||||
if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE)
|
if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE)
|
||||||
@@ -623,8 +615,8 @@ bool Position::pseudo_legal(const Move m) const {
|
|||||||
if (more_than_one(checkers()))
|
if (more_than_one(checkers()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Our move must be a blocking evasion or a capture of the checking piece
|
// Our move must be a blocking interposition or a capture of the checking piece
|
||||||
if (!((between_bb(lsb(checkers()), square<KING>(us)) | checkers()) & to))
|
if (!(between_bb(square<KING>(us), lsb(checkers())) & to))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// In case of king moves under check we have to remove king so as to catch
|
// In case of king moves under check we have to remove king so as to catch
|
||||||
@@ -668,7 +660,7 @@ bool Position::gives_check(Move m) const {
|
|||||||
// of direct checks and ordinary discovered check, so the only case we
|
// of direct checks and ordinary discovered check, so the only case we
|
||||||
// need to handle is the unusual case of a discovered check through
|
// need to handle is the unusual case of a discovered check through
|
||||||
// the captured pawn.
|
// the captured pawn.
|
||||||
case ENPASSANT:
|
case EN_PASSANT:
|
||||||
{
|
{
|
||||||
Square capsq = make_square(file_of(to), rank_of(from));
|
Square capsq = make_square(file_of(to), rank_of(from));
|
||||||
Bitboard b = (pieces() ^ from ^ capsq) | to;
|
Bitboard b = (pieces() ^ from ^ capsq) | to;
|
||||||
@@ -676,19 +668,15 @@ bool Position::gives_check(Move m) const {
|
|||||||
return (attacks_bb< ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
|
return (attacks_bb< ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
|
||||||
| (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
|
| (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
|
||||||
}
|
}
|
||||||
case CASTLING:
|
default: //CASTLING
|
||||||
{
|
{
|
||||||
Square kfrom = from;
|
// Castling is encoded as 'king captures the rook'
|
||||||
Square rfrom = to; // Castling is encoded as 'king captures the rook'
|
Square ksq = square<KING>(~sideToMove);
|
||||||
Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1);
|
Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1);
|
||||||
Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1);
|
|
||||||
|
|
||||||
return (attacks_bb<ROOK>(rto) & square<KING>(~sideToMove))
|
return (attacks_bb<ROOK>(rto) & ksq)
|
||||||
&& (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
|
&& (attacks_bb<ROOK>(rto, pieces() ^ from ^ to) & ksq);
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,10 +707,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
++st->pliesFromNull;
|
++st->pliesFromNull;
|
||||||
|
|
||||||
// Used by NNUE
|
// Used by NNUE
|
||||||
st->accumulator.computed_accumulation = false;
|
st->accumulator.computed[WHITE] = false;
|
||||||
st->accumulator.computed_score = false;
|
st->accumulator.computed[BLACK] = false;
|
||||||
PieceId dp0 = PIECE_ID_NONE;
|
|
||||||
PieceId dp1 = PIECE_ID_NONE;
|
|
||||||
auto& dp = st->dirtyPiece;
|
auto& dp = st->dirtyPiece;
|
||||||
dp.dirty_num = 1;
|
dp.dirty_num = 1;
|
||||||
|
|
||||||
@@ -731,7 +717,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
Square from = from_sq(m);
|
Square from = from_sq(m);
|
||||||
Square to = to_sq(m);
|
Square to = to_sq(m);
|
||||||
Piece pc = piece_on(from);
|
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(color_of(pc) == us);
|
||||||
assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us));
|
assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us));
|
||||||
@@ -757,7 +743,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
// update non-pawn material.
|
// update non-pawn material.
|
||||||
if (type_of(captured) == PAWN)
|
if (type_of(captured) == PAWN)
|
||||||
{
|
{
|
||||||
if (type_of(m) == ENPASSANT)
|
if (type_of(m) == EN_PASSANT)
|
||||||
{
|
{
|
||||||
capsq -= pawn_push(us);
|
capsq -= pawn_push(us);
|
||||||
|
|
||||||
@@ -773,20 +759,18 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
else
|
else
|
||||||
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
|
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
if (Eval::NNUE::useNNUE != Eval::NNUE::UseNNUEMode::False)
|
||||||
{
|
{
|
||||||
dp.dirty_num = 2; // 2 pieces moved
|
dp.dirty_num = 2; // 1 piece moved, 1 piece captured
|
||||||
dp1 = piece_id_on(capsq);
|
dp.piece[1] = captured;
|
||||||
dp.pieceId[1] = dp1;
|
dp.from[1] = capsq;
|
||||||
dp.old_piece[1] = evalList.piece_with_id(dp1);
|
dp.to[1] = SQ_NONE;
|
||||||
evalList.put_piece(dp1, capsq, NO_PIECE);
|
|
||||||
dp.new_piece[1] = evalList.piece_with_id(dp1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update board and piece lists
|
// Update board and piece lists
|
||||||
remove_piece(capsq);
|
remove_piece(capsq);
|
||||||
|
|
||||||
if (type_of(m) == ENPASSANT)
|
if (type_of(m) == EN_PASSANT)
|
||||||
board[capsq] = NO_PIECE;
|
board[capsq] = NO_PIECE;
|
||||||
|
|
||||||
// Update material hash key and prefetch access to materialTable
|
// Update material hash key and prefetch access to materialTable
|
||||||
@@ -819,13 +803,11 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
// Move the piece. The tricky Chess960 castling is handled earlier
|
// Move the piece. The tricky Chess960 castling is handled earlier
|
||||||
if (type_of(m) != CASTLING)
|
if (type_of(m) != CASTLING)
|
||||||
{
|
{
|
||||||
if (Eval::useNNUE)
|
if (Eval::NNUE::useNNUE != Eval::NNUE::UseNNUEMode::False)
|
||||||
{
|
{
|
||||||
dp0 = piece_id_on(from);
|
dp.piece[0] = pc;
|
||||||
dp.pieceId[0] = dp0;
|
dp.from[0] = from;
|
||||||
dp.old_piece[0] = evalList.piece_with_id(dp0);
|
dp.to[0] = to;
|
||||||
evalList.put_piece(dp0, to, pc);
|
|
||||||
dp.new_piece[0] = evalList.piece_with_id(dp0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
move_piece(from, to);
|
move_piece(from, to);
|
||||||
@@ -834,7 +816,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
// If the moving piece is a pawn do some special extra work
|
// If the moving piece is a pawn do some special extra work
|
||||||
if (type_of(pc) == PAWN)
|
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
|
if ( (int(to) ^ int(from)) == 16
|
||||||
&& (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
|
&& (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
|
||||||
{
|
{
|
||||||
@@ -852,11 +834,14 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
remove_piece(to);
|
remove_piece(to);
|
||||||
put_piece(promotion, to);
|
put_piece(promotion, to);
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
if (Eval::NNUE::useNNUE != Eval::NNUE::UseNNUEMode::False)
|
||||||
{
|
{
|
||||||
dp0 = piece_id_on(to);
|
// Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
|
||||||
evalList.put_piece(dp0, to, promotion);
|
dp.to[0] = SQ_NONE;
|
||||||
dp.new_piece[0] = evalList.piece_with_id(dp0);
|
dp.piece[dp.dirty_num] = promotion;
|
||||||
|
dp.from[dp.dirty_num] = SQ_NONE;
|
||||||
|
dp.to[dp.dirty_num] = to;
|
||||||
|
dp.dirty_num++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update hash keys
|
// Update hash keys
|
||||||
@@ -950,17 +935,11 @@ void Position::undo_move(Move m) {
|
|||||||
{
|
{
|
||||||
move_piece(to, from); // Put the piece back at the source square
|
move_piece(to, from); // Put the piece back at the source square
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
|
||||||
{
|
|
||||||
PieceId dp0 = st->dirtyPiece.pieceId[0];
|
|
||||||
evalList.put_piece(dp0, from, pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (st->capturedPiece)
|
if (st->capturedPiece)
|
||||||
{
|
{
|
||||||
Square capsq = to;
|
Square capsq = to;
|
||||||
|
|
||||||
if (type_of(m) == ENPASSANT)
|
if (type_of(m) == EN_PASSANT)
|
||||||
{
|
{
|
||||||
capsq -= pawn_push(us);
|
capsq -= pawn_push(us);
|
||||||
|
|
||||||
@@ -972,14 +951,6 @@ void Position::undo_move(Move m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
put_piece(st->capturedPiece, capsq); // Restore the captured piece
|
put_piece(st->capturedPiece, capsq); // Restore the captured piece
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
|
||||||
{
|
|
||||||
PieceId dp1 = st->dirtyPiece.pieceId[1];
|
|
||||||
assert(evalList.piece_with_id(dp1).from[WHITE] == PS_NONE);
|
|
||||||
assert(evalList.piece_with_id(dp1).from[BLACK] == PS_NONE);
|
|
||||||
evalList.put_piece(dp1, capsq, st->capturedPiece);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1001,32 +972,16 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
|
|||||||
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
|
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
|
||||||
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
|
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
if (Do && Eval::NNUE::useNNUE != Eval::NNUE::UseNNUEMode::False)
|
||||||
{
|
{
|
||||||
PieceId dp0, dp1;
|
|
||||||
auto& dp = st->dirtyPiece;
|
auto& dp = st->dirtyPiece;
|
||||||
dp.dirty_num = 2; // 2 pieces moved
|
dp.piece[0] = make_piece(us, KING);
|
||||||
|
dp.from[0] = from;
|
||||||
if (Do)
|
dp.to[0] = to;
|
||||||
{
|
dp.piece[1] = make_piece(us, ROOK);
|
||||||
dp0 = piece_id_on(from);
|
dp.from[1] = rfrom;
|
||||||
dp1 = piece_id_on(rfrom);
|
dp.to[1] = rto;
|
||||||
dp.pieceId[0] = dp0;
|
dp.dirty_num = 2;
|
||||||
dp.old_piece[0] = evalList.piece_with_id(dp0);
|
|
||||||
evalList.put_piece(dp0, to, make_piece(us, KING));
|
|
||||||
dp.new_piece[0] = evalList.piece_with_id(dp0);
|
|
||||||
dp.pieceId[1] = dp1;
|
|
||||||
dp.old_piece[1] = evalList.piece_with_id(dp1);
|
|
||||||
evalList.put_piece(dp1, rto, make_piece(us, ROOK));
|
|
||||||
dp.new_piece[1] = evalList.piece_with_id(dp1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dp0 = piece_id_on(to);
|
|
||||||
dp1 = piece_id_on(rto);
|
|
||||||
evalList.put_piece(dp0, from, make_piece(us, KING));
|
|
||||||
evalList.put_piece(dp1, rfrom, make_piece(us, ROOK));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove both pieces first since squares could overlap in Chess960
|
// Remove both pieces first since squares could overlap in Chess960
|
||||||
@@ -1038,7 +993,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips
|
/// Position::do_null_move() is used to do a "null move": it flips
|
||||||
/// the side to move without executing any move on the board.
|
/// the side to move without executing any move on the board.
|
||||||
|
|
||||||
void Position::do_null_move(StateInfo& newSt) {
|
void Position::do_null_move(StateInfo& newSt) {
|
||||||
@@ -1046,17 +1001,17 @@ void Position::do_null_move(StateInfo& newSt) {
|
|||||||
assert(!checkers());
|
assert(!checkers());
|
||||||
assert(&newSt != st);
|
assert(&newSt != st);
|
||||||
|
|
||||||
if (Eval::useNNUE)
|
std::memcpy(&newSt, st, offsetof(StateInfo, accumulator));
|
||||||
{
|
|
||||||
std::memcpy(&newSt, st, sizeof(StateInfo));
|
|
||||||
st->accumulator.computed_score = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
std::memcpy(&newSt, st, offsetof(StateInfo, accumulator));
|
|
||||||
|
|
||||||
newSt.previous = st;
|
newSt.previous = st;
|
||||||
st = &newSt;
|
st = &newSt;
|
||||||
|
|
||||||
|
// Used by NNUE
|
||||||
|
st->dirtyPiece.dirty_num = 0;
|
||||||
|
st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator()
|
||||||
|
st->accumulator.computed[WHITE] = false;
|
||||||
|
st->accumulator.computed[BLACK] = false;
|
||||||
|
|
||||||
if (st->epSquare != SQ_NONE)
|
if (st->epSquare != SQ_NONE)
|
||||||
{
|
{
|
||||||
st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
|
st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
|
||||||
@@ -1064,9 +1019,9 @@ void Position::do_null_move(StateInfo& newSt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
st->key ^= Zobrist::side;
|
st->key ^= Zobrist::side;
|
||||||
prefetch(TT.first_entry(st->key));
|
|
||||||
|
|
||||||
++st->rule50;
|
++st->rule50;
|
||||||
|
prefetch(TT.first_entry(key()));
|
||||||
|
|
||||||
st->pliesFromNull = 0;
|
st->pliesFromNull = 0;
|
||||||
|
|
||||||
sideToMove = ~sideToMove;
|
sideToMove = ~sideToMove;
|
||||||
@@ -1078,6 +1033,9 @@ void Position::do_null_move(StateInfo& newSt) {
|
|||||||
assert(pos_is_ok());
|
assert(pos_is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Position::undo_null_move() must be used to undo a "null move"
|
||||||
|
|
||||||
void Position::undo_null_move() {
|
void Position::undo_null_move() {
|
||||||
|
|
||||||
assert(!checkers());
|
assert(!checkers());
|
||||||
@@ -1089,7 +1047,7 @@ void Position::undo_null_move() {
|
|||||||
|
|
||||||
/// Position::key_after() computes the new hash key after the given move. Needed
|
/// Position::key_after() computes the new hash key after the given move. Needed
|
||||||
/// for speculative prefetch. It doesn't recognize special moves like castling,
|
/// 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 {
|
Key Position::key_after(Move m) const {
|
||||||
|
|
||||||
@@ -1114,7 +1072,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
|
|
||||||
assert(is_ok(m));
|
assert(is_ok(m));
|
||||||
|
|
||||||
// Only deal with normal moves, assume others pass a simple see
|
// Only deal with normal moves, assume others pass a simple SEE
|
||||||
if (type_of(m) != NORMAL)
|
if (type_of(m) != NORMAL)
|
||||||
return VALUE_ZERO >= threshold;
|
return VALUE_ZERO >= threshold;
|
||||||
|
|
||||||
@@ -1128,8 +1086,9 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
if (swap <= 0)
|
if (swap <= 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
assert(color_of(piece_on(from)) == sideToMove);
|
||||||
Bitboard occupied = pieces() ^ from ^ to;
|
Bitboard occupied = pieces() ^ from ^ to;
|
||||||
Color stm = color_of(piece_on(from));
|
Color stm = sideToMove;
|
||||||
Bitboard attackers = attackers_to(to, occupied);
|
Bitboard attackers = attackers_to(to, occupied);
|
||||||
Bitboard stmAttackers, bb;
|
Bitboard stmAttackers, bb;
|
||||||
int res = 1;
|
int res = 1;
|
||||||
@@ -1143,10 +1102,10 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
if (!(stmAttackers = attackers & pieces(stm)))
|
if (!(stmAttackers = attackers & pieces(stm)))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Don't allow pinned pieces to attack (except the king) as long as
|
// Don't allow pinned pieces to attack as long as there are
|
||||||
// there are pinners on their original square.
|
// pinners on their original square.
|
||||||
if (st->pinners[~stm] & occupied)
|
if (pinners(~stm) & occupied)
|
||||||
stmAttackers &= ~st->blockersForKing[stm];
|
stmAttackers &= ~blockers_for_king(stm);
|
||||||
|
|
||||||
if (!stmAttackers)
|
if (!stmAttackers)
|
||||||
break;
|
break;
|
||||||
@@ -1160,7 +1119,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
if ((swap = PawnValueMg - swap) < res)
|
if ((swap = PawnValueMg - swap) < res)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
occupied ^= lsb(bb);
|
occupied ^= least_significant_square_bb(bb);
|
||||||
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
|
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1169,7 +1128,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
if ((swap = KnightValueMg - swap) < res)
|
if ((swap = KnightValueMg - swap) < res)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
occupied ^= lsb(bb);
|
occupied ^= least_significant_square_bb(bb);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ((bb = stmAttackers & pieces(BISHOP)))
|
else if ((bb = stmAttackers & pieces(BISHOP)))
|
||||||
@@ -1177,7 +1136,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
if ((swap = BishopValueMg - swap) < res)
|
if ((swap = BishopValueMg - swap) < res)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
occupied ^= lsb(bb);
|
occupied ^= least_significant_square_bb(bb);
|
||||||
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
|
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1186,7 +1145,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
if ((swap = RookValueMg - swap) < res)
|
if ((swap = RookValueMg - swap) < res)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
occupied ^= lsb(bb);
|
occupied ^= least_significant_square_bb(bb);
|
||||||
attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
|
attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1195,7 +1154,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
if ((swap = QueenValueMg - swap) < res)
|
if ((swap = QueenValueMg - swap) < res)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
occupied ^= lsb(bb);
|
occupied ^= least_significant_square_bb(bb);
|
||||||
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
|
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
|
||||||
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
|
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
|
||||||
}
|
}
|
||||||
@@ -1224,6 +1183,22 @@ bool Position::is_draw(int ply) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Position::is_fifty_move_draw() returns true if a game can be claimed
|
||||||
|
/// by a fifty-move draw rule.
|
||||||
|
|
||||||
|
bool Position::is_fifty_move_draw() const {
|
||||||
|
|
||||||
|
return (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Position::is_three_fold_repetition() returns true if there is 3-fold repetition.
|
||||||
|
bool Position::is_three_fold_repetition() const {
|
||||||
|
|
||||||
|
return st->repetition < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Position::has_repeated() tests whether there has been at least one repetition
|
// Position::has_repeated() tests whether there has been at least one repetition
|
||||||
// of positions since the last capture or pawn move.
|
// of positions since the last capture or pawn move.
|
||||||
|
|
||||||
@@ -1269,7 +1244,7 @@ bool Position::has_game_cycle(int ply) const {
|
|||||||
Square s1 = from_sq(move);
|
Square s1 = from_sq(move);
|
||||||
Square s2 = to_sq(move);
|
Square s2 = to_sq(move);
|
||||||
|
|
||||||
if (!(between_bb(s1, s2) & pieces()))
|
if (!((between_bb(s1, s2) ^ s2) & pieces()))
|
||||||
{
|
{
|
||||||
if (ply > i)
|
if (ply > i)
|
||||||
return true;
|
return true;
|
||||||
@@ -1366,21 +1341,17 @@ bool Position::pos_is_ok() const {
|
|||||||
assert(0 && "pos_is_ok: Bitboards");
|
assert(0 && "pos_is_ok: Bitboards");
|
||||||
|
|
||||||
StateInfo si = *st;
|
StateInfo si = *st;
|
||||||
|
ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize);
|
||||||
|
|
||||||
set_state(&si);
|
set_state(&si);
|
||||||
if (std::memcmp(&si, st, sizeof(StateInfo)))
|
if (std::memcmp(&si, st, sizeof(StateInfo)))
|
||||||
assert(0 && "pos_is_ok: State");
|
assert(0 && "pos_is_ok: State");
|
||||||
|
|
||||||
for (Piece pc : Pieces)
|
for (Piece pc : Pieces)
|
||||||
{
|
|
||||||
if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
|
if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
|
||||||
|| pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
|
|| pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
|
||||||
assert(0 && "pos_is_ok: Pieces");
|
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 (Color c : { WHITE, BLACK })
|
||||||
for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})
|
for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})
|
||||||
{
|
{
|
||||||
@@ -1395,3 +1366,19 @@ bool Position::pos_is_ok() const {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a function that directly unpacks for speed. It's pretty tough.
|
||||||
|
// Write it by combining packer::unpack() and Position::set().
|
||||||
|
// If there is a problem with the passed phase and there is an error, non-zero is returned.
|
||||||
|
int Position::set_from_packed_sfen(const Tools::PackedSfen& sfen , StateInfo* si, Thread* th, bool frc)
|
||||||
|
{
|
||||||
|
return Tools::set_from_packed_sfen(*this, sfen, si, th, frc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the packed sfen. Returns to the buffer specified in the argument.
|
||||||
|
void Position::sfen_pack(Tools::PackedSfen& sfen, bool resetCastlingRights)
|
||||||
|
{
|
||||||
|
sfen = Tools::sfen_pack(*this, resetCastlingRights);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+69
-69
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,10 +26,15 @@
|
|||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "evaluate.h"
|
#include "evaluate.h"
|
||||||
|
#include "psqt.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#include "nnue/nnue_accumulator.h"
|
#include "nnue/nnue_accumulator.h"
|
||||||
|
|
||||||
|
#include "tools/packed_sfen.h"
|
||||||
|
#include "tools/sfen_packer.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
/// StateInfo struct stores information needed to restore a Position object to
|
/// 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
|
/// its previous state when we retract a move. Whenever a move is made on the
|
||||||
@@ -49,11 +54,11 @@ struct StateInfo {
|
|||||||
// Not copied when making a move (will be recomputed anyhow)
|
// Not copied when making a move (will be recomputed anyhow)
|
||||||
Key key;
|
Key key;
|
||||||
Bitboard checkersBB;
|
Bitboard checkersBB;
|
||||||
Piece capturedPiece;
|
|
||||||
StateInfo* previous;
|
StateInfo* previous;
|
||||||
Bitboard blockersForKing[COLOR_NB];
|
Bitboard blockersForKing[COLOR_NB];
|
||||||
Bitboard pinners[COLOR_NB];
|
Bitboard pinners[COLOR_NB];
|
||||||
Bitboard checkSquares[PIECE_TYPE_NB];
|
Bitboard checkSquares[PIECE_TYPE_NB];
|
||||||
|
Piece capturedPiece;
|
||||||
int repetition;
|
int repetition;
|
||||||
|
|
||||||
// Used by NNUE
|
// Used by NNUE
|
||||||
@@ -86,7 +91,7 @@ public:
|
|||||||
// FEN string input/output
|
// FEN string input/output
|
||||||
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
|
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
|
||||||
Position& set(const std::string& code, Color c, StateInfo* si);
|
Position& set(const std::string& code, Color c, StateInfo* si);
|
||||||
const std::string fen() const;
|
std::string fen() const;
|
||||||
|
|
||||||
// Position representation
|
// Position representation
|
||||||
Bitboard pieces(PieceType pt) const;
|
Bitboard pieces(PieceType pt) const;
|
||||||
@@ -99,7 +104,6 @@ public:
|
|||||||
bool empty(Square s) const;
|
bool empty(Square s) const;
|
||||||
template<PieceType Pt> int count(Color c) const;
|
template<PieceType Pt> int count(Color c) const;
|
||||||
template<PieceType Pt> int count() const;
|
template<PieceType Pt> int count() const;
|
||||||
template<PieceType Pt> const Square* squares(Color c) const;
|
|
||||||
template<PieceType Pt> Square square(Color c) const;
|
template<PieceType Pt> Square square(Color c) const;
|
||||||
bool is_on_semiopen_file(Color c, Square s) const;
|
bool is_on_semiopen_file(Color c, Square s) const;
|
||||||
|
|
||||||
@@ -113,12 +117,13 @@ public:
|
|||||||
Bitboard checkers() const;
|
Bitboard checkers() const;
|
||||||
Bitboard blockers_for_king(Color c) const;
|
Bitboard blockers_for_king(Color c) const;
|
||||||
Bitboard check_squares(PieceType pt) const;
|
Bitboard check_squares(PieceType pt) const;
|
||||||
bool is_discovery_check_on_king(Color c, Move m) const;
|
Bitboard pinners(Color c) const;
|
||||||
|
|
||||||
// Attacks to/from a given square
|
// Attacks to/from a given square
|
||||||
Bitboard attackers_to(Square s) const;
|
Bitboard attackers_to(Square s) const;
|
||||||
Bitboard attackers_to(Square s, Bitboard occupied) const;
|
Bitboard attackers_to(Square s, Bitboard occupied) const;
|
||||||
Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
|
Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
|
||||||
|
template<PieceType Pt> Bitboard attacks_by(Color c) const;
|
||||||
|
|
||||||
// Properties of moves
|
// Properties of moves
|
||||||
bool legal(Move m) const;
|
bool legal(Move m) const;
|
||||||
@@ -126,7 +131,6 @@ public:
|
|||||||
bool capture(Move m) const;
|
bool capture(Move m) const;
|
||||||
bool capture_or_promotion(Move m) const;
|
bool capture_or_promotion(Move m) const;
|
||||||
bool gives_check(Move m) const;
|
bool gives_check(Move m) const;
|
||||||
bool advanced_pawn_push(Move m) const;
|
|
||||||
Piece moved_piece(Move m) const;
|
Piece moved_piece(Move m) const;
|
||||||
Piece captured_piece() const;
|
Piece captured_piece() const;
|
||||||
|
|
||||||
@@ -157,6 +161,8 @@ public:
|
|||||||
bool is_chess960() const;
|
bool is_chess960() const;
|
||||||
Thread* this_thread() const;
|
Thread* this_thread() const;
|
||||||
bool is_draw(int ply) const;
|
bool is_draw(int ply) const;
|
||||||
|
bool is_fifty_move_draw() const;
|
||||||
|
bool is_three_fold_repetition() const;
|
||||||
bool has_game_cycle(int ply) const;
|
bool has_game_cycle(int ply) const;
|
||||||
bool has_repeated() const;
|
bool has_repeated() const;
|
||||||
int rule50_count() const;
|
int rule50_count() const;
|
||||||
@@ -170,7 +176,31 @@ public:
|
|||||||
|
|
||||||
// Used by NNUE
|
// Used by NNUE
|
||||||
StateInfo* state() const;
|
StateInfo* state() const;
|
||||||
const EvalList* eval_list() const;
|
|
||||||
|
// --sfenization helper
|
||||||
|
|
||||||
|
friend int Tools::set_from_packed_sfen(Position& pos, const Tools::PackedSfen& sfen, StateInfo* si, Thread* th, bool frc);
|
||||||
|
|
||||||
|
// Get the packed sfen. Returns to the buffer specified in the argument.
|
||||||
|
// Do not include gamePly in pack.
|
||||||
|
void sfen_pack(Tools::PackedSfen& sfen, bool resetCastlingRights);
|
||||||
|
|
||||||
|
// It is slow to go through sfen, so I made a function to set packed sfen directly.
|
||||||
|
// Equivalent to pos.set(sfen_unpack(data),si,th);.
|
||||||
|
// If there is a problem with the passed phase and there is an error, non-zero is returned.
|
||||||
|
// PackedSfen does not include gamePly so it cannot be restored. If you want to set it, specify it with an argument.
|
||||||
|
int set_from_packed_sfen(const Tools::PackedSfen& sfen, StateInfo* si, Thread* th, bool frc);
|
||||||
|
|
||||||
|
void clear() { std::memset(this, 0, sizeof(Position)); }
|
||||||
|
|
||||||
|
// Give the board, hand piece, and turn, and return the sfen.
|
||||||
|
//static std::string sfen_from_rawdata(Piece board[81], Hand hands[2], Color turn, int gamePly);
|
||||||
|
|
||||||
|
// Returns the position of the ball on the c side.
|
||||||
|
Square king_square(Color c) const { return lsb(pieces(c, KING)); }
|
||||||
|
|
||||||
|
void put_piece(Piece pc, Square s);
|
||||||
|
void remove_piece(Square s);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Initialization helpers (used while setting up a position)
|
// Initialization helpers (used while setting up a position)
|
||||||
@@ -179,40 +209,26 @@ private:
|
|||||||
void set_check_info(StateInfo* si) const;
|
void set_check_info(StateInfo* si) const;
|
||||||
|
|
||||||
// Other helpers
|
// Other helpers
|
||||||
void put_piece(Piece pc, Square s);
|
|
||||||
void remove_piece(Square s);
|
|
||||||
void move_piece(Square from, Square to);
|
void move_piece(Square from, Square to);
|
||||||
template<bool Do>
|
template<bool Do>
|
||||||
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
|
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
|
||||||
|
|
||||||
// ID of a piece on a given square
|
|
||||||
PieceId piece_id_on(Square sq) const;
|
|
||||||
|
|
||||||
// Data members
|
// Data members
|
||||||
Piece board[SQUARE_NB];
|
Piece board[SQUARE_NB];
|
||||||
Bitboard byTypeBB[PIECE_TYPE_NB];
|
Bitboard byTypeBB[PIECE_TYPE_NB];
|
||||||
Bitboard byColorBB[COLOR_NB];
|
Bitboard byColorBB[COLOR_NB];
|
||||||
int pieceCount[PIECE_NB];
|
int pieceCount[PIECE_NB];
|
||||||
Square pieceList[PIECE_NB][16];
|
|
||||||
int index[SQUARE_NB];
|
|
||||||
int castlingRightsMask[SQUARE_NB];
|
int castlingRightsMask[SQUARE_NB];
|
||||||
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
||||||
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
||||||
|
Thread* thisThread;
|
||||||
|
StateInfo* st;
|
||||||
int gamePly;
|
int gamePly;
|
||||||
Color sideToMove;
|
Color sideToMove;
|
||||||
Score psq;
|
Score psq;
|
||||||
Thread* thisThread;
|
|
||||||
StateInfo* st;
|
|
||||||
bool chess960;
|
bool chess960;
|
||||||
|
|
||||||
// List of pieces used in NNUE evaluation function
|
|
||||||
EvalList evalList;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace PSQT {
|
|
||||||
extern Score psq[PIECE_NB][SQUARE_NB];
|
|
||||||
}
|
|
||||||
|
|
||||||
extern std::ostream& operator<<(std::ostream& os, const Position& pos);
|
extern std::ostream& operator<<(std::ostream& os, const Position& pos);
|
||||||
|
|
||||||
inline Color Position::side_to_move() const {
|
inline Color Position::side_to_move() const {
|
||||||
@@ -260,13 +276,9 @@ template<PieceType Pt> inline int Position::count() const {
|
|||||||
return count<Pt>(WHITE) + count<Pt>(BLACK);
|
return count<Pt>(WHITE) + count<Pt>(BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PieceType Pt> inline const Square* Position::squares(Color c) const {
|
|
||||||
return pieceList[make_piece(c, Pt)];
|
|
||||||
}
|
|
||||||
|
|
||||||
template<PieceType Pt> inline Square Position::square(Color c) const {
|
template<PieceType Pt> inline Square Position::square(Color c) const {
|
||||||
assert(pieceCount[make_piece(c, Pt)] == 1);
|
assert(count<Pt>(c) == 1);
|
||||||
return squares<Pt>(c)[0];
|
return lsb(pieces(c, Pt));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Square Position::ep_square() const {
|
inline Square Position::ep_square() const {
|
||||||
@@ -301,6 +313,22 @@ inline Bitboard Position::attackers_to(Square s) const {
|
|||||||
return attackers_to(s, pieces());
|
return attackers_to(s, pieces());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<PieceType Pt>
|
||||||
|
inline Bitboard Position::attacks_by(Color c) const {
|
||||||
|
|
||||||
|
if constexpr (Pt == PAWN)
|
||||||
|
return c == WHITE ? pawn_attacks_bb<WHITE>(pieces(WHITE, PAWN))
|
||||||
|
: pawn_attacks_bb<BLACK>(pieces(BLACK, PAWN));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Bitboard threats = 0;
|
||||||
|
Bitboard attackers = pieces(c, Pt);
|
||||||
|
while (attackers)
|
||||||
|
threats |= attacks_bb<Pt>(pop_lsb(attackers), pieces());
|
||||||
|
return threats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline Bitboard Position::checkers() const {
|
inline Bitboard Position::checkers() const {
|
||||||
return st->checkersBB;
|
return st->checkersBB;
|
||||||
}
|
}
|
||||||
@@ -309,29 +337,25 @@ inline Bitboard Position::blockers_for_king(Color c) const {
|
|||||||
return st->blockersForKing[c];
|
return st->blockersForKing[c];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Bitboard Position::pinners(Color c) const {
|
||||||
|
return st->pinners[c];
|
||||||
|
}
|
||||||
|
|
||||||
inline Bitboard Position::check_squares(PieceType pt) const {
|
inline Bitboard Position::check_squares(PieceType pt) const {
|
||||||
return st->checkSquares[pt];
|
return st->checkSquares[pt];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Position::is_discovery_check_on_king(Color c, Move m) const {
|
|
||||||
return st->blockersForKing[c] & from_sq(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool Position::pawn_passed(Color c, Square s) const {
|
inline bool Position::pawn_passed(Color c, Square s) const {
|
||||||
return !(pieces(~c, PAWN) & passed_pawn_span(c, s));
|
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 {
|
inline int Position::pawns_on_same_color_squares(Color c, Square s) const {
|
||||||
return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares));
|
return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Key Position::key() const {
|
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 {
|
inline Key Position::pawn_key() const {
|
||||||
@@ -380,7 +404,7 @@ inline bool Position::capture_or_promotion(Move m) const {
|
|||||||
inline bool Position::capture(Move m) const {
|
inline bool Position::capture(Move m) const {
|
||||||
assert(is_ok(m));
|
assert(is_ok(m));
|
||||||
// Castling is encoded as "king captures rook"
|
// 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 {
|
inline Piece Position::captured_piece() const {
|
||||||
@@ -396,35 +420,25 @@ inline void Position::put_piece(Piece pc, Square s) {
|
|||||||
board[s] = pc;
|
board[s] = pc;
|
||||||
byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
|
byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
|
||||||
byColorBB[color_of(pc)] |= s;
|
byColorBB[color_of(pc)] |= s;
|
||||||
index[s] = pieceCount[pc]++;
|
pieceCount[pc]++;
|
||||||
pieceList[pc][index[s]] = s;
|
|
||||||
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
|
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
|
||||||
psq += PSQT::psq[pc][s];
|
psq += PSQT::psq[pc][s];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Position::remove_piece(Square 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];
|
Piece pc = board[s];
|
||||||
byTypeBB[ALL_PIECES] ^= s;
|
byTypeBB[ALL_PIECES] ^= s;
|
||||||
byTypeBB[type_of(pc)] ^= s;
|
byTypeBB[type_of(pc)] ^= s;
|
||||||
byColorBB[color_of(pc)] ^= s;
|
byColorBB[color_of(pc)] ^= s;
|
||||||
/* board[s] = NO_PIECE; Not needed, overwritten by the capturing one */
|
board[s] = NO_PIECE;
|
||||||
Square lastSquare = pieceList[pc][--pieceCount[pc]];
|
pieceCount[pc]--;
|
||||||
index[lastSquare] = index[s];
|
|
||||||
pieceList[pc][index[lastSquare]] = lastSquare;
|
|
||||||
pieceList[pc][pieceCount[pc]] = SQ_NONE;
|
|
||||||
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
|
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
|
||||||
psq -= PSQT::psq[pc][s];
|
psq -= PSQT::psq[pc][s];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Position::move_piece(Square from, Square to) {
|
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];
|
Piece pc = board[from];
|
||||||
Bitboard fromTo = from | to;
|
Bitboard fromTo = from | to;
|
||||||
byTypeBB[ALL_PIECES] ^= fromTo;
|
byTypeBB[ALL_PIECES] ^= fromTo;
|
||||||
@@ -432,8 +446,6 @@ inline void Position::move_piece(Square from, Square to) {
|
|||||||
byColorBB[color_of(pc)] ^= fromTo;
|
byColorBB[color_of(pc)] ^= fromTo;
|
||||||
board[from] = NO_PIECE;
|
board[from] = NO_PIECE;
|
||||||
board[to] = pc;
|
board[to] = pc;
|
||||||
index[to] = index[from];
|
|
||||||
pieceList[pc][index[to]] = to;
|
|
||||||
psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
|
psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,20 +458,8 @@ inline StateInfo* Position::state() const {
|
|||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const EvalList* Position::eval_list() const {
|
static const char* const StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||||
|
|
||||||
return &evalList;
|
} // namespace Stockfish
|
||||||
}
|
|
||||||
|
|
||||||
inline PieceId Position::piece_id_on(Square sq) const
|
|
||||||
{
|
|
||||||
|
|
||||||
assert(piece_on(sq) != NO_PIECE);
|
|
||||||
|
|
||||||
PieceId pid = evalList.piece_id_list[sq];
|
|
||||||
assert(is_ok(pid));
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // #ifndef POSITION_H_INCLUDED
|
#endif // #ifndef POSITION_H_INCLUDED
|
||||||
|
|||||||
+44
-35
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,19 +16,23 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "psqt.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
#include "bitboard.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
|
auto constexpr S = make_score;
|
||||||
// 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
|
// 'Bonus' contains Piece-Square parameters.
|
||||||
// second half of the files.
|
// Scores are explicit for files A to D, implicitly mirrored for E to H.
|
||||||
constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
|
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) }
|
{ S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) }
|
||||||
},
|
},
|
||||||
{ // Bishop
|
{ // Bishop
|
||||||
{ S(-53,-57), S( -5,-30), S( -8,-37), S(-23,-12) },
|
{ S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) },
|
||||||
{ S(-15,-37), S( 8,-13), S( 19,-17), S( 4, 1) },
|
{ S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) },
|
||||||
{ S( -7,-16), S( 21, -1), S( -5, -2), S( 17, 10) },
|
{ S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) },
|
||||||
{ S( -5,-20), S( 11, -6), S( 25, 0), S( 39, 17) },
|
{ S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) },
|
||||||
{ S(-12,-17), S( 29, -1), S( 22,-14), S( 31, 15) },
|
{ S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) },
|
||||||
{ S(-16,-30), S( 6, 6), S( 1, 4), S( 11, 6) },
|
{ S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) },
|
||||||
{ S(-17,-31), S(-14,-20), S( 5, -1), S( 0, 1) },
|
{ S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) },
|
||||||
{ S(-48,-46), S( 1,-42), S(-14,-37), S(-23,-24) }
|
{ S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) }
|
||||||
},
|
},
|
||||||
{ // Rook
|
{ // Rook
|
||||||
{ S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) },
|
{ 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
|
{ // Queen
|
||||||
{ S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) },
|
{ 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(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) },
|
||||||
{ S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) },
|
{ 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( 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(-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
|
{ // King
|
||||||
{ S(271, 1), S(327, 45), S(271, 85), S(198, 76) },
|
{ 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] =
|
constexpr Score PBonus[RANK_NB][FILE_NB] =
|
||||||
{ // Pawn (asymmetric distribution)
|
{ // 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( 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,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) },
|
{ 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( -4, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S( -8, -9) },
|
{ 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( 13, 10), S( 0, 5), S(-13, 4), S( 1, -5), S( 11, -5), S( -2, -5), S(-13, 14), S( 5, 9) },
|
{ 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( 5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S( -8, 13) },
|
{ 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, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) }
|
{ 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];
|
Score psq[PIECE_NB][SQUARE_NB];
|
||||||
|
|
||||||
|
|
||||||
// PSQT::init() initializes piece-square tables: the white halves of the tables are
|
// 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
|
// 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.
|
// 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})
|
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)
|
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||||
{
|
{
|
||||||
File f = File(edge_distance(file_of(s)));
|
File f = File(edge_distance(file_of(s)));
|
||||||
psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
|
psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
|
||||||
: Bonus[pc][rank_of(s)][f]);
|
: Bonus[pc][rank_of(s)][f]);
|
||||||
psq[~pc][flip_rank(s)] = -psq[pc][s];
|
psq[~pc][flip_rank(s)] = -psq[pc][s];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace PSQT
|
} // namespace PSQT
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,30 +16,23 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//Common header of input features of NNUE evaluation function
|
|
||||||
|
|
||||||
#ifndef NNUE_FEATURES_COMMON_H_INCLUDED
|
#ifndef PSQT_H_INCLUDED
|
||||||
#define NNUE_FEATURES_COMMON_H_INCLUDED
|
#define PSQT_H_INCLUDED
|
||||||
|
|
||||||
#include "../../evaluate.h"
|
|
||||||
#include "../nnue_common.h"
|
|
||||||
|
|
||||||
namespace Eval::NNUE::Features {
|
#include "types.h"
|
||||||
|
|
||||||
class IndexList;
|
|
||||||
|
|
||||||
template <typename... FeatureTypes>
|
namespace Stockfish::PSQT
|
||||||
class FeatureSet;
|
{
|
||||||
|
|
||||||
// Trigger to perform full calculations instead of difference only
|
extern Score psq[PIECE_NB][SQUARE_NB];
|
||||||
enum class TriggerEvent {
|
|
||||||
kFriendKingMoved // calculate full evaluation when own king moves
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Side {
|
// Fill psqt array from a set of internally linked parameters
|
||||||
kFriend // side to move
|
extern void init();
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Eval::NNUE::Features
|
} // namespace Stockfish::PSQT
|
||||||
|
|
||||||
#endif // #ifndef NNUE_FEATURES_COMMON_H_INCLUDED
|
|
||||||
|
#endif // PSQT_H_INCLUDED
|
||||||
+1769
-539
File diff suppressed because it is too large
Load Diff
+45
-3
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "movepick.h"
|
#include "movepick.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
class Position;
|
class Position;
|
||||||
|
|
||||||
@@ -32,6 +35,7 @@ namespace Search {
|
|||||||
/// Threshold used for countermoves based pruning
|
/// Threshold used for countermoves based pruning
|
||||||
constexpr int CounterMovePruneThreshold = 0;
|
constexpr int CounterMovePruneThreshold = 0;
|
||||||
|
|
||||||
|
extern bool prune_at_shallow_depth;
|
||||||
|
|
||||||
/// Stack struct keeps track of the information we need to remember from nodes
|
/// Stack struct keeps track of the information we need to remember from nodes
|
||||||
/// shallower and deeper in the tree during the search. Each search thread has
|
/// shallower and deeper in the tree during the search. Each search thread has
|
||||||
@@ -48,6 +52,10 @@ struct Stack {
|
|||||||
int statScore;
|
int statScore;
|
||||||
int moveCount;
|
int moveCount;
|
||||||
bool inCheck;
|
bool inCheck;
|
||||||
|
bool ttPv;
|
||||||
|
bool ttHit;
|
||||||
|
int doubleExtensions;
|
||||||
|
int cutoffCnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -67,9 +75,9 @@ struct RootMove {
|
|||||||
|
|
||||||
Value score = -VALUE_INFINITE;
|
Value score = -VALUE_INFINITE;
|
||||||
Value previousScore = -VALUE_INFINITE;
|
Value previousScore = -VALUE_INFINITE;
|
||||||
|
Value averageScore = -VALUE_INFINITE;
|
||||||
int selDepth = 0;
|
int selDepth = 0;
|
||||||
int tbRank = 0;
|
int tbRank = 0;
|
||||||
int bestMoveCount = 0;
|
|
||||||
Value tbScore;
|
Value tbScore;
|
||||||
std::vector<Move> pv;
|
std::vector<Move> pv;
|
||||||
};
|
};
|
||||||
@@ -86,6 +94,7 @@ struct LimitsType {
|
|||||||
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
|
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
|
||||||
movestogo = depth = mate = perft = infinite = 0;
|
movestogo = depth = mate = perft = infinite = 0;
|
||||||
nodes = 0;
|
nodes = 0;
|
||||||
|
silent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool use_time_management() const {
|
bool use_time_management() const {
|
||||||
@@ -96,6 +105,9 @@ struct LimitsType {
|
|||||||
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
|
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
|
||||||
int movestogo, depth, mate, perft, infinite;
|
int movestogo, depth, mate, perft, infinite;
|
||||||
int64_t nodes;
|
int64_t nodes;
|
||||||
|
// Silent mode that does not output to the screen (for continuous self-play in process)
|
||||||
|
// Do not output PV at this time.
|
||||||
|
bool silent;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern LimitsType Limits;
|
extern LimitsType Limits;
|
||||||
@@ -103,6 +115,36 @@ extern LimitsType Limits;
|
|||||||
void init();
|
void init();
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
} // namespace Search
|
// A pair of reader and evaluation value. Returned by Tools::search(),Tools::qsearch().
|
||||||
|
using ValueAndPV = std::pair<Value, std::vector<Move>>;
|
||||||
|
|
||||||
|
ValueAndPV qsearch(Position& pos);
|
||||||
|
ValueAndPV search(Position& pos, int depth_, size_t multiPV = 1, uint64_t nodesLimit = 0);
|
||||||
|
|
||||||
|
namespace MCTS {
|
||||||
|
|
||||||
|
struct MctsContinuation {
|
||||||
|
std::uint64_t numVisits;
|
||||||
|
Value value;
|
||||||
|
float actionValue;
|
||||||
|
std::vector<Move> pv;
|
||||||
|
};
|
||||||
|
|
||||||
|
ValueAndPV search_mcts(
|
||||||
|
Position& pos,
|
||||||
|
std::uint64_t nodes,
|
||||||
|
Depth leafDepth,
|
||||||
|
float explorationFactor);
|
||||||
|
|
||||||
|
std::vector<MctsContinuation> search_mcts_multipv(
|
||||||
|
Position& pos,
|
||||||
|
std::uint64_t numPlayouts,
|
||||||
|
Depth leafDepth,
|
||||||
|
float explorationFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef SEARCH_H_INCLUDED
|
#endif // #ifndef SEARCH_H_INCLUDED
|
||||||
|
|||||||
+387
@@ -0,0 +1,387 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STOCKFISH_SIMD_H_INCLUDED
|
||||||
|
#define STOCKFISH_SIMD_H_INCLUDED
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
# include <immintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_SSE41)
|
||||||
|
# include <smmintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_SSSE3)
|
||||||
|
# include <tmmintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_SSE2)
|
||||||
|
# include <emmintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_MMX)
|
||||||
|
# include <mmintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
# include <arm_neon.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The inline asm is only safe for GCC, where it is necessary to get good codegen.
|
||||||
|
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101693
|
||||||
|
// Clang does fine without it.
|
||||||
|
// Play around here: https://godbolt.org/z/7EWqrYq51
|
||||||
|
#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER))
|
||||||
|
#define USE_INLINE_ASM
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Use either the AVX512 or AVX-VNNI version of the VNNI instructions.
|
||||||
|
#if defined(USE_AVXVNNI)
|
||||||
|
#define VNNI_PREFIX "%{vex%} "
|
||||||
|
#else
|
||||||
|
#define VNNI_PREFIX ""
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Stockfish::Simd {
|
||||||
|
|
||||||
|
#if defined (USE_AVX512)
|
||||||
|
|
||||||
|
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
|
||||||
|
return _mm512_reduce_add_epi32(sum) + bias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parameters:
|
||||||
|
sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]]
|
||||||
|
sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]]
|
||||||
|
sum2 = [zmm2.i128[0], zmm2.i128[1], zmm2.i128[2], zmm2.i128[3]]
|
||||||
|
sum3 = [zmm3.i128[0], zmm3.i128[1], zmm3.i128[2], zmm3.i128[3]]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ret = [
|
||||||
|
reduce_add_epi32(zmm0.i128[0]), reduce_add_epi32(zmm1.i128[0]), reduce_add_epi32(zmm2.i128[0]), reduce_add_epi32(zmm3.i128[0]),
|
||||||
|
reduce_add_epi32(zmm0.i128[1]), reduce_add_epi32(zmm1.i128[1]), reduce_add_epi32(zmm2.i128[1]), reduce_add_epi32(zmm3.i128[1]),
|
||||||
|
reduce_add_epi32(zmm0.i128[2]), reduce_add_epi32(zmm1.i128[2]), reduce_add_epi32(zmm2.i128[2]), reduce_add_epi32(zmm3.i128[2]),
|
||||||
|
reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3])
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
[[maybe_unused]] static __m512i m512_hadd128x16_interleave(
|
||||||
|
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
|
||||||
|
|
||||||
|
__m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
|
||||||
|
__m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
|
||||||
|
|
||||||
|
__m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3);
|
||||||
|
__m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3);
|
||||||
|
|
||||||
|
__m512i sum01 = _mm512_add_epi32(sum01a, sum01b);
|
||||||
|
__m512i sum23 = _mm512_add_epi32(sum23a, sum23b);
|
||||||
|
|
||||||
|
__m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23);
|
||||||
|
__m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23);
|
||||||
|
|
||||||
|
return _mm512_add_epi32(sum0123a, sum0123b);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static __m128i m512_haddx4(
|
||||||
|
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3,
|
||||||
|
__m128i bias) {
|
||||||
|
|
||||||
|
__m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3);
|
||||||
|
|
||||||
|
__m256i sum256lo = _mm512_castsi512_si256(sum);
|
||||||
|
__m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1);
|
||||||
|
|
||||||
|
sum256lo = _mm256_add_epi32(sum256lo, sum256hi);
|
||||||
|
|
||||||
|
__m128i sum128lo = _mm256_castsi256_si128(sum256lo);
|
||||||
|
__m128i sum128hi = _mm256_extracti128_si256(sum256lo, 1);
|
||||||
|
|
||||||
|
return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static void m512_add_dpbusd_epi32(
|
||||||
|
__m512i& acc,
|
||||||
|
__m512i a,
|
||||||
|
__m512i b) {
|
||||||
|
|
||||||
|
# if defined (USE_VNNI)
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
asm(
|
||||||
|
"vpdpbusd %[b], %[a], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc)
|
||||||
|
: [a]"v"(a), [b]"vm"(b)
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
acc = _mm512_dpbusd_epi32(acc, a, b);
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
__m512i tmp = _mm512_maddubs_epi16(a, b);
|
||||||
|
asm(
|
||||||
|
"vpmaddwd %[tmp], %[ones], %[tmp]\n\t"
|
||||||
|
"vpaddd %[acc], %[tmp], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
|
||||||
|
: [ones]"v"(_mm512_set1_epi16(1))
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
__m512i product0 = _mm512_maddubs_epi16(a, b);
|
||||||
|
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
|
||||||
|
acc = _mm512_add_epi32(acc, product0);
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static void m512_add_dpbusd_epi32x2(
|
||||||
|
__m512i& acc,
|
||||||
|
__m512i a0, __m512i b0,
|
||||||
|
__m512i a1, __m512i b1) {
|
||||||
|
|
||||||
|
# if defined (USE_VNNI)
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
asm(
|
||||||
|
"vpdpbusd %[b0], %[a0], %[acc]\n\t"
|
||||||
|
"vpdpbusd %[b1], %[a1], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc)
|
||||||
|
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
acc = _mm512_dpbusd_epi32(acc, a0, b0);
|
||||||
|
acc = _mm512_dpbusd_epi32(acc, a1, b1);
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
__m512i tmp0 = _mm512_maddubs_epi16(a0, b0);
|
||||||
|
__m512i tmp1 = _mm512_maddubs_epi16(a1, b1);
|
||||||
|
asm(
|
||||||
|
"vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
|
||||||
|
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
|
||||||
|
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
|
||||||
|
: [tmp1]"v"(tmp1), [ones]"v"(_mm512_set1_epi16(1))
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
__m512i product0 = _mm512_maddubs_epi16(a0, b0);
|
||||||
|
__m512i product1 = _mm512_maddubs_epi16(a1, b1);
|
||||||
|
product0 = _mm512_adds_epi16(product0, product1);
|
||||||
|
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
|
||||||
|
acc = _mm512_add_epi32(acc, product0);
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined (USE_AVX2)
|
||||||
|
|
||||||
|
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
|
||||||
|
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
|
||||||
|
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
|
||||||
|
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
|
||||||
|
return _mm_cvtsi128_si32(sum128) + bias;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static __m128i m256_haddx4(
|
||||||
|
__m256i sum0, __m256i sum1, __m256i sum2, __m256i sum3,
|
||||||
|
__m128i bias) {
|
||||||
|
|
||||||
|
sum0 = _mm256_hadd_epi32(sum0, sum1);
|
||||||
|
sum2 = _mm256_hadd_epi32(sum2, sum3);
|
||||||
|
|
||||||
|
sum0 = _mm256_hadd_epi32(sum0, sum2);
|
||||||
|
|
||||||
|
__m128i sum128lo = _mm256_castsi256_si128(sum0);
|
||||||
|
__m128i sum128hi = _mm256_extracti128_si256(sum0, 1);
|
||||||
|
|
||||||
|
return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static void m256_add_dpbusd_epi32(
|
||||||
|
__m256i& acc,
|
||||||
|
__m256i a,
|
||||||
|
__m256i b) {
|
||||||
|
|
||||||
|
# if defined (USE_VNNI)
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
asm(
|
||||||
|
VNNI_PREFIX "vpdpbusd %[b], %[a], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc)
|
||||||
|
: [a]"v"(a), [b]"vm"(b)
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
acc = _mm256_dpbusd_epi32(acc, a, b);
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
__m256i tmp = _mm256_maddubs_epi16(a, b);
|
||||||
|
asm(
|
||||||
|
"vpmaddwd %[tmp], %[ones], %[tmp]\n\t"
|
||||||
|
"vpaddd %[acc], %[tmp], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
|
||||||
|
: [ones]"v"(_mm256_set1_epi16(1))
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
__m256i product0 = _mm256_maddubs_epi16(a, b);
|
||||||
|
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
|
||||||
|
acc = _mm256_add_epi32(acc, product0);
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static void m256_add_dpbusd_epi32x2(
|
||||||
|
__m256i& acc,
|
||||||
|
__m256i a0, __m256i b0,
|
||||||
|
__m256i a1, __m256i b1) {
|
||||||
|
|
||||||
|
# if defined (USE_VNNI)
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
asm(
|
||||||
|
VNNI_PREFIX "vpdpbusd %[b0], %[a0], %[acc]\n\t"
|
||||||
|
VNNI_PREFIX "vpdpbusd %[b1], %[a1], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc)
|
||||||
|
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
acc = _mm256_dpbusd_epi32(acc, a0, b0);
|
||||||
|
acc = _mm256_dpbusd_epi32(acc, a1, b1);
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
__m256i tmp0 = _mm256_maddubs_epi16(a0, b0);
|
||||||
|
__m256i tmp1 = _mm256_maddubs_epi16(a1, b1);
|
||||||
|
asm(
|
||||||
|
"vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
|
||||||
|
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
|
||||||
|
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
|
||||||
|
: [tmp1]"v"(tmp1), [ones]"v"(_mm256_set1_epi16(1))
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
__m256i product0 = _mm256_maddubs_epi16(a0, b0);
|
||||||
|
__m256i product1 = _mm256_maddubs_epi16(a1, b1);
|
||||||
|
product0 = _mm256_adds_epi16(product0, product1);
|
||||||
|
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
|
||||||
|
acc = _mm256_add_epi32(acc, product0);
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined (USE_SSSE3)
|
||||||
|
|
||||||
|
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
|
||||||
|
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
|
||||||
|
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
|
||||||
|
return _mm_cvtsi128_si32(sum) + bias;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static __m128i m128_haddx4(
|
||||||
|
__m128i sum0, __m128i sum1, __m128i sum2, __m128i sum3,
|
||||||
|
__m128i bias) {
|
||||||
|
|
||||||
|
sum0 = _mm_hadd_epi32(sum0, sum1);
|
||||||
|
sum2 = _mm_hadd_epi32(sum2, sum3);
|
||||||
|
sum0 = _mm_hadd_epi32(sum0, sum2);
|
||||||
|
return _mm_add_epi32(sum0, bias);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static void m128_add_dpbusd_epi32(
|
||||||
|
__m128i& acc,
|
||||||
|
__m128i a,
|
||||||
|
__m128i b) {
|
||||||
|
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
__m128i tmp = _mm_maddubs_epi16(a, b);
|
||||||
|
asm(
|
||||||
|
"pmaddwd %[ones], %[tmp]\n\t"
|
||||||
|
"paddd %[tmp], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
|
||||||
|
: [ones]"v"(_mm_set1_epi16(1))
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
__m128i product0 = _mm_maddubs_epi16(a, b);
|
||||||
|
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
||||||
|
acc = _mm_add_epi32(acc, product0);
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static void m128_add_dpbusd_epi32x2(
|
||||||
|
__m128i& acc,
|
||||||
|
__m128i a0, __m128i b0,
|
||||||
|
__m128i a1, __m128i b1) {
|
||||||
|
|
||||||
|
# if defined (USE_INLINE_ASM)
|
||||||
|
__m128i tmp0 = _mm_maddubs_epi16(a0, b0);
|
||||||
|
__m128i tmp1 = _mm_maddubs_epi16(a1, b1);
|
||||||
|
asm(
|
||||||
|
"paddsw %[tmp1], %[tmp0]\n\t"
|
||||||
|
"pmaddwd %[ones], %[tmp0]\n\t"
|
||||||
|
"paddd %[tmp0], %[acc]\n\t"
|
||||||
|
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
|
||||||
|
: [tmp1]"v"(tmp1), [ones]"v"(_mm_set1_epi16(1))
|
||||||
|
);
|
||||||
|
# else
|
||||||
|
__m128i product0 = _mm_maddubs_epi16(a0, b0);
|
||||||
|
__m128i product1 = _mm_maddubs_epi16(a1, b1);
|
||||||
|
product0 = _mm_adds_epi16(product0, product1);
|
||||||
|
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
||||||
|
acc = _mm_add_epi32(acc, product0);
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined (USE_NEON)
|
||||||
|
|
||||||
|
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
|
||||||
|
# if USE_NEON >= 8
|
||||||
|
return vaddvq_s32(s);
|
||||||
|
# else
|
||||||
|
return s[0] + s[1] + s[2] + s[3];
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
|
||||||
|
return neon_m128_reduce_add_epi32(sum) + bias;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static int32x4_t neon_m128_haddx4(
|
||||||
|
int32x4_t sum0, int32x4_t sum1, int32x4_t sum2, int32x4_t sum3,
|
||||||
|
int32x4_t bias) {
|
||||||
|
|
||||||
|
int32x4_t hsums {
|
||||||
|
neon_m128_reduce_add_epi32(sum0),
|
||||||
|
neon_m128_reduce_add_epi32(sum1),
|
||||||
|
neon_m128_reduce_add_epi32(sum2),
|
||||||
|
neon_m128_reduce_add_epi32(sum3)
|
||||||
|
};
|
||||||
|
return vaddq_s32(hsums, bias);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2(
|
||||||
|
int32x4_t& acc,
|
||||||
|
int8x8_t a0, int8x8_t b0,
|
||||||
|
int8x8_t a1, int8x8_t b1) {
|
||||||
|
|
||||||
|
int16x8_t product = vmull_s8(a0, b0);
|
||||||
|
product = vmlal_s8(product, a1, b1);
|
||||||
|
acc = vpadalq_s16(acc, product);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // STOCKFISH_SIMD_H_INCLUDED
|
||||||
+46
-29
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -50,9 +50,11 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using namespace Tablebases;
|
using namespace Stockfish::Tablebases;
|
||||||
|
|
||||||
int Tablebases::MaxCardinality;
|
int Stockfish::Tablebases::MaxCardinality = 0;
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -103,9 +105,6 @@ template<> inline void swap_endian<uint8_t>(uint8_t&) {}
|
|||||||
|
|
||||||
template<typename T, int LE> T number(void* addr)
|
template<typename T, int LE> T number(void* addr)
|
||||||
{
|
{
|
||||||
static const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
|
|
||||||
static const bool IsLittleEndian = (Le.c[0] == 4);
|
|
||||||
|
|
||||||
T v;
|
T v;
|
||||||
|
|
||||||
if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare)
|
if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare)
|
||||||
@@ -190,7 +189,8 @@ public:
|
|||||||
std::stringstream ss(Paths);
|
std::stringstream ss(Paths);
|
||||||
std::string path;
|
std::string path;
|
||||||
|
|
||||||
while (std::getline(ss, path, SepChar)) {
|
while (std::getline(ss, path, SepChar))
|
||||||
|
{
|
||||||
fname = path + "/" + f;
|
fname = path + "/" + f;
|
||||||
std::ifstream::open(fname);
|
std::ifstream::open(fname);
|
||||||
if (is_open())
|
if (is_open())
|
||||||
@@ -223,7 +223,9 @@ public:
|
|||||||
|
|
||||||
*mapping = statbuf.st_size;
|
*mapping = statbuf.st_size;
|
||||||
*baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
*baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||||
|
#if defined(MADV_RANDOM)
|
||||||
madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);
|
madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);
|
||||||
|
#endif
|
||||||
::close(fd);
|
::close(fd);
|
||||||
|
|
||||||
if (*baseAddress == MAP_FAILED)
|
if (*baseAddress == MAP_FAILED)
|
||||||
@@ -563,7 +565,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
|||||||
int buf64Size = 64;
|
int buf64Size = 64;
|
||||||
Sym sym;
|
Sym sym;
|
||||||
|
|
||||||
while (true) {
|
while (true)
|
||||||
|
{
|
||||||
int len = 0; // This is the symbol length - d->min_sym_len
|
int len = 0; // This is the symbol length - d->min_sym_len
|
||||||
|
|
||||||
// Now get the symbol length. For any symbol s64 of length l right-padded
|
// Now get the symbol length. For any symbol s64 of length l right-padded
|
||||||
@@ -601,8 +604,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
|||||||
// We binary-search for our value recursively expanding into the left and
|
// 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
|
// right child symbols until we reach a leaf node where symlen[sym] + 1 == 1
|
||||||
// that will store the value we need.
|
// that will store the value we need.
|
||||||
while (d->symlen[sym]) {
|
while (d->symlen[sym])
|
||||||
|
{
|
||||||
Sym left = d->btree[sym].get<LR::Left>();
|
Sym left = d->btree[sym].get<LR::Left>();
|
||||||
|
|
||||||
// If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and
|
// If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and
|
||||||
@@ -707,7 +710,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
|
|
||||||
leadPawns = b = pos.pieces(color_of(pc), PAWN);
|
leadPawns = b = pos.pieces(color_of(pc), PAWN);
|
||||||
do
|
do
|
||||||
squares[size++] = pop_lsb(&b) ^ flipSquares;
|
squares[size++] = pop_lsb(b) ^ flipSquares;
|
||||||
while (b);
|
while (b);
|
||||||
|
|
||||||
leadPawnsCnt = size;
|
leadPawnsCnt = size;
|
||||||
@@ -727,7 +730,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
// directly map them to the correct color and square.
|
// directly map them to the correct color and square.
|
||||||
b = pos.pieces() ^ leadPawns;
|
b = pos.pieces() ^ leadPawns;
|
||||||
do {
|
do {
|
||||||
Square s = pop_lsb(&b);
|
Square s = pop_lsb(b);
|
||||||
squares[size] = s ^ flipSquares;
|
squares[size] = s ^ flipSquares;
|
||||||
pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);
|
pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);
|
||||||
} while (b);
|
} while (b);
|
||||||
@@ -758,7 +761,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
if (entry->hasPawns) {
|
if (entry->hasPawns) {
|
||||||
idx = LeadPawnIdx[leadPawnsCnt][squares[0]];
|
idx = LeadPawnIdx[leadPawnsCnt][squares[0]];
|
||||||
|
|
||||||
std::sort(squares + 1, squares + leadPawnsCnt, pawns_comp);
|
std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp);
|
||||||
|
|
||||||
for (int i = 1; i < leadPawnsCnt; ++i)
|
for (int i = 1; i < leadPawnsCnt; ++i)
|
||||||
idx += Binomial[i][MapPawns[squares[i]]];
|
idx += Binomial[i][MapPawns[squares[i]]];
|
||||||
@@ -766,7 +769,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
goto encode_remaining; // With pawns we have finished special treatments
|
goto encode_remaining; // With pawns we have finished special treatments
|
||||||
}
|
}
|
||||||
|
|
||||||
// In positions withouth pawns, we further flip the squares to ensure leading
|
// In positions without pawns, we further flip the squares to ensure leading
|
||||||
// piece is below RANK_5.
|
// piece is below RANK_5.
|
||||||
if (rank_of(squares[0]) > RANK_4)
|
if (rank_of(squares[0]) > RANK_4)
|
||||||
for (int i = 0; i < size; ++i)
|
for (int i = 0; i < size; ++i)
|
||||||
@@ -809,7 +812,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
// Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be
|
// Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be
|
||||||
// swapped and still get the same position.)
|
// swapped and still get the same position.)
|
||||||
//
|
//
|
||||||
// In case we have at least 3 unique pieces (inlcuded kings) we encode them
|
// In case we have at least 3 unique pieces (included kings) we encode them
|
||||||
// together.
|
// together.
|
||||||
if (entry->hasUniquePieces) {
|
if (entry->hasUniquePieces) {
|
||||||
|
|
||||||
@@ -824,7 +827,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
+ (squares[1] - adjust1)) * 62
|
+ (squares[1] - adjust1)) * 62
|
||||||
+ squares[2] - adjust2;
|
+ squares[2] - adjust2;
|
||||||
|
|
||||||
// First piece is on a1-h8 diagonal, second below: map this occurence to
|
// First piece is on a1-h8 diagonal, second below: map this occurrence to
|
||||||
// 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal
|
// 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal
|
||||||
// to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27.
|
// to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27.
|
||||||
else if (off_A1H8(squares[1]))
|
else if (off_A1H8(squares[1]))
|
||||||
@@ -854,12 +857,12 @@ encode_remaining:
|
|||||||
idx *= d->groupIdx[0];
|
idx *= d->groupIdx[0];
|
||||||
Square* groupSq = squares + d->groupLen[0];
|
Square* groupSq = squares + d->groupLen[0];
|
||||||
|
|
||||||
// Encode remainig pawns then pieces according to square, in ascending order
|
// Encode remaining pawns then pieces according to square, in ascending order
|
||||||
bool remainingPawns = entry->hasPawns && entry->pawnCount[1];
|
bool remainingPawns = entry->hasPawns && entry->pawnCount[1];
|
||||||
|
|
||||||
while (d->groupLen[++next])
|
while (d->groupLen[++next])
|
||||||
{
|
{
|
||||||
std::sort(groupSq, groupSq + d->groupLen[next]);
|
std::stable_sort(groupSq, groupSq + d->groupLen[next]);
|
||||||
uint64_t n = 0;
|
uint64_t n = 0;
|
||||||
|
|
||||||
// Map down a square if "comes later" than a square in the previous
|
// Map down a square if "comes later" than a square in the previous
|
||||||
@@ -882,7 +885,7 @@ encode_remaining:
|
|||||||
|
|
||||||
// Group together pieces that will be encoded together. The general rule is that
|
// Group together pieces that will be encoded together. The general rule is that
|
||||||
// a group contains pieces of same type and color. The exception is the leading
|
// a group contains pieces of same type and color. The exception is the leading
|
||||||
// group that, in case of positions withouth pawns, can be formed by 3 different
|
// group that, in case of positions without pawns, can be formed by 3 different
|
||||||
// pieces (default) or by the king pair when there is not a unique piece apart
|
// pieces (default) or by the king pair when there is not a unique piece apart
|
||||||
// from the kings. When there are pawns, pawns are always first in pieces[].
|
// from the kings. When there are pawns, pawns are always first in pieces[].
|
||||||
//
|
//
|
||||||
@@ -914,7 +917,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
|
|||||||
//
|
//
|
||||||
// This ensures unique encoding for the whole position. The order of the
|
// This ensures unique encoding for the whole position. The order of the
|
||||||
// groups is a per-table parameter and could not follow the canonical leading
|
// groups is a per-table parameter and could not follow the canonical leading
|
||||||
// pawns/pieces -> remainig pawns -> remaining pieces. In particular the
|
// pawns/pieces -> remaining pawns -> remaining pieces. In particular the
|
||||||
// first group is at order[0] position and the remaining pawns, when present,
|
// first group is at order[0] position and the remaining pawns, when present,
|
||||||
// are at order[1] position.
|
// are at order[1] position.
|
||||||
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
|
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
|
||||||
@@ -934,7 +937,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
|
|||||||
d->groupIdx[1] = idx;
|
d->groupIdx[1] = idx;
|
||||||
idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]];
|
idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]];
|
||||||
}
|
}
|
||||||
else // Remainig pieces
|
else // Remaining pieces
|
||||||
{
|
{
|
||||||
d->groupIdx[next] = idx;
|
d->groupIdx[next] = idx;
|
||||||
idx *= Binomial[d->groupLen[next]][freeSquares];
|
idx *= Binomial[d->groupLen[next]][freeSquares];
|
||||||
@@ -944,7 +947,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
|
|||||||
d->groupIdx[n] = idx;
|
d->groupIdx[n] = idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In Recursive Pairing each symbol represents a pair of childern symbols. So
|
// In Recursive Pairing each symbol represents a pair of children symbols. So
|
||||||
// read d->btree[] symbols data and expand each one in his left and right child
|
// read d->btree[] symbols data and expand each one in his left and right child
|
||||||
// symbol until reaching the leafs that represent the symbol value.
|
// symbol until reaching the leafs that represent the symbol value.
|
||||||
uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
|
uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
|
||||||
@@ -998,7 +1001,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
|
|||||||
// so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian).
|
// 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
|
// 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].
|
// 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) {
|
for (int i = d->base64.size() - 2; i >= 0; --i) {
|
||||||
d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])
|
d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])
|
||||||
- number<Sym, LittleEndian>(&d->lowestSym[i + 1])) / 2;
|
- number<Sym, LittleEndian>(&d->lowestSym[i + 1])) / 2;
|
||||||
@@ -1139,7 +1142,7 @@ void* mapped(TBTable<Type>& e, const Position& pos) {
|
|||||||
if (e.ready.load(std::memory_order_acquire))
|
if (e.ready.load(std::memory_order_acquire))
|
||||||
return e.baseAddress; // Could be nullptr if file does not exist
|
return e.baseAddress; // Could be nullptr if file does not exist
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lk(mutex);
|
std::scoped_lock<std::mutex> lk(mutex);
|
||||||
|
|
||||||
if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock
|
if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock
|
||||||
return e.baseAddress;
|
return e.baseAddress;
|
||||||
@@ -1287,7 +1290,7 @@ void Tablebases::init(const std::string& paths) {
|
|||||||
for (auto s : diagonal)
|
for (auto s : diagonal)
|
||||||
MapA1D1D4[s] = code++;
|
MapA1D1D4[s] = code++;
|
||||||
|
|
||||||
// MapKK[] encodes all the 461 possible legal positions of two kings where
|
// MapKK[] encodes all the 462 possible legal positions of two kings where
|
||||||
// the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4
|
// the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4
|
||||||
// diagonal, the other one shall not to be above the a1-h8 diagonal.
|
// diagonal, the other one shall not to be above the a1-h8 diagonal.
|
||||||
std::vector<std::pair<int, Square>> bothOnDiagonal;
|
std::vector<std::pair<int, Square>> bothOnDiagonal;
|
||||||
@@ -1314,7 +1317,7 @@ void Tablebases::init(const std::string& paths) {
|
|||||||
for (auto p : bothOnDiagonal)
|
for (auto p : bothOnDiagonal)
|
||||||
MapKK[p.first][p.second] = code++;
|
MapKK[p.first][p.second] = code++;
|
||||||
|
|
||||||
// Binomial[] stores the Binomial Coefficents using Pascal rule. There
|
// Binomial[] stores the Binomial Coefficients using Pascal rule. There
|
||||||
// are Binomial[k][n] ways to choose k elements from a set of n elements.
|
// are Binomial[k][n] ways to choose k elements from a set of n elements.
|
||||||
Binomial[0][0] = 1;
|
Binomial[0][0] = 1;
|
||||||
|
|
||||||
@@ -1334,7 +1337,7 @@ void Tablebases::init(const std::string& paths) {
|
|||||||
for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)
|
for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)
|
||||||
for (File f = FILE_A; f <= FILE_D; ++f)
|
for (File f = FILE_A; f <= FILE_D; ++f)
|
||||||
{
|
{
|
||||||
// Restart the index at every file because TB table is splitted
|
// Restart the index at every file because TB table is split
|
||||||
// by file, so we can reuse the same index for different files.
|
// by file, so we can reuse the same index for different files.
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
|
||||||
@@ -1438,7 +1441,7 @@ WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) {
|
|||||||
// If n = 100 immediately after a capture or pawn move, then the position
|
// 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
|
// is also certainly a win, and during the whole phase until the next
|
||||||
// capture or pawn move, the inequality to be preserved is
|
// 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,
|
// 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.
|
// then do not accept moves leading to dtz + 50-move-counter == 100.
|
||||||
@@ -1533,6 +1536,14 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
|||||||
WDLScore wdl = -probe_wdl(pos, &result);
|
WDLScore wdl = -probe_wdl(pos, &result);
|
||||||
dtz = dtz_before_zeroing(wdl);
|
dtz = dtz_before_zeroing(wdl);
|
||||||
}
|
}
|
||||||
|
else if (pos.is_draw(1))
|
||||||
|
{
|
||||||
|
// In case a root move leads to a draw by repetition or
|
||||||
|
// 50-move rule, we set dtz to zero. Note: since we are
|
||||||
|
// only 1 ply from the root, this must be a true 3-fold
|
||||||
|
// repetition inside the game history.
|
||||||
|
dtz = 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Otherwise, take dtz for the new position and correct by 1 ply
|
// Otherwise, take dtz for the new position and correct by 1 ply
|
||||||
@@ -1583,6 +1594,7 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
|
|||||||
|
|
||||||
ProbeState result;
|
ProbeState result;
|
||||||
StateInfo st;
|
StateInfo st;
|
||||||
|
WDLScore wdl;
|
||||||
|
|
||||||
bool rule50 = Options["Syzygy50MoveRule"];
|
bool rule50 = Options["Syzygy50MoveRule"];
|
||||||
|
|
||||||
@@ -1591,7 +1603,10 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
|
|||||||
{
|
{
|
||||||
pos.do_move(m.pv[0], st);
|
pos.do_move(m.pv[0], st);
|
||||||
|
|
||||||
WDLScore wdl = -probe_wdl(pos, &result);
|
if (pos.is_draw(1))
|
||||||
|
wdl = WDLDraw;
|
||||||
|
else
|
||||||
|
wdl = -probe_wdl(pos, &result);
|
||||||
|
|
||||||
pos.undo_move(m.pv[0]);
|
pos.undo_move(m.pv[0]);
|
||||||
|
|
||||||
@@ -1608,3 +1623,5 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
#include "../search.h"
|
#include "../search.h"
|
||||||
|
|
||||||
namespace Tablebases {
|
namespace Stockfish::Tablebases {
|
||||||
|
|
||||||
enum WDLScore {
|
enum WDLScore {
|
||||||
WDLLoss = -2, // Loss
|
WDLLoss = -2, // Loss
|
||||||
@@ -38,7 +38,7 @@ enum WDLScore {
|
|||||||
// Possible states after a probing operation
|
// Possible states after a probing operation
|
||||||
enum ProbeState {
|
enum ProbeState {
|
||||||
FAIL = 0, // Probe failed (missing file table)
|
FAIL = 0, // Probe failed (missing file table)
|
||||||
OK = 1, // Probe succesful
|
OK = 1, // Probe successful
|
||||||
CHANGE_STM = -1, // DTZ should check the other side
|
CHANGE_STM = -1, // DTZ should check the other side
|
||||||
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
|
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
|
||||||
};
|
};
|
||||||
@@ -73,6 +73,6 @@ inline std::ostream& operator<<(std::ostream& os, const ProbeState v) {
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace Stockfish::Tablebases
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+69
-40
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,15 +26,18 @@
|
|||||||
#include "syzygy/tbprobe.h"
|
#include "syzygy/tbprobe.h"
|
||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
ThreadPool Threads; // Global object
|
ThreadPool Threads; // Global object
|
||||||
|
|
||||||
|
|
||||||
/// Thread constructor launches the thread and waits until it goes to sleep
|
/// Thread constructor launches the thread and waits until it goes to sleep
|
||||||
/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
||||||
|
|
||||||
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
|
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this), maxNodes(0) {
|
||||||
|
|
||||||
wait_for_search_finished();
|
wait_for_search_finished();
|
||||||
|
wait_for_worker_finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -51,32 +54,21 @@ Thread::~Thread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::bestMoveCount(Move move) return best move counter for the given root move
|
|
||||||
|
|
||||||
int Thread::best_move_count(Move move) const {
|
|
||||||
|
|
||||||
auto rm = std::find(rootMoves.begin() + pvIdx,
|
|
||||||
rootMoves.begin() + pvLast, move);
|
|
||||||
|
|
||||||
return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Thread::clear() reset histories, usually before a new game
|
/// Thread::clear() reset histories, usually before a new game
|
||||||
|
|
||||||
void Thread::clear() {
|
void Thread::clear() {
|
||||||
|
|
||||||
counterMoves.fill(MOVE_NONE);
|
counterMoves.fill(MOVE_NONE);
|
||||||
mainHistory.fill(0);
|
mainHistory.fill(0);
|
||||||
lowPlyHistory.fill(0);
|
|
||||||
captureHistory.fill(0);
|
captureHistory.fill(0);
|
||||||
|
previousDepth = 0;
|
||||||
|
|
||||||
for (bool inCheck : { false, true })
|
for (bool inCheck : { false, true })
|
||||||
for (StatsType c : { NoCaptures, Captures })
|
for (StatsType c : { NoCaptures, Captures })
|
||||||
{
|
{
|
||||||
for (auto& to : continuationHistory[inCheck][c])
|
for (auto& to : continuationHistory[inCheck][c])
|
||||||
for (auto& h : to)
|
for (auto& h : to)
|
||||||
h->fill(0);
|
h->fill(-71);
|
||||||
continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
|
continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,6 +83,14 @@ void Thread::start_searching() {
|
|||||||
cv.notify_one(); // Wake up the thread in idle_loop()
|
cv.notify_one(); // Wake up the thread in idle_loop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Thread::execute_with_worker(std::function<void(Thread&)> t)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(mutex);
|
||||||
|
worker = std::move(t);
|
||||||
|
searching = true;
|
||||||
|
cv.notify_one(); // Wake up the thread in idle_loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::wait_for_search_finished() blocks on the condition variable
|
/// Thread::wait_for_search_finished() blocks on the condition variable
|
||||||
/// until the thread has finished searching.
|
/// until the thread has finished searching.
|
||||||
@@ -102,6 +102,12 @@ void Thread::wait_for_search_finished() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Thread::wait_for_worker_finished() {
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
|
cv.wait(lk, [&]{ return !searching; });
|
||||||
|
}
|
||||||
|
|
||||||
/// Thread::idle_loop() is where the thread is parked, blocked on the
|
/// Thread::idle_loop() is where the thread is parked, blocked on the
|
||||||
/// condition variable, when it has no work to do.
|
/// condition variable, when it has no work to do.
|
||||||
|
|
||||||
@@ -119,15 +125,25 @@ void Thread::idle_loop() {
|
|||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lk(mutex);
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
searching = false;
|
searching = false;
|
||||||
|
worker = nullptr;
|
||||||
cv.notify_one(); // Wake up anyone waiting for search finished
|
cv.notify_one(); // Wake up anyone waiting for search finished
|
||||||
cv.wait(lk, [&]{ return searching; });
|
cv.wait(lk, [&]{ return searching; });
|
||||||
|
|
||||||
if (exit)
|
if (exit)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
auto wrk = std::move(worker);
|
||||||
|
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
|
|
||||||
search();
|
if (wrk)
|
||||||
|
{
|
||||||
|
wrk(*this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
search();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,14 +153,16 @@ void Thread::idle_loop() {
|
|||||||
|
|
||||||
void ThreadPool::set(size_t requested) {
|
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();
|
main()->wait_for_search_finished();
|
||||||
|
|
||||||
while (size() > 0)
|
while (size() > 0)
|
||||||
delete back(), pop_back();
|
delete back(), pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requested > 0) { // create new thread(s)
|
if (requested > 0) // create new thread(s)
|
||||||
|
{
|
||||||
push_back(new MainThread(0));
|
push_back(new MainThread(0));
|
||||||
|
|
||||||
while (size() < requested)
|
while (size() < requested)
|
||||||
@@ -169,9 +187,17 @@ void ThreadPool::clear() {
|
|||||||
|
|
||||||
main()->callsCnt = 0;
|
main()->callsCnt = 0;
|
||||||
main()->bestPreviousScore = VALUE_INFINITE;
|
main()->bestPreviousScore = VALUE_INFINITE;
|
||||||
|
main()->bestPreviousAverageScore = VALUE_INFINITE;
|
||||||
main()->previousTimeReduction = 1.0;
|
main()->previousTimeReduction = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ThreadPool::execute_with_workers(const std::function<void(Thread&)>& worker)
|
||||||
|
{
|
||||||
|
for(Thread* th : *this)
|
||||||
|
{
|
||||||
|
th->execute_with_worker(worker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
|
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
|
||||||
/// returns immediately. Main thread will wake up other threads and start the search.
|
/// returns immediately. Main thread will wake up other threads and start the search.
|
||||||
@@ -192,9 +218,6 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
|||||||
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
||||||
rootMoves.emplace_back(m);
|
rootMoves.emplace_back(m);
|
||||||
|
|
||||||
if (!rootMoves.empty())
|
|
||||||
Tablebases::rank_root_moves(pos, rootMoves);
|
|
||||||
|
|
||||||
// After ownership transfer 'states' becomes empty, so if we stop the search
|
// After ownership transfer 'states' becomes empty, so if we stop the search
|
||||||
// and call 'go' again without setting a new position states.get() == NULL.
|
// and call 'go' again without setting a new position states.get() == NULL.
|
||||||
assert(states.get() || setupStates.get());
|
assert(states.get() || setupStates.get());
|
||||||
@@ -204,21 +227,18 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
|||||||
|
|
||||||
// We use Position::set() to set root position across threads. But there are
|
// We use Position::set() to set root position across threads. But there are
|
||||||
// some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot
|
// some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot
|
||||||
// be deduced from a fen string, so set() clears them and to not lose the info
|
// be deduced from a fen string, so set() clears them and they are set from
|
||||||
// we need to backup and later restore setupStates->back(). Note that setupStates
|
// setupStates->back() later. The rootState is per thread, earlier states are shared
|
||||||
// is shared by threads but is accessed in read-only mode.
|
// since they are read-only.
|
||||||
StateInfo tmp = setupStates->back();
|
|
||||||
|
|
||||||
for (Thread* th : *this)
|
for (Thread* th : *this)
|
||||||
{
|
{
|
||||||
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
|
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
|
||||||
th->rootDepth = th->completedDepth = 0;
|
th->rootDepth = th->completedDepth = 0;
|
||||||
th->rootMoves = rootMoves;
|
th->rootMoves = rootMoves;
|
||||||
th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
|
th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
|
||||||
|
th->rootState = setupStates->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupStates->back() = tmp;
|
|
||||||
|
|
||||||
main()->start_searching();
|
main()->start_searching();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,16 +258,16 @@ Thread* ThreadPool::get_best_thread() const {
|
|||||||
votes[th->rootMoves[0].pv[0]] +=
|
votes[th->rootMoves[0].pv[0]] +=
|
||||||
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
||||||
|
|
||||||
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
||||||
{
|
{
|
||||||
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
||||||
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
||||||
bestThread = th;
|
bestThread = th;
|
||||||
}
|
}
|
||||||
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
||||||
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
||||||
&& votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]))
|
&& votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]))
|
||||||
bestThread = th;
|
bestThread = th;
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestThread;
|
return bestThread;
|
||||||
@@ -272,3 +292,12 @@ void ThreadPool::wait_for_search_finished() const {
|
|||||||
if (th != front())
|
if (th != front())
|
||||||
th->wait_for_search_finished();
|
th->wait_for_search_finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ThreadPool::wait_for_workers_finished() const {
|
||||||
|
|
||||||
|
for (Thread* th : *this)
|
||||||
|
th->wait_for_worker_finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+103
-7
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include "material.h"
|
#include "material.h"
|
||||||
#include "movepick.h"
|
#include "movepick.h"
|
||||||
@@ -32,47 +33,83 @@
|
|||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread_win32_osx.h"
|
#include "thread_win32_osx.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
/// Thread class keeps together all the thread-related stuff. We use
|
/// Thread class keeps together all the thread-related stuff. We use
|
||||||
/// per-thread pawn and material hash tables so that once we get a
|
/// per-thread pawn and material hash tables so that once we get a
|
||||||
/// pointer to an entry its life time is unlimited and we don't have
|
/// pointer to an entry its life time is unlimited and we don't have
|
||||||
/// to care about someone changing the entry under our feet.
|
/// to care about someone changing the entry under our feet.
|
||||||
|
|
||||||
|
namespace Detail {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct TypeIdentity {
|
||||||
|
using Type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class Thread {
|
class Thread {
|
||||||
|
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
std::condition_variable cv;
|
std::condition_variable cv;
|
||||||
size_t idx;
|
size_t idx;
|
||||||
bool exit = false, searching = true; // Set before starting std::thread
|
bool exit = false, searching = true; // Set before starting std::thread
|
||||||
|
std::function<void(Thread&)> worker;
|
||||||
|
std::function<void(Position&)> on_eval_callback;
|
||||||
NativeThread stdThread;
|
NativeThread stdThread;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Thread(size_t);
|
explicit Thread(size_t);
|
||||||
virtual ~Thread();
|
virtual ~Thread();
|
||||||
virtual void search();
|
virtual void search();
|
||||||
|
|
||||||
|
// The function object to be executed is taken by value to remove
|
||||||
|
// the need for separate lvalue and rvalue overloads.
|
||||||
|
// The worker thread needs to have ownership of the task
|
||||||
|
// to be executed because otherwise there's no way to manage its lifetime.
|
||||||
|
virtual void execute_with_worker(std::function<void(Thread&)> t);
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
void idle_loop();
|
void idle_loop();
|
||||||
void start_searching();
|
void start_searching();
|
||||||
void wait_for_search_finished();
|
void wait_for_search_finished();
|
||||||
int best_move_count(Move move) const;
|
size_t id() const { return idx; }
|
||||||
|
|
||||||
|
void wait_for_worker_finished();
|
||||||
|
|
||||||
|
template <typename FuncT>
|
||||||
|
void set_eval_callback(FuncT&& f) { on_eval_callback = std::forward<FuncT>(f); }
|
||||||
|
|
||||||
|
void clear_eval_callback() { on_eval_callback = nullptr; }
|
||||||
|
|
||||||
|
void on_eval() { if (on_eval_callback) on_eval_callback(rootPos); }
|
||||||
|
|
||||||
Pawns::Table pawnsTable;
|
Pawns::Table pawnsTable;
|
||||||
Material::Table materialTable;
|
Material::Table materialTable;
|
||||||
size_t pvIdx, pvLast;
|
size_t pvIdx, pvLast;
|
||||||
uint64_t ttHitAverage;
|
RunningAverage complexityAverage;
|
||||||
|
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
||||||
int selDepth, nmpMinPly;
|
int selDepth, nmpMinPly;
|
||||||
Color nmpColor;
|
Color nmpColor;
|
||||||
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
Value bestValue, optimism[COLOR_NB];
|
||||||
|
uint64_t maxNodes;
|
||||||
|
|
||||||
Position rootPos;
|
Position rootPos;
|
||||||
|
StateInfo rootState;
|
||||||
Search::RootMoves rootMoves;
|
Search::RootMoves rootMoves;
|
||||||
Depth rootDepth, completedDepth;
|
Depth rootDepth, completedDepth, depth, previousDepth;
|
||||||
|
Value rootDelta;
|
||||||
CounterMoveHistory counterMoves;
|
CounterMoveHistory counterMoves;
|
||||||
ButterflyHistory mainHistory;
|
ButterflyHistory mainHistory;
|
||||||
LowPlyHistory lowPlyHistory;
|
|
||||||
CapturePieceToHistory captureHistory;
|
CapturePieceToHistory captureHistory;
|
||||||
ContinuationHistory continuationHistory[2][2];
|
ContinuationHistory continuationHistory[2][2];
|
||||||
Score contempt;
|
Score trend;
|
||||||
|
int failedHighCnt;
|
||||||
|
bool rootInTB;
|
||||||
|
int Cardinality;
|
||||||
|
bool UseRule50;
|
||||||
|
Depth ProbeDepth;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -87,6 +124,7 @@ struct MainThread : public Thread {
|
|||||||
|
|
||||||
double previousTimeReduction;
|
double previousTimeReduction;
|
||||||
Value bestPreviousScore;
|
Value bestPreviousScore;
|
||||||
|
Value bestPreviousAverageScore;
|
||||||
Value iterValue[4];
|
Value iterValue[4];
|
||||||
int callsCnt;
|
int callsCnt;
|
||||||
bool stopOnPonderhit;
|
bool stopOnPonderhit;
|
||||||
@@ -100,6 +138,61 @@ struct MainThread : public Thread {
|
|||||||
|
|
||||||
struct ThreadPool : public std::vector<Thread*> {
|
struct ThreadPool : public std::vector<Thread*> {
|
||||||
|
|
||||||
|
// Each thread gets its own copy of the `worker` function object.
|
||||||
|
// This means that each worker thread will have exclusive access
|
||||||
|
// to the state of the `worker` function object.
|
||||||
|
void execute_with_workers(const std::function<void(Thread&)>& worker);
|
||||||
|
|
||||||
|
template <typename IndexT, typename FuncT>
|
||||||
|
void for_each_index_with_workers(
|
||||||
|
IndexT begin,
|
||||||
|
typename Detail::TypeIdentity<IndexT>::Type end,
|
||||||
|
FuncT func)
|
||||||
|
{
|
||||||
|
// This value must outlive the function call.
|
||||||
|
// It's fairly safe if we make it static
|
||||||
|
// because for_each_index_with_workers
|
||||||
|
// is not reentrant nor thread safe.
|
||||||
|
static std::atomic<IndexT> i_atomic;
|
||||||
|
i_atomic.store(begin);
|
||||||
|
|
||||||
|
execute_with_workers(
|
||||||
|
[end, func](Thread& th) mutable {
|
||||||
|
for(;;) {
|
||||||
|
const auto i = i_atomic.fetch_add(1);
|
||||||
|
if (i >= end)
|
||||||
|
break;
|
||||||
|
|
||||||
|
func(th, i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename IndexT, typename FuncT>
|
||||||
|
void for_each_index_chunk_with_workers(
|
||||||
|
IndexT begin,
|
||||||
|
typename Detail::TypeIdentity<IndexT>::Type end,
|
||||||
|
FuncT func)
|
||||||
|
{
|
||||||
|
// This value must outlive the function call.
|
||||||
|
// It's fairly safe if we make it static
|
||||||
|
// because for_each_index_with_workers
|
||||||
|
// is not reentrant nor thread safe.
|
||||||
|
const IndexT size = end - begin;
|
||||||
|
const IndexT chunk_size = (size + this->size()) / this->size();
|
||||||
|
|
||||||
|
execute_with_workers(
|
||||||
|
[chunk_size, end, func](Thread& th) mutable {
|
||||||
|
const IndexT thread_id = th.id();
|
||||||
|
const IndexT offset = chunk_size * thread_id;
|
||||||
|
if (offset >= end)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const IndexT count = offset + chunk_size > end ? end - offset : chunk_size;
|
||||||
|
func(th, offset, count);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
|
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
|
||||||
void clear();
|
void clear();
|
||||||
void set(size_t);
|
void set(size_t);
|
||||||
@@ -110,6 +203,7 @@ struct ThreadPool : public std::vector<Thread*> {
|
|||||||
Thread* get_best_thread() const;
|
Thread* get_best_thread() const;
|
||||||
void start_searching();
|
void start_searching();
|
||||||
void wait_for_search_finished() const;
|
void wait_for_search_finished() const;
|
||||||
|
void wait_for_workers_finished() const;
|
||||||
|
|
||||||
std::atomic_bool stop, increaseDepth;
|
std::atomic_bool stop, increaseDepth;
|
||||||
|
|
||||||
@@ -127,4 +221,6 @@ private:
|
|||||||
|
|
||||||
extern ThreadPool Threads;
|
extern ThreadPool Threads;
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef THREAD_H_INCLUDED
|
#endif // #ifndef THREAD_H_INCLUDED
|
||||||
|
|||||||
+10
-2
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,10 +27,12 @@
|
|||||||
/// The implementation calls pthread_create() with the stack size parameter
|
/// The implementation calls pthread_create() with the stack size parameter
|
||||||
/// equal to the linux 8MB default, on platforms that support it.
|
/// equal to the linux 8MB default, on platforms that support it.
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__)
|
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
||||||
|
|
||||||
template <class T, class P = std::pair<T*, void(T::*)()>>
|
template <class T, class P = std::pair<T*, void(T::*)()>>
|
||||||
@@ -57,10 +59,16 @@ public:
|
|||||||
void join() { pthread_join(thread, NULL); }
|
void join() { pthread_join(thread, NULL); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#else // Default case: use STL classes
|
#else // Default case: use STL classes
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
typedef std::thread NativeThread;
|
typedef std::thread NativeThread;
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
|
#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
|
||||||
|
|||||||
+20
-12
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,8 @@
|
|||||||
#include "timeman.h"
|
#include "timeman.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
TimeManagement Time; // Our global time management object
|
TimeManagement Time; // Our global time management object
|
||||||
|
|
||||||
|
|
||||||
@@ -38,9 +40,9 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||||||
TimePoint slowMover = TimePoint(Options["Slow Mover"]);
|
TimePoint slowMover = TimePoint(Options["Slow Mover"]);
|
||||||
TimePoint npmsec = TimePoint(Options["nodestime"]);
|
TimePoint npmsec = TimePoint(Options["nodestime"]);
|
||||||
|
|
||||||
// opt_scale is a percentage of available time to use for the current move.
|
// optScale is a percentage of available time to use for the current move.
|
||||||
// max_scale is a multiplier applied to optimumTime.
|
// maxScale is a multiplier applied to optimumTime.
|
||||||
double opt_scale, max_scale;
|
double optScale, maxScale;
|
||||||
|
|
||||||
// If we have to play in 'nodes as time' mode, then convert from time
|
// If we have to play in 'nodes as time' mode, then convert from time
|
||||||
// to nodes, and use resulting values in time management formulas.
|
// to nodes, and use resulting values in time management formulas.
|
||||||
@@ -66,6 +68,9 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||||||
TimePoint timeLeft = std::max(TimePoint(1),
|
TimePoint timeLeft = std::max(TimePoint(1),
|
||||||
limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg));
|
limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg));
|
||||||
|
|
||||||
|
// Use extra time with larger increments
|
||||||
|
double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12);
|
||||||
|
|
||||||
// A user may scale time usage by setting UCI option "Slow Mover"
|
// A user may scale time usage by setting UCI option "Slow Mover"
|
||||||
// Default is 100 and changing this value will probably lose elo.
|
// Default is 100 and changing this value will probably lose elo.
|
||||||
timeLeft = slowMover * timeLeft / 100;
|
timeLeft = slowMover * timeLeft / 100;
|
||||||
@@ -75,23 +80,26 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||||||
// game time for the current move, so also cap to 20% of available game time.
|
// game time for the current move, so also cap to 20% of available game time.
|
||||||
if (limits.movestogo == 0)
|
if (limits.movestogo == 0)
|
||||||
{
|
{
|
||||||
opt_scale = std::min(0.008 + std::pow(ply + 3.0, 0.5) / 250.0,
|
optScale = std::min(0.0084 + std::pow(ply + 3.0, 0.5) * 0.0042,
|
||||||
0.2 * limits.time[us] / double(timeLeft));
|
0.2 * limits.time[us] / double(timeLeft))
|
||||||
max_scale = std::min(7.0, 4.0 + ply / 12.0);
|
* optExtra;
|
||||||
|
maxScale = std::min(7.0, 4.0 + ply / 12.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// x moves in y seconds (+ z increment)
|
// x moves in y seconds (+ z increment)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
opt_scale = std::min((0.8 + ply / 128.0) / mtg,
|
optScale = std::min((0.88 + ply / 116.4) / mtg,
|
||||||
0.8 * limits.time[us] / double(timeLeft));
|
0.88 * limits.time[us] / double(timeLeft));
|
||||||
max_scale = std::min(6.3, 1.5 + 0.11 * mtg);
|
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never use more than 80% of the available time for this move
|
// Never use more than 80% of the available time for this move
|
||||||
optimumTime = TimePoint(opt_scale * timeLeft);
|
optimumTime = TimePoint(optScale * timeLeft);
|
||||||
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, max_scale * optimumTime));
|
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime));
|
||||||
|
|
||||||
if (Options["Ponder"])
|
if (Options["Ponder"])
|
||||||
optimumTime += optimumTime / 4;
|
optimumTime += optimumTime / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
/// The TimeManagement class computes the optimal time to think depending on
|
/// The TimeManagement class computes the optimal time to think depending on
|
||||||
/// the maximum available time, the game move number and other parameters.
|
/// the maximum available time, the game move number and other parameters.
|
||||||
|
|
||||||
@@ -44,4 +46,6 @@ private:
|
|||||||
|
|
||||||
extern TimeManagement Time;
|
extern TimeManagement Time;
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef TIMEMAN_H_INCLUDED
|
#endif // #ifndef TIMEMAN_H_INCLUDED
|
||||||
|
|||||||
@@ -0,0 +1,815 @@
|
|||||||
|
#include "convert.h"
|
||||||
|
|
||||||
|
#include "uci.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "tt.h"
|
||||||
|
|
||||||
|
#include "extra/nnue_data_binpack_format.h"
|
||||||
|
|
||||||
|
#include "nnue/evaluate_nnue.h"
|
||||||
|
|
||||||
|
#include "syzygy/tbprobe.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <list>
|
||||||
|
#include <cmath> // std::exp(),std::pow(),std::log()
|
||||||
|
#include <cstring> // memcpy()
|
||||||
|
#include <memory>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <chrono>
|
||||||
|
#include <random>
|
||||||
|
#include <regex>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
namespace sys = std::filesystem;
|
||||||
|
|
||||||
|
namespace Stockfish::Tools
|
||||||
|
{
|
||||||
|
bool fen_is_ok(Position& pos, std::string input_fen) {
|
||||||
|
std::string pos_fen = pos.fen();
|
||||||
|
std::istringstream ss_input(input_fen);
|
||||||
|
std::istringstream ss_pos(pos_fen);
|
||||||
|
|
||||||
|
// example : "2r4r/4kpp1/nb1np3/p2p3p/B2P1BP1/PP6/4NPKP/2R1R3 w - h6 0 24"
|
||||||
|
// --> "2r4r/4kpp1/nb1np3/p2p3p/B2P1BP1/PP6/4NPKP/2R1R3"
|
||||||
|
std::string str_input, str_pos;
|
||||||
|
ss_input >> str_input;
|
||||||
|
ss_pos >> str_pos;
|
||||||
|
|
||||||
|
// Only compare "Piece placement field" between input_fen and pos.fen().
|
||||||
|
return str_input == str_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_bin(
|
||||||
|
const vector<string>& filenames,
|
||||||
|
const string& output_file_name,
|
||||||
|
const int ply_minimum,
|
||||||
|
const int ply_maximum,
|
||||||
|
const int interpolate_eval,
|
||||||
|
const int src_score_min_value,
|
||||||
|
const int src_score_max_value,
|
||||||
|
const int dest_score_min_value,
|
||||||
|
const int dest_score_max_value,
|
||||||
|
const bool check_invalid_fen,
|
||||||
|
const bool check_illegal_move)
|
||||||
|
{
|
||||||
|
std::cout << "check_invalid_fen=" << check_invalid_fen << std::endl;
|
||||||
|
std::cout << "check_illegal_move=" << check_illegal_move << std::endl;
|
||||||
|
|
||||||
|
std::fstream fs;
|
||||||
|
uint64_t data_size = 0;
|
||||||
|
uint64_t filtered_size = 0;
|
||||||
|
uint64_t filtered_size_fen = 0;
|
||||||
|
uint64_t filtered_size_move = 0;
|
||||||
|
uint64_t filtered_size_ply = 0;
|
||||||
|
auto th = Threads.main();
|
||||||
|
auto& tpos = th->rootPos;
|
||||||
|
// convert plain rag to packed sfenvalue for Yaneura king
|
||||||
|
fs.open(output_file_name, ios::app | ios::binary);
|
||||||
|
StateListPtr states;
|
||||||
|
for (auto filename : filenames) {
|
||||||
|
std::cout << "convert " << filename << " ... ";
|
||||||
|
std::string line;
|
||||||
|
ifstream ifs;
|
||||||
|
ifs.open(filename);
|
||||||
|
PackedSfenValue p;
|
||||||
|
data_size = 0;
|
||||||
|
filtered_size = 0;
|
||||||
|
filtered_size_fen = 0;
|
||||||
|
filtered_size_move = 0;
|
||||||
|
filtered_size_ply = 0;
|
||||||
|
p.gamePly = 1; // Not included in apery format. Should be initialized
|
||||||
|
bool ignore_flag_fen = false;
|
||||||
|
bool ignore_flag_move = false;
|
||||||
|
bool ignore_flag_ply = false;
|
||||||
|
while (std::getline(ifs, line)) {
|
||||||
|
std::stringstream ss(line);
|
||||||
|
std::string token;
|
||||||
|
std::string value;
|
||||||
|
ss >> token;
|
||||||
|
if (token == "fen") {
|
||||||
|
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
|
||||||
|
std::string input_fen = line.substr(4);
|
||||||
|
tpos.set(input_fen, false, &states->back(), Threads.main());
|
||||||
|
if (check_invalid_fen && !fen_is_ok(tpos, input_fen)) {
|
||||||
|
ignore_flag_fen = true;
|
||||||
|
filtered_size_fen++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tpos.sfen_pack(p.sfen, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (token == "move") {
|
||||||
|
ss >> value;
|
||||||
|
Move move = UCI::to_move(tpos, value);
|
||||||
|
if (check_illegal_move && move == MOVE_NONE) {
|
||||||
|
ignore_flag_move = true;
|
||||||
|
filtered_size_move++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p.move = move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (token == "score") {
|
||||||
|
double score;
|
||||||
|
ss >> score;
|
||||||
|
// Training Formula ?Issue #71 ?nodchip/Stockfish https://github.com/nodchip/Stockfish/issues/71
|
||||||
|
// Normalize to [0.0, 1.0].
|
||||||
|
score = (score - src_score_min_value) / (src_score_max_value - src_score_min_value);
|
||||||
|
// Scale to [dest_score_min_value, dest_score_max_value].
|
||||||
|
score = score * (dest_score_max_value - dest_score_min_value) + dest_score_min_value;
|
||||||
|
p.score = std::clamp((int32_t)std::round(score), -(int32_t)VALUE_MATE, (int32_t)VALUE_MATE);
|
||||||
|
}
|
||||||
|
else if (token == "ply") {
|
||||||
|
int temp;
|
||||||
|
ss >> temp;
|
||||||
|
if (temp < ply_minimum || temp > ply_maximum) {
|
||||||
|
ignore_flag_ply = true;
|
||||||
|
filtered_size_ply++;
|
||||||
|
}
|
||||||
|
p.gamePly = uint16_t(temp); // No cast here?
|
||||||
|
if (interpolate_eval != 0) {
|
||||||
|
p.score = min(3000, interpolate_eval * temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (token == "result") {
|
||||||
|
int temp;
|
||||||
|
ss >> temp;
|
||||||
|
p.game_result = int8_t(temp); // Do you need a cast here?
|
||||||
|
if (interpolate_eval) {
|
||||||
|
p.score = p.score * p.game_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (token == "e") {
|
||||||
|
if (!(ignore_flag_fen || ignore_flag_move || ignore_flag_ply)) {
|
||||||
|
fs.write((char*)&p, sizeof(PackedSfenValue));
|
||||||
|
data_size += 1;
|
||||||
|
// debug
|
||||||
|
// std::cout<<tpos<<std::endl;
|
||||||
|
// std::cout<<p.score<<","<<int(p.gamePly)<<","<<int(p.game_result)<<std::endl;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
filtered_size++;
|
||||||
|
}
|
||||||
|
ignore_flag_fen = false;
|
||||||
|
ignore_flag_move = false;
|
||||||
|
ignore_flag_ply = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "done " << data_size << " parsed " << filtered_size << " is filtered"
|
||||||
|
<< " (invalid fen:" << filtered_size_fen << ", illegal move:" << filtered_size_move << ", invalid ply:" << filtered_size_ply << ")" << std::endl;
|
||||||
|
ifs.close();
|
||||||
|
}
|
||||||
|
std::cout << "all done" << std::endl;
|
||||||
|
fs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ltrim(std::string& s) {
|
||||||
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
|
||||||
|
return !std::isspace(ch);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rtrim(std::string& s) {
|
||||||
|
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
|
||||||
|
return !std::isspace(ch);
|
||||||
|
}).base(), s.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void trim(std::string& s) {
|
||||||
|
ltrim(s);
|
||||||
|
rtrim(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
int parse_game_result_from_pgn_extract(std::string result) {
|
||||||
|
// White Win
|
||||||
|
if (result == "\"1-0\"") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Black Win
|
||||||
|
else if (result == "\"0-1\"") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Draw
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0.25 --> 0.25 * PawnValueEg
|
||||||
|
// #-4 --> -mate_in(4)
|
||||||
|
// #3 --> mate_in(3)
|
||||||
|
// -M4 --> -mate_in(4)
|
||||||
|
// +M3 --> mate_in(3)
|
||||||
|
Value parse_score_from_pgn_extract(std::string eval, bool& success) {
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
if (eval.substr(0, 1) == "#") {
|
||||||
|
if (eval.substr(1, 1) == "-") {
|
||||||
|
return -mate_in(stoi(eval.substr(2, eval.length() - 2)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return mate_in(stoi(eval.substr(1, eval.length() - 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (eval.substr(0, 2) == "-M") {
|
||||||
|
//std::cout << "eval=" << eval << std::endl;
|
||||||
|
return -mate_in(stoi(eval.substr(2, eval.length() - 2)));
|
||||||
|
}
|
||||||
|
else if (eval.substr(0, 2) == "+M") {
|
||||||
|
//std::cout << "eval=" << eval << std::endl;
|
||||||
|
return mate_in(stoi(eval.substr(2, eval.length() - 2)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
char* endptr;
|
||||||
|
double value = strtod(eval.c_str(), &endptr);
|
||||||
|
|
||||||
|
if (*endptr != '\0') {
|
||||||
|
success = false;
|
||||||
|
return VALUE_ZERO;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Value(value * static_cast<double>(PawnValueEg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for Debug
|
||||||
|
//#define DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT
|
||||||
|
|
||||||
|
bool is_like_fen(std::string fen) {
|
||||||
|
int count_space = std::count(fen.cbegin(), fen.cend(), ' ');
|
||||||
|
int count_slash = std::count(fen.cbegin(), fen.cend(), '/');
|
||||||
|
|
||||||
|
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
|
||||||
|
//std::cout << "count_space=" << count_space << std::endl;
|
||||||
|
//std::cout << "count_slash=" << count_slash << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return count_space == 5 && count_slash == 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_bin_from_pgn_extract(
|
||||||
|
const vector<string>& filenames,
|
||||||
|
const string& output_file_name,
|
||||||
|
const bool pgn_eval_side_to_move,
|
||||||
|
const bool convert_no_eval_fens_as_score_zero)
|
||||||
|
{
|
||||||
|
std::cout << "pgn_eval_side_to_move=" << pgn_eval_side_to_move << std::endl;
|
||||||
|
std::cout << "convert_no_eval_fens_as_score_zero=" << convert_no_eval_fens_as_score_zero << std::endl;
|
||||||
|
|
||||||
|
auto th = Threads.main();
|
||||||
|
auto& pos = th->rootPos;
|
||||||
|
|
||||||
|
std::fstream ofs;
|
||||||
|
ofs.open(output_file_name, ios::out | ios::binary);
|
||||||
|
|
||||||
|
int game_count = 0;
|
||||||
|
int fen_count = 0;
|
||||||
|
|
||||||
|
for (auto filename : filenames) {
|
||||||
|
std::cout << now_string() << " convert " << filename << std::endl;
|
||||||
|
ifstream ifs;
|
||||||
|
ifs.open(filename);
|
||||||
|
|
||||||
|
int game_result = 0;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(ifs, line)) {
|
||||||
|
|
||||||
|
if (line.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (line.substr(0, 1) == "[") {
|
||||||
|
std::regex pattern_result(R"(\[Result (.+?)\])");
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
// example: [Result "1-0"]
|
||||||
|
if (std::regex_search(line, match, pattern_result)) {
|
||||||
|
game_result = parse_game_result_from_pgn_extract(match.str(1));
|
||||||
|
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
|
||||||
|
std::cout << "game_result=" << game_result << std::endl;
|
||||||
|
#endif
|
||||||
|
game_count++;
|
||||||
|
if (game_count % 10000 == 0) {
|
||||||
|
std::cout << now_string() << " game_count=" << game_count << ", fen_count=" << fen_count << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
int gamePly = 1;
|
||||||
|
auto itr = line.cbegin();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
gamePly++;
|
||||||
|
|
||||||
|
PackedSfenValue psv;
|
||||||
|
memset((char*)&psv, 0, sizeof(PackedSfenValue));
|
||||||
|
|
||||||
|
// fen
|
||||||
|
{
|
||||||
|
bool fen_found = false;
|
||||||
|
|
||||||
|
while (!fen_found) {
|
||||||
|
std::regex pattern_bracket(R"(\{(.+?)\})");
|
||||||
|
std::smatch match;
|
||||||
|
if (!std::regex_search(itr, line.cend(), match, pattern_bracket)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
itr += match.position(0) + match.length(0) - 1;
|
||||||
|
std::string str_fen = match.str(1);
|
||||||
|
trim(str_fen);
|
||||||
|
|
||||||
|
if (is_like_fen(str_fen)) {
|
||||||
|
fen_found = true;
|
||||||
|
|
||||||
|
StateInfo si;
|
||||||
|
pos.set(str_fen, false, &si, th);
|
||||||
|
pos.sfen_pack(psv.sfen, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
|
||||||
|
std::cout << "str_fen=" << str_fen << std::endl;
|
||||||
|
std::cout << "fen_found=" << fen_found << std::endl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fen_found) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// move
|
||||||
|
{
|
||||||
|
std::regex pattern_move(R"(\}(.+?)\{)");
|
||||||
|
std::smatch match;
|
||||||
|
if (!std::regex_search(itr, line.cend(), match, pattern_move)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
itr += match.position(0) + match.length(0) - 1;
|
||||||
|
std::string str_move = match.str(1);
|
||||||
|
trim(str_move);
|
||||||
|
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
|
||||||
|
std::cout << "str_move=" << str_move << std::endl;
|
||||||
|
#endif
|
||||||
|
psv.move = UCI::to_move(pos, str_move);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eval
|
||||||
|
bool eval_found = false;
|
||||||
|
{
|
||||||
|
std::regex pattern_bracket(R"(\{(.+?)\})");
|
||||||
|
std::smatch match;
|
||||||
|
if (!std::regex_search(itr, line.cend(), match, pattern_bracket)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str_eval_clk = match.str(1);
|
||||||
|
trim(str_eval_clk);
|
||||||
|
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
|
||||||
|
std::cout << "str_eval_clk=" << str_eval_clk << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// example: { [%eval 0.25] [%clk 0:10:00] }
|
||||||
|
// example: { [%eval #-4] [%clk 0:10:00] }
|
||||||
|
// example: { [%eval #3] [%clk 0:10:00] }
|
||||||
|
// example: { +0.71/22 1.2s }
|
||||||
|
// example: { -M4/7 0.003s }
|
||||||
|
// example: { M3/245 0.017s }
|
||||||
|
// example: { +M1/245 0.010s, White mates }
|
||||||
|
// example: { 0.60 }
|
||||||
|
// example: { book }
|
||||||
|
// example: { rnbqkb1r/pp3ppp/2p1pn2/3p4/2PP4/2N2N2/PP2PPPP/R1BQKB1R w KQkq - 0 5 }
|
||||||
|
|
||||||
|
// Considering the absence of eval
|
||||||
|
if (!is_like_fen(str_eval_clk)) {
|
||||||
|
itr += match.position(0) + match.length(0) - 1;
|
||||||
|
|
||||||
|
if (str_eval_clk != "book") {
|
||||||
|
std::regex pattern_eval1(R"(\[\%eval (.+?)\])");
|
||||||
|
std::regex pattern_eval2(R"((.+?)\/)");
|
||||||
|
|
||||||
|
std::string str_eval;
|
||||||
|
if (std::regex_search(str_eval_clk, match, pattern_eval1) ||
|
||||||
|
std::regex_search(str_eval_clk, match, pattern_eval2)) {
|
||||||
|
str_eval = match.str(1);
|
||||||
|
trim(str_eval);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
str_eval = str_eval_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
Value value = parse_score_from_pgn_extract(str_eval, success);
|
||||||
|
if (success) {
|
||||||
|
eval_found = true;
|
||||||
|
psv.score = std::clamp(value, -VALUE_MATE, VALUE_MATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(DEBUG_CONVERT_BIN_FROM_PGN_EXTRACT)
|
||||||
|
std::cout << "str_eval=" << str_eval << std::endl;
|
||||||
|
std::cout << "success=" << success << ", psv.score=" << psv.score << std::endl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write
|
||||||
|
if (eval_found || convert_no_eval_fens_as_score_zero) {
|
||||||
|
if (!eval_found && convert_no_eval_fens_as_score_zero) {
|
||||||
|
psv.score = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
psv.gamePly = gamePly;
|
||||||
|
psv.game_result = game_result;
|
||||||
|
|
||||||
|
if (pos.side_to_move() == BLACK) {
|
||||||
|
if (!pgn_eval_side_to_move) {
|
||||||
|
psv.score *= -1;
|
||||||
|
}
|
||||||
|
psv.game_result *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ofs.write((char*)&psv, sizeof(PackedSfenValue));
|
||||||
|
|
||||||
|
fen_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game_result = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << now_string() << " game_count=" << game_count << ", fen_count=" << fen_count << std::endl;
|
||||||
|
std::cout << now_string() << " all done" << std::endl;
|
||||||
|
ofs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_plain(
|
||||||
|
const vector<string>& filenames,
|
||||||
|
const string& output_file_name)
|
||||||
|
{
|
||||||
|
Position tpos;
|
||||||
|
std::ofstream ofs;
|
||||||
|
ofs.open(output_file_name, ios::app);
|
||||||
|
auto th = Threads.main();
|
||||||
|
for (auto filename : filenames) {
|
||||||
|
std::cout << "convert " << filename << " ... ";
|
||||||
|
|
||||||
|
// Just convert packedsfenvalue to text
|
||||||
|
std::fstream fs;
|
||||||
|
fs.open(filename, ios::in | ios::binary);
|
||||||
|
PackedSfenValue p;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (fs.read((char*)&p, sizeof(PackedSfenValue))) {
|
||||||
|
StateInfo si;
|
||||||
|
tpos.set_from_packed_sfen(p.sfen, &si, th, false);
|
||||||
|
|
||||||
|
// write as plain text
|
||||||
|
ofs << "fen " << tpos.fen() << std::endl;
|
||||||
|
ofs << "move " << UCI::move(Move(p.move), false) << std::endl;
|
||||||
|
ofs << "score " << p.score << std::endl;
|
||||||
|
ofs << "ply " << int(p.gamePly) << std::endl;
|
||||||
|
ofs << "result " << int(p.game_result) << std::endl;
|
||||||
|
ofs << "e" << std::endl;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.close();
|
||||||
|
std::cout << "done" << std::endl;
|
||||||
|
}
|
||||||
|
ofs.close();
|
||||||
|
std::cout << "all done" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const std::string plain_extension = ".plain";
|
||||||
|
static inline const std::string bin_extension = ".bin";
|
||||||
|
static inline const std::string binpack_extension = ".binpack";
|
||||||
|
|
||||||
|
static bool file_exists(const std::string& name)
|
||||||
|
{
|
||||||
|
std::ifstream f(name);
|
||||||
|
return f.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ends_with(const std::string& lhs, const std::string& end)
|
||||||
|
{
|
||||||
|
if (end.size() > lhs.size()) return false;
|
||||||
|
|
||||||
|
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_convert_of_type(
|
||||||
|
const std::string& input_path,
|
||||||
|
const std::string& output_path,
|
||||||
|
const std::string& expected_input_extension,
|
||||||
|
const std::string& expected_output_extension)
|
||||||
|
{
|
||||||
|
return ends_with(input_path, expected_input_extension)
|
||||||
|
&& ends_with(output_path, expected_output_extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
using ConvertFunctionType = void(std::string inputPath, std::string outputPath, std::ios_base::openmode om, bool validate);
|
||||||
|
|
||||||
|
static ConvertFunctionType* get_convert_function(const std::string& input_path, const std::string& output_path)
|
||||||
|
{
|
||||||
|
if (is_convert_of_type(input_path, output_path, plain_extension, bin_extension))
|
||||||
|
return binpack::convertPlainToBin;
|
||||||
|
if (is_convert_of_type(input_path, output_path, plain_extension, binpack_extension))
|
||||||
|
return binpack::convertPlainToBinpack;
|
||||||
|
|
||||||
|
if (is_convert_of_type(input_path, output_path, bin_extension, plain_extension))
|
||||||
|
return binpack::convertBinToPlain;
|
||||||
|
if (is_convert_of_type(input_path, output_path, bin_extension, binpack_extension))
|
||||||
|
return binpack::convertBinToBinpack;
|
||||||
|
|
||||||
|
if (is_convert_of_type(input_path, output_path, binpack_extension, plain_extension))
|
||||||
|
return binpack::convertBinpackToPlain;
|
||||||
|
if (is_convert_of_type(input_path, output_path, binpack_extension, bin_extension))
|
||||||
|
return binpack::convertBinpackToBin;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convert(const std::string& input_path, const std::string& output_path, std::ios_base::openmode om, bool validate)
|
||||||
|
{
|
||||||
|
if(!file_exists(input_path))
|
||||||
|
{
|
||||||
|
std::cerr << "Input file does not exist.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto func = get_convert_function(input_path, output_path);
|
||||||
|
if (func != nullptr)
|
||||||
|
{
|
||||||
|
func(input_path, output_path, om, validate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Conversion between files of these types is not supported.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convert(const std::vector<std::string>& args)
|
||||||
|
{
|
||||||
|
if (args.size() < 2 || args.size() > 4)
|
||||||
|
{
|
||||||
|
std::cerr << "Invalid arguments.\n";
|
||||||
|
std::cerr << "Usage: convert from_path to_path [append] [validate]\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool append = std::find(args.begin() + 2, args.end(), "append") != args.end();
|
||||||
|
const bool validate = std::find(args.begin() + 2, args.end(), "validate") != args.end();
|
||||||
|
|
||||||
|
const std::ios_base::openmode openmode =
|
||||||
|
append
|
||||||
|
? std::ios_base::app
|
||||||
|
: std::ios_base::trunc;
|
||||||
|
|
||||||
|
convert(args[0], args[1], openmode, validate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert(istringstream& is)
|
||||||
|
{
|
||||||
|
std::vector<std::string> args;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::string token = "";
|
||||||
|
is >> token;
|
||||||
|
if (token == "")
|
||||||
|
break;
|
||||||
|
|
||||||
|
args.push_back(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_files_from_dir(
|
||||||
|
std::vector<std::string>& filenames,
|
||||||
|
const std::string& base_dir,
|
||||||
|
const std::string& target_dir)
|
||||||
|
{
|
||||||
|
string kif_base_dir = Path::combine(base_dir, target_dir);
|
||||||
|
|
||||||
|
sys::path p(kif_base_dir); // Origin of enumeration
|
||||||
|
std::for_each(sys::directory_iterator(p), sys::directory_iterator(),
|
||||||
|
[&](const sys::path& path) {
|
||||||
|
if (sys::is_regular_file(path))
|
||||||
|
filenames.push_back(Path::combine(target_dir, path.filename().generic_string()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rebase_files(
|
||||||
|
std::vector<std::string>& filenames,
|
||||||
|
const std::string& base_dir)
|
||||||
|
{
|
||||||
|
for (auto& file : filenames)
|
||||||
|
{
|
||||||
|
file = Path::combine(base_dir, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_bin_from_pgn_extract(std::istringstream& is)
|
||||||
|
{
|
||||||
|
std::vector<std::string> filenames;
|
||||||
|
|
||||||
|
string base_dir;
|
||||||
|
string target_dir;
|
||||||
|
|
||||||
|
bool pgn_eval_side_to_move = false;
|
||||||
|
bool convert_no_eval_fens_as_score_zero = false;
|
||||||
|
|
||||||
|
string output_file_name = "shuffled_sfen.bin";
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
string option;
|
||||||
|
is >> option;
|
||||||
|
|
||||||
|
if (option == "")
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (option == "targetdir") is >> target_dir;
|
||||||
|
else if (option == "targetfile")
|
||||||
|
{
|
||||||
|
std::string filename;
|
||||||
|
is >> filename;
|
||||||
|
filenames.push_back(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (option == "basedir") is >> base_dir;
|
||||||
|
|
||||||
|
else if (option == "pgn_eval_side_to_move") is >> pgn_eval_side_to_move;
|
||||||
|
else if (option == "convert_no_eval_fens_as_score_zero") is >> convert_no_eval_fens_as_score_zero;
|
||||||
|
else if (option == "output_file_name") is >> output_file_name;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "Unknown option: " << option << ". Ignoring.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target_dir.empty())
|
||||||
|
{
|
||||||
|
append_files_from_dir(filenames, base_dir, target_dir);
|
||||||
|
}
|
||||||
|
rebase_files(filenames, base_dir);
|
||||||
|
|
||||||
|
Eval::NNUE::init();
|
||||||
|
|
||||||
|
cout << "convert_bin_from_pgn-extract.." << endl;
|
||||||
|
convert_bin_from_pgn_extract(
|
||||||
|
filenames,
|
||||||
|
output_file_name,
|
||||||
|
pgn_eval_side_to_move,
|
||||||
|
convert_no_eval_fens_as_score_zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_bin(std::istringstream& is)
|
||||||
|
{
|
||||||
|
std::vector<std::string> filenames;
|
||||||
|
|
||||||
|
string base_dir;
|
||||||
|
string target_dir;
|
||||||
|
|
||||||
|
int ply_minimum = 0;
|
||||||
|
int ply_maximum = 114514;
|
||||||
|
bool interpolate_eval = 0;
|
||||||
|
bool check_invalid_fen = false;
|
||||||
|
bool check_illegal_move = false;
|
||||||
|
|
||||||
|
bool pgn_eval_side_to_move = false;
|
||||||
|
bool convert_no_eval_fens_as_score_zero = false;
|
||||||
|
|
||||||
|
double src_score_min_value = 0.0;
|
||||||
|
double src_score_max_value = 1.0;
|
||||||
|
double dest_score_min_value = 0.0;
|
||||||
|
double dest_score_max_value = 1.0;
|
||||||
|
|
||||||
|
string output_file_name = "shuffled_sfen.bin";
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
string option;
|
||||||
|
is >> option;
|
||||||
|
|
||||||
|
if (option == "")
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (option == "targetdir") is >> target_dir;
|
||||||
|
else if (option == "targetfile")
|
||||||
|
{
|
||||||
|
std::string filename;
|
||||||
|
is >> filename;
|
||||||
|
filenames.push_back(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (option == "basedir") is >> base_dir;
|
||||||
|
|
||||||
|
else if (option == "ply_minimum") is >> ply_minimum;
|
||||||
|
else if (option == "ply_maximum") is >> ply_maximum;
|
||||||
|
else if (option == "interpolate_eval") is >> interpolate_eval;
|
||||||
|
else if (option == "check_invalid_fen") is >> check_invalid_fen;
|
||||||
|
else if (option == "check_illegal_move") is >> check_illegal_move;
|
||||||
|
else if (option == "pgn_eval_side_to_move") is >> pgn_eval_side_to_move;
|
||||||
|
else if (option == "convert_no_eval_fens_as_score_zero") is >> convert_no_eval_fens_as_score_zero;
|
||||||
|
else if (option == "src_score_min_value") is >> src_score_min_value;
|
||||||
|
else if (option == "src_score_max_value") is >> src_score_max_value;
|
||||||
|
else if (option == "dest_score_min_value") is >> dest_score_min_value;
|
||||||
|
else if (option == "dest_score_max_value") is >> dest_score_max_value;
|
||||||
|
else if (option == "output_file_name") is >> output_file_name;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "Unknown option: " << option << ". Ignoring.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target_dir.empty())
|
||||||
|
{
|
||||||
|
append_files_from_dir(filenames, base_dir, target_dir);
|
||||||
|
}
|
||||||
|
rebase_files(filenames, base_dir);
|
||||||
|
|
||||||
|
Eval::NNUE::init();
|
||||||
|
|
||||||
|
cout << "convert_bin.." << endl;
|
||||||
|
convert_bin(
|
||||||
|
filenames,
|
||||||
|
output_file_name,
|
||||||
|
ply_minimum,
|
||||||
|
ply_maximum,
|
||||||
|
interpolate_eval,
|
||||||
|
src_score_min_value,
|
||||||
|
src_score_max_value,
|
||||||
|
dest_score_min_value,
|
||||||
|
dest_score_max_value,
|
||||||
|
check_invalid_fen,
|
||||||
|
check_illegal_move
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void convert_plain(std::istringstream& is)
|
||||||
|
{
|
||||||
|
std::vector<std::string> filenames;
|
||||||
|
|
||||||
|
string base_dir;
|
||||||
|
string target_dir;
|
||||||
|
|
||||||
|
string output_file_name = "shuffled_sfen.bin";
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
string option;
|
||||||
|
is >> option;
|
||||||
|
|
||||||
|
if (option == "")
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (option == "targetdir") is >> target_dir;
|
||||||
|
else if (option == "targetfile")
|
||||||
|
{
|
||||||
|
std::string filename;
|
||||||
|
is >> filename;
|
||||||
|
filenames.push_back(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (option == "basedir") is >> base_dir;
|
||||||
|
|
||||||
|
else if (option == "output_file_name") is >> output_file_name;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "Unknown option: " << option << ". Ignoring.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target_dir.empty())
|
||||||
|
{
|
||||||
|
append_files_from_dir(filenames, base_dir, target_dir);
|
||||||
|
}
|
||||||
|
rebase_files(filenames, base_dir);
|
||||||
|
|
||||||
|
Eval::NNUE::init();
|
||||||
|
|
||||||
|
cout << "convert_plain.." << endl;
|
||||||
|
convert_plain(filenames, output_file_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef _CONVERT_H_
|
||||||
|
#define _CONVERT_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
void convert(std::istringstream& is);
|
||||||
|
|
||||||
|
void convert_bin_from_pgn_extract(std::istringstream& is);
|
||||||
|
|
||||||
|
void convert_bin(std::istringstream& is);
|
||||||
|
|
||||||
|
void convert_plain(std::istringstream& is);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include "opening_book.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
EpdOpeningBook::EpdOpeningBook(const std::string& file, PRNG& prng) :
|
||||||
|
OpeningBook(file)
|
||||||
|
{
|
||||||
|
std::ifstream in(file);
|
||||||
|
if (!in)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(in, line))
|
||||||
|
{
|
||||||
|
if (line.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fens.emplace_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Algo::shuffle(fens, prng);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ends_with(const std::string& lhs, const std::string& end)
|
||||||
|
{
|
||||||
|
if (end.size() > lhs.size()) return false;
|
||||||
|
|
||||||
|
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<OpeningBook> open_opening_book(const std::string& filename, PRNG& prng)
|
||||||
|
{
|
||||||
|
if (ends_with(filename, ".epd"))
|
||||||
|
return std::make_unique<EpdOpeningBook>(filename, prng);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#ifndef LEARN_OPENING_BOOK_H
|
||||||
|
#define LEARN_OPENING_BOOK_H
|
||||||
|
|
||||||
|
#include "misc.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "thread.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <random>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
struct OpeningBook {
|
||||||
|
|
||||||
|
const std::string& next_fen()
|
||||||
|
{
|
||||||
|
assert(fens.size() > 0);
|
||||||
|
|
||||||
|
std::unique_lock lock(mutex);
|
||||||
|
|
||||||
|
auto& fen = fens[current_index++];
|
||||||
|
if (current_index >= fens.size())
|
||||||
|
current_index = 0;
|
||||||
|
|
||||||
|
return fen;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const { return fens.size(); }
|
||||||
|
|
||||||
|
const std::string& get_filename() const { return filename; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
OpeningBook(const std::string& file) :
|
||||||
|
filename(file),
|
||||||
|
current_index(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
std::string filename;
|
||||||
|
std::vector<std::string> fens;
|
||||||
|
std::size_t current_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EpdOpeningBook : OpeningBook {
|
||||||
|
|
||||||
|
EpdOpeningBook(const std::string& file, PRNG& prng);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<OpeningBook> open_opening_book(const std::string& filename, PRNG& prng);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef _PACKED_SFEN_H_
|
||||||
|
#define _PACKED_SFEN_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
// packed sfen
|
||||||
|
struct PackedSfen { std::uint8_t data[32]; };
|
||||||
|
|
||||||
|
// Structure in which PackedSfen and evaluation value are integrated
|
||||||
|
// If you write different contents for each option, it will be a problem when reusing the teacher game
|
||||||
|
// For the time being, write all the following members regardless of the options.
|
||||||
|
struct PackedSfenValue
|
||||||
|
{
|
||||||
|
// phase
|
||||||
|
PackedSfen sfen;
|
||||||
|
|
||||||
|
// Evaluation value returned from Tools::search()
|
||||||
|
std::int16_t score;
|
||||||
|
|
||||||
|
// PV first move
|
||||||
|
// Used when finding the match rate with the teacher
|
||||||
|
std::uint16_t move;
|
||||||
|
|
||||||
|
// Trouble of the phase from the initial phase.
|
||||||
|
std::uint16_t gamePly;
|
||||||
|
|
||||||
|
// 1 if the player on this side ultimately wins the game. -1 if you are losing.
|
||||||
|
// 0 if a draw is reached.
|
||||||
|
// The draw is in the teacher position generation command gensfen,
|
||||||
|
// Only write if LEARN_GENSFEN_DRAW_RESULT is enabled.
|
||||||
|
std::int8_t game_result;
|
||||||
|
|
||||||
|
// When exchanging the file that wrote the teacher aspect with other people
|
||||||
|
//Because this structure size is not fixed, pad it so that it is 40 bytes in any environment.
|
||||||
|
std::uint8_t padding;
|
||||||
|
|
||||||
|
// 32 + 2 + 2 + 2 + 1 + 1 = 40bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
// Phase array: PSVector stands for packed sfen vector.
|
||||||
|
using PSVector = std::vector<PackedSfenValue>;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
#include "sfen_packer.h"
|
||||||
|
|
||||||
|
#include "packed_sfen.h"
|
||||||
|
|
||||||
|
#include "misc.h"
|
||||||
|
#include "position.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <cstring> // std::memset()
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
// Class that handles bitstream
|
||||||
|
// useful when doing aspect encoding
|
||||||
|
struct BitStream
|
||||||
|
{
|
||||||
|
// Set the memory to store the data in advance.
|
||||||
|
// Assume that memory is cleared to 0.
|
||||||
|
void set_data(std::uint8_t* data_) { data = data_; reset(); }
|
||||||
|
|
||||||
|
// Get the pointer passed in set_data().
|
||||||
|
uint8_t* get_data() const { return data; }
|
||||||
|
|
||||||
|
// Get the cursor.
|
||||||
|
int get_cursor() const { return bit_cursor; }
|
||||||
|
|
||||||
|
// reset the cursor
|
||||||
|
void reset() { bit_cursor = 0; }
|
||||||
|
|
||||||
|
// Write 1bit to the stream.
|
||||||
|
// If b is non-zero, write out 1. If 0, write 0.
|
||||||
|
void write_one_bit(int b)
|
||||||
|
{
|
||||||
|
if (b)
|
||||||
|
data[bit_cursor / 8] |= 1 << (bit_cursor & 7);
|
||||||
|
|
||||||
|
++bit_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 1 bit from the stream.
|
||||||
|
int read_one_bit()
|
||||||
|
{
|
||||||
|
int b = (data[bit_cursor / 8] >> (bit_cursor & 7)) & 1;
|
||||||
|
++bit_cursor;
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write n bits of data
|
||||||
|
// Data shall be written out from the lower order of d.
|
||||||
|
void write_n_bit(int d, int n)
|
||||||
|
{
|
||||||
|
for (int i = 0; i <n; ++i)
|
||||||
|
write_one_bit(d & (1 << i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// read n bits of data
|
||||||
|
// Reverse conversion of write_n_bit().
|
||||||
|
int read_n_bit(int n)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
result |= read_one_bit() ? (1 << i) : 0;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Next bit position to read/write.
|
||||||
|
int bit_cursor;
|
||||||
|
|
||||||
|
// data entity
|
||||||
|
std::uint8_t* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Class for compressing/decompressing sfen
|
||||||
|
// sfen can be packed to 256bit (32bytes) by Huffman coding.
|
||||||
|
// This is proven by mini. The above is Huffman coding.
|
||||||
|
//
|
||||||
|
// Internal format = 1-bit turn + 7-bit king position *2 + piece on board (Huffman coding) + hand piece (Huffman coding)
|
||||||
|
// Side to move (White = 0, Black = 1) (1bit)
|
||||||
|
// White King Position (6 bits)
|
||||||
|
// Black King Position (6 bits)
|
||||||
|
// Huffman Encoding of the board
|
||||||
|
// Castling availability (1 bit x 4)
|
||||||
|
// En passant square (1 or 1 + 6 bits)
|
||||||
|
// Rule 50 (6 bits)
|
||||||
|
// Game play (8 bits)
|
||||||
|
//
|
||||||
|
// TODO(someone): Rename SFEN to FEN.
|
||||||
|
//
|
||||||
|
struct SfenPacker
|
||||||
|
{
|
||||||
|
void pack(const Position& pos, bool resetCastlingRights);
|
||||||
|
|
||||||
|
// sfen packed by pack() (256bit = 32bytes)
|
||||||
|
// Or sfen to decode with unpack()
|
||||||
|
uint8_t *data; // uint8_t[32];
|
||||||
|
|
||||||
|
BitStream stream;
|
||||||
|
|
||||||
|
// Output the board pieces to stream.
|
||||||
|
void write_board_piece_to_stream(Piece pc);
|
||||||
|
|
||||||
|
// Read one board piece from stream
|
||||||
|
Piece read_board_piece_from_stream();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Huffman coding
|
||||||
|
// * is simplified from mini encoding to make conversion easier.
|
||||||
|
//
|
||||||
|
// Huffman Encoding
|
||||||
|
//
|
||||||
|
// Empty xxxxxxx0
|
||||||
|
// Pawn xxxxx001 + 1 bit (Color)
|
||||||
|
// Knight xxxxx011 + 1 bit (Color)
|
||||||
|
// Bishop xxxxx101 + 1 bit (Color)
|
||||||
|
// Rook xxxxx111 + 1 bit (Color)
|
||||||
|
// Queen xxxx1001 + 1 bit (Color)
|
||||||
|
//
|
||||||
|
// Worst case:
|
||||||
|
// - 32 empty squares 32 bits
|
||||||
|
// - 30 pieces 150 bits
|
||||||
|
// - 2 kings 12 bits
|
||||||
|
// - castling rights 4 bits
|
||||||
|
// - ep square 7 bits
|
||||||
|
// - rule50 7 bits
|
||||||
|
// - game ply 16 bits
|
||||||
|
// - TOTAL 228 bits < 256 bits
|
||||||
|
|
||||||
|
struct HuffmanedPiece
|
||||||
|
{
|
||||||
|
int code; // how it will be coded
|
||||||
|
int bits; // How many bits do you have
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr HuffmanedPiece huffman_table[] =
|
||||||
|
{
|
||||||
|
{0b0000,1}, // NO_PIECE
|
||||||
|
{0b0001,4}, // PAWN
|
||||||
|
{0b0011,4}, // KNIGHT
|
||||||
|
{0b0101,4}, // BISHOP
|
||||||
|
{0b0111,4}, // ROOK
|
||||||
|
{0b1001,4}, // QUEEN
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pack sfen and store in data[32].
|
||||||
|
void SfenPacker::pack(const Position& pos, bool resetCastlingRights)
|
||||||
|
{
|
||||||
|
memset(data, 0, 32 /* 256bit */);
|
||||||
|
stream.set_data(data);
|
||||||
|
|
||||||
|
// turn
|
||||||
|
// Side to move.
|
||||||
|
stream.write_one_bit((int)(pos.side_to_move()));
|
||||||
|
|
||||||
|
// 7-bit positions for leading and trailing balls
|
||||||
|
// White king and black king, 6 bits for each.
|
||||||
|
for(auto c: Colors)
|
||||||
|
stream.write_n_bit(pos.king_square(c), 6);
|
||||||
|
|
||||||
|
// Write the pieces on the board other than the kings.
|
||||||
|
for (Rank r = RANK_8; r >= RANK_1; --r)
|
||||||
|
{
|
||||||
|
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||||
|
{
|
||||||
|
Piece pc = pos.piece_on(make_square(f, r));
|
||||||
|
if (type_of(pc) == KING)
|
||||||
|
continue;
|
||||||
|
write_board_piece_to_stream(pc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resetCastlingRights)
|
||||||
|
{
|
||||||
|
stream.write_n_bit(0, 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.write_one_bit(pos.can_castle(WHITE_OO));
|
||||||
|
stream.write_one_bit(pos.can_castle(WHITE_OOO));
|
||||||
|
stream.write_one_bit(pos.can_castle(BLACK_OO));
|
||||||
|
stream.write_one_bit(pos.can_castle(BLACK_OOO));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos.ep_square() == SQ_NONE) {
|
||||||
|
stream.write_one_bit(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stream.write_one_bit(1);
|
||||||
|
stream.write_n_bit(static_cast<int>(pos.ep_square()), 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.write_n_bit(pos.state()->rule50, 6);
|
||||||
|
|
||||||
|
const int fm = 1 + (pos.game_ply()-(pos.side_to_move() == BLACK)) / 2;
|
||||||
|
stream.write_n_bit(fm, 8);
|
||||||
|
|
||||||
|
// Write high bits of half move. This is a fix for the
|
||||||
|
// limited range of half move counter.
|
||||||
|
// This is backwards compatibile.
|
||||||
|
stream.write_n_bit(fm >> 8, 8);
|
||||||
|
|
||||||
|
// Write the highest bit of rule50 at the end. This is a backwards
|
||||||
|
// compatibile fix for rule50 having only 6 bits stored.
|
||||||
|
// This bit is just ignored by the old parsers.
|
||||||
|
stream.write_n_bit(pos.state()->rule50 >> 6, 1);
|
||||||
|
|
||||||
|
assert(stream.get_cursor() <= 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the board pieces to stream.
|
||||||
|
void SfenPacker::write_board_piece_to_stream(Piece pc)
|
||||||
|
{
|
||||||
|
// piece type
|
||||||
|
PieceType pr = type_of(pc);
|
||||||
|
auto c = huffman_table[pr];
|
||||||
|
stream.write_n_bit(c.code, c.bits);
|
||||||
|
|
||||||
|
if (pc == NO_PIECE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// first and second flag
|
||||||
|
stream.write_one_bit(color_of(pc));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read one board piece from stream
|
||||||
|
Piece SfenPacker::read_board_piece_from_stream()
|
||||||
|
{
|
||||||
|
PieceType pr = NO_PIECE_TYPE;
|
||||||
|
int code = 0, bits = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
code |= stream.read_one_bit() << bits;
|
||||||
|
++bits;
|
||||||
|
|
||||||
|
assert(bits <= 6);
|
||||||
|
|
||||||
|
for (pr = NO_PIECE_TYPE; pr <KING; ++pr)
|
||||||
|
if (huffman_table[pr].code == code
|
||||||
|
&& huffman_table[pr].bits == bits)
|
||||||
|
goto Found;
|
||||||
|
}
|
||||||
|
Found:;
|
||||||
|
if (pr == NO_PIECE_TYPE)
|
||||||
|
return NO_PIECE;
|
||||||
|
|
||||||
|
// first and second flag
|
||||||
|
Color c = (Color)stream.read_one_bit();
|
||||||
|
|
||||||
|
return make_piece(c, pr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th, bool frc)
|
||||||
|
{
|
||||||
|
SfenPacker packer;
|
||||||
|
auto& stream = packer.stream;
|
||||||
|
|
||||||
|
// TODO: separate streams for writing and reading. Here we actually have to
|
||||||
|
// const_cast which is not safe in the long run.
|
||||||
|
stream.set_data(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(&sfen)));
|
||||||
|
|
||||||
|
pos.clear();
|
||||||
|
std::memset(si, 0, sizeof(StateInfo));
|
||||||
|
si->accumulator.computed[WHITE] = false;
|
||||||
|
si->accumulator.computed[BLACK] = false;
|
||||||
|
pos.st = si;
|
||||||
|
|
||||||
|
// Active color
|
||||||
|
pos.sideToMove = (Color)stream.read_one_bit();
|
||||||
|
|
||||||
|
// First the position of the ball
|
||||||
|
for (auto c : Colors)
|
||||||
|
pos.board[stream.read_n_bit(6)] = make_piece(c, KING);
|
||||||
|
|
||||||
|
// Piece placement
|
||||||
|
for (Rank r = RANK_8; r >= RANK_1; --r)
|
||||||
|
{
|
||||||
|
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||||
|
{
|
||||||
|
auto sq = make_square(f, r);
|
||||||
|
|
||||||
|
// it seems there are already balls
|
||||||
|
Piece pc;
|
||||||
|
if (type_of(pos.board[sq]) != KING)
|
||||||
|
{
|
||||||
|
assert(pos.board[sq] == NO_PIECE);
|
||||||
|
pc = packer.read_board_piece_from_stream();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pc = pos.board[sq];
|
||||||
|
// put_piece() will catch ASSERT unless you remove it all.
|
||||||
|
pos.board[sq] = NO_PIECE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There may be no pieces, so skip in that case.
|
||||||
|
if (pc == NO_PIECE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
pos.put_piece(Piece(pc), sq);
|
||||||
|
|
||||||
|
if (stream.get_cursor()> 256)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Castling availability.
|
||||||
|
pos.st->castlingRights = 0;
|
||||||
|
if (stream.read_one_bit()) {
|
||||||
|
Square rsq;
|
||||||
|
for (rsq = relative_square(WHITE, SQ_H1); pos.piece_on(rsq) != W_ROOK; --rsq) {}
|
||||||
|
pos.set_castling_right(WHITE, rsq);
|
||||||
|
}
|
||||||
|
if (stream.read_one_bit()) {
|
||||||
|
Square rsq;
|
||||||
|
for (rsq = relative_square(WHITE, SQ_A1); pos.piece_on(rsq) != W_ROOK; ++rsq) {}
|
||||||
|
pos.set_castling_right(WHITE, rsq);
|
||||||
|
}
|
||||||
|
if (stream.read_one_bit()) {
|
||||||
|
Square rsq;
|
||||||
|
for (rsq = relative_square(BLACK, SQ_H1); pos.piece_on(rsq) != B_ROOK; --rsq) {}
|
||||||
|
pos.set_castling_right(BLACK, rsq);
|
||||||
|
}
|
||||||
|
if (stream.read_one_bit()) {
|
||||||
|
Square rsq;
|
||||||
|
for (rsq = relative_square(BLACK, SQ_A1); pos.piece_on(rsq) != B_ROOK; ++rsq) {}
|
||||||
|
pos.set_castling_right(BLACK, rsq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// En passant square. Ignore if no pawn capture is possible
|
||||||
|
if (stream.read_one_bit()) {
|
||||||
|
Square ep_square = static_cast<Square>(stream.read_n_bit(6));
|
||||||
|
pos.st->epSquare = ep_square;
|
||||||
|
|
||||||
|
if (!(pos.attackers_to(pos.st->epSquare) & pos.pieces(pos.sideToMove, PAWN))
|
||||||
|
|| !(pos.pieces(~pos.sideToMove, PAWN) & (pos.st->epSquare + pawn_push(~pos.sideToMove))))
|
||||||
|
pos.st->epSquare = SQ_NONE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pos.st->epSquare = SQ_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Halfmove clock
|
||||||
|
pos.st->rule50 = stream.read_n_bit(6);
|
||||||
|
|
||||||
|
// Fullmove number
|
||||||
|
pos.gamePly = stream.read_n_bit(8);
|
||||||
|
|
||||||
|
// Read the highest bit of rule50. This was added as a fix for rule50
|
||||||
|
// counter having only 6 bits stored.
|
||||||
|
// In older entries this will just be a zero bit.
|
||||||
|
pos.gamePly |= stream.read_n_bit(8) << 8;
|
||||||
|
|
||||||
|
// Read the highest bit of rule50. This was added as a fix for rule50
|
||||||
|
// counter having only 6 bits stored.
|
||||||
|
// In older entries this will just be a zero bit.
|
||||||
|
pos.st->rule50 |= stream.read_n_bit(1) << 6;
|
||||||
|
|
||||||
|
// Convert from fullmove starting from 1 to gamePly starting from 0,
|
||||||
|
// handle also common incorrect FEN with fullmove = 0.
|
||||||
|
pos.gamePly = std::max(2 * (pos.gamePly - 1), 0) + (pos.sideToMove == BLACK);
|
||||||
|
|
||||||
|
assert(stream.get_cursor() <= 256);
|
||||||
|
|
||||||
|
pos.chess960 = frc;
|
||||||
|
pos.thisThread = th;
|
||||||
|
pos.set_state(pos.st);
|
||||||
|
|
||||||
|
assert(pos.pos_is_ok());
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackedSfen sfen_pack(Position& pos, bool resetCastlingRights)
|
||||||
|
{
|
||||||
|
PackedSfen sfen;
|
||||||
|
|
||||||
|
SfenPacker sp;
|
||||||
|
sp.data = (uint8_t*)&sfen;
|
||||||
|
sp.pack(pos, resetCastlingRights);
|
||||||
|
|
||||||
|
return sfen;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef _SFEN_PACKER_H_
|
||||||
|
#define _SFEN_PACKER_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
#include "packed_sfen.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
class Position;
|
||||||
|
struct StateInfo;
|
||||||
|
class Thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th, bool frc);
|
||||||
|
PackedSfen sfen_pack(Position& pos, bool resetCastlingRights);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
#include "sfen_stream.h"
|
||||||
|
|
||||||
|
#include "packed_sfen.h"
|
||||||
|
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <list>
|
||||||
|
#include <atomic>
|
||||||
|
#include <optional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <thread>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools{
|
||||||
|
|
||||||
|
enum struct SfenReaderMode
|
||||||
|
{
|
||||||
|
Sequential,
|
||||||
|
Cyclic
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sfen reader
|
||||||
|
struct SfenReader
|
||||||
|
{
|
||||||
|
// Number of phases buffered by each thread 0.1M phases. 4M phase at 40HT
|
||||||
|
static constexpr size_t DEFAULT_THREAD_BUFFER_SIZE = 10 * 1000;
|
||||||
|
|
||||||
|
// Buffer for reading files (If this is made larger,
|
||||||
|
// the shuffle becomes larger and the phases may vary.
|
||||||
|
// If it is too large, the memory consumption will increase.
|
||||||
|
// SFEN_READ_SIZE is a multiple of THREAD_BUFFER_SIZE.
|
||||||
|
static constexpr const size_t DEFAULT_SFEN_READ_SIZE = 1000 * 1000 * 10;
|
||||||
|
|
||||||
|
// Do not use std::random_device().
|
||||||
|
// Because it always the same integers on MinGW.
|
||||||
|
SfenReader(
|
||||||
|
const std::vector<std::string>& filenames_,
|
||||||
|
bool do_shuffle,
|
||||||
|
SfenReaderMode mode_,
|
||||||
|
int thread_num,
|
||||||
|
const std::string& seed,
|
||||||
|
size_t read_size = DEFAULT_SFEN_READ_SIZE,
|
||||||
|
size_t buffer_size = DEFAULT_THREAD_BUFFER_SIZE
|
||||||
|
) :
|
||||||
|
filenames(filenames_.begin(), filenames_.end()),
|
||||||
|
mode(mode_),
|
||||||
|
// Due to the implementation of waiting for buffer empty a bit
|
||||||
|
// the read size must be at least twice the buffer size.
|
||||||
|
sfen_read_size(std::max(read_size, buffer_size * 2)),
|
||||||
|
thread_buffer_size(buffer_size),
|
||||||
|
prng(seed)
|
||||||
|
{
|
||||||
|
packed_sfens.resize(thread_num);
|
||||||
|
total_read = 0;
|
||||||
|
end_of_files = false;
|
||||||
|
shuffle = do_shuffle;
|
||||||
|
stop_flag = false;
|
||||||
|
num_buffers_in_pool.store(0);
|
||||||
|
|
||||||
|
file_worker_thread = std::thread([&] {
|
||||||
|
this->file_read_worker();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
~SfenReader()
|
||||||
|
{
|
||||||
|
stop_flag = true;
|
||||||
|
|
||||||
|
if (file_worker_thread.joinable())
|
||||||
|
file_worker_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the phase for calculation such as mse.
|
||||||
|
PSVector read_some(uint64_t count, uint64_t count_tries, std::function<bool(const PackedSfenValue&)> do_take)
|
||||||
|
{
|
||||||
|
PSVector psv;
|
||||||
|
psv.reserve(count);
|
||||||
|
|
||||||
|
for (uint64_t i = 0; i < count_tries; ++i)
|
||||||
|
{
|
||||||
|
PackedSfenValue ps;
|
||||||
|
if (!read_to_thread_buffer(0, ps))
|
||||||
|
{
|
||||||
|
std::cout << "ERROR (sfen_reader): Reading failed." << std::endl;
|
||||||
|
return psv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_take(ps))
|
||||||
|
{
|
||||||
|
psv.push_back(ps);
|
||||||
|
|
||||||
|
if (psv.size() >= count)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return psv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ASYNC] Thread returns one aspect. Otherwise returns false.
|
||||||
|
bool read_to_thread_buffer(size_t thread_id, PackedSfenValue& ps)
|
||||||
|
{
|
||||||
|
// If there are any positions left in the thread buffer
|
||||||
|
// then retrieve one and return it.
|
||||||
|
auto& thread_ps = packed_sfens[thread_id];
|
||||||
|
|
||||||
|
// Fill the read buffer if there is no remaining buffer,
|
||||||
|
// but if it doesn't even exist, finish.
|
||||||
|
// If the buffer is empty, fill it.
|
||||||
|
if ((thread_ps == nullptr || thread_ps->empty())
|
||||||
|
&& !read_to_thread_buffer_impl(thread_id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// read_to_thread_buffer_impl() returned true,
|
||||||
|
// Since the filling of the thread buffer with the
|
||||||
|
// phase has been completed successfully
|
||||||
|
// thread_ps->rbegin() is alive.
|
||||||
|
|
||||||
|
ps = thread_ps->back();
|
||||||
|
thread_ps->pop_back();
|
||||||
|
|
||||||
|
// If you've run out of buffers, call delete yourself to free this buffer.
|
||||||
|
if (thread_ps->empty())
|
||||||
|
{
|
||||||
|
thread_ps.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ASYNC] Read some aspects into thread buffer.
|
||||||
|
bool read_to_thread_buffer_impl(size_t thread_id)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
|
// If you can fill from the file buffer, that's fine.
|
||||||
|
if (packed_sfens_pool.size() != 0)
|
||||||
|
{
|
||||||
|
// It seems that filling is possible, so fill and finish.
|
||||||
|
|
||||||
|
packed_sfens[thread_id] = std::move(packed_sfens_pool.front());
|
||||||
|
packed_sfens_pool.pop_front();
|
||||||
|
num_buffers_in_pool.fetch_sub(1);
|
||||||
|
|
||||||
|
total_read += thread_buffer_size;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The file to read is already gone. No more use.
|
||||||
|
if (end_of_files)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Waiting for file worker to fill packed_sfens_pool.
|
||||||
|
// The mutex isn't locked, so it should fill up soon.
|
||||||
|
// Poor man's condition variable.
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_read_worker()
|
||||||
|
{
|
||||||
|
std::string currentFilename;
|
||||||
|
uint64_t numEntriesReadFromCurrentFile = 0;
|
||||||
|
|
||||||
|
auto open_next_file = [&]() {
|
||||||
|
// no more
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
sfen_input_stream.reset();
|
||||||
|
|
||||||
|
if (filenames.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Get the next file name.
|
||||||
|
currentFilename = filenames.front();
|
||||||
|
filenames.pop_front();
|
||||||
|
|
||||||
|
numEntriesReadFromCurrentFile = 0;
|
||||||
|
|
||||||
|
sfen_input_stream = open_sfen_input_file(currentFilename);
|
||||||
|
|
||||||
|
auto out = sync_region_cout.new_region();
|
||||||
|
if (sfen_input_stream == nullptr)
|
||||||
|
{
|
||||||
|
out << "INFO (sfen_reader): File does not exist: " << currentFilename << '\n';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << "INFO (sfen_reader): Opened file for reading: " << currentFilename << '\n';
|
||||||
|
|
||||||
|
// in case the file is empty or was deleted.
|
||||||
|
if (sfen_input_stream->eof())
|
||||||
|
{
|
||||||
|
out << " - File empty, nothing to read.\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sfen_input_stream == nullptr && !open_next_file())
|
||||||
|
{
|
||||||
|
auto out = sync_region_cout.new_region();
|
||||||
|
out << "INFO (sfen_reader): End of files." << std::endl;
|
||||||
|
end_of_files = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to set the `end_of_files` only after we read everything AND copy to the buffer pool.
|
||||||
|
bool local_end_of_files = false;
|
||||||
|
while (!local_end_of_files)
|
||||||
|
{
|
||||||
|
// Wait for the buffer to run out.
|
||||||
|
// This size() is read only, so you don't need to lock it.
|
||||||
|
while (!stop_flag && num_buffers_in_pool.load() >= sfen_read_size / thread_buffer_size)
|
||||||
|
sleep(100);
|
||||||
|
|
||||||
|
if (stop_flag)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PSVector sfens;
|
||||||
|
sfens.reserve(sfen_read_size);
|
||||||
|
|
||||||
|
// Read from the file into the file buffer.
|
||||||
|
while (sfens.size() < sfen_read_size)
|
||||||
|
{
|
||||||
|
std::optional<PackedSfenValue> p = sfen_input_stream->next();
|
||||||
|
if (p.has_value())
|
||||||
|
{
|
||||||
|
sfens.push_back(*p);
|
||||||
|
++numEntriesReadFromCurrentFile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mode == SfenReaderMode::Cyclic
|
||||||
|
&& numEntriesReadFromCurrentFile > 0)
|
||||||
|
{
|
||||||
|
// The file contained data so we add it again to the end of the queue.
|
||||||
|
filenames.emplace_back(currentFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!open_next_file())
|
||||||
|
{
|
||||||
|
// There was no next file. Abort.
|
||||||
|
auto out = sync_region_cout.new_region();
|
||||||
|
out << "INFO (sfen_reader): End of files." << std::endl;
|
||||||
|
local_end_of_files = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle the read phase data.
|
||||||
|
if (shuffle)
|
||||||
|
{
|
||||||
|
Algo::shuffle(sfens, prng);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<PSVector>> buffers;
|
||||||
|
for (size_t offset = 0; offset < sfens.size(); offset += thread_buffer_size)
|
||||||
|
{
|
||||||
|
const size_t count =
|
||||||
|
offset + thread_buffer_size > sfens.size()
|
||||||
|
? sfens.size() - offset
|
||||||
|
: thread_buffer_size;
|
||||||
|
|
||||||
|
// Delete this pointer on the receiving side.
|
||||||
|
auto buf = std::make_unique<PSVector>();
|
||||||
|
buf->resize(count);
|
||||||
|
memcpy(
|
||||||
|
buf->data(),
|
||||||
|
&sfens[offset],
|
||||||
|
sizeof(PackedSfenValue) * count);
|
||||||
|
|
||||||
|
buffers.emplace_back(std::move(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
|
|
||||||
|
// The mutex lock is required because the%
|
||||||
|
// contents of packed_sfens_pool are changed.
|
||||||
|
|
||||||
|
for (auto& buf : buffers)
|
||||||
|
{
|
||||||
|
num_buffers_in_pool.fetch_add(1);
|
||||||
|
packed_sfens_pool.emplace_back(std::move(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end_of_files = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// worker thread reading file in background
|
||||||
|
std::thread file_worker_thread;
|
||||||
|
|
||||||
|
// sfen files
|
||||||
|
std::deque<std::string> filenames;
|
||||||
|
|
||||||
|
std::atomic<bool> stop_flag;
|
||||||
|
|
||||||
|
// number of phases read (file to memory buffer)
|
||||||
|
std::atomic<uint64_t> total_read;
|
||||||
|
|
||||||
|
// Do not shuffle when reading the phase.
|
||||||
|
bool shuffle;
|
||||||
|
|
||||||
|
SfenReaderMode mode;
|
||||||
|
|
||||||
|
size_t sfen_read_size;
|
||||||
|
size_t thread_buffer_size;
|
||||||
|
|
||||||
|
// Random number to shuffle when reading the phase
|
||||||
|
PRNG prng;
|
||||||
|
|
||||||
|
// Did you read the files and reached the end?
|
||||||
|
std::atomic<bool> end_of_files;
|
||||||
|
|
||||||
|
// handle of sfen file
|
||||||
|
std::unique_ptr<BasicSfenInputStream> sfen_input_stream;
|
||||||
|
|
||||||
|
// sfen for each thread
|
||||||
|
// (When the thread is used up, the thread should call delete to release it.)
|
||||||
|
std::vector<std::unique_ptr<PSVector>> packed_sfens;
|
||||||
|
|
||||||
|
// Mutex when accessing packed_sfens_pool
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
// pool of sfen. The worker thread read from the file is added here.
|
||||||
|
// Each worker thread fills its own packed_sfens[thread_id] from here.
|
||||||
|
// * Lock and access the mutex.
|
||||||
|
std::list<std::unique_ptr<PSVector>> packed_sfens_pool;
|
||||||
|
std::atomic<size_t> num_buffers_in_pool;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
#ifndef _SFEN_STREAM_H_
|
||||||
|
#define _SFEN_STREAM_H_
|
||||||
|
|
||||||
|
#include "packed_sfen.h"
|
||||||
|
|
||||||
|
#include "extra/nnue_data_binpack_format.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
enum struct SfenOutputType
|
||||||
|
{
|
||||||
|
Bin,
|
||||||
|
Binpack
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool ends_with(const std::string& lhs, const std::string& end)
|
||||||
|
{
|
||||||
|
if (end.size() > lhs.size()) return false;
|
||||||
|
|
||||||
|
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool has_extension(const std::string& filename, const std::string& extension)
|
||||||
|
{
|
||||||
|
return ends_with(filename, "." + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string filename_with_extension(const std::string& filename, const std::string& ext)
|
||||||
|
{
|
||||||
|
if (ends_with(filename, ext))
|
||||||
|
{
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return filename + "." + ext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BasicSfenInputStream
|
||||||
|
{
|
||||||
|
virtual std::optional<PackedSfenValue> next() = 0;
|
||||||
|
virtual bool eof() const = 0;
|
||||||
|
virtual ~BasicSfenInputStream() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BinSfenInputStream : BasicSfenInputStream
|
||||||
|
{
|
||||||
|
static constexpr auto openmode = std::ios::in | std::ios::binary;
|
||||||
|
static inline const std::string extension = "bin";
|
||||||
|
|
||||||
|
BinSfenInputStream(std::string filename) :
|
||||||
|
m_stream(filename, openmode),
|
||||||
|
m_eof(!m_stream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<PackedSfenValue> next() override
|
||||||
|
{
|
||||||
|
PackedSfenValue e;
|
||||||
|
if(m_stream.read(reinterpret_cast<char*>(&e), sizeof(PackedSfenValue)))
|
||||||
|
{
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_eof = true;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eof() const override
|
||||||
|
{
|
||||||
|
return m_eof;
|
||||||
|
}
|
||||||
|
|
||||||
|
~BinSfenInputStream() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::fstream m_stream;
|
||||||
|
bool m_eof;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BinpackSfenInputStream : BasicSfenInputStream
|
||||||
|
{
|
||||||
|
static constexpr auto openmode = std::ios::in | std::ios::binary;
|
||||||
|
static inline const std::string extension = "binpack";
|
||||||
|
|
||||||
|
BinpackSfenInputStream(std::string filename) :
|
||||||
|
m_stream(filename, openmode),
|
||||||
|
m_eof(!m_stream.hasNext())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<PackedSfenValue> next() override
|
||||||
|
{
|
||||||
|
static_assert(sizeof(binpack::nodchip::PackedSfenValue) == sizeof(PackedSfenValue));
|
||||||
|
|
||||||
|
if (!m_stream.hasNext())
|
||||||
|
{
|
||||||
|
m_eof = true;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto training_data_entry = m_stream.next();
|
||||||
|
auto v = binpack::trainingDataEntryToPackedSfenValue(training_data_entry);
|
||||||
|
PackedSfenValue psv;
|
||||||
|
// same layout, different types. One is from generic library.
|
||||||
|
std::memcpy(&psv, &v, sizeof(PackedSfenValue));
|
||||||
|
|
||||||
|
return psv;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eof() const override
|
||||||
|
{
|
||||||
|
return m_eof;
|
||||||
|
}
|
||||||
|
|
||||||
|
~BinpackSfenInputStream() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
binpack::CompressedTrainingDataEntryReader m_stream;
|
||||||
|
bool m_eof;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BasicSfenOutputStream
|
||||||
|
{
|
||||||
|
virtual void write(const PSVector& sfens) = 0;
|
||||||
|
virtual ~BasicSfenOutputStream() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BinSfenOutputStream : BasicSfenOutputStream
|
||||||
|
{
|
||||||
|
static constexpr auto openmode = std::ios::out | std::ios::binary | std::ios::app;
|
||||||
|
static inline const std::string extension = "bin";
|
||||||
|
|
||||||
|
BinSfenOutputStream(std::string filename) :
|
||||||
|
m_stream(filename_with_extension(filename, extension), openmode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const PSVector& sfens) override
|
||||||
|
{
|
||||||
|
m_stream.write(reinterpret_cast<const char*>(sfens.data()), sizeof(PackedSfenValue) * sfens.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
~BinSfenOutputStream() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::fstream m_stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BinpackSfenOutputStream : BasicSfenOutputStream
|
||||||
|
{
|
||||||
|
static constexpr auto openmode = std::ios::out | std::ios::binary | std::ios::app;
|
||||||
|
static inline const std::string extension = "binpack";
|
||||||
|
|
||||||
|
BinpackSfenOutputStream(std::string filename) :
|
||||||
|
m_stream(filename_with_extension(filename, extension), openmode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const PSVector& sfens) override
|
||||||
|
{
|
||||||
|
static_assert(sizeof(binpack::nodchip::PackedSfenValue) == sizeof(PackedSfenValue));
|
||||||
|
|
||||||
|
for(auto& sfen : sfens)
|
||||||
|
{
|
||||||
|
// The library uses a type that's different but layout-compatibile.
|
||||||
|
binpack::nodchip::PackedSfenValue e;
|
||||||
|
std::memcpy(&e, &sfen, sizeof(binpack::nodchip::PackedSfenValue));
|
||||||
|
m_stream.addTrainingDataEntry(binpack::packedSfenValueToTrainingDataEntry(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~BinpackSfenOutputStream() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
binpack::CompressedTrainingDataEntryWriter m_stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::unique_ptr<BasicSfenInputStream> open_sfen_input_file(const std::string& filename)
|
||||||
|
{
|
||||||
|
if (has_extension(filename, BinSfenInputStream::extension))
|
||||||
|
return std::make_unique<BinSfenInputStream>(filename);
|
||||||
|
else if (has_extension(filename, BinpackSfenInputStream::extension))
|
||||||
|
return std::make_unique<BinpackSfenInputStream>(filename);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::unique_ptr<BasicSfenOutputStream> create_new_sfen_output(const std::string& filename, SfenOutputType sfen_output_type)
|
||||||
|
{
|
||||||
|
switch(sfen_output_type)
|
||||||
|
{
|
||||||
|
case SfenOutputType::Bin:
|
||||||
|
return std::make_unique<BinSfenOutputStream>(filename);
|
||||||
|
case SfenOutputType::Binpack:
|
||||||
|
return std::make_unique<BinpackSfenOutputStream>(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::unique_ptr<BasicSfenOutputStream> create_new_sfen_output(const std::string& filename)
|
||||||
|
{
|
||||||
|
if (has_extension(filename, BinSfenOutputStream::extension))
|
||||||
|
return std::make_unique<BinSfenOutputStream>(filename);
|
||||||
|
else if (has_extension(filename, BinpackSfenOutputStream::extension))
|
||||||
|
return std::make_unique<BinpackSfenOutputStream>(filename);
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
#include "packed_sfen.h"
|
||||||
|
#include "sfen_stream.h"
|
||||||
|
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
|
#include "extra/nnue_data_binpack_format.h"
|
||||||
|
|
||||||
|
#include "syzygy/tbprobe.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
// Helper class for exporting Sfen
|
||||||
|
struct SfenWriter
|
||||||
|
{
|
||||||
|
// Amount of sfens required to flush the buffer.
|
||||||
|
static constexpr size_t SFEN_WRITE_SIZE = 5000;
|
||||||
|
|
||||||
|
// File name to write and number of threads to create
|
||||||
|
SfenWriter(std::string filename_, int thread_num, uint64_t save_count, SfenOutputType sfen_output_type)
|
||||||
|
{
|
||||||
|
sfen_buffers_pool.reserve((size_t)thread_num * 10);
|
||||||
|
sfen_buffers.resize(thread_num);
|
||||||
|
|
||||||
|
auto out = sync_region_cout.new_region();
|
||||||
|
out << "INFO (sfen_writer): Creating new data file at " << filename_ << std::endl;
|
||||||
|
|
||||||
|
sfen_format = sfen_output_type;
|
||||||
|
output_file_stream = create_new_sfen_output(filename_, sfen_format);
|
||||||
|
filename = filename_;
|
||||||
|
save_every = save_count;
|
||||||
|
|
||||||
|
finished = false;
|
||||||
|
|
||||||
|
file_worker_thread = std::thread([&] { this->file_write_worker(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
~SfenWriter()
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
|
||||||
|
finished = true;
|
||||||
|
file_worker_thread.join();
|
||||||
|
output_file_stream.reset();
|
||||||
|
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
{
|
||||||
|
// All buffers should be empty since file_worker_thread
|
||||||
|
// should have written everything before exiting.
|
||||||
|
for (const auto& p : sfen_buffers) { assert(p == nullptr); (void)p ; }
|
||||||
|
assert(sfen_buffers_pool.empty());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(size_t thread_id, const PackedSfenValue& psv)
|
||||||
|
{
|
||||||
|
// We have a buffer for each thread and add it there.
|
||||||
|
// If the buffer overflows, write it to a file.
|
||||||
|
|
||||||
|
// This buffer is prepared for each thread.
|
||||||
|
auto& buf = sfen_buffers[thread_id];
|
||||||
|
|
||||||
|
// Secure since there is no buf at the first time
|
||||||
|
// and immediately after writing the thread buffer.
|
||||||
|
if (!buf)
|
||||||
|
{
|
||||||
|
buf = std::make_unique<PSVector>();
|
||||||
|
buf->reserve(SFEN_WRITE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer is exclusive to this thread.
|
||||||
|
// There is no need for a critical section.
|
||||||
|
buf->push_back(psv);
|
||||||
|
|
||||||
|
if (buf->size() >= SFEN_WRITE_SIZE)
|
||||||
|
{
|
||||||
|
// If you load it in sfen_buffers_pool, the worker will do the rest.
|
||||||
|
|
||||||
|
// Critical section since sfen_buffers_pool is shared among threads.
|
||||||
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
|
sfen_buffers_pool.emplace_back(std::move(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < sfen_buffers.size(); ++i)
|
||||||
|
{
|
||||||
|
flush(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move what remains in the buffer for your thread to a buffer for writing to a file.
|
||||||
|
void flush(size_t thread_id)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
|
|
||||||
|
auto& buf = sfen_buffers[thread_id];
|
||||||
|
|
||||||
|
// There is a case that buf==nullptr, so that check is necessary.
|
||||||
|
if (buf && buf->size() != 0)
|
||||||
|
{
|
||||||
|
sfen_buffers_pool.emplace_back(std::move(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dedicated thread to write to file
|
||||||
|
void file_write_worker()
|
||||||
|
{
|
||||||
|
while (!finished || sfen_buffers_pool.size())
|
||||||
|
{
|
||||||
|
std::vector<std::unique_ptr<PSVector>> buffers;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
|
|
||||||
|
// Atomically swap take the filled buffers and
|
||||||
|
// create a new buffer pool for threads to fill.
|
||||||
|
buffers = std::move(sfen_buffers_pool);
|
||||||
|
sfen_buffers_pool = std::vector<std::unique_ptr<PSVector>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buffers.size())
|
||||||
|
{
|
||||||
|
// Poor man's condition variable.
|
||||||
|
sleep(100);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto& buf : buffers)
|
||||||
|
{
|
||||||
|
output_file_stream->write(*buf);
|
||||||
|
|
||||||
|
sfen_write_count += buf->size();
|
||||||
|
|
||||||
|
// Add the processed number here, and if it exceeds save_every,
|
||||||
|
// change the file name and reset this counter.
|
||||||
|
sfen_write_count_current_file += buf->size();
|
||||||
|
if (sfen_write_count_current_file >= save_every)
|
||||||
|
{
|
||||||
|
sfen_write_count_current_file = 0;
|
||||||
|
|
||||||
|
// Sequential number attached to the file
|
||||||
|
int n = (int)(sfen_write_count / save_every);
|
||||||
|
|
||||||
|
// Rename the file and open it again.
|
||||||
|
// Add ios::app in consideration of overwriting.
|
||||||
|
// (Depending on the operation, it may not be necessary.)
|
||||||
|
std::string new_filename = filename + "_" + std::to_string(n);
|
||||||
|
output_file_stream = create_new_sfen_output(new_filename, sfen_format);
|
||||||
|
|
||||||
|
auto out = sync_region_cout.new_region();
|
||||||
|
out << "INFO (sfen_writer): Creating new data file at " << new_filename << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::unique_ptr<BasicSfenOutputStream> output_file_stream;
|
||||||
|
|
||||||
|
// A new net is saved after every save_every sfens are processed.
|
||||||
|
uint64_t save_every = std::numeric_limits<uint64_t>::max();
|
||||||
|
|
||||||
|
// File name passed in the constructor
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
|
// Thread to write to the file
|
||||||
|
std::thread file_worker_thread;
|
||||||
|
|
||||||
|
// Flag that all threads have finished
|
||||||
|
std::atomic<bool> finished;
|
||||||
|
|
||||||
|
SfenOutputType sfen_format;
|
||||||
|
|
||||||
|
// buffer before writing to file
|
||||||
|
// sfen_buffers is the buffer for each thread
|
||||||
|
// sfen_buffers_pool is a buffer for writing.
|
||||||
|
// After loading the phase in the former buffer by SFEN_WRITE_SIZE,
|
||||||
|
// transfer it to the latter.
|
||||||
|
std::vector<std::unique_ptr<PSVector>> sfen_buffers;
|
||||||
|
std::vector<std::unique_ptr<PSVector>> sfen_buffers_pool;
|
||||||
|
|
||||||
|
// Mutex required to access sfen_buffers_pool
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
// Number of sfens written in total, and the
|
||||||
|
// number of sfens written in the current file.
|
||||||
|
uint64_t sfen_write_count = 0;
|
||||||
|
uint64_t sfen_write_count_current_file = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
+1278
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef _STATS_H_
|
||||||
|
#define _STATS_H_
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools::Stats {
|
||||||
|
|
||||||
|
void gather_statistics(std::istringstream& is);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,952 @@
|
|||||||
|
#include "training_data_generator.h"
|
||||||
|
|
||||||
|
#include "sfen_writer.h"
|
||||||
|
#include "packed_sfen.h"
|
||||||
|
#include "opening_book.h"
|
||||||
|
|
||||||
|
#include "misc.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "tt.h"
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
|
#include "extra/nnue_data_binpack_format.h"
|
||||||
|
|
||||||
|
#include "nnue/evaluate_nnue.h"
|
||||||
|
|
||||||
|
#include "syzygy/tbprobe.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <climits>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <limits>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <random>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace Stockfish::Tools
|
||||||
|
{
|
||||||
|
// Class to generate sfen with multiple threads
|
||||||
|
struct TrainingDataGenerator
|
||||||
|
{
|
||||||
|
struct Params
|
||||||
|
{
|
||||||
|
// Min and max depths for search during gensfen
|
||||||
|
int search_depth_min = 3;
|
||||||
|
int search_depth_max = -1;
|
||||||
|
|
||||||
|
// Number of the nodes to be searched.
|
||||||
|
// 0 represents no limits.
|
||||||
|
uint64_t nodes = 0;
|
||||||
|
|
||||||
|
// Upper limit of evaluation value of generated situation
|
||||||
|
int eval_limit = 3000;
|
||||||
|
|
||||||
|
// minimum ply with random move
|
||||||
|
// maximum ply with random move
|
||||||
|
// Number of random moves in one station
|
||||||
|
int random_move_minply = 1;
|
||||||
|
int random_move_maxply = 24;
|
||||||
|
int random_move_count = 5;
|
||||||
|
|
||||||
|
// Move kings with a probability of 1/N when randomly moving like Apery software.
|
||||||
|
// When you move the king again, there is a 1/N chance that it will randomly moved
|
||||||
|
// once in the opponent's turn.
|
||||||
|
// Apery has N=2. Specifying 0 here disables this function.
|
||||||
|
int random_move_like_apery = 0;
|
||||||
|
|
||||||
|
// For when using multi pv instead of random move.
|
||||||
|
// random_multi_pv is the number of candidates for MultiPV.
|
||||||
|
// When adopting the move of the candidate move, the difference
|
||||||
|
// between the evaluation value of the move of the 1st place
|
||||||
|
// and the evaluation value of the move of the Nth place is.
|
||||||
|
// Must be in the range random_multi_pv_diff.
|
||||||
|
// random_multi_pv_depth is the search depth for MultiPV.
|
||||||
|
int random_multi_pv = 0;
|
||||||
|
int random_multi_pv_diff = 32000;
|
||||||
|
int random_multi_pv_depth = -1;
|
||||||
|
uint64_t random_multi_pv_nodes = 0;
|
||||||
|
|
||||||
|
// The minimum and maximum ply (number of steps from
|
||||||
|
// the initial phase) of the sfens to write out.
|
||||||
|
int write_minply = 16;
|
||||||
|
int write_maxply = 400;
|
||||||
|
|
||||||
|
uint64_t save_every = std::numeric_limits<uint64_t>::max();
|
||||||
|
|
||||||
|
std::string output_file_name = "training_data";
|
||||||
|
|
||||||
|
SfenOutputType sfen_format = SfenOutputType::Binpack;
|
||||||
|
|
||||||
|
std::string seed;
|
||||||
|
|
||||||
|
bool write_out_draw_game_in_training_data_generation = true;
|
||||||
|
bool detect_draw_by_consecutive_low_score = true;
|
||||||
|
bool detect_draw_by_insufficient_mating_material = true;
|
||||||
|
|
||||||
|
uint64_t num_threads;
|
||||||
|
|
||||||
|
std::string book;
|
||||||
|
|
||||||
|
void enforce_constraints()
|
||||||
|
{
|
||||||
|
search_depth_max = std::max(search_depth_min, search_depth_max);
|
||||||
|
|
||||||
|
// Limit the maximum to a one-stop score. (Otherwise you might not end the loop)
|
||||||
|
eval_limit = std::min(eval_limit, (int)mate_in(2));
|
||||||
|
|
||||||
|
save_every = std::max(save_every, REPORT_STATS_EVERY);
|
||||||
|
|
||||||
|
num_threads = Options["Threads"];
|
||||||
|
|
||||||
|
if (random_multi_pv_depth == -1)
|
||||||
|
random_multi_pv_depth = search_depth_max;
|
||||||
|
|
||||||
|
if (random_multi_pv_nodes == 0)
|
||||||
|
random_multi_pv_nodes = nodes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hash to limit the export of identical sfens
|
||||||
|
static constexpr uint64_t GENSFEN_HASH_SIZE = 64 * 1024 * 1024;
|
||||||
|
// It must be 2**N because it will be used as the mask to calculate hash_index.
|
||||||
|
static_assert((GENSFEN_HASH_SIZE& (GENSFEN_HASH_SIZE - 1)) == 0);
|
||||||
|
|
||||||
|
static constexpr uint64_t REPORT_DOT_EVERY = 5000;
|
||||||
|
static constexpr uint64_t REPORT_STATS_EVERY = 200000;
|
||||||
|
static_assert(REPORT_STATS_EVERY % REPORT_DOT_EVERY == 0);
|
||||||
|
|
||||||
|
TrainingDataGenerator(
|
||||||
|
const Params& prm
|
||||||
|
) :
|
||||||
|
params(prm),
|
||||||
|
sfen_writer(prm.output_file_name, prm.num_threads, prm.save_every, prm.sfen_format)
|
||||||
|
{
|
||||||
|
hash.resize(GENSFEN_HASH_SIZE);
|
||||||
|
prngs.reserve(prm.num_threads);
|
||||||
|
auto seed = prm.seed;
|
||||||
|
for (uint64_t i = 0; i < prm.num_threads; ++i)
|
||||||
|
{
|
||||||
|
prngs.emplace_back(seed);
|
||||||
|
seed = prngs.back().next_random_seed();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prm.book.empty())
|
||||||
|
{
|
||||||
|
opening_book = open_opening_book(prm.book, prngs[0]);
|
||||||
|
if (opening_book == nullptr)
|
||||||
|
{
|
||||||
|
std::cout << "WARNING: Failed to open opening book " << prm.book << ". Falling back to startpos.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output seed to veryfy by the user if it's not identical by chance.
|
||||||
|
std::cout << prngs[0] << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate(uint64_t limit, uint64_t limit_seconds);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
|
||||||
|
std::vector<PRNG> prngs;
|
||||||
|
|
||||||
|
std::mutex stats_mutex;
|
||||||
|
TimePoint last_stats_report_time;
|
||||||
|
|
||||||
|
// sfen exporter
|
||||||
|
SfenWriter sfen_writer;
|
||||||
|
|
||||||
|
SynchronizedRegionLogger::Region out;
|
||||||
|
|
||||||
|
vector<Key> hash; // 64MB*sizeof(HASH_KEY) = 512MB
|
||||||
|
|
||||||
|
std::unique_ptr<OpeningBook> opening_book;
|
||||||
|
|
||||||
|
static void set_gensfen_search_limits();
|
||||||
|
|
||||||
|
void generate_worker(
|
||||||
|
Thread& th,
|
||||||
|
std::atomic<uint64_t>& counter,
|
||||||
|
uint64_t limit,
|
||||||
|
uint64_t limit_seconds);
|
||||||
|
|
||||||
|
bool was_seen_before(const Position& pos);
|
||||||
|
|
||||||
|
optional<int8_t> get_current_game_result(
|
||||||
|
Position& pos,
|
||||||
|
const vector<int>& move_hist_scores) const;
|
||||||
|
|
||||||
|
vector<uint8_t> generate_random_move_flags(PRNG& prng);
|
||||||
|
|
||||||
|
optional<Move> choose_random_move(
|
||||||
|
PRNG& prng,
|
||||||
|
Position& pos,
|
||||||
|
std::vector<uint8_t>& random_move_flag,
|
||||||
|
int ply,
|
||||||
|
int& random_move_c);
|
||||||
|
|
||||||
|
bool commit_psv(
|
||||||
|
Thread& th,
|
||||||
|
PSVector& sfens,
|
||||||
|
int8_t lastTurnIsWin,
|
||||||
|
std::atomic<uint64_t>& counter,
|
||||||
|
uint64_t limit,
|
||||||
|
Color result_color);
|
||||||
|
|
||||||
|
void initial_report();
|
||||||
|
void report(uint64_t done, uint64_t new_done);
|
||||||
|
|
||||||
|
void maybe_report(uint64_t done);
|
||||||
|
};
|
||||||
|
|
||||||
|
void TrainingDataGenerator::set_gensfen_search_limits()
|
||||||
|
{
|
||||||
|
// About Search::Limits
|
||||||
|
// Be careful because this member variable is global and affects other threads.
|
||||||
|
auto& limits = Search::Limits;
|
||||||
|
|
||||||
|
// Make the search equivalent to the "go infinite" command. (Because it is troublesome if time management is done)
|
||||||
|
limits.infinite = true;
|
||||||
|
|
||||||
|
// Since PV is an obstacle when displayed, erase it.
|
||||||
|
limits.silent = true;
|
||||||
|
|
||||||
|
// If you use this, it will be compared with the accumulated nodes of each thread. Therefore, do not use it.
|
||||||
|
limits.nodes = 0;
|
||||||
|
|
||||||
|
// depth is also processed by the one passed as an argument of Tools::search().
|
||||||
|
limits.depth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGenerator::generate(uint64_t limit, uint64_t limit_seconds)
|
||||||
|
{
|
||||||
|
last_stats_report_time = 0;
|
||||||
|
|
||||||
|
initial_report();
|
||||||
|
|
||||||
|
set_gensfen_search_limits();
|
||||||
|
|
||||||
|
std::atomic<uint64_t> counter{0};
|
||||||
|
Threads.execute_with_workers([&counter, limit, limit_seconds, this](Thread& th) {
|
||||||
|
generate_worker(th, counter, limit, limit_seconds);
|
||||||
|
});
|
||||||
|
Threads.wait_for_workers_finished();
|
||||||
|
|
||||||
|
sfen_writer.flush();
|
||||||
|
|
||||||
|
report(counter.load(), counter.load() % REPORT_STATS_EVERY + 1);
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGenerator::generate_worker(
|
||||||
|
Thread& th,
|
||||||
|
std::atomic<uint64_t>& counter,
|
||||||
|
uint64_t limit,
|
||||||
|
uint64_t limit_seconds)
|
||||||
|
{
|
||||||
|
// For the time being, it will be treated as a draw
|
||||||
|
// at the maximum number of steps to write.
|
||||||
|
// Maximum StateInfo + Search PV to advance to leaf buffer
|
||||||
|
std::vector<StateInfo, AlignedAllocator<StateInfo>> states(
|
||||||
|
params.write_maxply + MAX_PLY /* == search_depth_min + α */);
|
||||||
|
|
||||||
|
StateInfo si;
|
||||||
|
|
||||||
|
auto& prng = prngs[th.id()];
|
||||||
|
|
||||||
|
// end flag
|
||||||
|
bool quit = false;
|
||||||
|
|
||||||
|
const auto start_time = now();
|
||||||
|
|
||||||
|
const bool frc = Options["UCI_Chess960"];
|
||||||
|
// repeat until the specified number of times
|
||||||
|
while (!quit)
|
||||||
|
{
|
||||||
|
if (static_cast<uint64_t>(now() - start_time) / 1000 >= limit_seconds)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is necessary to set a dependent thread for Position.
|
||||||
|
// When parallelizing, Threads (since this is a vector<Thread*>,
|
||||||
|
// Do the same for up to Threads[0]...Threads[thread_num-1].
|
||||||
|
auto& pos = th.rootPos;
|
||||||
|
if (opening_book != nullptr)
|
||||||
|
{
|
||||||
|
auto& fen = opening_book->next_fen();
|
||||||
|
pos.set(fen, frc, &si, &th);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos.set(StartFEN, frc, &si, &th);
|
||||||
|
}
|
||||||
|
|
||||||
|
int resign_counter = 0;
|
||||||
|
bool should_resign = prng.rand(10) > 1;
|
||||||
|
// Vector for holding the sfens in the current simulated game.
|
||||||
|
PSVector packed_sfens;
|
||||||
|
packed_sfens.reserve(params.write_maxply + MAX_PLY);
|
||||||
|
|
||||||
|
// Precomputed flags. Used internally by choose_random_move.
|
||||||
|
vector<uint8_t> random_move_flag = generate_random_move_flags(prng);
|
||||||
|
|
||||||
|
// A counter that keeps track of the number of random moves
|
||||||
|
// When random_move_minply == -1, random moves are
|
||||||
|
// performed continuously, so use it at this time.
|
||||||
|
// Used internally by choose_random_move.
|
||||||
|
int actual_random_move_count = 0;
|
||||||
|
|
||||||
|
// Save history of move scores for adjudication
|
||||||
|
vector<int> move_hist_scores;
|
||||||
|
|
||||||
|
auto flush_psv = [&](int8_t result) {
|
||||||
|
quit = commit_psv(th, packed_sfens, result, counter, limit, pos.side_to_move());
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int ply = 0; ; ++ply)
|
||||||
|
{
|
||||||
|
// Current search depth
|
||||||
|
const int depth = params.search_depth_min + (int)prng.rand(params.search_depth_max - params.search_depth_min + 1);
|
||||||
|
|
||||||
|
// Starting search calls init_for_search
|
||||||
|
auto [search_value, search_pv] = Search::search(pos, depth, 1, params.nodes);
|
||||||
|
|
||||||
|
// This has to be performed after search because it needs to know
|
||||||
|
// rootMoves which are filled in init_for_search.
|
||||||
|
const auto result = get_current_game_result(pos, move_hist_scores);
|
||||||
|
if (result.has_value())
|
||||||
|
{
|
||||||
|
flush_psv(result.value());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always adjudivate by eval limit.
|
||||||
|
// Also because of this we don't have to check for TB/MATE scores
|
||||||
|
if (abs(search_value) >= params.eval_limit)
|
||||||
|
{
|
||||||
|
resign_counter++;
|
||||||
|
if ((should_resign && resign_counter >= 4) || abs(search_value) >= VALUE_KNOWN_WIN) {
|
||||||
|
flush_psv((search_value >= params.eval_limit) ? 1 : -1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resign_counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case there is no PV and the game was not ended here
|
||||||
|
// there is nothing we can do, we can't continue the game,
|
||||||
|
// we don't know the result, so discard this game.
|
||||||
|
if (search_pv.empty())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the move score for adjudication.
|
||||||
|
move_hist_scores.push_back(search_value);
|
||||||
|
|
||||||
|
// Discard stuff before write_minply is reached
|
||||||
|
// because it can harm training due to overfitting.
|
||||||
|
// Initial positions would be too common.
|
||||||
|
if (ply >= params.write_minply && !was_seen_before(pos))
|
||||||
|
{
|
||||||
|
auto& psv = packed_sfens.emplace_back();
|
||||||
|
|
||||||
|
// Here we only write the position data.
|
||||||
|
// Result is added after the whole game is done.
|
||||||
|
pos.sfen_pack(psv.sfen, pos.is_chess960());
|
||||||
|
|
||||||
|
psv.score = search_value;
|
||||||
|
psv.move = search_pv[0];
|
||||||
|
psv.gamePly = ply;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the next move according to best search result or random move.
|
||||||
|
auto random_move = choose_random_move(prng, pos, random_move_flag, ply, actual_random_move_count);
|
||||||
|
const Move next_move = random_move.has_value() ? *random_move : search_pv[0];
|
||||||
|
|
||||||
|
// We don't have the whole game yet, but it ended,
|
||||||
|
// so the writing process ends and the next game starts.
|
||||||
|
// This shouldn't really happen.
|
||||||
|
if (!is_ok(next_move))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do move.
|
||||||
|
pos.do_move(next_move, states[ply]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TrainingDataGenerator::was_seen_before(const Position& pos)
|
||||||
|
{
|
||||||
|
// Look into the position hashtable to see if the same
|
||||||
|
// position was seen before.
|
||||||
|
// This is a good heuristic to exlude already seen
|
||||||
|
// positions without many false positives.
|
||||||
|
auto key = pos.key();
|
||||||
|
auto hash_index = (size_t)(key & (GENSFEN_HASH_SIZE - 1));
|
||||||
|
auto old_key = hash[hash_index];
|
||||||
|
if (key == old_key)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Replace with the current key.
|
||||||
|
hash[hash_index] = key;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<int8_t> TrainingDataGenerator::get_current_game_result(
|
||||||
|
Position& pos,
|
||||||
|
const vector<int>& move_hist_scores) const
|
||||||
|
{
|
||||||
|
// Variables for draw adjudication.
|
||||||
|
// Todo: Make this as an option.
|
||||||
|
|
||||||
|
// start the adjudication when ply reaches this value
|
||||||
|
constexpr int adj_draw_ply = 80;
|
||||||
|
|
||||||
|
// 4 move scores for each side have to be checked
|
||||||
|
constexpr int adj_draw_cnt = 8;
|
||||||
|
|
||||||
|
// move score in CP
|
||||||
|
constexpr int adj_draw_score = 0;
|
||||||
|
|
||||||
|
// For the time being, it will be treated as a
|
||||||
|
// draw at the maximum number of steps to write.
|
||||||
|
const int ply = move_hist_scores.size();
|
||||||
|
|
||||||
|
// has it reached the max length or is a draw by fifty-move rule
|
||||||
|
// or by 3-fold repetition
|
||||||
|
if (ply >= params.write_maxply
|
||||||
|
|| pos.is_fifty_move_draw()
|
||||||
|
|| pos.is_three_fold_repetition())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos.this_thread()->rootMoves.empty())
|
||||||
|
{
|
||||||
|
// If there is no legal move
|
||||||
|
return pos.checkers()
|
||||||
|
? -1 /* mate */
|
||||||
|
: 0 /* stalemate */;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjudicate game to a draw if the last 4 scores of each engine is 0.
|
||||||
|
if (params.detect_draw_by_consecutive_low_score)
|
||||||
|
{
|
||||||
|
if (ply >= adj_draw_ply)
|
||||||
|
{
|
||||||
|
int num_cons_plies_within_draw_score = 0;
|
||||||
|
bool is_adj_draw = false;
|
||||||
|
|
||||||
|
for (auto it = move_hist_scores.rbegin();
|
||||||
|
it != move_hist_scores.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (abs(*it) <= adj_draw_score)
|
||||||
|
{
|
||||||
|
num_cons_plies_within_draw_score++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Draw scores must happen on consecutive plies
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_cons_plies_within_draw_score >= adj_draw_cnt)
|
||||||
|
{
|
||||||
|
is_adj_draw = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_adj_draw)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw by insufficient mating material
|
||||||
|
if (params.detect_draw_by_insufficient_mating_material)
|
||||||
|
{
|
||||||
|
if (pos.count<ALL_PIECES>() <= 4)
|
||||||
|
{
|
||||||
|
int num_pieces = pos.count<ALL_PIECES>();
|
||||||
|
|
||||||
|
// (1) KvK
|
||||||
|
if (num_pieces == 2)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (2) KvK + 1 minor piece
|
||||||
|
if (num_pieces == 3)
|
||||||
|
{
|
||||||
|
int minor_pc = pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE) +
|
||||||
|
pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK);
|
||||||
|
if (minor_pc == 1)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (3) KBvKB, bishops of the same color
|
||||||
|
else if (num_pieces == 4)
|
||||||
|
{
|
||||||
|
if (pos.count<BISHOP>(WHITE) == 1 && pos.count<BISHOP>(BLACK) == 1)
|
||||||
|
{
|
||||||
|
// Color of bishops is black.
|
||||||
|
if ((pos.pieces(WHITE, BISHOP) & DarkSquares)
|
||||||
|
&& (pos.pieces(BLACK, BISHOP) & DarkSquares))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Color of bishops is white.
|
||||||
|
if ((pos.pieces(WHITE, BISHOP) & ~DarkSquares)
|
||||||
|
&& (pos.pieces(BLACK, BISHOP) & ~DarkSquares))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<uint8_t> TrainingDataGenerator::generate_random_move_flags(PRNG& prng)
|
||||||
|
{
|
||||||
|
vector<uint8_t> random_move_flag;
|
||||||
|
|
||||||
|
// Depending on random move selection parameters setup
|
||||||
|
// the array of flags that indicates whether a random move
|
||||||
|
// be taken at a given ply.
|
||||||
|
|
||||||
|
// Make an array like a[0] = 0 ,a[1] = 1, ...
|
||||||
|
// Fisher-Yates shuffle and take out the first N items.
|
||||||
|
// Actually, I only want N pieces, so I only need
|
||||||
|
// to shuffle the first N pieces with Fisher-Yates.
|
||||||
|
|
||||||
|
vector<int> a;
|
||||||
|
a.reserve((size_t)params.random_move_maxply);
|
||||||
|
|
||||||
|
// random_move_minply ,random_move_maxply is specified by 1 origin,
|
||||||
|
// Note that we are handling 0 origin here.
|
||||||
|
for (int i = std::max(params.random_move_minply - 1, 0); i < params.random_move_maxply; ++i)
|
||||||
|
{
|
||||||
|
a.push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of Apery random move, insert() may be called random_move_count times.
|
||||||
|
// Reserve only the size considering it.
|
||||||
|
random_move_flag.resize((size_t)params.random_move_maxply + params.random_move_count);
|
||||||
|
|
||||||
|
// A random move that exceeds the size() of a[] cannot be applied, so limit it.
|
||||||
|
for (int i = 0; i < std::min(params.random_move_count, (int)a.size()); ++i)
|
||||||
|
{
|
||||||
|
swap(a[i], a[prng.rand((uint64_t)a.size() - i) + i]);
|
||||||
|
random_move_flag[a[i]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return random_move_flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<Move> TrainingDataGenerator::choose_random_move(
|
||||||
|
PRNG& prng,
|
||||||
|
Position& pos,
|
||||||
|
std::vector<uint8_t>& random_move_flag,
|
||||||
|
int ply,
|
||||||
|
int& random_move_c)
|
||||||
|
{
|
||||||
|
optional<Move> random_move;
|
||||||
|
|
||||||
|
// Randomly choose one from legal move
|
||||||
|
if (
|
||||||
|
// 1. Random move of random_move_count times from random_move_minply to random_move_maxply
|
||||||
|
(params.random_move_minply != -1 && ply < (int)random_move_flag.size() && random_move_flag[ply]) ||
|
||||||
|
// 2. A mode to perform random move of random_move_count times after leaving the startpos
|
||||||
|
(params.random_move_minply == -1 && random_move_c < params.random_move_count))
|
||||||
|
{
|
||||||
|
++random_move_c;
|
||||||
|
|
||||||
|
// It's not a mate, so there should be one legal move...
|
||||||
|
if (params.random_multi_pv == 0)
|
||||||
|
{
|
||||||
|
// Normal random move
|
||||||
|
MoveList<LEGAL> list(pos);
|
||||||
|
|
||||||
|
// I don't really know the goodness and badness of making this the Apery method.
|
||||||
|
if (params.random_move_like_apery == 0
|
||||||
|
|| prng.rand(params.random_move_like_apery) != 0)
|
||||||
|
{
|
||||||
|
// Normally one move from legal move
|
||||||
|
random_move = list.at((size_t)prng.rand((uint64_t)list.size()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if you can move the king, move the king
|
||||||
|
Move moves[8]; // Near 8
|
||||||
|
Move* p = &moves[0];
|
||||||
|
for (auto& m : list)
|
||||||
|
{
|
||||||
|
if (type_of(pos.moved_piece(m)) == KING)
|
||||||
|
{
|
||||||
|
*(p++) = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t n = p - &moves[0];
|
||||||
|
if (n != 0)
|
||||||
|
{
|
||||||
|
// move to move the king
|
||||||
|
random_move = moves[prng.rand(n)];
|
||||||
|
|
||||||
|
// In Apery method, at this time there is a 1/2 chance
|
||||||
|
// that the opponent will also move randomly
|
||||||
|
if (prng.rand(2) == 0)
|
||||||
|
{
|
||||||
|
// Is it a simple hack to add a "1" next to random_move_flag[ply]?
|
||||||
|
random_move_flag.insert(random_move_flag.begin() + ply + 1, 1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Normally one move from legal move
|
||||||
|
random_move = list.at((size_t)prng.rand((uint64_t)list.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Search::search(pos, params.random_multi_pv_depth, params.random_multi_pv, params.random_multi_pv_nodes);
|
||||||
|
|
||||||
|
// Select one from the top N hands of root Moves
|
||||||
|
auto& rm = pos.this_thread()->rootMoves;
|
||||||
|
|
||||||
|
uint64_t s = min((uint64_t)rm.size(), (uint64_t)params.random_multi_pv);
|
||||||
|
for (uint64_t i = 1; i < s; ++i)
|
||||||
|
{
|
||||||
|
// The difference from the evaluation value of rm[0] must
|
||||||
|
// be within the range of random_multi_pv_diff.
|
||||||
|
// It can be assumed that rm[x].score is arranged in descending order.
|
||||||
|
if (rm[0].score > rm[i].score + params.random_multi_pv_diff)
|
||||||
|
{
|
||||||
|
s = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
random_move = rm[prng.rand(s)].pv[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return random_move;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out the phases loaded in sfens to a file.
|
||||||
|
// result: win/loss in the next phase after the final phase in sfens
|
||||||
|
// 1 when winning. -1 when losing. Pass 0 for a draw.
|
||||||
|
// Return value: true if the specified number of
|
||||||
|
// sfens has already been reached and the process ends.
|
||||||
|
bool TrainingDataGenerator::commit_psv(
|
||||||
|
Thread& th,
|
||||||
|
PSVector& sfens,
|
||||||
|
int8_t result,
|
||||||
|
std::atomic<uint64_t>& counter,
|
||||||
|
uint64_t limit,
|
||||||
|
Color result_color)
|
||||||
|
{
|
||||||
|
if (!params.write_out_draw_game_in_training_data_generation && result == 0)
|
||||||
|
{
|
||||||
|
// We didn't write anything so why quit.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto side_to_move_from_sfen = [](auto& sfen){
|
||||||
|
return (Color)(sfen.sfen.data[0] & 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// From the final stage (one step before) to the first stage, give information on the outcome of the game for each stage.
|
||||||
|
// The phases stored in sfens are assumed to be continuous (in order).
|
||||||
|
for (auto it = sfens.rbegin(); it != sfens.rend(); ++it)
|
||||||
|
{
|
||||||
|
// The side to move is packed as the lowest bit of the first byte
|
||||||
|
const Color side_to_move = side_to_move_from_sfen(*it);
|
||||||
|
it->game_result = side_to_move == result_color ? result : -result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool frc = th.rootPos.is_chess960();
|
||||||
|
// Write sfens in move order to make potential compression easier
|
||||||
|
for (auto& sfen : sfens)
|
||||||
|
{
|
||||||
|
// Skip positions with castling bestmove in FRC so that we don't
|
||||||
|
// need to support it in the trainer.
|
||||||
|
if (frc && type_of((Move)sfen.move) == CASTLING)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if there is already enough data generated.
|
||||||
|
const auto iter = counter.fetch_add(1);
|
||||||
|
if (iter >= limit)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// because `iter` was done, now we do one more
|
||||||
|
maybe_report(iter + 1);
|
||||||
|
|
||||||
|
// Write out one sfen.
|
||||||
|
sfen_writer.write(th.id(), sfen);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGenerator::initial_report()
|
||||||
|
{
|
||||||
|
out = sync_region_cout.new_region();
|
||||||
|
|
||||||
|
const auto now_time = now();
|
||||||
|
|
||||||
|
out
|
||||||
|
<< '\n'
|
||||||
|
<< 0 << " sfens, "
|
||||||
|
<< "at " << now_string() << endl;
|
||||||
|
|
||||||
|
last_stats_report_time = now_time;
|
||||||
|
|
||||||
|
out = sync_region_cout.new_region();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGenerator::report(uint64_t done, uint64_t new_done)
|
||||||
|
{
|
||||||
|
const auto now_time = now();
|
||||||
|
const TimePoint elapsed = now_time - last_stats_report_time + 1;
|
||||||
|
|
||||||
|
out
|
||||||
|
<< '\n'
|
||||||
|
<< done << " sfens, "
|
||||||
|
<< new_done * 1000 / elapsed << " sfens/second, "
|
||||||
|
<< "at " << now_string() << endl;
|
||||||
|
|
||||||
|
last_stats_report_time = now_time;
|
||||||
|
|
||||||
|
out = sync_region_cout.new_region();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGenerator::maybe_report(uint64_t done)
|
||||||
|
{
|
||||||
|
if (done % REPORT_DOT_EVERY == 0)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(stats_mutex);
|
||||||
|
|
||||||
|
if (last_stats_report_time == 0)
|
||||||
|
{
|
||||||
|
last_stats_report_time = now();
|
||||||
|
out = sync_region_cout.new_region();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done != 0)
|
||||||
|
{
|
||||||
|
out << '.';
|
||||||
|
|
||||||
|
if (done % REPORT_STATS_EVERY == 0)
|
||||||
|
{
|
||||||
|
report(done, REPORT_STATS_EVERY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command to generate a game record
|
||||||
|
void generate_training_data(istringstream& is)
|
||||||
|
{
|
||||||
|
// Number of generated game records default = 8 billion phases (Ponanza specification)
|
||||||
|
uint64_t loop_max = 8000000000UL;
|
||||||
|
uint64_t time_max = 8000000000UL;
|
||||||
|
|
||||||
|
TrainingDataGenerator::Params params;
|
||||||
|
|
||||||
|
// Add a random number to the end of the file name.
|
||||||
|
bool random_file_name = false;
|
||||||
|
std::string sfen_format = "binpack";
|
||||||
|
|
||||||
|
string token;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
token = "";
|
||||||
|
is >> token;
|
||||||
|
if (token == "")
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (token == "depth")
|
||||||
|
{
|
||||||
|
is >> params.search_depth_min;
|
||||||
|
params.search_depth_max = params.search_depth_min;
|
||||||
|
}
|
||||||
|
else if (token == "min_depth")
|
||||||
|
is >> params.search_depth_min;
|
||||||
|
else if (token == "max_depth")
|
||||||
|
is >> params.search_depth_max;
|
||||||
|
else if (token == "nodes")
|
||||||
|
is >> params.nodes;
|
||||||
|
else if (token == "count")
|
||||||
|
is >> loop_max;
|
||||||
|
else if (token == "max_time_seconds")
|
||||||
|
is >> time_max;
|
||||||
|
else if (token == "max_time_minutes")
|
||||||
|
{
|
||||||
|
is >> time_max;
|
||||||
|
time_max *= 60;
|
||||||
|
}
|
||||||
|
else if (token == "max_time_hours")
|
||||||
|
{
|
||||||
|
is >> time_max;
|
||||||
|
time_max *= 3600;
|
||||||
|
}
|
||||||
|
else if (token == "output_file_name")
|
||||||
|
is >> params.output_file_name;
|
||||||
|
else if (token == "eval_limit")
|
||||||
|
is >> params.eval_limit;
|
||||||
|
else if (token == "random_move_min_ply")
|
||||||
|
is >> params.random_move_minply;
|
||||||
|
else if (token == "random_move_max_ply")
|
||||||
|
is >> params.random_move_maxply;
|
||||||
|
else if (token == "random_move_count")
|
||||||
|
is >> params.random_move_count;
|
||||||
|
else if (token == "random_move_like_apery")
|
||||||
|
is >> params.random_move_like_apery;
|
||||||
|
else if (token == "random_multi_pv")
|
||||||
|
is >> params.random_multi_pv;
|
||||||
|
else if (token == "random_multi_pv_diff")
|
||||||
|
is >> params.random_multi_pv_diff;
|
||||||
|
else if (token == "random_multi_pv_depth")
|
||||||
|
is >> params.random_multi_pv_depth;
|
||||||
|
else if (token == "random_multi_pv_nodes")
|
||||||
|
is >> params.random_multi_pv_nodes;
|
||||||
|
else if (token == "write_min_ply")
|
||||||
|
is >> params.write_minply;
|
||||||
|
else if (token == "write_max_ply")
|
||||||
|
is >> params.write_maxply;
|
||||||
|
else if (token == "save_every")
|
||||||
|
is >> params.save_every;
|
||||||
|
else if (token == "book")
|
||||||
|
is >> params.book;
|
||||||
|
else if (token == "random_file_name")
|
||||||
|
is >> random_file_name;
|
||||||
|
else if (token == "keep_draws")
|
||||||
|
is >> params.write_out_draw_game_in_training_data_generation;
|
||||||
|
else if (token == "adjudicate_draws_by_score")
|
||||||
|
is >> params.detect_draw_by_consecutive_low_score;
|
||||||
|
else if (token == "adjudicate_draws_by_insufficient_material")
|
||||||
|
is >> params.detect_draw_by_insufficient_mating_material;
|
||||||
|
else if (token == "data_format")
|
||||||
|
is >> sfen_format;
|
||||||
|
else if (token == "seed")
|
||||||
|
is >> params.seed;
|
||||||
|
else if (token == "set_recommended_uci_options")
|
||||||
|
{
|
||||||
|
UCI::setoption("Skill Level", "20");
|
||||||
|
UCI::setoption("UCI_LimitStrength", "false");
|
||||||
|
UCI::setoption("PruneAtShallowDepth", "false");
|
||||||
|
UCI::setoption("EnableTranspositionTable", "true");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "ERROR: Unknown option " << token << ". Exiting...\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sfen_format.empty())
|
||||||
|
{
|
||||||
|
if (sfen_format == "bin")
|
||||||
|
params.sfen_format = SfenOutputType::Bin;
|
||||||
|
else if (sfen_format == "binpack")
|
||||||
|
params.sfen_format = SfenOutputType::Binpack;
|
||||||
|
else
|
||||||
|
cout << "WARNING: Unknown sfen format `" << sfen_format << "`. Using bin\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (random_file_name)
|
||||||
|
{
|
||||||
|
// Give a random number to output_file_name at this point.
|
||||||
|
// Do not use std::random_device(). Because it always the same integers on MinGW.
|
||||||
|
PRNG r(params.seed);
|
||||||
|
|
||||||
|
// Just in case, reassign the random numbers.
|
||||||
|
for (int i = 0; i < 10; ++i)
|
||||||
|
r.rand(1);
|
||||||
|
|
||||||
|
auto to_hex = [](uint64_t u) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << u;
|
||||||
|
return ss.str();
|
||||||
|
};
|
||||||
|
|
||||||
|
// I don't want to wear 64bit numbers by accident, so I'next_move going to make a 64bit number 2 just in case.
|
||||||
|
params.output_file_name += "_" + to_hex(r.rand<uint64_t>()) + to_hex(r.rand<uint64_t>());
|
||||||
|
}
|
||||||
|
|
||||||
|
params.enforce_constraints();
|
||||||
|
|
||||||
|
std::cout << "INFO: Executing generate_training_data command\n";
|
||||||
|
|
||||||
|
std::cout << "INFO: Parameters:\n";
|
||||||
|
std::cout
|
||||||
|
<< " - search_depth_min = " << params.search_depth_min << endl
|
||||||
|
<< " - search_depth_max = " << params.search_depth_max << endl
|
||||||
|
<< " - nodes = " << params.nodes << endl
|
||||||
|
<< " - count = " << loop_max << endl
|
||||||
|
<< " - max_time_seconds = " << time_max << endl
|
||||||
|
<< " - eval_limit = " << params.eval_limit << endl
|
||||||
|
<< " - num threads (UCI) = " << params.num_threads << endl
|
||||||
|
<< " - random_move_min_ply = " << params.random_move_minply << endl
|
||||||
|
<< " - random_move_max_ply = " << params.random_move_maxply << endl
|
||||||
|
<< " - random_move_count = " << params.random_move_count << endl
|
||||||
|
<< " - random_move_like_apery = " << params.random_move_like_apery << endl
|
||||||
|
<< " - random_multi_pv = " << params.random_multi_pv << endl
|
||||||
|
<< " - random_multi_pv_diff = " << params.random_multi_pv_diff << endl
|
||||||
|
<< " - random_multi_pv_depth = " << params.random_multi_pv_depth << endl
|
||||||
|
<< " - random_multi_pv_nodes = " << params.random_multi_pv_nodes << endl
|
||||||
|
<< " - write_min_ply = " << params.write_minply << endl
|
||||||
|
<< " - write_max_ply = " << params.write_maxply << endl
|
||||||
|
<< " - book = " << params.book << endl
|
||||||
|
<< " - output_file_name = " << params.output_file_name << endl
|
||||||
|
<< " - save_every = " << params.save_every << endl
|
||||||
|
<< " - random_file_name = " << random_file_name << endl
|
||||||
|
<< " - write_drawn_games = " << params.write_out_draw_game_in_training_data_generation << endl
|
||||||
|
<< " - draw by low score = " << params.detect_draw_by_consecutive_low_score << endl
|
||||||
|
<< " - draw by insuff. mat. = " << params.detect_draw_by_insufficient_mating_material << endl;
|
||||||
|
|
||||||
|
// Show if the training data generator uses NNUE.
|
||||||
|
Eval::NNUE::verify();
|
||||||
|
|
||||||
|
Threads.main()->ponder = false;
|
||||||
|
|
||||||
|
TrainingDataGenerator gensfen(params);
|
||||||
|
gensfen.generate(loop_max, time_max);
|
||||||
|
|
||||||
|
std::cout << "INFO: generate_training_data finished." << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef _GENSFEN_H_
|
||||||
|
#define _GENSFEN_H_
|
||||||
|
|
||||||
|
#include "position.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
// Automatic generation of teacher position
|
||||||
|
void generate_training_data(std::istringstream& is);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,498 @@
|
|||||||
|
#include "training_data_generator_nonpv.h"
|
||||||
|
|
||||||
|
#include "sfen_writer.h"
|
||||||
|
#include "packed_sfen.h"
|
||||||
|
#include "opening_book.h"
|
||||||
|
|
||||||
|
#include "misc.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "tt.h"
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
|
#include "extra/nnue_data_binpack_format.h"
|
||||||
|
|
||||||
|
#include "nnue/evaluate_nnue.h"
|
||||||
|
|
||||||
|
#include "syzygy/tbprobe.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <climits>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <limits>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <random>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace Stockfish::Tools
|
||||||
|
{
|
||||||
|
// Class to generate sfen with multiple threads
|
||||||
|
struct TrainingDataGeneratorNonPv
|
||||||
|
{
|
||||||
|
struct Params
|
||||||
|
{
|
||||||
|
// The depth for search on the fens gathered during exploration
|
||||||
|
int search_depth = 3;
|
||||||
|
|
||||||
|
// the min/max number of nodes to use for exploration per ply
|
||||||
|
int exploration_min_nodes = 5000;
|
||||||
|
int exploration_max_nodes = 15000;
|
||||||
|
|
||||||
|
// The pct of positions explored that are saved for rescoring
|
||||||
|
float exploration_save_rate = 0.01;
|
||||||
|
|
||||||
|
// Upper limit of evaluation value of generated situation
|
||||||
|
int eval_limit = 4000;
|
||||||
|
|
||||||
|
// the upper limit on evaluation during exploration selfplay
|
||||||
|
int exploration_eval_limit = 4000;
|
||||||
|
|
||||||
|
int exploration_max_ply = 200;
|
||||||
|
|
||||||
|
int exploration_min_pieces = 8;
|
||||||
|
|
||||||
|
std::string output_file_name = "training_data_nonpv";
|
||||||
|
|
||||||
|
SfenOutputType sfen_format = SfenOutputType::Binpack;
|
||||||
|
|
||||||
|
std::string seed;
|
||||||
|
|
||||||
|
int num_threads;
|
||||||
|
|
||||||
|
std::string book;
|
||||||
|
|
||||||
|
bool smart_fen_skipping = false;
|
||||||
|
|
||||||
|
void enforce_constraints()
|
||||||
|
{
|
||||||
|
// Limit the maximum to a one-stop score. (Otherwise you might not end the loop)
|
||||||
|
eval_limit = std::min(eval_limit, (int)mate_in(2));
|
||||||
|
exploration_eval_limit = std::min(eval_limit, (int)mate_in(2));
|
||||||
|
exploration_min_nodes = std::max(100, exploration_min_nodes);
|
||||||
|
exploration_max_nodes = std::max(exploration_min_nodes, exploration_max_nodes);
|
||||||
|
|
||||||
|
num_threads = Options["Threads"];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint64_t REPORT_DOT_EVERY = 5000;
|
||||||
|
static constexpr uint64_t REPORT_STATS_EVERY = 200000;
|
||||||
|
static_assert(REPORT_STATS_EVERY % REPORT_DOT_EVERY == 0);
|
||||||
|
|
||||||
|
TrainingDataGeneratorNonPv(
|
||||||
|
const Params& prm
|
||||||
|
) :
|
||||||
|
params(prm),
|
||||||
|
prng(prm.seed),
|
||||||
|
sfen_writer(prm.output_file_name, prm.num_threads, std::numeric_limits<uint64_t>::max(), prm.sfen_format)
|
||||||
|
{
|
||||||
|
if (!prm.book.empty())
|
||||||
|
{
|
||||||
|
opening_book = open_opening_book(prm.book, prng);
|
||||||
|
if (opening_book == nullptr)
|
||||||
|
{
|
||||||
|
std::cout << "WARNING: Failed to open opening book " << prm.book << ". Falling back to startpos.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output seed to veryfy by the user if it's not identical by chance.
|
||||||
|
std::cout << prng << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate(uint64_t limit);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params params;
|
||||||
|
|
||||||
|
PRNG prng;
|
||||||
|
|
||||||
|
std::mutex stats_mutex;
|
||||||
|
TimePoint last_stats_report_time;
|
||||||
|
|
||||||
|
// sfen exporter
|
||||||
|
SfenWriter sfen_writer;
|
||||||
|
|
||||||
|
SynchronizedRegionLogger::Region out;
|
||||||
|
|
||||||
|
std::unique_ptr<OpeningBook> opening_book;
|
||||||
|
|
||||||
|
static void set_gensfen_search_limits();
|
||||||
|
|
||||||
|
void generate_worker(
|
||||||
|
Thread& th,
|
||||||
|
std::atomic<uint64_t>& counter,
|
||||||
|
uint64_t limit);
|
||||||
|
|
||||||
|
bool commit_psv(
|
||||||
|
Thread& th,
|
||||||
|
PSVector& sfens,
|
||||||
|
std::atomic<uint64_t>& counter,
|
||||||
|
uint64_t limit);
|
||||||
|
|
||||||
|
PSVector do_exploration(
|
||||||
|
Thread& th,
|
||||||
|
int count);
|
||||||
|
|
||||||
|
void report(uint64_t done, uint64_t new_done);
|
||||||
|
|
||||||
|
void maybe_report(uint64_t done);
|
||||||
|
};
|
||||||
|
|
||||||
|
void TrainingDataGeneratorNonPv::set_gensfen_search_limits()
|
||||||
|
{
|
||||||
|
// About Search::Limits
|
||||||
|
// Be careful because this member variable is global and affects other threads.
|
||||||
|
auto& limits = Search::Limits;
|
||||||
|
|
||||||
|
// Make the search equivalent to the "go infinite" command. (Because it is troublesome if time management is done)
|
||||||
|
limits.infinite = true;
|
||||||
|
|
||||||
|
// Since PV is an obstacle when displayed, erase it.
|
||||||
|
limits.silent = true;
|
||||||
|
|
||||||
|
// If you use this, it will be compared with the accumulated nodes of each thread. Therefore, do not use it.
|
||||||
|
limits.nodes = 0;
|
||||||
|
|
||||||
|
// depth is also processed by the one passed as an argument of Tools::search().
|
||||||
|
limits.depth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGeneratorNonPv::generate(uint64_t limit)
|
||||||
|
{
|
||||||
|
last_stats_report_time = 0;
|
||||||
|
|
||||||
|
set_gensfen_search_limits();
|
||||||
|
|
||||||
|
std::atomic<uint64_t> counter{0};
|
||||||
|
Threads.execute_with_workers([&counter, limit, this](Thread& th) {
|
||||||
|
generate_worker(th, counter, limit);
|
||||||
|
});
|
||||||
|
Threads.wait_for_workers_finished();
|
||||||
|
|
||||||
|
sfen_writer.flush();
|
||||||
|
|
||||||
|
if (limit % REPORT_STATS_EVERY != 0)
|
||||||
|
{
|
||||||
|
report(limit, limit % REPORT_STATS_EVERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
PSVector TrainingDataGeneratorNonPv::do_exploration(
|
||||||
|
Thread& th,
|
||||||
|
int count)
|
||||||
|
{
|
||||||
|
constexpr int max_depth = 30;
|
||||||
|
|
||||||
|
PSVector psv;
|
||||||
|
|
||||||
|
std::vector<StateInfo, AlignedAllocator<StateInfo>> states(
|
||||||
|
max_depth + MAX_PLY /* == search_depth_min + α */);
|
||||||
|
|
||||||
|
th.set_eval_callback([this, &psv](Position& pos) {
|
||||||
|
if ((double)prng.rand<uint64_t>() / std::numeric_limits<uint64_t>::max() < params.exploration_save_rate)
|
||||||
|
{
|
||||||
|
psv.emplace_back();
|
||||||
|
pos.sfen_pack(psv.back().sfen, pos.is_chess960());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto& pos = th.rootPos;
|
||||||
|
StateInfo si;
|
||||||
|
|
||||||
|
const bool frc = Options["UCI_Chess960"];
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (opening_book != nullptr)
|
||||||
|
{
|
||||||
|
auto& fen = opening_book->next_fen();
|
||||||
|
pos.set(fen, frc, &si, &th);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos.set(StartFEN, frc, &si, &th);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int ply = 0; ply < params.exploration_max_ply; ++ply)
|
||||||
|
{
|
||||||
|
auto nodes = prng.rand(params.exploration_max_nodes - params.exploration_min_nodes + 1) + params.exploration_min_nodes;
|
||||||
|
|
||||||
|
auto [search_value, search_pv] = Search::search(pos, max_depth, 1, nodes);
|
||||||
|
|
||||||
|
if (search_pv.empty())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::abs(search_value) > params.exploration_eval_limit)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos.do_move(search_pv[0], states[ply]);
|
||||||
|
|
||||||
|
if (popcount(pos.pieces()) < params.exploration_min_pieces)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th.clear_eval_callback();
|
||||||
|
|
||||||
|
return psv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGeneratorNonPv::generate_worker(
|
||||||
|
Thread& th,
|
||||||
|
std::atomic<uint64_t>& counter,
|
||||||
|
uint64_t limit)
|
||||||
|
{
|
||||||
|
constexpr int exploration_batch_size = 1;
|
||||||
|
|
||||||
|
StateInfo si;
|
||||||
|
|
||||||
|
PSVector psv;
|
||||||
|
|
||||||
|
// end flag
|
||||||
|
bool quit = false;
|
||||||
|
|
||||||
|
const bool frc = Options["UCI_Chess960"];
|
||||||
|
// repeat until the specified number of times
|
||||||
|
while (!quit)
|
||||||
|
{
|
||||||
|
// It is necessary to set a dependent thread for Position.
|
||||||
|
// When parallelizing, Threads (since this is a vector<Thread*>,
|
||||||
|
// Do the same for up to Threads[0]...Threads[thread_num-1].
|
||||||
|
auto& pos = th.rootPos;
|
||||||
|
|
||||||
|
auto packed_sfens = do_exploration(th, exploration_batch_size);
|
||||||
|
psv.clear();
|
||||||
|
|
||||||
|
for (auto& ps : packed_sfens)
|
||||||
|
{
|
||||||
|
pos.set_from_packed_sfen(ps.sfen, &si, &th, frc);
|
||||||
|
pos.state()->rule50 = 0;
|
||||||
|
|
||||||
|
if (params.smart_fen_skipping && pos.checkers())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [search_value, search_pv] = Search::search(pos, params.search_depth, 1);
|
||||||
|
|
||||||
|
if (search_pv.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::abs(search_value) > params.eval_limit)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.smart_fen_skipping && pos.capture_or_promotion(search_pv[0]))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& new_ps = psv.emplace_back();
|
||||||
|
pos.sfen_pack(new_ps.sfen, pos.is_chess960());
|
||||||
|
new_ps.score = search_value;
|
||||||
|
new_ps.move = search_pv[0];
|
||||||
|
new_ps.gamePly = 1;
|
||||||
|
new_ps.game_result = 0;
|
||||||
|
new_ps.padding = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
quit = commit_psv(th, psv, counter, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out the phases loaded in sfens to a file.
|
||||||
|
// result: win/loss in the next phase after the final phase in sfens
|
||||||
|
// 1 when winning. -1 when losing. Pass 0 for a draw.
|
||||||
|
// Return value: true if the specified number of
|
||||||
|
// sfens has already been reached and the process ends.
|
||||||
|
bool TrainingDataGeneratorNonPv::commit_psv(
|
||||||
|
Thread& th,
|
||||||
|
PSVector& sfens,
|
||||||
|
std::atomic<uint64_t>& counter,
|
||||||
|
uint64_t limit)
|
||||||
|
{
|
||||||
|
const bool frc = th.rootPos.is_chess960();
|
||||||
|
// Write sfens in move order to make potential compression easier
|
||||||
|
for (auto& sfen : sfens)
|
||||||
|
{
|
||||||
|
// Skip positions with castling bestmove in FRC so that we don't
|
||||||
|
// need to support it in the trainer.
|
||||||
|
if (frc && type_of((Move)sfen.move) == CASTLING)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if there is already enough data generated.
|
||||||
|
const auto iter = counter.fetch_add(1);
|
||||||
|
if (iter >= limit)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// because `iter` was done, now we do one more
|
||||||
|
maybe_report(iter + 1);
|
||||||
|
|
||||||
|
// Write out one sfen.
|
||||||
|
sfen_writer.write(th.id(), sfen);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGeneratorNonPv::report(uint64_t done, uint64_t new_done)
|
||||||
|
{
|
||||||
|
const auto now_time = now();
|
||||||
|
const TimePoint elapsed = now_time - last_stats_report_time + 1;
|
||||||
|
|
||||||
|
out
|
||||||
|
<< endl
|
||||||
|
<< done << " sfens, "
|
||||||
|
<< new_done * 1000 / elapsed << " sfens/second, "
|
||||||
|
<< "at " << now_string() << sync_endl;
|
||||||
|
|
||||||
|
last_stats_report_time = now_time;
|
||||||
|
|
||||||
|
out = sync_region_cout.new_region();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrainingDataGeneratorNonPv::maybe_report(uint64_t done)
|
||||||
|
{
|
||||||
|
if (done % REPORT_DOT_EVERY == 0)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(stats_mutex);
|
||||||
|
|
||||||
|
if (last_stats_report_time == 0)
|
||||||
|
{
|
||||||
|
last_stats_report_time = now();
|
||||||
|
out = sync_region_cout.new_region();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done != 0)
|
||||||
|
{
|
||||||
|
out << '.';
|
||||||
|
|
||||||
|
if (done % REPORT_STATS_EVERY == 0)
|
||||||
|
{
|
||||||
|
report(done, REPORT_STATS_EVERY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command to generate a game record
|
||||||
|
void generate_training_data_nonpv(istringstream& is)
|
||||||
|
{
|
||||||
|
// Number of generated game records default = 8 billion phases (Ponanza specification)
|
||||||
|
TrainingDataGeneratorNonPv::Params params;
|
||||||
|
|
||||||
|
uint64_t count = 1'000'000;
|
||||||
|
|
||||||
|
// Add a random number to the end of the file name.
|
||||||
|
std::string sfen_format = "binpack";
|
||||||
|
|
||||||
|
string token;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
token = "";
|
||||||
|
is >> token;
|
||||||
|
if (token == "")
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (token == "depth")
|
||||||
|
is >> params.search_depth;
|
||||||
|
else if (token == "count")
|
||||||
|
is >> count;
|
||||||
|
else if (token == "output_file")
|
||||||
|
is >> params.output_file_name;
|
||||||
|
else if (token == "exploration_eval_limit")
|
||||||
|
is >> params.exploration_eval_limit;
|
||||||
|
else if (token == "eval_limit")
|
||||||
|
is >> params.eval_limit;
|
||||||
|
else if (token == "exploration_min_nodes")
|
||||||
|
is >> params.exploration_min_nodes;
|
||||||
|
else if (token == "exploration_max_nodes")
|
||||||
|
is >> params.exploration_max_nodes;
|
||||||
|
else if (token == "exploration_min_pieces")
|
||||||
|
is >> params.exploration_min_pieces;
|
||||||
|
else if (token == "exploration_save_rate")
|
||||||
|
is >> params.exploration_save_rate;
|
||||||
|
else if (token == "book")
|
||||||
|
is >> params.book;
|
||||||
|
else if (token == "data_format")
|
||||||
|
is >> sfen_format;
|
||||||
|
else if (token == "seed")
|
||||||
|
is >> params.seed;
|
||||||
|
else if (token == "smart_fen_skipping")
|
||||||
|
params.smart_fen_skipping = true;
|
||||||
|
else if (token == "set_recommended_uci_options")
|
||||||
|
{
|
||||||
|
UCI::setoption("Skill Level", "20");
|
||||||
|
UCI::setoption("UCI_LimitStrength", "false");
|
||||||
|
UCI::setoption("PruneAtShallowDepth", "false");
|
||||||
|
UCI::setoption("EnableTranspositionTable", "true");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "ERROR: Unknown option " << token << ". Exiting...\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sfen_format.empty())
|
||||||
|
{
|
||||||
|
if (sfen_format == "bin")
|
||||||
|
params.sfen_format = SfenOutputType::Bin;
|
||||||
|
else if (sfen_format == "binpack")
|
||||||
|
params.sfen_format = SfenOutputType::Binpack;
|
||||||
|
else
|
||||||
|
cout << "WARNING: Unknown sfen format `" << sfen_format << "`. Using bin\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
params.enforce_constraints();
|
||||||
|
|
||||||
|
std::cout << "INFO: Executing generate_training_data_nonpv command\n";
|
||||||
|
|
||||||
|
std::cout << "INFO: Parameters:\n";
|
||||||
|
std::cout
|
||||||
|
<< " - search_depth = " << params.search_depth << endl
|
||||||
|
<< " - output_file = " << params.output_file_name << endl
|
||||||
|
<< " - exploration_eval_limit = " << params.exploration_eval_limit << endl
|
||||||
|
<< " - eval_limit = " << params.eval_limit << endl
|
||||||
|
<< " - exploration_min_nodes = " << params.exploration_min_nodes << endl
|
||||||
|
<< " - exploration_max_nodes = " << params.exploration_max_nodes << endl
|
||||||
|
<< " - exploration_min_pieces = " << params.exploration_min_pieces << endl
|
||||||
|
<< " - exploration_save_rate = " << params.exploration_save_rate << endl
|
||||||
|
<< " - book = " << params.book << endl
|
||||||
|
<< " - data_format = " << sfen_format << endl
|
||||||
|
<< " - seed = " << params.seed << endl
|
||||||
|
<< " - count = " << count << endl;
|
||||||
|
|
||||||
|
// Show if the training data generator uses NNUE.
|
||||||
|
Eval::NNUE::verify();
|
||||||
|
|
||||||
|
Threads.main()->ponder = false;
|
||||||
|
|
||||||
|
TrainingDataGeneratorNonPv gensfen(params);
|
||||||
|
gensfen.generate(count);
|
||||||
|
|
||||||
|
std::cout << "INFO: generate_training_data_nonpv finished." << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef _GENSFEN_NONPV_H_
|
||||||
|
#define _GENSFEN_NONPV_H_
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
// Automatic generation of teacher position
|
||||||
|
void generate_training_data_nonpv(std::istringstream& is);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef _TRANSFORM_H_
|
||||||
|
#define _TRANSFORM_H_
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
|
||||||
|
void transform(std::istringstream& is);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
#include "validate_training_data.h"
|
||||||
|
|
||||||
|
#include "uci.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "tt.h"
|
||||||
|
|
||||||
|
#include "extra/nnue_data_binpack_format.h"
|
||||||
|
|
||||||
|
#include "nnue/evaluate_nnue.h"
|
||||||
|
|
||||||
|
#include "syzygy/tbprobe.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <list>
|
||||||
|
#include <cmath> // std::exp(),std::pow(),std::log()
|
||||||
|
#include <cstring> // memcpy()
|
||||||
|
#include <memory>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <chrono>
|
||||||
|
#include <random>
|
||||||
|
#include <regex>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
namespace sys = std::filesystem;
|
||||||
|
|
||||||
|
namespace Stockfish::Tools
|
||||||
|
{
|
||||||
|
static inline const std::string plain_extension = ".plain";
|
||||||
|
static inline const std::string bin_extension = ".bin";
|
||||||
|
static inline const std::string binpack_extension = ".binpack";
|
||||||
|
|
||||||
|
static bool file_exists(const std::string& name)
|
||||||
|
{
|
||||||
|
std::ifstream f(name);
|
||||||
|
return f.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ends_with(const std::string& lhs, const std::string& end)
|
||||||
|
{
|
||||||
|
if (end.size() > lhs.size()) return false;
|
||||||
|
|
||||||
|
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_validation_of_type(
|
||||||
|
const std::string& input_path,
|
||||||
|
const std::string& expected_input_extension)
|
||||||
|
{
|
||||||
|
return ends_with(input_path, expected_input_extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
using ValidateFunctionType = void(std::string inputPath);
|
||||||
|
|
||||||
|
static ValidateFunctionType* get_validate_function(const std::string& input_path)
|
||||||
|
{
|
||||||
|
if (is_validation_of_type(input_path, plain_extension))
|
||||||
|
return binpack::validatePlain;
|
||||||
|
|
||||||
|
if (is_validation_of_type(input_path, bin_extension))
|
||||||
|
return binpack::validateBin;
|
||||||
|
|
||||||
|
if (is_validation_of_type(input_path, binpack_extension))
|
||||||
|
return binpack::validateBinpack;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validate_training_data(const std::string& input_path)
|
||||||
|
{
|
||||||
|
if(!file_exists(input_path))
|
||||||
|
{
|
||||||
|
std::cerr << "Input file does not exist.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto func = get_validate_function(input_path);
|
||||||
|
if (func != nullptr)
|
||||||
|
{
|
||||||
|
func(input_path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Validation of files of this type is not supported.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validate_training_data(const std::vector<std::string>& args)
|
||||||
|
{
|
||||||
|
if (args.size() != 1)
|
||||||
|
{
|
||||||
|
std::cerr << "Invalid arguments.\n";
|
||||||
|
std::cerr << "Usage: validate in_path\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_training_data(args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void validate_training_data(istringstream& is)
|
||||||
|
{
|
||||||
|
std::vector<std::string> args;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::string token = "";
|
||||||
|
is >> token;
|
||||||
|
if (token == "")
|
||||||
|
break;
|
||||||
|
|
||||||
|
args.push_back(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_training_data(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef _VALIDATE_TRAINING_DATA_H_
|
||||||
|
#define _VALIDATE_TRAINING_DATA_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace Stockfish::Tools {
|
||||||
|
void validate_training_data(std::istringstream& is);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
+37
-20
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,29 +26,37 @@
|
|||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
TranspositionTable TT; // Our global transposition table
|
TranspositionTable TT; // Our global transposition table
|
||||||
|
|
||||||
|
bool TranspositionTable::enable_transposition_table = true;
|
||||||
|
|
||||||
/// TTEntry::save() populates the TTEntry with a new node's data, possibly
|
/// TTEntry::save() populates the TTEntry with a new node's data, possibly
|
||||||
/// overwriting an old position. Update is not atomic and can be racy.
|
/// overwriting an old position. Update is not atomic and can be racy.
|
||||||
|
|
||||||
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
|
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
|
||||||
|
|
||||||
|
if (!TranspositionTable::enable_transposition_table) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Preserve any existing move for the same position
|
// Preserve any existing move for the same position
|
||||||
if (m || (uint16_t)k != key16)
|
if (m || (uint16_t)k != key16)
|
||||||
move16 = (uint16_t)m;
|
move16 = (uint16_t)m;
|
||||||
|
|
||||||
// Overwrite less valuable entries
|
// Overwrite less valuable entries (cheapest checks first)
|
||||||
if ((uint16_t)k != key16
|
if ( b == BOUND_EXACT
|
||||||
|| d - DEPTH_OFFSET > depth8 - 4
|
|| (uint16_t)k != key16
|
||||||
|| b == BOUND_EXACT)
|
|| d - DEPTH_OFFSET + 2 * pv > depth8 - 4)
|
||||||
{
|
{
|
||||||
assert(d >= DEPTH_OFFSET);
|
assert(d > DEPTH_OFFSET);
|
||||||
|
assert(d < 256 + DEPTH_OFFSET);
|
||||||
|
|
||||||
key16 = (uint16_t)k;
|
key16 = (uint16_t)k;
|
||||||
|
depth8 = (uint8_t)(d - DEPTH_OFFSET);
|
||||||
|
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
|
||||||
value16 = (int16_t)v;
|
value16 = (int16_t)v;
|
||||||
eval16 = (int16_t)ev;
|
eval16 = (int16_t)ev;
|
||||||
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
|
|
||||||
depth8 = (uint8_t)(d - DEPTH_OFFSET);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,11 +69,12 @@ void TranspositionTable::resize(size_t mbSize) {
|
|||||||
|
|
||||||
Threads.main()->wait_for_search_finished();
|
Threads.main()->wait_for_search_finished();
|
||||||
|
|
||||||
aligned_ttmem_free(mem);
|
aligned_large_pages_free(table);
|
||||||
|
|
||||||
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
||||||
table = static_cast<Cluster*>(aligned_ttmem_alloc(clusterCount * sizeof(Cluster), mem));
|
|
||||||
if (!mem)
|
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
|
||||||
|
if (!table)
|
||||||
{
|
{
|
||||||
std::cerr << "Failed to allocate " << mbSize
|
std::cerr << "Failed to allocate " << mbSize
|
||||||
<< "MB for transposition table." << std::endl;
|
<< "MB for transposition table." << std::endl;
|
||||||
@@ -115,26 +124,32 @@ void TranspositionTable::clear() {
|
|||||||
|
|
||||||
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
||||||
|
|
||||||
|
if (!enable_transposition_table) {
|
||||||
|
found = false;
|
||||||
|
return first_entry(0);
|
||||||
|
}
|
||||||
|
|
||||||
TTEntry* const tte = first_entry(key);
|
TTEntry* const tte = first_entry(key);
|
||||||
const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
|
const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
|
||||||
|
|
||||||
for (int i = 0; i < ClusterSize; ++i)
|
for (int i = 0; i < ClusterSize; ++i)
|
||||||
if (!tte[i].key16 || tte[i].key16 == key16)
|
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].key16, &tte[i];
|
return found = (bool)tte[i].depth8, &tte[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find an entry to be replaced according to the replacement strategy
|
// Find an entry to be replaced according to the replacement strategy
|
||||||
TTEntry* replace = tte;
|
TTEntry* replace = tte;
|
||||||
for (int i = 1; i < ClusterSize; ++i)
|
for (int i = 1; i < ClusterSize; ++i)
|
||||||
// Due to our packed storage format for generation and its cyclic
|
// 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
|
// nature we add GENERATION_CYCLE (256 is the modulus, plus what
|
||||||
// lowest three bits from affecting the result) to calculate the entry
|
// is needed to keep the unrelated lowest n bits from affecting
|
||||||
// age correctly even after generation8 overflows into the next cycle.
|
// the result) to calculate the entry age correctly even after
|
||||||
if ( replace->depth8 - ((263 + generation8 - replace->genBound8) & 0xF8)
|
// generation8 overflows into the next cycle.
|
||||||
> tte[i].depth8 - ((263 + generation8 - tte[i].genBound8) & 0xF8))
|
if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK)
|
||||||
|
> tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK))
|
||||||
replace = &tte[i];
|
replace = &tte[i];
|
||||||
|
|
||||||
return found = false, replace;
|
return found = false, replace;
|
||||||
@@ -149,7 +164,9 @@ int TranspositionTable::hashfull() const {
|
|||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
for (int i = 0; i < 1000; ++i)
|
for (int i = 0; i < 1000; ++i)
|
||||||
for (int j = 0; j < ClusterSize; ++j)
|
for (int j = 0; j < ClusterSize; ++j)
|
||||||
cnt += (table[i].entry[j].genBound8 & 0xF8) == generation8;
|
cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
|
||||||
|
|
||||||
return cnt / ClusterSize;
|
return cnt / ClusterSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,16 +22,18 @@
|
|||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
|
/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
|
||||||
///
|
///
|
||||||
/// key 16 bit
|
/// key 16 bit
|
||||||
/// move 16 bit
|
/// depth 8 bit
|
||||||
/// value 16 bit
|
|
||||||
/// eval value 16 bit
|
|
||||||
/// generation 5 bit
|
/// generation 5 bit
|
||||||
/// pv node 1 bit
|
/// pv node 1 bit
|
||||||
/// bound type 2 bit
|
/// bound type 2 bit
|
||||||
/// depth 8 bit
|
/// move 16 bit
|
||||||
|
/// value 16 bit
|
||||||
|
/// eval value 16 bit
|
||||||
|
|
||||||
struct TTEntry {
|
struct TTEntry {
|
||||||
|
|
||||||
@@ -47,11 +49,11 @@ private:
|
|||||||
friend class TranspositionTable;
|
friend class TranspositionTable;
|
||||||
|
|
||||||
uint16_t key16;
|
uint16_t key16;
|
||||||
|
uint8_t depth8;
|
||||||
|
uint8_t genBound8;
|
||||||
uint16_t move16;
|
uint16_t move16;
|
||||||
int16_t value16;
|
int16_t value16;
|
||||||
int16_t eval16;
|
int16_t eval16;
|
||||||
uint8_t genBound8;
|
|
||||||
uint8_t depth8;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -72,9 +74,15 @@ class TranspositionTable {
|
|||||||
|
|
||||||
static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
|
static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
|
||||||
|
|
||||||
|
// Constants used to refresh the hash table periodically
|
||||||
|
static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things
|
||||||
|
static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field
|
||||||
|
static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length
|
||||||
|
static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~TranspositionTable() { aligned_ttmem_free(mem); }
|
~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;
|
TTEntry* probe(const Key key, bool& found) const;
|
||||||
int hashfull() const;
|
int hashfull() const;
|
||||||
void resize(size_t mbSize);
|
void resize(size_t mbSize);
|
||||||
@@ -84,15 +92,18 @@ public:
|
|||||||
return &table[mul_hi64(key, clusterCount)].entry[0];
|
return &table[mul_hi64(key, clusterCount)].entry[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool enable_transposition_table;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct TTEntry;
|
friend struct TTEntry;
|
||||||
|
|
||||||
size_t clusterCount;
|
size_t clusterCount;
|
||||||
Cluster* table;
|
Cluster* table;
|
||||||
void* mem;
|
|
||||||
uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
|
uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
|
||||||
};
|
};
|
||||||
|
|
||||||
extern TranspositionTable TT;
|
extern TranspositionTable TT;
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef TT_H_INCLUDED
|
#endif // #ifndef TT_H_INCLUDED
|
||||||
|
|||||||
+8
-19
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,9 +26,10 @@
|
|||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
bool Tune::update_on_last;
|
bool Tune::update_on_last;
|
||||||
const UCI::Option* LastOption = nullptr;
|
const UCI::Option* LastOption = nullptr;
|
||||||
BoolConditions Conditions;
|
|
||||||
static std::map<std::string, int> TuneResults;
|
static std::map<std::string, int> TuneResults;
|
||||||
|
|
||||||
string Tune::next(string& names, bool pop) {
|
string Tune::next(string& names, bool pop) {
|
||||||
@@ -108,23 +109,7 @@ template<> void Tune::Entry<Score>::read_option() {
|
|||||||
template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
|
template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
|
||||||
template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
|
template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
// Set binary conditions according to a probability that depends
|
|
||||||
// on the corresponding parameter value.
|
|
||||||
|
|
||||||
void BoolConditions::set() {
|
|
||||||
|
|
||||||
static PRNG rng(now());
|
|
||||||
static bool startup = true; // To workaround fishtest bench
|
|
||||||
|
|
||||||
for (size_t i = 0; i < binary.size(); i++)
|
|
||||||
binary[i] = !startup && (values[i] + int(rng.rand<unsigned>() % variance) > threshold);
|
|
||||||
|
|
||||||
startup = false;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < binary.size(); i++)
|
|
||||||
sync_cout << binary[i] << sync_endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Init options with tuning session results instead of default values. Useful to
|
// Init options with tuning session results instead of default values. Useful to
|
||||||
@@ -138,7 +123,11 @@ void BoolConditions::set() {
|
|||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
void Tune::read_results() {
|
void Tune::read_results() {
|
||||||
|
|
||||||
/* ...insert your values here... */
|
/* ...insert your values here... */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
+8
-38
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,8 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
typedef std::pair<int, int> Range; // Option's min-max values
|
typedef std::pair<int, int> Range; // Option's min-max values
|
||||||
typedef Range (RangeFun) (int);
|
typedef Range (RangeFun) (int);
|
||||||
|
|
||||||
@@ -44,27 +46,6 @@ struct SetRange {
|
|||||||
#define SetDefaultRange SetRange(default_range)
|
#define SetDefaultRange SetRange(default_range)
|
||||||
|
|
||||||
|
|
||||||
/// BoolConditions struct is used to tune boolean conditions in the
|
|
||||||
/// code by toggling them on/off according to a probability that
|
|
||||||
/// depends on the value of a tuned integer parameter: for high
|
|
||||||
/// values of the parameter condition is always disabled, for low
|
|
||||||
/// values is always enabled, otherwise it is enabled with a given
|
|
||||||
/// probability that depnends on the parameter under tuning.
|
|
||||||
|
|
||||||
struct BoolConditions {
|
|
||||||
void init(size_t size) { values.resize(size, defaultValue), binary.resize(size, 0); }
|
|
||||||
void set();
|
|
||||||
|
|
||||||
std::vector<int> binary, values;
|
|
||||||
int defaultValue = 465, variance = 40, threshold = 500;
|
|
||||||
SetRange range = SetRange(0, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
extern BoolConditions Conditions;
|
|
||||||
|
|
||||||
inline void set_conditions() { Conditions.set(); }
|
|
||||||
|
|
||||||
|
|
||||||
/// Tune class implements the 'magic' code that makes the setup of a fishtest
|
/// Tune class implements the 'magic' code that makes the setup of a fishtest
|
||||||
/// tuning session as easy as it can be. Mainly you have just to remove const
|
/// tuning session as easy as it can be. Mainly you have just to remove const
|
||||||
/// qualifiers from the variables you want to tune and flag them for tuning, so
|
/// qualifiers from the variables you want to tune and flag them for tuning, so
|
||||||
@@ -103,7 +84,7 @@ class Tune {
|
|||||||
|
|
||||||
static Tune& instance() { static Tune t; return t; } // Singleton
|
static Tune& instance() { static Tune t; return t; } // Singleton
|
||||||
|
|
||||||
// Use polymorphism to accomodate Entry of different types in the same vector
|
// Use polymorphism to accommodate Entry of different types in the same vector
|
||||||
struct EntryBase {
|
struct EntryBase {
|
||||||
virtual ~EntryBase() = default;
|
virtual ~EntryBase() = default;
|
||||||
virtual void init_option() = 0;
|
virtual void init_option() = 0;
|
||||||
@@ -130,9 +111,9 @@ class Tune {
|
|||||||
SetRange range;
|
SetRange range;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Our facilty to fill the container, each Entry corresponds to a parameter to tune.
|
// Our facility to fill the container, each Entry corresponds to a parameter
|
||||||
// We use variadic templates to deal with an unspecified number of entries, each one
|
// to tune. We use variadic templates to deal with an unspecified number of
|
||||||
// of a possible different type.
|
// entries, each one of a possible different type.
|
||||||
static std::string next(std::string& names, bool pop = true);
|
static std::string next(std::string& names, bool pop = true);
|
||||||
|
|
||||||
int add(const SetRange&, std::string&&) { return 0; }
|
int add(const SetRange&, std::string&&) { return 0; }
|
||||||
@@ -157,14 +138,6 @@ class Tune {
|
|||||||
return add(value, (next(names), std::move(names)), args...);
|
return add(value, (next(names), std::move(names)), args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template specialization for BoolConditions
|
|
||||||
template<typename... Args>
|
|
||||||
int add(const SetRange& range, std::string&& names, BoolConditions& cond, Args&&... args) {
|
|
||||||
for (size_t size = cond.values.size(), i = 0; i < size; i++)
|
|
||||||
add(cond.range, next(names, i == size - 1) + "_" + std::to_string(i), cond.values[i]);
|
|
||||||
return add(range, std::move(names), args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<EntryBase>> list;
|
std::vector<std::unique_ptr<EntryBase>> list;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -185,9 +158,6 @@ public:
|
|||||||
|
|
||||||
#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true
|
#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true
|
||||||
|
|
||||||
// Some macro to tune toggling of boolean conditions
|
} // namespace Stockfish
|
||||||
#define CONDITION(x) (Conditions.binary[__COUNTER__] || (x))
|
|
||||||
#define TUNE_CONDITIONS() int UNIQUE(c, __LINE__) = (Conditions.init(__COUNTER__), 0); \
|
|
||||||
TUNE(Conditions, set_conditions)
|
|
||||||
|
|
||||||
#endif // #ifndef TUNE_H_INCLUDED
|
#endif // #ifndef TUNE_H_INCLUDED
|
||||||
|
|||||||
+30
-117
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -57,6 +57,12 @@
|
|||||||
/// _WIN32 Building on Windows (any)
|
/// _WIN32 Building on Windows (any)
|
||||||
/// _WIN64 Building on Windows 64 bit
|
/// _WIN64 Building on Windows 64 bit
|
||||||
|
|
||||||
|
#if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__)
|
||||||
|
#define ALIGNAS_ON_STACK_VARIABLES_BROKEN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
|
||||||
|
|
||||||
#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
|
#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
|
||||||
# include <intrin.h> // Microsoft header for _BitScanForward64()
|
# include <intrin.h> // Microsoft header for _BitScanForward64()
|
||||||
# define IS_64BIT
|
# define IS_64BIT
|
||||||
@@ -77,6 +83,8 @@
|
|||||||
# define pext(b, m) 0
|
# define pext(b, m) 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
#ifdef USE_POPCNT
|
#ifdef USE_POPCNT
|
||||||
constexpr bool HasPopCnt = true;
|
constexpr bool HasPopCnt = true;
|
||||||
#else
|
#else
|
||||||
@@ -107,7 +115,7 @@ constexpr int MAX_PLY = 246;
|
|||||||
/// bit 6-11: origin square (from 0 to 63)
|
/// bit 6-11: origin square (from 0 to 63)
|
||||||
/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2)
|
/// bit 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)
|
/// 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
|
/// 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
|
/// any normal move destination square is always different from origin square
|
||||||
@@ -121,7 +129,7 @@ enum Move : int {
|
|||||||
enum MoveType {
|
enum MoveType {
|
||||||
NORMAL,
|
NORMAL,
|
||||||
PROMOTION = 1 << 14,
|
PROMOTION = 1 << 14,
|
||||||
ENPASSANT = 2 << 14,
|
EN_PASSANT = 2 << 14,
|
||||||
CASTLING = 3 << 14
|
CASTLING = 3 << 14
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,6 +137,8 @@ enum Color {
|
|||||||
WHITE, BLACK, COLOR_NB = 2
|
WHITE, BLACK, COLOR_NB = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr Color Colors[2] = { WHITE, BLACK };
|
||||||
|
|
||||||
enum CastlingRights {
|
enum CastlingRights {
|
||||||
NO_CASTLING,
|
NO_CASTLING,
|
||||||
WHITE_OO,
|
WHITE_OO,
|
||||||
@@ -178,12 +188,11 @@ enum Value : int {
|
|||||||
VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY,
|
VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY,
|
||||||
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY,
|
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY,
|
||||||
|
|
||||||
PawnValueMg = 124, PawnValueEg = 206,
|
PawnValueMg = 126, PawnValueEg = 208,
|
||||||
KnightValueMg = 781, KnightValueEg = 854,
|
KnightValueMg = 781, KnightValueEg = 854,
|
||||||
BishopValueMg = 825, BishopValueEg = 915,
|
BishopValueMg = 825, BishopValueEg = 915,
|
||||||
RookValueMg = 1276, RookValueEg = 1380,
|
RookValueMg = 1276, RookValueEg = 1380,
|
||||||
QueenValueMg = 2538, QueenValueEg = 2682,
|
QueenValueMg = 2538, QueenValueEg = 2682,
|
||||||
Tempo = 28,
|
|
||||||
|
|
||||||
MidgameLimit = 15258, EndgameLimit = 3915
|
MidgameLimit = 15258, EndgameLimit = 3915
|
||||||
};
|
};
|
||||||
@@ -196,27 +205,11 @@ enum PieceType {
|
|||||||
|
|
||||||
enum Piece {
|
enum Piece {
|
||||||
NO_PIECE,
|
NO_PIECE,
|
||||||
W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
|
W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
|
||||||
B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
|
B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
|
||||||
PIECE_NB = 16
|
PIECE_NB = 16
|
||||||
};
|
};
|
||||||
|
|
||||||
// An ID used to track the pieces. Max. 32 pieces on board.
|
|
||||||
enum PieceId {
|
|
||||||
PIECE_ID_ZERO = 0,
|
|
||||||
PIECE_ID_KING = 30,
|
|
||||||
PIECE_ID_WKING = 30,
|
|
||||||
PIECE_ID_BKING = 31,
|
|
||||||
PIECE_ID_NONE = 32
|
|
||||||
};
|
|
||||||
|
|
||||||
inline PieceId operator++(PieceId& d, int) {
|
|
||||||
|
|
||||||
PieceId x = d;
|
|
||||||
d = PieceId(int(d) + 1);
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
|
constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
|
||||||
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO,
|
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO,
|
||||||
VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO },
|
VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO },
|
||||||
@@ -232,7 +225,8 @@ enum : int {
|
|||||||
DEPTH_QS_RECAPTURES = -5,
|
DEPTH_QS_RECAPTURES = -5,
|
||||||
|
|
||||||
DEPTH_NONE = -6,
|
DEPTH_NONE = -6,
|
||||||
DEPTH_OFFSET = DEPTH_NONE
|
|
||||||
|
DEPTH_OFFSET = -7 // value used only for TT entry occupancy check
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Square : int {
|
enum Square : int {
|
||||||
@@ -270,93 +264,20 @@ enum Rank : int {
|
|||||||
RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
|
RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
|
||||||
};
|
};
|
||||||
|
|
||||||
// unique number for each piece type on each square
|
// Keep track of what a move changes on the board (used by NNUE)
|
||||||
enum PieceSquare : uint32_t {
|
|
||||||
PS_NONE = 0,
|
|
||||||
PS_W_PAWN = 1,
|
|
||||||
PS_B_PAWN = 1 * SQUARE_NB + 1,
|
|
||||||
PS_W_KNIGHT = 2 * SQUARE_NB + 1,
|
|
||||||
PS_B_KNIGHT = 3 * SQUARE_NB + 1,
|
|
||||||
PS_W_BISHOP = 4 * SQUARE_NB + 1,
|
|
||||||
PS_B_BISHOP = 5 * SQUARE_NB + 1,
|
|
||||||
PS_W_ROOK = 6 * SQUARE_NB + 1,
|
|
||||||
PS_B_ROOK = 7 * SQUARE_NB + 1,
|
|
||||||
PS_W_QUEEN = 8 * SQUARE_NB + 1,
|
|
||||||
PS_B_QUEEN = 9 * SQUARE_NB + 1,
|
|
||||||
PS_W_KING = 10 * SQUARE_NB + 1,
|
|
||||||
PS_END = PS_W_KING, // pieces without kings (pawns included)
|
|
||||||
PS_B_KING = 11 * SQUARE_NB + 1,
|
|
||||||
PS_END2 = 12 * SQUARE_NB + 1
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ExtPieceSquare {
|
|
||||||
PieceSquare from[COLOR_NB];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Array for finding the PieceSquare corresponding to the piece on the board
|
|
||||||
extern ExtPieceSquare kpp_board_index[PIECE_NB];
|
|
||||||
|
|
||||||
constexpr bool is_ok(PieceId pid);
|
|
||||||
constexpr Square rotate180(Square sq);
|
|
||||||
|
|
||||||
// Structure holding which tracked piece (PieceId) is where (PieceSquare)
|
|
||||||
class EvalList {
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Max. number of pieces without kings is 30 but must be a multiple of 4 in AVX2
|
|
||||||
static const int MAX_LENGTH = 32;
|
|
||||||
|
|
||||||
// Array that holds the piece id for the pieces on the board
|
|
||||||
PieceId piece_id_list[SQUARE_NB];
|
|
||||||
|
|
||||||
// List of pieces, separate from White and Black POV
|
|
||||||
PieceSquare* piece_list_fw() const { return const_cast<PieceSquare*>(pieceListFw); }
|
|
||||||
PieceSquare* piece_list_fb() const { return const_cast<PieceSquare*>(pieceListFb); }
|
|
||||||
|
|
||||||
// Place the piece pc with piece_id on the square sq on the board
|
|
||||||
void put_piece(PieceId piece_id, Square sq, Piece pc)
|
|
||||||
{
|
|
||||||
assert(is_ok(piece_id));
|
|
||||||
if (pc != NO_PIECE)
|
|
||||||
{
|
|
||||||
pieceListFw[piece_id] = PieceSquare(kpp_board_index[pc].from[WHITE] + sq);
|
|
||||||
pieceListFb[piece_id] = PieceSquare(kpp_board_index[pc].from[BLACK] + rotate180(sq));
|
|
||||||
piece_id_list[sq] = piece_id;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pieceListFw[piece_id] = PS_NONE;
|
|
||||||
pieceListFb[piece_id] = PS_NONE;
|
|
||||||
piece_id_list[sq] = piece_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the specified piece_id piece to ExtPieceSquare type and return it
|
|
||||||
ExtPieceSquare piece_with_id(PieceId piece_id) const
|
|
||||||
{
|
|
||||||
ExtPieceSquare eps;
|
|
||||||
eps.from[WHITE] = pieceListFw[piece_id];
|
|
||||||
eps.from[BLACK] = pieceListFb[piece_id];
|
|
||||||
return eps;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
PieceSquare pieceListFw[MAX_LENGTH];
|
|
||||||
PieceSquare pieceListFb[MAX_LENGTH];
|
|
||||||
};
|
|
||||||
|
|
||||||
// For differential evaluation of pieces that changed since last turn
|
|
||||||
struct DirtyPiece {
|
struct DirtyPiece {
|
||||||
|
|
||||||
// Number of changed pieces
|
// Number of changed pieces
|
||||||
int dirty_num;
|
int dirty_num;
|
||||||
|
|
||||||
// The ids of changed pieces, max. 2 pieces can change in one move
|
// Max 3 pieces can change in one move. A promotion with capture moves
|
||||||
PieceId pieceId[2];
|
// both the pawn and the captured piece to SQ_NONE and the piece promoted
|
||||||
|
// to from SQ_NONE to the capture square.
|
||||||
|
Piece piece[3];
|
||||||
|
|
||||||
// What changed from the piece with that piece number
|
// From and to squares, which may be SQ_NONE
|
||||||
ExtPieceSquare old_piece[2];
|
Square from[3];
|
||||||
ExtPieceSquare new_piece[2];
|
Square to[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Score enum stores a middlegame and an endgame value in a single integer (enum).
|
/// Score enum stores a middlegame and an endgame value in a single integer (enum).
|
||||||
@@ -406,8 +327,6 @@ ENABLE_FULL_OPERATORS_ON(Value)
|
|||||||
ENABLE_FULL_OPERATORS_ON(Direction)
|
ENABLE_FULL_OPERATORS_ON(Direction)
|
||||||
|
|
||||||
ENABLE_INCR_OPERATORS_ON(Piece)
|
ENABLE_INCR_OPERATORS_ON(Piece)
|
||||||
ENABLE_INCR_OPERATORS_ON(PieceSquare)
|
|
||||||
ENABLE_INCR_OPERATORS_ON(PieceId)
|
|
||||||
ENABLE_INCR_OPERATORS_ON(PieceType)
|
ENABLE_INCR_OPERATORS_ON(PieceType)
|
||||||
ENABLE_INCR_OPERATORS_ON(Square)
|
ENABLE_INCR_OPERATORS_ON(Square)
|
||||||
ENABLE_INCR_OPERATORS_ON(File)
|
ENABLE_INCR_OPERATORS_ON(File)
|
||||||
@@ -496,10 +415,6 @@ inline Color color_of(Piece pc) {
|
|||||||
return Color(pc >> 3);
|
return Color(pc >> 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool is_ok(PieceId pid) {
|
|
||||||
return pid < PIECE_ID_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool is_ok(Square s) {
|
constexpr bool is_ok(Square s) {
|
||||||
return s >= SQ_A1 && s <= SQ_H8;
|
return s >= SQ_A1 && s <= SQ_H8;
|
||||||
}
|
}
|
||||||
@@ -538,11 +453,11 @@ constexpr Square to_sq(Move m) {
|
|||||||
|
|
||||||
// Return relative square when turning the board 180 degrees
|
// Return relative square when turning the board 180 degrees
|
||||||
constexpr Square rotate180(Square sq) {
|
constexpr Square rotate180(Square sq) {
|
||||||
return (Square)(sq ^ 0x3F);
|
return (Square)(sq ^ 0x3F);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int from_to(Move m) {
|
constexpr int from_to(Move m) {
|
||||||
return m & 0xFFF;
|
return m & 0xFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr MoveType type_of(Move m) {
|
constexpr MoveType type_of(Move m) {
|
||||||
@@ -557,10 +472,6 @@ constexpr Move make_move(Square from, Square to) {
|
|||||||
return Move((from << 6) + to);
|
return Move((from << 6) + to);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr Move reverse_move(Move m) {
|
|
||||||
return make_move(to_sq(m), from_sq(m));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<MoveType T>
|
template<MoveType T>
|
||||||
constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {
|
constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {
|
||||||
return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);
|
return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);
|
||||||
@@ -575,6 +486,8 @@ constexpr Key make_key(uint64_t seed) {
|
|||||||
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef TYPES_H_INCLUDED
|
#endif // #ifndef TYPES_H_INCLUDED
|
||||||
|
|
||||||
#include "tune.h" // Global visibility to tuning setup
|
#include "tune.h" // Global visibility to tuning setup
|
||||||
|
|||||||
+133
-20
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,26 +22,32 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "nnue/evaluate_nnue.h"
|
||||||
#include "evaluate.h"
|
#include "evaluate.h"
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
|
#include "syzygy/tbprobe.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "timeman.h"
|
#include "timeman.h"
|
||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
#include "syzygy/tbprobe.h"
|
|
||||||
|
#include "tools/validate_training_data.h"
|
||||||
|
#include "tools/training_data_generator.h"
|
||||||
|
#include "tools/training_data_generator_nonpv.h"
|
||||||
|
#include "tools/convert.h"
|
||||||
|
#include "tools/transform.h"
|
||||||
|
#include "tools/stats.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
extern vector<string> setup_bench(const Position&, istream&);
|
extern vector<string> setup_bench(const Position&, istream&);
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// FEN string of the initial position, normal chess
|
|
||||||
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
||||||
|
|
||||||
|
|
||||||
// position() is called when engine receives the "position" UCI command.
|
// position() is called when engine receives the "position" UCI command.
|
||||||
// The function sets up the position described in the given FEN string ("fen")
|
// The function sets up the position described in the given FEN string ("fen")
|
||||||
// or the starting position ("startpos") and then makes the moves given in the
|
// or the starting position ("startpos") and then makes the moves given in the
|
||||||
@@ -85,7 +91,7 @@ namespace {
|
|||||||
Position p;
|
Position p;
|
||||||
p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
|
p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
|
||||||
|
|
||||||
Eval::verify_NNUE();
|
Eval::NNUE::verify();
|
||||||
|
|
||||||
sync_cout << "\n" << Eval::trace(p) << sync_endl;
|
sync_cout << "\n" << Eval::trace(p) << sync_endl;
|
||||||
}
|
}
|
||||||
@@ -94,7 +100,7 @@ namespace {
|
|||||||
// setoption() is called when engine receives the "setoption" UCI command. The
|
// setoption() is called when engine receives the "setoption" UCI command. The
|
||||||
// function updates the UCI option ("name") to the given value ("value").
|
// function updates the UCI option ("name") to the given value ("value").
|
||||||
|
|
||||||
void setoption(istringstream& is) {
|
void setoption_from_stream(istringstream& is) {
|
||||||
|
|
||||||
string token, name, value;
|
string token, name, value;
|
||||||
|
|
||||||
@@ -108,10 +114,7 @@ namespace {
|
|||||||
while (is >> token)
|
while (is >> token)
|
||||||
value += (value.empty() ? "" : " ") + token;
|
value += (value.empty() ? "" : " ") + token;
|
||||||
|
|
||||||
if (Options.count(name))
|
UCI::setoption(name, value);
|
||||||
Options[name] = value;
|
|
||||||
else
|
|
||||||
sync_cout << "No such option: " << name << sync_endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -170,7 +173,7 @@ namespace {
|
|||||||
|
|
||||||
if (token == "go" || token == "eval")
|
if (token == "go" || token == "eval")
|
||||||
{
|
{
|
||||||
cerr << "\nPosition: " << cnt++ << '/' << num << endl;
|
cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl;
|
||||||
if (token == "go")
|
if (token == "go")
|
||||||
{
|
{
|
||||||
go(pos, is, states);
|
go(pos, is, states);
|
||||||
@@ -180,7 +183,7 @@ namespace {
|
|||||||
else
|
else
|
||||||
trace_eval(pos);
|
trace_eval(pos);
|
||||||
}
|
}
|
||||||
else if (token == "setoption") setoption(is);
|
else if (token == "setoption") setoption_from_stream(is);
|
||||||
else if (token == "position") position(pos, is, states);
|
else if (token == "position") position(pos, is, states);
|
||||||
else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while
|
else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while
|
||||||
}
|
}
|
||||||
@@ -195,6 +198,18 @@ namespace {
|
|||||||
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
|
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace UCI {
|
||||||
|
|
||||||
|
void setoption(const std::string& name, const std::string& value)
|
||||||
|
{
|
||||||
|
if (Options.count(name))
|
||||||
|
Options[name] = value;
|
||||||
|
else
|
||||||
|
sync_cout << "No such option: " << name << sync_endl;
|
||||||
|
}
|
||||||
|
|
||||||
// The win rate model returns the probability (per mille) of winning given an eval
|
// The win rate model returns the probability (per mille) of winning given an eval
|
||||||
// and a game-ply. The model fits rather accurately the LTC fishtest statistics.
|
// and a game-ply. The model fits rather accurately the LTC fishtest statistics.
|
||||||
int win_rate_model(Value v, int ply) {
|
int win_rate_model(Value v, int ply) {
|
||||||
@@ -205,13 +220,13 @@ namespace {
|
|||||||
// Coefficients of a 3rd order polynomial fit based on fishtest data
|
// Coefficients of a 3rd order polynomial fit based on fishtest data
|
||||||
// for two parameters needed to transform eval to the argument of a
|
// for two parameters needed to transform eval to the argument of a
|
||||||
// logistic function.
|
// logistic function.
|
||||||
double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679};
|
double as[] = {-1.17202460e-01, 5.94729104e-01, 1.12065546e+01, 1.22606222e+02};
|
||||||
double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751};
|
double bs[] = {-1.79066759, 11.30759193, -17.43677612, 36.47147479};
|
||||||
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
|
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
|
||||||
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
||||||
|
|
||||||
// Transform eval to centipawns with limited range
|
// Transform eval to centipawns with limited range
|
||||||
double x = Utility::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0);
|
double x = std::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0);
|
||||||
|
|
||||||
// Return win rate in per mille (rounded to nearest)
|
// Return win rate in per mille (rounded to nearest)
|
||||||
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
|
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
|
||||||
@@ -219,6 +234,68 @@ namespace {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Call qsearch(),search() directly for testing
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
void qsearch_cmd(Position& pos)
|
||||||
|
{
|
||||||
|
cout << "qsearch : ";
|
||||||
|
auto pv = Search::qsearch(pos);
|
||||||
|
cout << "Value = " << pv.first << " , " << UCI::value(pv.first) << " , PV = ";
|
||||||
|
for (auto m : pv.second)
|
||||||
|
cout << UCI::move(m, false) << " ";
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void search_cmd(Position& pos, istringstream& is)
|
||||||
|
{
|
||||||
|
string token;
|
||||||
|
int depth = 1;
|
||||||
|
int multi_pv = (int)Options["MultiPV"];
|
||||||
|
while (is >> token)
|
||||||
|
{
|
||||||
|
if (token == "depth")
|
||||||
|
is >> depth;
|
||||||
|
if (token == "multipv")
|
||||||
|
is >> multi_pv;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "search depth = " << depth << " , multi_pv = " << multi_pv << " : ";
|
||||||
|
auto pv = Search::search(pos, depth, multi_pv);
|
||||||
|
cout << "Value = " << pv.first << " , " << UCI::value(pv.first) << " , PV = ";
|
||||||
|
for (auto m : pv.second)
|
||||||
|
cout << UCI::move(m, false) << " ";
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void search_mcts_cmd(Position& pos, istringstream& is)
|
||||||
|
{
|
||||||
|
string token;
|
||||||
|
int nodes = 1000;
|
||||||
|
int leafDepth = 3;
|
||||||
|
float explorationFactor = 0.25f;
|
||||||
|
while (is >> token)
|
||||||
|
{
|
||||||
|
if (token == "nodes")
|
||||||
|
is >> nodes;
|
||||||
|
if (token == "leaf_depth")
|
||||||
|
is >> leafDepth;
|
||||||
|
if (token == "exploration_factor")
|
||||||
|
is >> explorationFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "search nodes = " << nodes << " , leaf_depth = " << leafDepth << " :\n";
|
||||||
|
auto continuations = Search::MCTS::search_mcts_multipv(pos, nodes, leafDepth, explorationFactor);
|
||||||
|
for (auto&& [numVisits, value, actionValue, pv] : continuations)
|
||||||
|
{
|
||||||
|
cout << "NumVisits = " << numVisits << " , Value = " << UCI::value(value) << " , ActionValue = " << actionValue << " , PV = ";
|
||||||
|
for (auto m : pv)
|
||||||
|
cout << UCI::move(m, false) << " ";
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
}
|
||||||
|
|
||||||
/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate
|
/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate
|
||||||
/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the
|
/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the
|
||||||
@@ -262,7 +339,7 @@ void UCI::loop(int argc, char* argv[]) {
|
|||||||
<< "\n" << Options
|
<< "\n" << Options
|
||||||
<< "\nuciok" << sync_endl;
|
<< "\nuciok" << sync_endl;
|
||||||
|
|
||||||
else if (token == "setoption") setoption(is);
|
else if (token == "setoption") setoption_from_stream(is);
|
||||||
else if (token == "go") go(pos, is, states);
|
else if (token == "go") go(pos, is, states);
|
||||||
else if (token == "position") position(pos, is, states);
|
else if (token == "position") position(pos, is, states);
|
||||||
else if (token == "ucinewgame") Search::clear();
|
else if (token == "ucinewgame") Search::clear();
|
||||||
@@ -275,8 +352,42 @@ void UCI::loop(int argc, char* argv[]) {
|
|||||||
else if (token == "d") sync_cout << pos << sync_endl;
|
else if (token == "d") sync_cout << pos << sync_endl;
|
||||||
else if (token == "eval") trace_eval(pos);
|
else if (token == "eval") trace_eval(pos);
|
||||||
else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
|
else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
|
||||||
else
|
else if (token == "export_net")
|
||||||
sync_cout << "Unknown command: " << cmd << sync_endl;
|
{
|
||||||
|
std::optional<std::string> filename;
|
||||||
|
std::string f;
|
||||||
|
if (is >> skipws >> f)
|
||||||
|
filename = f;
|
||||||
|
Eval::NNUE::save_eval(filename);
|
||||||
|
}
|
||||||
|
else if (token == "generate_training_data") Tools::generate_training_data(is);
|
||||||
|
else if (token == "generate_training_data_nonpv") Tools::generate_training_data_nonpv(is);
|
||||||
|
else if (token == "convert") Tools::convert(is);
|
||||||
|
else if (token == "validate_training_data") Tools::validate_training_data(is);
|
||||||
|
else if (token == "convert_bin") Tools::convert_bin(is);
|
||||||
|
else if (token == "convert_plain") Tools::convert_plain(is);
|
||||||
|
else if (token == "convert_bin_from_pgn_extract") Tools::convert_bin_from_pgn_extract(is);
|
||||||
|
else if (token == "transform") Tools::transform(is);
|
||||||
|
else if (token == "gather_statistics") Tools::Stats::gather_statistics(is);
|
||||||
|
|
||||||
|
// Command to call qsearch(),search() directly for testing
|
||||||
|
else if (token == "qsearch") qsearch_cmd(pos);
|
||||||
|
else if (token == "search_mcts") search_mcts_cmd(pos, is);
|
||||||
|
else if (token == "search") search_cmd(pos, is);
|
||||||
|
else if (token == "tasktest")
|
||||||
|
{
|
||||||
|
Threads.execute_with_workers([](auto& th) {
|
||||||
|
std::cout << th.id() << '\n';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
|
||||||
|
sync_cout << "\nStockfish is a powerful chess engine and free software licensed under the GNU GPLv3."
|
||||||
|
"\nStockfish is normally used with a separate graphical user interface (GUI)."
|
||||||
|
"\nStockfish implements the universal chess interface (UCI) to exchange information."
|
||||||
|
"\nFor further information see https://github.com/official-stockfish/Stockfish#readme"
|
||||||
|
"\nor the corresponding README.md and Copying.txt files distributed with this program.\n" << sync_endl;
|
||||||
|
else if (!token.empty() && token[0] != '#')
|
||||||
|
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl;
|
||||||
|
|
||||||
} while (token != "quit" && argc == 1); // Command line args are one-shot
|
} while (token != "quit" && argc == 1); // Command line args are one-shot
|
||||||
}
|
}
|
||||||
@@ -369,3 +480,5 @@ Move UCI::to_move(const Position& pos, string& str) {
|
|||||||
|
|
||||||
return MOVE_NONE;
|
return MOVE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,8 @@
|
|||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
class Position;
|
class Position;
|
||||||
|
|
||||||
namespace UCI {
|
namespace UCI {
|
||||||
@@ -73,9 +75,12 @@ std::string move(Move m, bool chess960);
|
|||||||
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
|
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
|
||||||
std::string wdl(Value v, int ply);
|
std::string wdl(Value v, int ply);
|
||||||
Move to_move(const Position& pos, std::string& str);
|
Move to_move(const Position& pos, std::string& str);
|
||||||
|
void setoption(const std::string& name, const std::string& value);
|
||||||
|
|
||||||
} // namespace UCI
|
} // namespace UCI
|
||||||
|
|
||||||
extern UCI::OptionsMap Options;
|
extern UCI::OptionsMap Options;
|
||||||
|
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef UCI_H_INCLUDED
|
#endif // #ifndef UCI_H_INCLUDED
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user