Commit
•
bda2ed7
1
Parent(s):
c4088d6
Upload 57 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- src/.DS_Store +0 -0
- src/Makefile +1009 -0
- src/benchmark.cpp +175 -0
- src/bitbase.cpp +172 -0
- src/bitboard.cpp +222 -0
- src/bitboard.h +451 -0
- src/endgame.cpp +747 -0
- src/endgame.h +126 -0
- src/evaluate.cpp +1171 -0
- src/evaluate.h +62 -0
- src/incbin/UNLICENCE +26 -0
- src/incbin/incbin.h +368 -0
- src/main.cpp +53 -0
- src/material.cpp +229 -0
- src/material.h +71 -0
- src/misc.cpp +687 -0
- src/misc.h +198 -0
- src/movegen.cpp +276 -0
- src/movegen.h +77 -0
- src/movepick.cpp +296 -0
- src/movepick.h +157 -0
- src/nnue/.DS_Store +0 -0
- src/nnue/evaluate_nnue.cpp +406 -0
- src/nnue/evaluate_nnue.h +59 -0
- src/nnue/features/half_ka_v2_hm.cpp +84 -0
- src/nnue/features/half_ka_v2_hm.h +152 -0
- src/nnue/layers/affine_transform.h +545 -0
- src/nnue/layers/clipped_relu.h +180 -0
- src/nnue/layers/simd.h +387 -0
- src/nnue/layers/sqr_clipped_relu.h +120 -0
- src/nnue/nnue_accumulator.h +37 -0
- src/nnue/nnue_architecture.h +138 -0
- src/nnue/nnue_common.h +164 -0
- src/nnue/nnue_feature_transformer.h +589 -0
- src/pawns.cpp +305 -0
- src/pawns.h +70 -0
- src/position.cpp +1353 -0
- src/position.h +443 -0
- src/psqt.cpp +131 -0
- src/psqt.h +38 -0
- src/search.cpp +1959 -0
- src/search.h +115 -0
- src/syzygy/tbprobe.cpp +1628 -0
- src/syzygy/tbprobe.h +76 -0
- src/thread.cpp +268 -0
- src/thread.h +135 -0
- src/thread_win32_osx.h +74 -0
- src/timeman.cpp +105 -0
- src/timeman.h +51 -0
- src/tt.cpp +162 -0
src/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
src/Makefile
ADDED
@@ -0,0 +1,1009 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
2 |
+
# Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
3 |
+
#
|
4 |
+
# Stockfish is free software: you can redistribute it and/or modify
|
5 |
+
# it under the terms of the GNU General Public License as published by
|
6 |
+
# the Free Software Foundation, either version 3 of the License, or
|
7 |
+
# (at your option) any later version.
|
8 |
+
#
|
9 |
+
# Stockfish is distributed in the hope that it will be useful,
|
10 |
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11 |
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12 |
+
# GNU General Public License for more details.
|
13 |
+
#
|
14 |
+
# You should have received a copy of the GNU General Public License
|
15 |
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16 |
+
|
17 |
+
|
18 |
+
### ==========================================================================
|
19 |
+
### Section 1. General Configuration
|
20 |
+
### ==========================================================================
|
21 |
+
|
22 |
+
### Establish the operating system name
|
23 |
+
KERNEL = $(shell uname -s)
|
24 |
+
ifeq ($(KERNEL),Linux)
|
25 |
+
OS = $(shell uname -o)
|
26 |
+
endif
|
27 |
+
|
28 |
+
### Target Windows OS
|
29 |
+
ifeq ($(OS),Windows_NT)
|
30 |
+
ifneq ($(COMP),ndk)
|
31 |
+
target_windows = yes
|
32 |
+
endif
|
33 |
+
else ifeq ($(COMP),mingw)
|
34 |
+
target_windows = yes
|
35 |
+
ifeq ($(WINE_PATH),)
|
36 |
+
WINE_PATH = $(shell which wine)
|
37 |
+
endif
|
38 |
+
endif
|
39 |
+
|
40 |
+
### Executable name
|
41 |
+
ifeq ($(target_windows),yes)
|
42 |
+
EXE = stockfish.exe
|
43 |
+
else
|
44 |
+
EXE = stockfish
|
45 |
+
endif
|
46 |
+
|
47 |
+
### Installation dir definitions
|
48 |
+
PREFIX = /usr/local
|
49 |
+
BINDIR = $(PREFIX)/bin
|
50 |
+
|
51 |
+
### Built-in benchmark for pgo-builds
|
52 |
+
ifeq ($(SDE_PATH),)
|
53 |
+
PGOBENCH = $(WINE_PATH) ./$(EXE) bench
|
54 |
+
else
|
55 |
+
PGOBENCH = $(SDE_PATH) -- $(WINE_PATH) ./$(EXE) bench
|
56 |
+
endif
|
57 |
+
|
58 |
+
### Source and object files
|
59 |
+
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
|
60 |
+
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
|
61 |
+
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
|
62 |
+
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp
|
63 |
+
|
64 |
+
OBJS = $(notdir $(SRCS:.cpp=.o))
|
65 |
+
|
66 |
+
VPATH = syzygy:nnue:nnue/features
|
67 |
+
|
68 |
+
### ==========================================================================
|
69 |
+
### Section 2. High-level Configuration
|
70 |
+
### ==========================================================================
|
71 |
+
#
|
72 |
+
# flag --- Comp switch --- Description
|
73 |
+
# ----------------------------------------------------------------------------
|
74 |
+
#
|
75 |
+
# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
|
76 |
+
# sanitize = none/<sanitizer> ... (-fsanitize )
|
77 |
+
# --- ( undefined ) --- enable undefined behavior checks
|
78 |
+
# --- ( thread ) --- enable threading error checks
|
79 |
+
# --- ( address ) --- enable memory access checks
|
80 |
+
# --- ...etc... --- see compiler documentation for supported sanitizers
|
81 |
+
# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations
|
82 |
+
# arch = (name) --- (-arch) --- Target architecture
|
83 |
+
# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system
|
84 |
+
# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction
|
85 |
+
# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction
|
86 |
+
# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction
|
87 |
+
# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions
|
88 |
+
# mmx = yes/no --- -mmmx --- Use Intel MMX instructions
|
89 |
+
# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2
|
90 |
+
# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3
|
91 |
+
# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1
|
92 |
+
# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2
|
93 |
+
# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX
|
94 |
+
# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
|
95 |
+
# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256
|
96 |
+
# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
|
97 |
+
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
|
98 |
+
#
|
99 |
+
# Note that Makefile is space sensitive, so when adding new architectures
|
100 |
+
# or modifying existing flags, you have to make sure there are no extra spaces
|
101 |
+
# at the end of the line for flag values.
|
102 |
+
#
|
103 |
+
# Example of use for these flags:
|
104 |
+
# make build ARCH=x86-64-avx512 debug=yes sanitize="address undefined"
|
105 |
+
|
106 |
+
|
107 |
+
### 2.1. General and architecture defaults
|
108 |
+
|
109 |
+
ifeq ($(ARCH),)
|
110 |
+
ARCH = x86-64-modern
|
111 |
+
help_skip_sanity = yes
|
112 |
+
endif
|
113 |
+
# explicitly check for the list of supported architectures (as listed with make help),
|
114 |
+
# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
|
115 |
+
ifeq ($(ARCH), $(filter $(ARCH), \
|
116 |
+
x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \
|
117 |
+
x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
|
118 |
+
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \
|
119 |
+
armv7 armv7-neon armv8 apple-silicon general-64 general-32 riscv64))
|
120 |
+
SUPPORTED_ARCH=true
|
121 |
+
else
|
122 |
+
SUPPORTED_ARCH=false
|
123 |
+
endif
|
124 |
+
|
125 |
+
optimize = yes
|
126 |
+
debug = no
|
127 |
+
sanitize = none
|
128 |
+
bits = 64
|
129 |
+
prefetch = no
|
130 |
+
popcnt = no
|
131 |
+
pext = no
|
132 |
+
sse = no
|
133 |
+
mmx = no
|
134 |
+
sse2 = no
|
135 |
+
ssse3 = no
|
136 |
+
sse41 = no
|
137 |
+
avx2 = no
|
138 |
+
avxvnni = no
|
139 |
+
avx512 = no
|
140 |
+
vnni256 = no
|
141 |
+
vnni512 = no
|
142 |
+
neon = no
|
143 |
+
arm_version = 0
|
144 |
+
STRIP = strip
|
145 |
+
|
146 |
+
### 2.2 Architecture specific
|
147 |
+
|
148 |
+
ifeq ($(findstring x86,$(ARCH)),x86)
|
149 |
+
|
150 |
+
# x86-32/64
|
151 |
+
|
152 |
+
ifeq ($(findstring x86-32,$(ARCH)),x86-32)
|
153 |
+
arch = i386
|
154 |
+
bits = 32
|
155 |
+
sse = no
|
156 |
+
mmx = yes
|
157 |
+
else
|
158 |
+
arch = x86_64
|
159 |
+
sse = yes
|
160 |
+
sse2 = yes
|
161 |
+
endif
|
162 |
+
|
163 |
+
ifeq ($(findstring -sse,$(ARCH)),-sse)
|
164 |
+
sse = yes
|
165 |
+
endif
|
166 |
+
|
167 |
+
ifeq ($(findstring -popcnt,$(ARCH)),-popcnt)
|
168 |
+
popcnt = yes
|
169 |
+
endif
|
170 |
+
|
171 |
+
ifeq ($(findstring -mmx,$(ARCH)),-mmx)
|
172 |
+
mmx = yes
|
173 |
+
endif
|
174 |
+
|
175 |
+
ifeq ($(findstring -sse2,$(ARCH)),-sse2)
|
176 |
+
sse = yes
|
177 |
+
sse2 = yes
|
178 |
+
endif
|
179 |
+
|
180 |
+
ifeq ($(findstring -ssse3,$(ARCH)),-ssse3)
|
181 |
+
sse = yes
|
182 |
+
sse2 = yes
|
183 |
+
ssse3 = yes
|
184 |
+
endif
|
185 |
+
|
186 |
+
ifeq ($(findstring -sse41,$(ARCH)),-sse41)
|
187 |
+
sse = yes
|
188 |
+
sse2 = yes
|
189 |
+
ssse3 = yes
|
190 |
+
sse41 = yes
|
191 |
+
endif
|
192 |
+
|
193 |
+
ifeq ($(findstring -modern,$(ARCH)),-modern)
|
194 |
+
popcnt = yes
|
195 |
+
sse = yes
|
196 |
+
sse2 = yes
|
197 |
+
ssse3 = yes
|
198 |
+
sse41 = yes
|
199 |
+
endif
|
200 |
+
|
201 |
+
ifeq ($(findstring -avx2,$(ARCH)),-avx2)
|
202 |
+
popcnt = yes
|
203 |
+
sse = yes
|
204 |
+
sse2 = yes
|
205 |
+
ssse3 = yes
|
206 |
+
sse41 = yes
|
207 |
+
avx2 = yes
|
208 |
+
endif
|
209 |
+
|
210 |
+
ifeq ($(findstring -avxvnni,$(ARCH)),-avxvnni)
|
211 |
+
popcnt = yes
|
212 |
+
sse = yes
|
213 |
+
sse2 = yes
|
214 |
+
ssse3 = yes
|
215 |
+
sse41 = yes
|
216 |
+
avx2 = yes
|
217 |
+
avxvnni = yes
|
218 |
+
pext = yes
|
219 |
+
endif
|
220 |
+
|
221 |
+
ifeq ($(findstring -bmi2,$(ARCH)),-bmi2)
|
222 |
+
popcnt = yes
|
223 |
+
sse = yes
|
224 |
+
sse2 = yes
|
225 |
+
ssse3 = yes
|
226 |
+
sse41 = yes
|
227 |
+
avx2 = yes
|
228 |
+
pext = yes
|
229 |
+
endif
|
230 |
+
|
231 |
+
ifeq ($(findstring -avx512,$(ARCH)),-avx512)
|
232 |
+
popcnt = yes
|
233 |
+
sse = yes
|
234 |
+
sse2 = yes
|
235 |
+
ssse3 = yes
|
236 |
+
sse41 = yes
|
237 |
+
avx2 = yes
|
238 |
+
pext = yes
|
239 |
+
avx512 = yes
|
240 |
+
endif
|
241 |
+
|
242 |
+
ifeq ($(findstring -vnni256,$(ARCH)),-vnni256)
|
243 |
+
popcnt = yes
|
244 |
+
sse = yes
|
245 |
+
sse2 = yes
|
246 |
+
ssse3 = yes
|
247 |
+
sse41 = yes
|
248 |
+
avx2 = yes
|
249 |
+
pext = yes
|
250 |
+
vnni256 = yes
|
251 |
+
endif
|
252 |
+
|
253 |
+
ifeq ($(findstring -vnni512,$(ARCH)),-vnni512)
|
254 |
+
popcnt = yes
|
255 |
+
sse = yes
|
256 |
+
sse2 = yes
|
257 |
+
ssse3 = yes
|
258 |
+
sse41 = yes
|
259 |
+
avx2 = yes
|
260 |
+
pext = yes
|
261 |
+
avx512 = yes
|
262 |
+
vnni512 = yes
|
263 |
+
endif
|
264 |
+
|
265 |
+
ifeq ($(sse),yes)
|
266 |
+
prefetch = yes
|
267 |
+
endif
|
268 |
+
|
269 |
+
# 64-bit pext is not available on x86-32
|
270 |
+
ifeq ($(bits),32)
|
271 |
+
pext = no
|
272 |
+
endif
|
273 |
+
|
274 |
+
else
|
275 |
+
|
276 |
+
# all other architectures
|
277 |
+
|
278 |
+
ifeq ($(ARCH),general-32)
|
279 |
+
arch = any
|
280 |
+
bits = 32
|
281 |
+
endif
|
282 |
+
|
283 |
+
ifeq ($(ARCH),general-64)
|
284 |
+
arch = any
|
285 |
+
endif
|
286 |
+
|
287 |
+
ifeq ($(ARCH),armv7)
|
288 |
+
arch = armv7
|
289 |
+
prefetch = yes
|
290 |
+
bits = 32
|
291 |
+
arm_version = 7
|
292 |
+
endif
|
293 |
+
|
294 |
+
ifeq ($(ARCH),armv7-neon)
|
295 |
+
arch = armv7
|
296 |
+
prefetch = yes
|
297 |
+
popcnt = yes
|
298 |
+
neon = yes
|
299 |
+
bits = 32
|
300 |
+
arm_version = 7
|
301 |
+
endif
|
302 |
+
|
303 |
+
ifeq ($(ARCH),armv8)
|
304 |
+
arch = armv8
|
305 |
+
prefetch = yes
|
306 |
+
popcnt = yes
|
307 |
+
neon = yes
|
308 |
+
arm_version = 8
|
309 |
+
endif
|
310 |
+
|
311 |
+
ifeq ($(ARCH),apple-silicon)
|
312 |
+
arch = arm64
|
313 |
+
prefetch = yes
|
314 |
+
popcnt = yes
|
315 |
+
neon = yes
|
316 |
+
arm_version = 8
|
317 |
+
endif
|
318 |
+
|
319 |
+
ifeq ($(ARCH),ppc-32)
|
320 |
+
arch = ppc
|
321 |
+
bits = 32
|
322 |
+
endif
|
323 |
+
|
324 |
+
ifeq ($(ARCH),ppc-64)
|
325 |
+
arch = ppc64
|
326 |
+
popcnt = yes
|
327 |
+
prefetch = yes
|
328 |
+
endif
|
329 |
+
|
330 |
+
ifeq ($(findstring e2k,$(ARCH)),e2k)
|
331 |
+
arch = e2k
|
332 |
+
mmx = yes
|
333 |
+
bits = 64
|
334 |
+
sse = yes
|
335 |
+
sse2 = yes
|
336 |
+
ssse3 = yes
|
337 |
+
sse41 = yes
|
338 |
+
popcnt = yes
|
339 |
+
endif
|
340 |
+
|
341 |
+
ifeq ($(ARCH),riscv64)
|
342 |
+
arch = riscv64
|
343 |
+
endif
|
344 |
+
endif
|
345 |
+
|
346 |
+
|
347 |
+
### ==========================================================================
|
348 |
+
### Section 3. Low-level Configuration
|
349 |
+
### ==========================================================================
|
350 |
+
|
351 |
+
### 3.1 Selecting compiler (default = gcc)
|
352 |
+
ifeq ($(MAKELEVEL),0)
|
353 |
+
export ENV_CXXFLAGS := $(CXXFLAGS)
|
354 |
+
export ENV_DEPENDFLAGS := $(DEPENDFLAGS)
|
355 |
+
export ENV_LDFLAGS := $(LDFLAGS)
|
356 |
+
endif
|
357 |
+
|
358 |
+
CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
|
359 |
+
DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17
|
360 |
+
LDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS)
|
361 |
+
|
362 |
+
ifeq ($(COMP),)
|
363 |
+
COMP=gcc
|
364 |
+
endif
|
365 |
+
|
366 |
+
ifeq ($(COMP),gcc)
|
367 |
+
comp=gcc
|
368 |
+
CXX=g++
|
369 |
+
CXXFLAGS += -pedantic -Wextra -Wshadow
|
370 |
+
|
371 |
+
ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
|
372 |
+
ifeq ($(OS),Android)
|
373 |
+
CXXFLAGS += -m$(bits)
|
374 |
+
LDFLAGS += -m$(bits)
|
375 |
+
endif
|
376 |
+
ifeq ($(ARCH),riscv64)
|
377 |
+
CXXFLAGS += -latomic
|
378 |
+
endif
|
379 |
+
else
|
380 |
+
CXXFLAGS += -m$(bits)
|
381 |
+
LDFLAGS += -m$(bits)
|
382 |
+
endif
|
383 |
+
|
384 |
+
ifeq ($(arch),$(filter $(arch),armv7))
|
385 |
+
LDFLAGS += -latomic
|
386 |
+
endif
|
387 |
+
|
388 |
+
ifneq ($(KERNEL),Darwin)
|
389 |
+
LDFLAGS += -Wl,--no-as-needed
|
390 |
+
endif
|
391 |
+
endif
|
392 |
+
|
393 |
+
ifeq ($(target_windows),yes)
|
394 |
+
LDFLAGS += -static
|
395 |
+
endif
|
396 |
+
|
397 |
+
ifeq ($(COMP),mingw)
|
398 |
+
comp=mingw
|
399 |
+
|
400 |
+
ifeq ($(bits),64)
|
401 |
+
ifeq ($(shell which x86_64-w64-mingw32-c++-posix 2> /dev/null),)
|
402 |
+
CXX=x86_64-w64-mingw32-c++
|
403 |
+
else
|
404 |
+
CXX=x86_64-w64-mingw32-c++-posix
|
405 |
+
endif
|
406 |
+
else
|
407 |
+
ifeq ($(shell which i686-w64-mingw32-c++-posix 2> /dev/null),)
|
408 |
+
CXX=i686-w64-mingw32-c++
|
409 |
+
else
|
410 |
+
CXX=i686-w64-mingw32-c++-posix
|
411 |
+
endif
|
412 |
+
endif
|
413 |
+
CXXFLAGS += -pedantic -Wextra -Wshadow
|
414 |
+
endif
|
415 |
+
|
416 |
+
ifeq ($(COMP),icc)
|
417 |
+
comp=icc
|
418 |
+
CXX=icpc
|
419 |
+
CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi
|
420 |
+
endif
|
421 |
+
|
422 |
+
ifeq ($(COMP),clang)
|
423 |
+
comp=clang
|
424 |
+
CXX=clang++
|
425 |
+
ifeq ($(target_windows),yes)
|
426 |
+
CXX=x86_64-w64-mingw32-clang++
|
427 |
+
endif
|
428 |
+
|
429 |
+
CXXFLAGS += -pedantic -Wextra -Wshadow
|
430 |
+
|
431 |
+
ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)
|
432 |
+
ifeq ($(target_windows),)
|
433 |
+
ifneq ($(RTLIB),compiler-rt)
|
434 |
+
LDFLAGS += -latomic
|
435 |
+
endif
|
436 |
+
endif
|
437 |
+
endif
|
438 |
+
|
439 |
+
ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
|
440 |
+
ifeq ($(OS),Android)
|
441 |
+
CXXFLAGS += -m$(bits)
|
442 |
+
LDFLAGS += -m$(bits)
|
443 |
+
endif
|
444 |
+
ifeq ($(ARCH),riscv64)
|
445 |
+
CXXFLAGS += -latomic
|
446 |
+
endif
|
447 |
+
else
|
448 |
+
CXXFLAGS += -m$(bits)
|
449 |
+
LDFLAGS += -m$(bits)
|
450 |
+
endif
|
451 |
+
endif
|
452 |
+
|
453 |
+
ifeq ($(KERNEL),Darwin)
|
454 |
+
CXXFLAGS += -mmacosx-version-min=10.14
|
455 |
+
LDFLAGS += -mmacosx-version-min=10.14
|
456 |
+
ifneq ($(arch),any)
|
457 |
+
CXXFLAGS += -arch $(arch)
|
458 |
+
LDFLAGS += -arch $(arch)
|
459 |
+
endif
|
460 |
+
XCRUN = xcrun
|
461 |
+
endif
|
462 |
+
|
463 |
+
# To cross-compile for Android, NDK version r21 or later is recommended.
|
464 |
+
# In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils.
|
465 |
+
# Currently we don't know how to make PGO builds with the NDK yet.
|
466 |
+
ifeq ($(COMP),ndk)
|
467 |
+
CXXFLAGS += -stdlib=libc++ -fPIE
|
468 |
+
comp=clang
|
469 |
+
ifeq ($(arch),armv7)
|
470 |
+
CXX=armv7a-linux-androideabi16-clang++
|
471 |
+
CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon
|
472 |
+
ifneq ($(shell which arm-linux-androideabi-strip 2>/dev/null),)
|
473 |
+
STRIP=arm-linux-androideabi-strip
|
474 |
+
else
|
475 |
+
STRIP=llvm-strip
|
476 |
+
endif
|
477 |
+
endif
|
478 |
+
ifeq ($(arch),armv8)
|
479 |
+
CXX=aarch64-linux-android21-clang++
|
480 |
+
ifneq ($(shell which aarch64-linux-android-strip 2>/dev/null),)
|
481 |
+
STRIP=aarch64-linux-android-strip
|
482 |
+
else
|
483 |
+
STRIP=llvm-strip
|
484 |
+
endif
|
485 |
+
endif
|
486 |
+
LDFLAGS += -static-libstdc++ -pie -lm -latomic
|
487 |
+
endif
|
488 |
+
|
489 |
+
ifeq ($(comp),icc)
|
490 |
+
profile_make = icc-profile-make
|
491 |
+
profile_use = icc-profile-use
|
492 |
+
else ifeq ($(comp),clang)
|
493 |
+
profile_make = clang-profile-make
|
494 |
+
profile_use = clang-profile-use
|
495 |
+
else
|
496 |
+
profile_make = gcc-profile-make
|
497 |
+
profile_use = gcc-profile-use
|
498 |
+
ifeq ($(KERNEL),Darwin)
|
499 |
+
EXTRAPROFILEFLAGS = -fvisibility=hidden
|
500 |
+
endif
|
501 |
+
endif
|
502 |
+
|
503 |
+
### Travis CI script uses COMPILER to overwrite CXX
|
504 |
+
ifdef COMPILER
|
505 |
+
COMPCXX=$(COMPILER)
|
506 |
+
endif
|
507 |
+
|
508 |
+
### Allow overwriting CXX from command line
|
509 |
+
ifdef COMPCXX
|
510 |
+
CXX=$(COMPCXX)
|
511 |
+
endif
|
512 |
+
|
513 |
+
### Sometimes gcc is really clang
|
514 |
+
ifeq ($(COMP),gcc)
|
515 |
+
gccversion = $(shell $(CXX) --version)
|
516 |
+
gccisclang = $(findstring clang,$(gccversion))
|
517 |
+
ifneq ($(gccisclang),)
|
518 |
+
profile_make = clang-profile-make
|
519 |
+
profile_use = clang-profile-use
|
520 |
+
endif
|
521 |
+
endif
|
522 |
+
|
523 |
+
### On mingw use Windows threads, otherwise POSIX
|
524 |
+
ifneq ($(comp),mingw)
|
525 |
+
CXXFLAGS += -DUSE_PTHREADS
|
526 |
+
# On Android Bionic's C library comes with its own pthread implementation bundled in
|
527 |
+
ifneq ($(OS),Android)
|
528 |
+
# Haiku has pthreads in its libroot, so only link it in on other platforms
|
529 |
+
ifneq ($(KERNEL),Haiku)
|
530 |
+
ifneq ($(COMP),ndk)
|
531 |
+
LDFLAGS += -lpthread
|
532 |
+
endif
|
533 |
+
endif
|
534 |
+
endif
|
535 |
+
endif
|
536 |
+
|
537 |
+
### 3.2.1 Debugging
|
538 |
+
ifeq ($(debug),no)
|
539 |
+
CXXFLAGS += -DNDEBUG
|
540 |
+
else
|
541 |
+
CXXFLAGS += -g
|
542 |
+
endif
|
543 |
+
|
544 |
+
### 3.2.2 Debugging with undefined behavior sanitizers
|
545 |
+
ifneq ($(sanitize),none)
|
546 |
+
CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize))
|
547 |
+
LDFLAGS += $(addprefix -fsanitize=,$(sanitize))
|
548 |
+
endif
|
549 |
+
|
550 |
+
### 3.3 Optimization
|
551 |
+
ifeq ($(optimize),yes)
|
552 |
+
|
553 |
+
CXXFLAGS += -O3
|
554 |
+
|
555 |
+
ifeq ($(comp),gcc)
|
556 |
+
ifeq ($(OS), Android)
|
557 |
+
CXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp
|
558 |
+
endif
|
559 |
+
endif
|
560 |
+
|
561 |
+
ifeq ($(KERNEL),Darwin)
|
562 |
+
ifeq ($(comp),$(filter $(comp),clang icc))
|
563 |
+
CXXFLAGS += -mdynamic-no-pic
|
564 |
+
endif
|
565 |
+
|
566 |
+
ifeq ($(comp),gcc)
|
567 |
+
ifneq ($(arch),arm64)
|
568 |
+
CXXFLAGS += -mdynamic-no-pic
|
569 |
+
endif
|
570 |
+
endif
|
571 |
+
endif
|
572 |
+
|
573 |
+
ifeq ($(comp),clang)
|
574 |
+
CXXFLAGS += -fexperimental-new-pass-manager
|
575 |
+
endif
|
576 |
+
endif
|
577 |
+
|
578 |
+
### 3.4 Bits
|
579 |
+
ifeq ($(bits),64)
|
580 |
+
CXXFLAGS += -DIS_64BIT
|
581 |
+
endif
|
582 |
+
|
583 |
+
### 3.5 prefetch and popcount
|
584 |
+
ifeq ($(prefetch),yes)
|
585 |
+
ifeq ($(sse),yes)
|
586 |
+
CXXFLAGS += -msse
|
587 |
+
endif
|
588 |
+
else
|
589 |
+
CXXFLAGS += -DNO_PREFETCH
|
590 |
+
endif
|
591 |
+
|
592 |
+
ifeq ($(popcnt),yes)
|
593 |
+
ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64))
|
594 |
+
CXXFLAGS += -DUSE_POPCNT
|
595 |
+
else ifeq ($(comp),icc)
|
596 |
+
CXXFLAGS += -msse3 -DUSE_POPCNT
|
597 |
+
else
|
598 |
+
CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT
|
599 |
+
endif
|
600 |
+
endif
|
601 |
+
|
602 |
+
### 3.6 SIMD architectures
|
603 |
+
ifeq ($(avx2),yes)
|
604 |
+
CXXFLAGS += -DUSE_AVX2
|
605 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
606 |
+
CXXFLAGS += -mavx2 -mbmi
|
607 |
+
endif
|
608 |
+
endif
|
609 |
+
|
610 |
+
ifeq ($(avxvnni),yes)
|
611 |
+
CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI
|
612 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
613 |
+
CXXFLAGS += -mavxvnni
|
614 |
+
endif
|
615 |
+
endif
|
616 |
+
|
617 |
+
ifeq ($(avx512),yes)
|
618 |
+
CXXFLAGS += -DUSE_AVX512
|
619 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
620 |
+
CXXFLAGS += -mavx512f -mavx512bw
|
621 |
+
endif
|
622 |
+
endif
|
623 |
+
|
624 |
+
ifeq ($(vnni256),yes)
|
625 |
+
CXXFLAGS += -DUSE_VNNI
|
626 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
627 |
+
CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256
|
628 |
+
endif
|
629 |
+
endif
|
630 |
+
|
631 |
+
ifeq ($(vnni512),yes)
|
632 |
+
CXXFLAGS += -DUSE_VNNI
|
633 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
634 |
+
CXXFLAGS += -mavx512vnni -mavx512dq -mavx512vl
|
635 |
+
endif
|
636 |
+
endif
|
637 |
+
|
638 |
+
ifeq ($(sse41),yes)
|
639 |
+
CXXFLAGS += -DUSE_SSE41
|
640 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
641 |
+
CXXFLAGS += -msse4.1
|
642 |
+
endif
|
643 |
+
endif
|
644 |
+
|
645 |
+
ifeq ($(ssse3),yes)
|
646 |
+
CXXFLAGS += -DUSE_SSSE3
|
647 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
648 |
+
CXXFLAGS += -mssse3
|
649 |
+
endif
|
650 |
+
endif
|
651 |
+
|
652 |
+
ifeq ($(sse2),yes)
|
653 |
+
CXXFLAGS += -DUSE_SSE2
|
654 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
655 |
+
CXXFLAGS += -msse2
|
656 |
+
endif
|
657 |
+
endif
|
658 |
+
|
659 |
+
ifeq ($(mmx),yes)
|
660 |
+
CXXFLAGS += -DUSE_MMX
|
661 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
662 |
+
CXXFLAGS += -mmmx
|
663 |
+
endif
|
664 |
+
endif
|
665 |
+
|
666 |
+
ifeq ($(neon),yes)
|
667 |
+
CXXFLAGS += -DUSE_NEON=$(arm_version)
|
668 |
+
ifeq ($(KERNEL),Linux)
|
669 |
+
ifneq ($(COMP),ndk)
|
670 |
+
ifneq ($(arch),armv8)
|
671 |
+
CXXFLAGS += -mfpu=neon
|
672 |
+
endif
|
673 |
+
endif
|
674 |
+
endif
|
675 |
+
endif
|
676 |
+
|
677 |
+
### 3.7 pext
|
678 |
+
ifeq ($(pext),yes)
|
679 |
+
CXXFLAGS += -DUSE_PEXT
|
680 |
+
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
681 |
+
CXXFLAGS += -mbmi2
|
682 |
+
endif
|
683 |
+
endif
|
684 |
+
|
685 |
+
### 3.7.1 Try to include git commit sha for versioning
|
686 |
+
GIT_SHA = $(shell git rev-parse --short HEAD 2>/dev/null)
|
687 |
+
ifneq ($(GIT_SHA), )
|
688 |
+
CXXFLAGS += -DGIT_SHA=\"$(GIT_SHA)\"
|
689 |
+
endif
|
690 |
+
|
691 |
+
### 3.7.2 Try to include git commit date for versioning
|
692 |
+
GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null)
|
693 |
+
ifneq ($(GIT_DATE), )
|
694 |
+
CXXFLAGS += -DGIT_DATE=\"$(GIT_DATE)\"
|
695 |
+
endif
|
696 |
+
|
697 |
+
### 3.8 Link Time Optimization
|
698 |
+
### This is a mix of compile and link time options because the lto link phase
|
699 |
+
### needs access to the optimization flags.
|
700 |
+
ifeq ($(optimize),yes)
|
701 |
+
ifeq ($(debug), no)
|
702 |
+
ifeq ($(comp),clang)
|
703 |
+
CXXFLAGS += -flto=full
|
704 |
+
ifeq ($(target_windows),yes)
|
705 |
+
CXXFLAGS += -fuse-ld=lld
|
706 |
+
endif
|
707 |
+
LDFLAGS += $(CXXFLAGS)
|
708 |
+
|
709 |
+
# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be
|
710 |
+
# GCC on some systems.
|
711 |
+
else ifeq ($(comp),gcc)
|
712 |
+
ifeq ($(gccisclang),)
|
713 |
+
CXXFLAGS += -flto -flto-partition=one
|
714 |
+
LDFLAGS += $(CXXFLAGS) -flto=jobserver
|
715 |
+
else
|
716 |
+
CXXFLAGS += -flto=full
|
717 |
+
LDFLAGS += $(CXXFLAGS)
|
718 |
+
endif
|
719 |
+
|
720 |
+
# To use LTO and static linking on Windows,
|
721 |
+
# the tool chain requires gcc version 10.1 or later.
|
722 |
+
else ifeq ($(comp),mingw)
|
723 |
+
CXXFLAGS += -flto -flto-partition=one
|
724 |
+
LDFLAGS += $(CXXFLAGS) -save-temps
|
725 |
+
endif
|
726 |
+
endif
|
727 |
+
endif
|
728 |
+
|
729 |
+
### 3.9 Android 5 can only run position independent executables. Note that this
|
730 |
+
### breaks Android 4.0 and earlier.
|
731 |
+
ifeq ($(OS), Android)
|
732 |
+
CXXFLAGS += -fPIE
|
733 |
+
LDFLAGS += -fPIE -pie
|
734 |
+
endif
|
735 |
+
|
736 |
+
### ==========================================================================
|
737 |
+
### Section 4. Public Targets
|
738 |
+
### ==========================================================================
|
739 |
+
|
740 |
+
|
741 |
+
help:
|
742 |
+
@echo ""
|
743 |
+
@echo "To compile stockfish, type: "
|
744 |
+
@echo ""
|
745 |
+
@echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]"
|
746 |
+
@echo ""
|
747 |
+
@echo "Supported targets:"
|
748 |
+
@echo ""
|
749 |
+
@echo "help > Display architecture details"
|
750 |
+
@echo "build > Standard build"
|
751 |
+
@echo "net > Download the default nnue net"
|
752 |
+
@echo "profile-build > Faster build (with profile-guided optimization)"
|
753 |
+
@echo "strip > Strip executable"
|
754 |
+
@echo "install > Install executable"
|
755 |
+
@echo "clean > Clean up"
|
756 |
+
@echo ""
|
757 |
+
@echo "Supported archs:"
|
758 |
+
@echo ""
|
759 |
+
@echo "x86-64-vnni512 > x86 64-bit with vnni support 512bit wide"
|
760 |
+
@echo "x86-64-vnni256 > x86 64-bit with vnni support 256bit wide"
|
761 |
+
@echo "x86-64-avx512 > x86 64-bit with avx512 support"
|
762 |
+
@echo "x86-64-avxvnni > x86 64-bit with avxvnni support"
|
763 |
+
@echo "x86-64-bmi2 > x86 64-bit with bmi2 support"
|
764 |
+
@echo "x86-64-avx2 > x86 64-bit with avx2 support"
|
765 |
+
@echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support"
|
766 |
+
@echo "x86-64-modern > common modern CPU, currently x86-64-sse41-popcnt"
|
767 |
+
@echo "x86-64-ssse3 > x86 64-bit with ssse3 support"
|
768 |
+
@echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support"
|
769 |
+
@echo "x86-64 > x86 64-bit generic (with sse2 support)"
|
770 |
+
@echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support"
|
771 |
+
@echo "x86-32-sse2 > x86 32-bit with sse2 support"
|
772 |
+
@echo "x86-32 > x86 32-bit generic (with mmx and sse support)"
|
773 |
+
@echo "ppc-64 > PPC 64-bit"
|
774 |
+
@echo "ppc-32 > PPC 32-bit"
|
775 |
+
@echo "armv7 > ARMv7 32-bit"
|
776 |
+
@echo "armv7-neon > ARMv7 32-bit with popcnt and neon"
|
777 |
+
@echo "armv8 > ARMv8 64-bit with popcnt and neon"
|
778 |
+
@echo "e2k > Elbrus 2000"
|
779 |
+
@echo "apple-silicon > Apple silicon ARM64"
|
780 |
+
@echo "general-64 > unspecified 64-bit"
|
781 |
+
@echo "general-32 > unspecified 32-bit"
|
782 |
+
@echo "riscv64 > RISC-V 64-bit"
|
783 |
+
@echo ""
|
784 |
+
@echo "Supported compilers:"
|
785 |
+
@echo ""
|
786 |
+
@echo "gcc > Gnu compiler (default)"
|
787 |
+
@echo "mingw > Gnu compiler with MinGW under Windows"
|
788 |
+
@echo "clang > LLVM Clang compiler"
|
789 |
+
@echo "icc > Intel compiler"
|
790 |
+
@echo "ndk > Google NDK to cross-compile for Android"
|
791 |
+
@echo ""
|
792 |
+
@echo "Simple examples. If you don't know what to do, you likely want to run: "
|
793 |
+
@echo ""
|
794 |
+
@echo "make -j build ARCH=x86-64 (A portable, slow compile for 64-bit systems)"
|
795 |
+
@echo "make -j build ARCH=x86-32 (A portable, slow compile for 32-bit systems)"
|
796 |
+
@echo ""
|
797 |
+
@echo "Advanced examples, for experienced users looking for performance: "
|
798 |
+
@echo ""
|
799 |
+
@echo "make help ARCH=x86-64-bmi2"
|
800 |
+
@echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0"
|
801 |
+
@echo "make -j build ARCH=x86-64-ssse3 COMP=clang"
|
802 |
+
@echo ""
|
803 |
+
@echo "-------------------------------"
|
804 |
+
ifeq ($(SUPPORTED_ARCH)$(help_skip_sanity), true)
|
805 |
+
@echo "The selected architecture $(ARCH) will enable the following configuration: "
|
806 |
+
@$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity
|
807 |
+
else
|
808 |
+
@echo "Specify a supported architecture with the ARCH option for more details"
|
809 |
+
@echo ""
|
810 |
+
endif
|
811 |
+
|
812 |
+
|
813 |
+
.PHONY: help build profile-build strip install clean net objclean profileclean \
|
814 |
+
config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
|
815 |
+
clang-profile-use clang-profile-make FORCE
|
816 |
+
|
817 |
+
build: net config-sanity
|
818 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
|
819 |
+
|
820 |
+
profile-build: net config-sanity objclean profileclean
|
821 |
+
@echo ""
|
822 |
+
@echo "Step 1/4. Building instrumented executable ..."
|
823 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
|
824 |
+
@echo ""
|
825 |
+
@echo "Step 2/4. Running benchmark for pgo-build ..."
|
826 |
+
$(PGOBENCH) 2>&1 | tail -n 4
|
827 |
+
@echo ""
|
828 |
+
@echo "Step 3/4. Building optimized executable ..."
|
829 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
|
830 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use)
|
831 |
+
@echo ""
|
832 |
+
@echo "Step 4/4. Deleting profile data ..."
|
833 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean
|
834 |
+
|
835 |
+
strip:
|
836 |
+
$(STRIP) $(EXE)
|
837 |
+
|
838 |
+
install:
|
839 |
+
-mkdir -p -m 755 $(BINDIR)
|
840 |
+
-cp $(EXE) $(BINDIR)
|
841 |
+
$(STRIP) $(BINDIR)/$(EXE)
|
842 |
+
|
843 |
+
# clean all
|
844 |
+
clean: objclean profileclean
|
845 |
+
@rm -f .depend *~ core
|
846 |
+
|
847 |
+
# evaluation network (nnue)
|
848 |
+
net:
|
849 |
+
$(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
|
850 |
+
@echo "Default net: $(nnuenet)"
|
851 |
+
$(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet))
|
852 |
+
$(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet))
|
853 |
+
$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi))
|
854 |
+
@if [ "x$(curl_or_wget)" = "x" ]; then \
|
855 |
+
echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \
|
856 |
+
fi
|
857 |
+
$(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi))
|
858 |
+
@if [ "x$(shasum_command)" = "x" ]; then \
|
859 |
+
echo "shasum / sha256sum not found, skipping net validation"; \
|
860 |
+
fi
|
861 |
+
@for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \
|
862 |
+
if test -f "$(nnuenet)"; then \
|
863 |
+
echo "$(nnuenet) available."; \
|
864 |
+
else \
|
865 |
+
if [ "x$(curl_or_wget)" != "x" ]; then \
|
866 |
+
echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\
|
867 |
+
fi; \
|
868 |
+
fi; \
|
869 |
+
if [ "x$(shasum_command)" != "x" ]; then \
|
870 |
+
if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
|
871 |
+
echo "Removing failed download"; rm -f $(nnuenet); \
|
872 |
+
else \
|
873 |
+
echo "Network validated"; break; \
|
874 |
+
fi; \
|
875 |
+
fi; \
|
876 |
+
done
|
877 |
+
@if ! test -f "$(nnuenet)"; then \
|
878 |
+
echo "Failed to download $(nnuenet)."; \
|
879 |
+
fi
|
880 |
+
|
881 |
+
# clean binaries and objects
|
882 |
+
objclean:
|
883 |
+
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
|
884 |
+
|
885 |
+
# clean auxiliary profiling files
|
886 |
+
profileclean:
|
887 |
+
@rm -rf profdir
|
888 |
+
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s
|
889 |
+
@rm -f stockfish.profdata *.profraw
|
890 |
+
@rm -f stockfish.*args*
|
891 |
+
@rm -f stockfish.*lt*
|
892 |
+
@rm -f stockfish.res
|
893 |
+
@rm -f ./-lstdc++.res
|
894 |
+
|
895 |
+
default:
|
896 |
+
help
|
897 |
+
|
898 |
+
### ==========================================================================
|
899 |
+
### Section 5. Private Targets
|
900 |
+
### ==========================================================================
|
901 |
+
|
902 |
+
all: $(EXE) .depend
|
903 |
+
|
904 |
+
config-sanity: net
|
905 |
+
@echo ""
|
906 |
+
@echo "Config:"
|
907 |
+
@echo "debug: '$(debug)'"
|
908 |
+
@echo "sanitize: '$(sanitize)'"
|
909 |
+
@echo "optimize: '$(optimize)'"
|
910 |
+
@echo "arch: '$(arch)'"
|
911 |
+
@echo "bits: '$(bits)'"
|
912 |
+
@echo "kernel: '$(KERNEL)'"
|
913 |
+
@echo "os: '$(OS)'"
|
914 |
+
@echo "prefetch: '$(prefetch)'"
|
915 |
+
@echo "popcnt: '$(popcnt)'"
|
916 |
+
@echo "pext: '$(pext)'"
|
917 |
+
@echo "sse: '$(sse)'"
|
918 |
+
@echo "mmx: '$(mmx)'"
|
919 |
+
@echo "sse2: '$(sse2)'"
|
920 |
+
@echo "ssse3: '$(ssse3)'"
|
921 |
+
@echo "sse41: '$(sse41)'"
|
922 |
+
@echo "avx2: '$(avx2)'"
|
923 |
+
@echo "avxvnni: '$(avxvnni)'"
|
924 |
+
@echo "avx512: '$(avx512)'"
|
925 |
+
@echo "vnni256: '$(vnni256)'"
|
926 |
+
@echo "vnni512: '$(vnni512)'"
|
927 |
+
@echo "neon: '$(neon)'"
|
928 |
+
@echo "arm_version: '$(arm_version)'"
|
929 |
+
@echo ""
|
930 |
+
@echo "Flags:"
|
931 |
+
@echo "CXX: $(CXX)"
|
932 |
+
@echo "CXXFLAGS: $(CXXFLAGS)"
|
933 |
+
@echo "LDFLAGS: $(LDFLAGS)"
|
934 |
+
@echo ""
|
935 |
+
@echo "Testing config sanity. If this fails, try 'make help' ..."
|
936 |
+
@echo ""
|
937 |
+
@test "$(debug)" = "yes" || test "$(debug)" = "no"
|
938 |
+
@test "$(optimize)" = "yes" || test "$(optimize)" = "no"
|
939 |
+
@test "$(SUPPORTED_ARCH)" = "true"
|
940 |
+
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
|
941 |
+
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
|
942 |
+
test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64"
|
943 |
+
@test "$(bits)" = "32" || test "$(bits)" = "64"
|
944 |
+
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
|
945 |
+
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
|
946 |
+
@test "$(pext)" = "yes" || test "$(pext)" = "no"
|
947 |
+
@test "$(sse)" = "yes" || test "$(sse)" = "no"
|
948 |
+
@test "$(mmx)" = "yes" || test "$(mmx)" = "no"
|
949 |
+
@test "$(sse2)" = "yes" || test "$(sse2)" = "no"
|
950 |
+
@test "$(ssse3)" = "yes" || test "$(ssse3)" = "no"
|
951 |
+
@test "$(sse41)" = "yes" || test "$(sse41)" = "no"
|
952 |
+
@test "$(avx2)" = "yes" || test "$(avx2)" = "no"
|
953 |
+
@test "$(avx512)" = "yes" || test "$(avx512)" = "no"
|
954 |
+
@test "$(vnni256)" = "yes" || test "$(vnni256)" = "no"
|
955 |
+
@test "$(vnni512)" = "yes" || test "$(vnni512)" = "no"
|
956 |
+
@test "$(neon)" = "yes" || test "$(neon)" = "no"
|
957 |
+
@test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \
|
958 |
+
|| test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang"
|
959 |
+
|
960 |
+
$(EXE): $(OBJS)
|
961 |
+
+$(CXX) -o $@ $(OBJS) $(LDFLAGS)
|
962 |
+
|
963 |
+
# Force recompilation to ensure version info is up-to-date
|
964 |
+
misc.o: FORCE
|
965 |
+
FORCE:
|
966 |
+
|
967 |
+
clang-profile-make:
|
968 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
969 |
+
EXTRACXXFLAGS='-fprofile-instr-generate ' \
|
970 |
+
EXTRALDFLAGS=' -fprofile-instr-generate' \
|
971 |
+
all
|
972 |
+
|
973 |
+
clang-profile-use:
|
974 |
+
$(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw
|
975 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
976 |
+
EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \
|
977 |
+
EXTRALDFLAGS='-fprofile-use ' \
|
978 |
+
all
|
979 |
+
|
980 |
+
gcc-profile-make:
|
981 |
+
@mkdir -p profdir
|
982 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
983 |
+
EXTRACXXFLAGS='-fprofile-generate=profdir' \
|
984 |
+
EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \
|
985 |
+
EXTRALDFLAGS='-lgcov' \
|
986 |
+
all
|
987 |
+
|
988 |
+
gcc-profile-use:
|
989 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
990 |
+
EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \
|
991 |
+
EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \
|
992 |
+
EXTRALDFLAGS='-lgcov' \
|
993 |
+
all
|
994 |
+
|
995 |
+
icc-profile-make:
|
996 |
+
@mkdir -p profdir
|
997 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
998 |
+
EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \
|
999 |
+
all
|
1000 |
+
|
1001 |
+
icc-profile-use:
|
1002 |
+
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
1003 |
+
EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
|
1004 |
+
all
|
1005 |
+
|
1006 |
+
.depend: $(SRCS)
|
1007 |
+
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
|
1008 |
+
|
1009 |
+
-include .depend
|
src/benchmark.cpp
ADDED
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <fstream>
|
20 |
+
#include <iostream>
|
21 |
+
#include <istream>
|
22 |
+
#include <vector>
|
23 |
+
|
24 |
+
#include "position.h"
|
25 |
+
|
26 |
+
using namespace std;
|
27 |
+
|
28 |
+
namespace {
|
29 |
+
|
30 |
+
const vector<string> Defaults = {
|
31 |
+
"setoption name UCI_Chess960 value false",
|
32 |
+
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
33 |
+
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10",
|
34 |
+
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11",
|
35 |
+
"4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19",
|
36 |
+
"rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6",
|
37 |
+
"r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4",
|
38 |
+
"r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15",
|
39 |
+
"r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13",
|
40 |
+
"r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16",
|
41 |
+
"4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17",
|
42 |
+
"2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11",
|
43 |
+
"r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16",
|
44 |
+
"3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22",
|
45 |
+
"r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18",
|
46 |
+
"4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22",
|
47 |
+
"3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26",
|
48 |
+
"6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1",
|
49 |
+
"3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1",
|
50 |
+
"2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3",
|
51 |
+
"8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1",
|
52 |
+
"7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1",
|
53 |
+
"8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1",
|
54 |
+
"8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1",
|
55 |
+
"8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1",
|
56 |
+
"8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1",
|
57 |
+
"5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1",
|
58 |
+
"6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1",
|
59 |
+
"1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1",
|
60 |
+
"6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1",
|
61 |
+
"8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1",
|
62 |
+
"5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90",
|
63 |
+
"4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21",
|
64 |
+
"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
|
65 |
+
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
|
66 |
+
"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
|
67 |
+
|
68 |
+
// 5-man positions
|
69 |
+
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate
|
70 |
+
"8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate
|
71 |
+
"8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", // draw
|
72 |
+
|
73 |
+
// 6-man positions
|
74 |
+
"8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1", // Re5 - mate
|
75 |
+
"8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1", // Ka2 - mate
|
76 |
+
"8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1", // Nd2 - draw
|
77 |
+
|
78 |
+
// 7-man positions
|
79 |
+
"8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw
|
80 |
+
|
81 |
+
// Mate and stalemate positions
|
82 |
+
"6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1",
|
83 |
+
"r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1",
|
84 |
+
"8/8/8/8/8/6k1/6p1/6K1 w - -",
|
85 |
+
"7k/7P/6K1/8/3B4/8/8/8 b - -",
|
86 |
+
|
87 |
+
// Chess 960
|
88 |
+
"setoption name UCI_Chess960 value true",
|
89 |
+
"bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
|
90 |
+
"nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
|
91 |
+
"setoption name UCI_Chess960 value false"
|
92 |
+
};
|
93 |
+
|
94 |
+
} // namespace
|
95 |
+
|
96 |
+
namespace Stockfish {
|
97 |
+
|
98 |
+
/// setup_bench() builds a list of UCI commands to be run by bench. There
|
99 |
+
/// are five parameters: TT size in MB, number of search threads that
|
100 |
+
/// should be used, the limit value spent for each position, a file name
|
101 |
+
/// where to look for positions in FEN format, the type of the limit:
|
102 |
+
/// depth, perft, nodes and movetime (in millisecs), and evaluation type
|
103 |
+
/// mixed (default), classical, NNUE.
|
104 |
+
///
|
105 |
+
/// bench -> search default positions up to depth 13
|
106 |
+
/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB)
|
107 |
+
/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec
|
108 |
+
/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each
|
109 |
+
/// bench 16 1 5 default perft -> run a perft 5 on default positions
|
110 |
+
|
111 |
+
vector<string> setup_bench(const Position& current, istream& is) {
|
112 |
+
|
113 |
+
vector<string> fens, list;
|
114 |
+
string go, token;
|
115 |
+
|
116 |
+
// Assign default values to missing arguments
|
117 |
+
string ttSize = (is >> token) ? token : "16";
|
118 |
+
string threads = (is >> token) ? token : "1";
|
119 |
+
string limit = (is >> token) ? token : "13";
|
120 |
+
string fenFile = (is >> token) ? token : "default";
|
121 |
+
string limitType = (is >> token) ? token : "depth";
|
122 |
+
string evalType = (is >> token) ? token : "mixed";
|
123 |
+
|
124 |
+
go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
|
125 |
+
|
126 |
+
if (fenFile == "default")
|
127 |
+
fens = Defaults;
|
128 |
+
|
129 |
+
else if (fenFile == "current")
|
130 |
+
fens.push_back(current.fen());
|
131 |
+
|
132 |
+
else
|
133 |
+
{
|
134 |
+
string fen;
|
135 |
+
ifstream file(fenFile);
|
136 |
+
|
137 |
+
if (!file.is_open())
|
138 |
+
{
|
139 |
+
cerr << "Unable to open file " << fenFile << endl;
|
140 |
+
exit(EXIT_FAILURE);
|
141 |
+
}
|
142 |
+
|
143 |
+
while (getline(file, fen))
|
144 |
+
if (!fen.empty())
|
145 |
+
fens.push_back(fen);
|
146 |
+
|
147 |
+
file.close();
|
148 |
+
}
|
149 |
+
|
150 |
+
list.emplace_back("setoption name Threads value " + threads);
|
151 |
+
list.emplace_back("setoption name Hash value " + ttSize);
|
152 |
+
list.emplace_back("ucinewgame");
|
153 |
+
|
154 |
+
size_t posCounter = 0;
|
155 |
+
|
156 |
+
for (const string& fen : fens)
|
157 |
+
if (fen.find("setoption") != string::npos)
|
158 |
+
list.emplace_back(fen);
|
159 |
+
else
|
160 |
+
{
|
161 |
+
if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0))
|
162 |
+
list.emplace_back("setoption name Use NNUE value false");
|
163 |
+
else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0))
|
164 |
+
list.emplace_back("setoption name Use NNUE value true");
|
165 |
+
list.emplace_back("position fen " + fen);
|
166 |
+
list.emplace_back(go);
|
167 |
+
++posCounter;
|
168 |
+
}
|
169 |
+
|
170 |
+
list.emplace_back("setoption name Use NNUE value true");
|
171 |
+
|
172 |
+
return list;
|
173 |
+
}
|
174 |
+
|
175 |
+
} // namespace Stockfish
|
src/bitbase.cpp
ADDED
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <cassert>
|
20 |
+
#include <vector>
|
21 |
+
#include <bitset>
|
22 |
+
|
23 |
+
#include "bitboard.h"
|
24 |
+
#include "types.h"
|
25 |
+
|
26 |
+
namespace Stockfish {
|
27 |
+
|
28 |
+
namespace {
|
29 |
+
|
30 |
+
// There are 24 possible pawn squares: files A to D and ranks from 2 to 7.
|
31 |
+
// Positions with the pawn on files E to H will be mirrored before probing.
|
32 |
+
constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608
|
33 |
+
|
34 |
+
std::bitset<MAX_INDEX> KPKBitbase;
|
35 |
+
|
36 |
+
// A KPK bitbase index is an integer in [0, IndexMax] range
|
37 |
+
//
|
38 |
+
// Information is mapped in a way that minimizes the number of iterations:
|
39 |
+
//
|
40 |
+
// bit 0- 5: white king square (from SQ_A1 to SQ_H8)
|
41 |
+
// bit 6-11: black king square (from SQ_A1 to SQ_H8)
|
42 |
+
// bit 12: side to move (WHITE or BLACK)
|
43 |
+
// bit 13-14: white pawn file (from FILE_A to FILE_D)
|
44 |
+
// bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2)
|
45 |
+
unsigned index(Color stm, Square bksq, Square wksq, Square psq) {
|
46 |
+
return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15);
|
47 |
+
}
|
48 |
+
|
49 |
+
enum Result {
|
50 |
+
INVALID = 0,
|
51 |
+
UNKNOWN = 1,
|
52 |
+
DRAW = 2,
|
53 |
+
WIN = 4
|
54 |
+
};
|
55 |
+
|
56 |
+
Result& operator|=(Result& r, Result v) { return r = Result(r | v); }
|
57 |
+
|
58 |
+
struct KPKPosition {
|
59 |
+
KPKPosition() = default;
|
60 |
+
explicit KPKPosition(unsigned idx);
|
61 |
+
operator Result() const { return result; }
|
62 |
+
Result classify(const std::vector<KPKPosition>& db);
|
63 |
+
|
64 |
+
Color stm;
|
65 |
+
Square ksq[COLOR_NB], psq;
|
66 |
+
Result result;
|
67 |
+
};
|
68 |
+
|
69 |
+
} // namespace
|
70 |
+
|
71 |
+
bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) {
|
72 |
+
|
73 |
+
assert(file_of(wpsq) <= FILE_D);
|
74 |
+
|
75 |
+
return KPKBitbase[index(stm, bksq, wksq, wpsq)];
|
76 |
+
}
|
77 |
+
|
78 |
+
|
79 |
+
void Bitbases::init() {
|
80 |
+
|
81 |
+
std::vector<KPKPosition> db(MAX_INDEX);
|
82 |
+
unsigned idx, repeat = 1;
|
83 |
+
|
84 |
+
// Initialize db with known win / draw positions
|
85 |
+
for (idx = 0; idx < MAX_INDEX; ++idx)
|
86 |
+
db[idx] = KPKPosition(idx);
|
87 |
+
|
88 |
+
// Iterate through the positions until none of the unknown positions can be
|
89 |
+
// changed to either wins or draws (15 cycles needed).
|
90 |
+
while (repeat)
|
91 |
+
for (repeat = idx = 0; idx < MAX_INDEX; ++idx)
|
92 |
+
repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN);
|
93 |
+
|
94 |
+
// Fill the bitbase with the decisive results
|
95 |
+
for (idx = 0; idx < MAX_INDEX; ++idx)
|
96 |
+
if (db[idx] == WIN)
|
97 |
+
KPKBitbase.set(idx);
|
98 |
+
}
|
99 |
+
|
100 |
+
namespace {
|
101 |
+
|
102 |
+
KPKPosition::KPKPosition(unsigned idx) {
|
103 |
+
|
104 |
+
ksq[WHITE] = Square((idx >> 0) & 0x3F);
|
105 |
+
ksq[BLACK] = Square((idx >> 6) & 0x3F);
|
106 |
+
stm = Color ((idx >> 12) & 0x01);
|
107 |
+
psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
|
108 |
+
|
109 |
+
// Invalid if two pieces are on the same square or if a king can be captured
|
110 |
+
if ( distance(ksq[WHITE], ksq[BLACK]) <= 1
|
111 |
+
|| ksq[WHITE] == psq
|
112 |
+
|| ksq[BLACK] == psq
|
113 |
+
|| (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK])))
|
114 |
+
result = INVALID;
|
115 |
+
|
116 |
+
// Win if the pawn can be promoted without getting captured
|
117 |
+
else if ( stm == WHITE
|
118 |
+
&& rank_of(psq) == RANK_7
|
119 |
+
&& ksq[WHITE] != psq + NORTH
|
120 |
+
&& ( distance(ksq[BLACK], psq + NORTH) > 1
|
121 |
+
|| (distance(ksq[WHITE], psq + NORTH) == 1)))
|
122 |
+
result = WIN;
|
123 |
+
|
124 |
+
// Draw if it is stalemate or the black king can capture the pawn
|
125 |
+
else if ( stm == BLACK
|
126 |
+
&& ( !(attacks_bb<KING>(ksq[BLACK]) & ~(attacks_bb<KING>(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq)))
|
127 |
+
|| (attacks_bb<KING>(ksq[BLACK]) & ~attacks_bb<KING>(ksq[WHITE]) & psq)))
|
128 |
+
result = DRAW;
|
129 |
+
|
130 |
+
// Position will be classified later
|
131 |
+
else
|
132 |
+
result = UNKNOWN;
|
133 |
+
}
|
134 |
+
|
135 |
+
Result KPKPosition::classify(const std::vector<KPKPosition>& db) {
|
136 |
+
|
137 |
+
// White to move: If one move leads to a position classified as WIN, the result
|
138 |
+
// of the current position is WIN. If all moves lead to positions classified
|
139 |
+
// as DRAW, the current position is classified as DRAW, otherwise the current
|
140 |
+
// position is classified as UNKNOWN.
|
141 |
+
//
|
142 |
+
// Black to move: If one move leads to a position classified as DRAW, the result
|
143 |
+
// of the current position is DRAW. If all moves lead to positions classified
|
144 |
+
// as WIN, the position is classified as WIN, otherwise the current position is
|
145 |
+
// classified as UNKNOWN.
|
146 |
+
const Result Good = (stm == WHITE ? WIN : DRAW);
|
147 |
+
const Result Bad = (stm == WHITE ? DRAW : WIN);
|
148 |
+
|
149 |
+
Result r = INVALID;
|
150 |
+
Bitboard b = attacks_bb<KING>(ksq[stm]);
|
151 |
+
|
152 |
+
while (b)
|
153 |
+
r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)]
|
154 |
+
: db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)];
|
155 |
+
|
156 |
+
if (stm == WHITE)
|
157 |
+
{
|
158 |
+
if (rank_of(psq) < RANK_7) // Single push
|
159 |
+
r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)];
|
160 |
+
|
161 |
+
if ( rank_of(psq) == RANK_2 // Double push
|
162 |
+
&& psq + NORTH != ksq[WHITE]
|
163 |
+
&& psq + NORTH != ksq[BLACK])
|
164 |
+
r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)];
|
165 |
+
}
|
166 |
+
|
167 |
+
return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad;
|
168 |
+
}
|
169 |
+
|
170 |
+
} // namespace
|
171 |
+
|
172 |
+
} // namespace Stockfish
|
src/bitboard.cpp
ADDED
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <algorithm>
|
20 |
+
#include <bitset>
|
21 |
+
|
22 |
+
#include "bitboard.h"
|
23 |
+
#include "misc.h"
|
24 |
+
|
25 |
+
namespace Stockfish {
|
26 |
+
|
27 |
+
uint8_t PopCnt16[1 << 16];
|
28 |
+
uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
29 |
+
|
30 |
+
Bitboard SquareBB[SQUARE_NB];
|
31 |
+
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
32 |
+
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
33 |
+
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
34 |
+
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
|
35 |
+
|
36 |
+
Magic RookMagics[SQUARE_NB];
|
37 |
+
Magic BishopMagics[SQUARE_NB];
|
38 |
+
|
39 |
+
namespace {
|
40 |
+
|
41 |
+
Bitboard RookTable[0x19000]; // To store rook attacks
|
42 |
+
Bitboard BishopTable[0x1480]; // To store bishop attacks
|
43 |
+
|
44 |
+
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
|
45 |
+
|
46 |
+
}
|
47 |
+
|
48 |
+
/// safe_destination() returns the bitboard of target square for the given step
|
49 |
+
/// from the given square. If the step is off the board, returns empty bitboard.
|
50 |
+
|
51 |
+
inline Bitboard safe_destination(Square s, int step) {
|
52 |
+
Square to = Square(s + step);
|
53 |
+
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
|
54 |
+
}
|
55 |
+
|
56 |
+
|
57 |
+
/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
|
58 |
+
/// to be printed to standard output. Useful for debugging.
|
59 |
+
|
60 |
+
std::string Bitboards::pretty(Bitboard b) {
|
61 |
+
|
62 |
+
std::string s = "+---+---+---+---+---+---+---+---+\n";
|
63 |
+
|
64 |
+
for (Rank r = RANK_8; r >= RANK_1; --r)
|
65 |
+
{
|
66 |
+
for (File f = FILE_A; f <= FILE_H; ++f)
|
67 |
+
s += b & make_square(f, r) ? "| X " : "| ";
|
68 |
+
|
69 |
+
s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
|
70 |
+
}
|
71 |
+
s += " a b c d e f g h\n";
|
72 |
+
|
73 |
+
return s;
|
74 |
+
}
|
75 |
+
|
76 |
+
|
77 |
+
/// Bitboards::init() initializes various bitboard tables. It is called at
|
78 |
+
/// startup and relies on global objects to be already zero-initialized.
|
79 |
+
|
80 |
+
void Bitboards::init() {
|
81 |
+
|
82 |
+
for (unsigned i = 0; i < (1 << 16); ++i)
|
83 |
+
PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
|
84 |
+
|
85 |
+
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
86 |
+
SquareBB[s] = (1ULL << s);
|
87 |
+
|
88 |
+
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
89 |
+
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
90 |
+
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
|
91 |
+
|
92 |
+
init_magics(ROOK, RookTable, RookMagics);
|
93 |
+
init_magics(BISHOP, BishopTable, BishopMagics);
|
94 |
+
|
95 |
+
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
96 |
+
{
|
97 |
+
PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
|
98 |
+
PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
|
99 |
+
|
100 |
+
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} )
|
101 |
+
PseudoAttacks[KING][s1] |= safe_destination(s1, step);
|
102 |
+
|
103 |
+
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} )
|
104 |
+
PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
|
105 |
+
|
106 |
+
PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
|
107 |
+
PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0);
|
108 |
+
|
109 |
+
for (PieceType pt : { BISHOP, ROOK })
|
110 |
+
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
111 |
+
{
|
112 |
+
if (PseudoAttacks[pt][s1] & s2)
|
113 |
+
{
|
114 |
+
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
|
115 |
+
BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
|
116 |
+
}
|
117 |
+
BetweenBB[s1][s2] |= s2;
|
118 |
+
}
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
namespace {
|
123 |
+
|
124 |
+
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
|
125 |
+
|
126 |
+
Bitboard attacks = 0;
|
127 |
+
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
|
128 |
+
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
|
129 |
+
|
130 |
+
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
|
131 |
+
{
|
132 |
+
Square s = sq;
|
133 |
+
while (safe_destination(s, d) && !(occupied & s))
|
134 |
+
attacks |= (s += d);
|
135 |
+
}
|
136 |
+
|
137 |
+
return attacks;
|
138 |
+
}
|
139 |
+
|
140 |
+
|
141 |
+
// init_magics() computes all rook and bishop attacks at startup. Magic
|
142 |
+
// bitboards are used to look up attacks of sliding pieces. As a reference see
|
143 |
+
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
|
144 |
+
// called "fancy" approach.
|
145 |
+
|
146 |
+
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
|
147 |
+
|
148 |
+
// Optimal PRNG seeds to pick the correct magics in the shortest time
|
149 |
+
int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 },
|
150 |
+
{ 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } };
|
151 |
+
|
152 |
+
Bitboard occupancy[4096], reference[4096], edges, b;
|
153 |
+
int epoch[4096] = {}, cnt = 0, size = 0;
|
154 |
+
|
155 |
+
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
156 |
+
{
|
157 |
+
// Board edges are not considered in the relevant occupancies
|
158 |
+
edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
|
159 |
+
|
160 |
+
// Given a square 's', the mask is the bitboard of sliding attacks from
|
161 |
+
// 's' computed on an empty board. The index must be big enough to contain
|
162 |
+
// all the attacks for each possible subset of the mask and so is 2 power
|
163 |
+
// the number of 1s of the mask. Hence we deduce the size of the shift to
|
164 |
+
// apply to the 64 or 32 bits word to get the index.
|
165 |
+
Magic& m = magics[s];
|
166 |
+
m.mask = sliding_attack(pt, s, 0) & ~edges;
|
167 |
+
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
|
168 |
+
|
169 |
+
// Set the offset for the attacks table of the square. We have individual
|
170 |
+
// table sizes for each square with "Fancy Magic Bitboards".
|
171 |
+
m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size;
|
172 |
+
|
173 |
+
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
|
174 |
+
// store the corresponding sliding attack bitboard in reference[].
|
175 |
+
b = size = 0;
|
176 |
+
do {
|
177 |
+
occupancy[size] = b;
|
178 |
+
reference[size] = sliding_attack(pt, s, b);
|
179 |
+
|
180 |
+
if (HasPext)
|
181 |
+
m.attacks[pext(b, m.mask)] = reference[size];
|
182 |
+
|
183 |
+
size++;
|
184 |
+
b = (b - m.mask) & m.mask;
|
185 |
+
} while (b);
|
186 |
+
|
187 |
+
if (HasPext)
|
188 |
+
continue;
|
189 |
+
|
190 |
+
PRNG rng(seeds[Is64Bit][rank_of(s)]);
|
191 |
+
|
192 |
+
// Find a magic for square 's' picking up an (almost) random number
|
193 |
+
// until we find the one that passes the verification test.
|
194 |
+
for (int i = 0; i < size; )
|
195 |
+
{
|
196 |
+
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; )
|
197 |
+
m.magic = rng.sparse_rand<Bitboard>();
|
198 |
+
|
199 |
+
// A good magic must map every possible occupancy to an index that
|
200 |
+
// looks up the correct sliding attack in the attacks[s] database.
|
201 |
+
// Note that we build up the database for square 's' as a side
|
202 |
+
// effect of verifying the magic. Keep track of the attempt count
|
203 |
+
// and save it in epoch[], little speed-up trick to avoid resetting
|
204 |
+
// m.attacks[] after every failed attempt.
|
205 |
+
for (++cnt, i = 0; i < size; ++i)
|
206 |
+
{
|
207 |
+
unsigned idx = m.index(occupancy[i]);
|
208 |
+
|
209 |
+
if (epoch[idx] < cnt)
|
210 |
+
{
|
211 |
+
epoch[idx] = cnt;
|
212 |
+
m.attacks[idx] = reference[i];
|
213 |
+
}
|
214 |
+
else if (m.attacks[idx] != reference[i])
|
215 |
+
break;
|
216 |
+
}
|
217 |
+
}
|
218 |
+
}
|
219 |
+
}
|
220 |
+
}
|
221 |
+
|
222 |
+
} // namespace Stockfish
|
src/bitboard.h
ADDED
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef BITBOARD_H_INCLUDED
|
20 |
+
#define BITBOARD_H_INCLUDED
|
21 |
+
|
22 |
+
#include <string>
|
23 |
+
|
24 |
+
#include "types.h"
|
25 |
+
|
26 |
+
namespace Stockfish {
|
27 |
+
|
28 |
+
namespace Bitbases {
|
29 |
+
|
30 |
+
void init();
|
31 |
+
bool probe(Square wksq, Square wpsq, Square bksq, Color us);
|
32 |
+
|
33 |
+
} // namespace Stockfish::Bitbases
|
34 |
+
|
35 |
+
namespace Bitboards {
|
36 |
+
|
37 |
+
void init();
|
38 |
+
std::string pretty(Bitboard b);
|
39 |
+
|
40 |
+
} // namespace Stockfish::Bitboards
|
41 |
+
|
42 |
+
constexpr Bitboard AllSquares = ~Bitboard(0);
|
43 |
+
constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL;
|
44 |
+
|
45 |
+
constexpr Bitboard FileABB = 0x0101010101010101ULL;
|
46 |
+
constexpr Bitboard FileBBB = FileABB << 1;
|
47 |
+
constexpr Bitboard FileCBB = FileABB << 2;
|
48 |
+
constexpr Bitboard FileDBB = FileABB << 3;
|
49 |
+
constexpr Bitboard FileEBB = FileABB << 4;
|
50 |
+
constexpr Bitboard FileFBB = FileABB << 5;
|
51 |
+
constexpr Bitboard FileGBB = FileABB << 6;
|
52 |
+
constexpr Bitboard FileHBB = FileABB << 7;
|
53 |
+
|
54 |
+
constexpr Bitboard Rank1BB = 0xFF;
|
55 |
+
constexpr Bitboard Rank2BB = Rank1BB << (8 * 1);
|
56 |
+
constexpr Bitboard Rank3BB = Rank1BB << (8 * 2);
|
57 |
+
constexpr Bitboard Rank4BB = Rank1BB << (8 * 3);
|
58 |
+
constexpr Bitboard Rank5BB = Rank1BB << (8 * 4);
|
59 |
+
constexpr Bitboard Rank6BB = Rank1BB << (8 * 5);
|
60 |
+
constexpr Bitboard Rank7BB = Rank1BB << (8 * 6);
|
61 |
+
constexpr Bitboard Rank8BB = Rank1BB << (8 * 7);
|
62 |
+
|
63 |
+
constexpr Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB;
|
64 |
+
constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB;
|
65 |
+
constexpr Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB;
|
66 |
+
constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB);
|
67 |
+
|
68 |
+
constexpr Bitboard KingFlank[FILE_NB] = {
|
69 |
+
QueenSide ^ FileDBB, QueenSide, QueenSide,
|
70 |
+
CenterFiles, CenterFiles,
|
71 |
+
KingSide, KingSide, KingSide ^ FileEBB
|
72 |
+
};
|
73 |
+
|
74 |
+
extern uint8_t PopCnt16[1 << 16];
|
75 |
+
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
76 |
+
|
77 |
+
extern Bitboard SquareBB[SQUARE_NB];
|
78 |
+
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
79 |
+
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
80 |
+
extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
81 |
+
extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
|
82 |
+
|
83 |
+
|
84 |
+
/// Magic holds all magic bitboards relevant data for a single square
|
85 |
+
struct Magic {
|
86 |
+
Bitboard mask;
|
87 |
+
Bitboard magic;
|
88 |
+
Bitboard* attacks;
|
89 |
+
unsigned shift;
|
90 |
+
|
91 |
+
// Compute the attack's index using the 'magic bitboards' approach
|
92 |
+
unsigned index(Bitboard occupied) const {
|
93 |
+
|
94 |
+
if (HasPext)
|
95 |
+
return unsigned(pext(occupied, mask));
|
96 |
+
|
97 |
+
if (Is64Bit)
|
98 |
+
return unsigned(((occupied & mask) * magic) >> shift);
|
99 |
+
|
100 |
+
unsigned lo = unsigned(occupied) & unsigned(mask);
|
101 |
+
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
|
102 |
+
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
|
103 |
+
}
|
104 |
+
};
|
105 |
+
|
106 |
+
extern Magic RookMagics[SQUARE_NB];
|
107 |
+
extern Magic BishopMagics[SQUARE_NB];
|
108 |
+
|
109 |
+
inline Bitboard square_bb(Square s) {
|
110 |
+
assert(is_ok(s));
|
111 |
+
return SquareBB[s];
|
112 |
+
}
|
113 |
+
|
114 |
+
|
115 |
+
/// Overloads of bitwise operators between a Bitboard and a Square for testing
|
116 |
+
/// whether a given bit is set in a bitboard, and for setting and clearing bits.
|
117 |
+
|
118 |
+
inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); }
|
119 |
+
inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); }
|
120 |
+
inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); }
|
121 |
+
inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); }
|
122 |
+
inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); }
|
123 |
+
|
124 |
+
inline Bitboard operator&(Square s, Bitboard b) { return b & s; }
|
125 |
+
inline Bitboard operator|(Square s, Bitboard b) { return b | s; }
|
126 |
+
inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; }
|
127 |
+
|
128 |
+
inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
|
129 |
+
|
130 |
+
constexpr bool more_than_one(Bitboard b) {
|
131 |
+
return b & (b - 1);
|
132 |
+
}
|
133 |
+
|
134 |
+
|
135 |
+
constexpr bool opposite_colors(Square s1, Square s2) {
|
136 |
+
return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1;
|
137 |
+
}
|
138 |
+
|
139 |
+
|
140 |
+
/// rank_bb() and file_bb() return a bitboard representing all the squares on
|
141 |
+
/// the given file or rank.
|
142 |
+
|
143 |
+
constexpr Bitboard rank_bb(Rank r) {
|
144 |
+
return Rank1BB << (8 * r);
|
145 |
+
}
|
146 |
+
|
147 |
+
constexpr Bitboard rank_bb(Square s) {
|
148 |
+
return rank_bb(rank_of(s));
|
149 |
+
}
|
150 |
+
|
151 |
+
constexpr Bitboard file_bb(File f) {
|
152 |
+
return FileABB << f;
|
153 |
+
}
|
154 |
+
|
155 |
+
constexpr Bitboard file_bb(Square s) {
|
156 |
+
return file_bb(file_of(s));
|
157 |
+
}
|
158 |
+
|
159 |
+
|
160 |
+
/// shift() moves a bitboard one or two steps as specified by the direction D
|
161 |
+
|
162 |
+
template<Direction D>
|
163 |
+
constexpr Bitboard shift(Bitboard b) {
|
164 |
+
return D == NORTH ? b << 8 : D == SOUTH ? b >> 8
|
165 |
+
: D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16
|
166 |
+
: D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1
|
167 |
+
: D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7
|
168 |
+
: D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9
|
169 |
+
: 0;
|
170 |
+
}
|
171 |
+
|
172 |
+
|
173 |
+
/// pawn_attacks_bb() returns the squares attacked by pawns of the given color
|
174 |
+
/// from the squares in the given bitboard.
|
175 |
+
|
176 |
+
template<Color C>
|
177 |
+
constexpr Bitboard pawn_attacks_bb(Bitboard b) {
|
178 |
+
return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
|
179 |
+
: shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
|
180 |
+
}
|
181 |
+
|
182 |
+
inline Bitboard pawn_attacks_bb(Color c, Square s) {
|
183 |
+
|
184 |
+
assert(is_ok(s));
|
185 |
+
return PawnAttacks[c][s];
|
186 |
+
}
|
187 |
+
|
188 |
+
|
189 |
+
/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the
|
190 |
+
/// given color from the squares in the given bitboard.
|
191 |
+
|
192 |
+
template<Color C>
|
193 |
+
constexpr Bitboard pawn_double_attacks_bb(Bitboard b) {
|
194 |
+
return C == WHITE ? shift<NORTH_WEST>(b) & shift<NORTH_EAST>(b)
|
195 |
+
: shift<SOUTH_WEST>(b) & shift<SOUTH_EAST>(b);
|
196 |
+
}
|
197 |
+
|
198 |
+
|
199 |
+
/// adjacent_files_bb() returns a bitboard representing all the squares on the
|
200 |
+
/// adjacent files of a given square.
|
201 |
+
|
202 |
+
constexpr Bitboard adjacent_files_bb(Square s) {
|
203 |
+
return shift<EAST>(file_bb(s)) | shift<WEST>(file_bb(s));
|
204 |
+
}
|
205 |
+
|
206 |
+
|
207 |
+
/// line_bb() returns a bitboard representing an entire line (from board edge
|
208 |
+
/// to board edge) that intersects the two given squares. If the given squares
|
209 |
+
/// are not on a same file/rank/diagonal, the function returns 0. For instance,
|
210 |
+
/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal.
|
211 |
+
|
212 |
+
inline Bitboard line_bb(Square s1, Square s2) {
|
213 |
+
|
214 |
+
assert(is_ok(s1) && is_ok(s2));
|
215 |
+
|
216 |
+
return LineBB[s1][s2];
|
217 |
+
}
|
218 |
+
|
219 |
+
|
220 |
+
/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open
|
221 |
+
/// segment between the squares s1 and s2 (excluding s1 but including s2). If the
|
222 |
+
/// given squares are not on a same file/rank/diagonal, it returns s2. For instance,
|
223 |
+
/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but
|
224 |
+
/// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick
|
225 |
+
/// allows to generate non-king evasion moves faster: the defending piece must either
|
226 |
+
/// interpose itself to cover the check or capture the checking piece.
|
227 |
+
|
228 |
+
inline Bitboard between_bb(Square s1, Square s2) {
|
229 |
+
|
230 |
+
assert(is_ok(s1) && is_ok(s2));
|
231 |
+
|
232 |
+
return BetweenBB[s1][s2];
|
233 |
+
}
|
234 |
+
|
235 |
+
|
236 |
+
/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in
|
237 |
+
/// front of the given one, from the point of view of the given color. For instance,
|
238 |
+
/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2.
|
239 |
+
|
240 |
+
constexpr Bitboard forward_ranks_bb(Color c, Square s) {
|
241 |
+
return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s)
|
242 |
+
: ~Rank8BB >> 8 * relative_rank(BLACK, s);
|
243 |
+
}
|
244 |
+
|
245 |
+
|
246 |
+
/// forward_file_bb() returns a bitboard representing all the squares along the
|
247 |
+
/// line in front of the given one, from the point of view of the given color.
|
248 |
+
|
249 |
+
constexpr Bitboard forward_file_bb(Color c, Square s) {
|
250 |
+
return forward_ranks_bb(c, s) & file_bb(s);
|
251 |
+
}
|
252 |
+
|
253 |
+
|
254 |
+
/// pawn_attack_span() returns a bitboard representing all the squares that can
|
255 |
+
/// be attacked by a pawn of the given color when it moves along its file, starting
|
256 |
+
/// from the given square.
|
257 |
+
|
258 |
+
constexpr Bitboard pawn_attack_span(Color c, Square s) {
|
259 |
+
return forward_ranks_bb(c, s) & adjacent_files_bb(s);
|
260 |
+
}
|
261 |
+
|
262 |
+
|
263 |
+
/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of
|
264 |
+
/// the given color and on the given square is a passed pawn.
|
265 |
+
|
266 |
+
constexpr Bitboard passed_pawn_span(Color c, Square s) {
|
267 |
+
return pawn_attack_span(c, s) | forward_file_bb(c, s);
|
268 |
+
}
|
269 |
+
|
270 |
+
|
271 |
+
/// aligned() returns true if the squares s1, s2 and s3 are aligned either on a
|
272 |
+
/// straight or on a diagonal line.
|
273 |
+
|
274 |
+
inline bool aligned(Square s1, Square s2, Square s3) {
|
275 |
+
return line_bb(s1, s2) & s3;
|
276 |
+
}
|
277 |
+
|
278 |
+
|
279 |
+
/// distance() functions return the distance between x and y, defined as the
|
280 |
+
/// number of steps for a king in x to reach y.
|
281 |
+
|
282 |
+
template<typename T1 = Square> inline int distance(Square x, Square y);
|
283 |
+
template<> inline int distance<File>(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); }
|
284 |
+
template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
|
285 |
+
template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
|
286 |
+
|
287 |
+
inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
|
288 |
+
inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); }
|
289 |
+
|
290 |
+
|
291 |
+
/// attacks_bb(Square) returns the pseudo attacks of the give piece type
|
292 |
+
/// assuming an empty board.
|
293 |
+
|
294 |
+
template<PieceType Pt>
|
295 |
+
inline Bitboard attacks_bb(Square s) {
|
296 |
+
|
297 |
+
assert((Pt != PAWN) && (is_ok(s)));
|
298 |
+
|
299 |
+
return PseudoAttacks[Pt][s];
|
300 |
+
}
|
301 |
+
|
302 |
+
|
303 |
+
/// attacks_bb(Square, Bitboard) returns the attacks by the given piece
|
304 |
+
/// assuming the board is occupied according to the passed Bitboard.
|
305 |
+
/// Sliding piece attacks do not continue passed an occupied square.
|
306 |
+
|
307 |
+
template<PieceType Pt>
|
308 |
+
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
309 |
+
|
310 |
+
assert((Pt != PAWN) && (is_ok(s)));
|
311 |
+
|
312 |
+
switch (Pt)
|
313 |
+
{
|
314 |
+
case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
|
315 |
+
case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)];
|
316 |
+
case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
317 |
+
default : return PseudoAttacks[Pt][s];
|
318 |
+
}
|
319 |
+
}
|
320 |
+
|
321 |
+
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
|
322 |
+
|
323 |
+
assert((pt != PAWN) && (is_ok(s)));
|
324 |
+
|
325 |
+
switch (pt)
|
326 |
+
{
|
327 |
+
case BISHOP: return attacks_bb<BISHOP>(s, occupied);
|
328 |
+
case ROOK : return attacks_bb< ROOK>(s, occupied);
|
329 |
+
case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
330 |
+
default : return PseudoAttacks[pt][s];
|
331 |
+
}
|
332 |
+
}
|
333 |
+
|
334 |
+
|
335 |
+
/// popcount() counts the number of non-zero bits in a bitboard
|
336 |
+
|
337 |
+
inline int popcount(Bitboard b) {
|
338 |
+
|
339 |
+
#ifndef USE_POPCNT
|
340 |
+
|
341 |
+
union { Bitboard bb; uint16_t u[4]; } v = { b };
|
342 |
+
return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]];
|
343 |
+
|
344 |
+
#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
|
345 |
+
|
346 |
+
return (int)_mm_popcnt_u64(b);
|
347 |
+
|
348 |
+
#else // Assumed gcc or compatible compiler
|
349 |
+
|
350 |
+
return __builtin_popcountll(b);
|
351 |
+
|
352 |
+
#endif
|
353 |
+
}
|
354 |
+
|
355 |
+
|
356 |
+
/// lsb() and msb() return the least/most significant bit in a non-zero bitboard
|
357 |
+
|
358 |
+
#if defined(__GNUC__) // GCC, Clang, ICC
|
359 |
+
|
360 |
+
inline Square lsb(Bitboard b) {
|
361 |
+
assert(b);
|
362 |
+
return Square(__builtin_ctzll(b));
|
363 |
+
}
|
364 |
+
|
365 |
+
inline Square msb(Bitboard b) {
|
366 |
+
assert(b);
|
367 |
+
return Square(63 ^ __builtin_clzll(b));
|
368 |
+
}
|
369 |
+
|
370 |
+
#elif defined(_MSC_VER) // MSVC
|
371 |
+
|
372 |
+
#ifdef _WIN64 // MSVC, WIN64
|
373 |
+
|
374 |
+
inline Square lsb(Bitboard b) {
|
375 |
+
assert(b);
|
376 |
+
unsigned long idx;
|
377 |
+
_BitScanForward64(&idx, b);
|
378 |
+
return (Square) idx;
|
379 |
+
}
|
380 |
+
|
381 |
+
inline Square msb(Bitboard b) {
|
382 |
+
assert(b);
|
383 |
+
unsigned long idx;
|
384 |
+
_BitScanReverse64(&idx, b);
|
385 |
+
return (Square) idx;
|
386 |
+
}
|
387 |
+
|
388 |
+
#else // MSVC, WIN32
|
389 |
+
|
390 |
+
inline Square lsb(Bitboard b) {
|
391 |
+
assert(b);
|
392 |
+
unsigned long idx;
|
393 |
+
|
394 |
+
if (b & 0xffffffff) {
|
395 |
+
_BitScanForward(&idx, int32_t(b));
|
396 |
+
return Square(idx);
|
397 |
+
} else {
|
398 |
+
_BitScanForward(&idx, int32_t(b >> 32));
|
399 |
+
return Square(idx + 32);
|
400 |
+
}
|
401 |
+
}
|
402 |
+
|
403 |
+
inline Square msb(Bitboard b) {
|
404 |
+
assert(b);
|
405 |
+
unsigned long idx;
|
406 |
+
|
407 |
+
if (b >> 32) {
|
408 |
+
_BitScanReverse(&idx, int32_t(b >> 32));
|
409 |
+
return Square(idx + 32);
|
410 |
+
} else {
|
411 |
+
_BitScanReverse(&idx, int32_t(b));
|
412 |
+
return Square(idx);
|
413 |
+
}
|
414 |
+
}
|
415 |
+
|
416 |
+
#endif
|
417 |
+
|
418 |
+
#else // Compiler is neither GCC nor MSVC compatible
|
419 |
+
|
420 |
+
#error "Compiler not supported."
|
421 |
+
|
422 |
+
#endif
|
423 |
+
|
424 |
+
/// least_significant_square_bb() returns the bitboard of the least significant
|
425 |
+
/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)).
|
426 |
+
|
427 |
+
inline Bitboard least_significant_square_bb(Bitboard b) {
|
428 |
+
assert(b);
|
429 |
+
return b & -b;
|
430 |
+
}
|
431 |
+
|
432 |
+
/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
|
433 |
+
|
434 |
+
inline Square pop_lsb(Bitboard& b) {
|
435 |
+
assert(b);
|
436 |
+
const Square s = lsb(b);
|
437 |
+
b &= b - 1;
|
438 |
+
return s;
|
439 |
+
}
|
440 |
+
|
441 |
+
|
442 |
+
/// frontmost_sq() returns the most advanced square for the given color,
|
443 |
+
/// requires a non-zero bitboard.
|
444 |
+
inline Square frontmost_sq(Color c, Bitboard b) {
|
445 |
+
assert(b);
|
446 |
+
return c == WHITE ? msb(b) : lsb(b);
|
447 |
+
}
|
448 |
+
|
449 |
+
} // namespace Stockfish
|
450 |
+
|
451 |
+
#endif // #ifndef BITBOARD_H_INCLUDED
|
src/endgame.cpp
ADDED
@@ -0,0 +1,747 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <cassert>
|
20 |
+
|
21 |
+
#include "bitboard.h"
|
22 |
+
#include "endgame.h"
|
23 |
+
#include "movegen.h"
|
24 |
+
|
25 |
+
namespace Stockfish {
|
26 |
+
|
27 |
+
namespace {
|
28 |
+
|
29 |
+
// Used to drive the king towards the edge of the board
|
30 |
+
// in KX vs K and KQ vs KR endgames.
|
31 |
+
// Values range from 27 (center squares) to 90 (in the corners)
|
32 |
+
inline int push_to_edge(Square s) {
|
33 |
+
int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s));
|
34 |
+
return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2);
|
35 |
+
}
|
36 |
+
|
37 |
+
// Used to drive the king towards A1H8 corners in KBN vs K endgames.
|
38 |
+
// Values range from 0 on A8H1 diagonal to 7 in A1H8 corners
|
39 |
+
inline int push_to_corner(Square s) {
|
40 |
+
return abs(7 - rank_of(s) - file_of(s));
|
41 |
+
}
|
42 |
+
|
43 |
+
// Drive a piece close to or away from another piece
|
44 |
+
inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); }
|
45 |
+
inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); }
|
46 |
+
|
47 |
+
#ifndef NDEBUG
|
48 |
+
bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
|
49 |
+
return pos.non_pawn_material(c) == npm && pos.count<PAWN>(c) == pawnsCnt;
|
50 |
+
}
|
51 |
+
#endif
|
52 |
+
|
53 |
+
// Map the square as if strongSide is white and strongSide's only pawn
|
54 |
+
// is on the left half of the board.
|
55 |
+
Square normalize(const Position& pos, Color strongSide, Square sq) {
|
56 |
+
|
57 |
+
assert(pos.count<PAWN>(strongSide) == 1);
|
58 |
+
|
59 |
+
if (file_of(pos.square<PAWN>(strongSide)) >= FILE_E)
|
60 |
+
sq = flip_file(sq);
|
61 |
+
|
62 |
+
return strongSide == WHITE ? sq : flip_rank(sq);
|
63 |
+
}
|
64 |
+
|
65 |
+
} // namespace
|
66 |
+
|
67 |
+
|
68 |
+
namespace Endgames {
|
69 |
+
|
70 |
+
std::pair<Map<Value>, Map<ScaleFactor>> maps;
|
71 |
+
|
72 |
+
void init() {
|
73 |
+
|
74 |
+
add<KPK>("KPK");
|
75 |
+
add<KNNK>("KNNK");
|
76 |
+
add<KBNK>("KBNK");
|
77 |
+
add<KRKP>("KRKP");
|
78 |
+
add<KRKB>("KRKB");
|
79 |
+
add<KRKN>("KRKN");
|
80 |
+
add<KQKP>("KQKP");
|
81 |
+
add<KQKR>("KQKR");
|
82 |
+
add<KNNKP>("KNNKP");
|
83 |
+
|
84 |
+
add<KRPKR>("KRPKR");
|
85 |
+
add<KRPKB>("KRPKB");
|
86 |
+
add<KBPKB>("KBPKB");
|
87 |
+
add<KBPKN>("KBPKN");
|
88 |
+
add<KBPPKB>("KBPPKB");
|
89 |
+
add<KRPPKRP>("KRPPKRP");
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
|
94 |
+
/// Mate with KX vs K. This function is used to evaluate positions with
|
95 |
+
/// king and plenty of material vs a lone king. It simply gives the
|
96 |
+
/// attacking side a bonus for driving the defending king towards the edge
|
97 |
+
/// of the board, and for keeping the distance between the two kings small.
|
98 |
+
template<>
|
99 |
+
Value Endgame<KXK>::operator()(const Position& pos) const {
|
100 |
+
|
101 |
+
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
102 |
+
assert(!pos.checkers()); // Eval is never called when in check
|
103 |
+
|
104 |
+
// Stalemate detection with lone king
|
105 |
+
if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size())
|
106 |
+
return VALUE_DRAW;
|
107 |
+
|
108 |
+
Square strongKing = pos.square<KING>(strongSide);
|
109 |
+
Square weakKing = pos.square<KING>(weakSide);
|
110 |
+
|
111 |
+
Value result = pos.non_pawn_material(strongSide)
|
112 |
+
+ pos.count<PAWN>(strongSide) * PawnValueEg
|
113 |
+
+ push_to_edge(weakKing)
|
114 |
+
+ push_close(strongKing, weakKing);
|
115 |
+
|
116 |
+
if ( pos.count<QUEEN>(strongSide)
|
117 |
+
|| pos.count<ROOK>(strongSide)
|
118 |
+
||(pos.count<BISHOP>(strongSide) && pos.count<KNIGHT>(strongSide))
|
119 |
+
|| ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares)
|
120 |
+
&& (pos.pieces(strongSide, BISHOP) & DarkSquares)))
|
121 |
+
result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
122 |
+
|
123 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
124 |
+
}
|
125 |
+
|
126 |
+
|
127 |
+
/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the
|
128 |
+
/// defending king towards a corner square that our bishop attacks.
|
129 |
+
template<>
|
130 |
+
Value Endgame<KBNK>::operator()(const Position& pos) const {
|
131 |
+
|
132 |
+
assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
|
133 |
+
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
134 |
+
|
135 |
+
Square strongKing = pos.square<KING>(strongSide);
|
136 |
+
Square strongBishop = pos.square<BISHOP>(strongSide);
|
137 |
+
Square weakKing = pos.square<KING>(weakSide);
|
138 |
+
|
139 |
+
// If our bishop does not attack A1/H8, we flip the enemy king square
|
140 |
+
// to drive to opposite corners (A8/H1).
|
141 |
+
|
142 |
+
Value result = (VALUE_KNOWN_WIN + 3520)
|
143 |
+
+ push_close(strongKing, weakKing)
|
144 |
+
+ 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing);
|
145 |
+
|
146 |
+
assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY);
|
147 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
148 |
+
}
|
149 |
+
|
150 |
+
|
151 |
+
/// KP vs K. This endgame is evaluated with the help of a bitbase
|
152 |
+
template<>
|
153 |
+
Value Endgame<KPK>::operator()(const Position& pos) const {
|
154 |
+
|
155 |
+
assert(verify_material(pos, strongSide, VALUE_ZERO, 1));
|
156 |
+
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
157 |
+
|
158 |
+
// Assume strongSide is white and the pawn is on files A-D
|
159 |
+
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
160 |
+
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
161 |
+
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
162 |
+
|
163 |
+
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
|
164 |
+
|
165 |
+
if (!Bitbases::probe(strongKing, strongPawn, weakKing, us))
|
166 |
+
return VALUE_DRAW;
|
167 |
+
|
168 |
+
Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn));
|
169 |
+
|
170 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
171 |
+
}
|
172 |
+
|
173 |
+
|
174 |
+
/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without
|
175 |
+
/// a bitbase. The function below returns drawish scores when the pawn is
|
176 |
+
/// far advanced with support of the king, while the attacking king is far
|
177 |
+
/// away.
|
178 |
+
template<>
|
179 |
+
Value Endgame<KRKP>::operator()(const Position& pos) const {
|
180 |
+
|
181 |
+
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
182 |
+
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
183 |
+
|
184 |
+
Square strongKing = pos.square<KING>(strongSide);
|
185 |
+
Square weakKing = pos.square<KING>(weakSide);
|
186 |
+
Square strongRook = pos.square<ROOK>(strongSide);
|
187 |
+
Square weakPawn = pos.square<PAWN>(weakSide);
|
188 |
+
Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8));
|
189 |
+
Value result;
|
190 |
+
|
191 |
+
// If the stronger side's king is in front of the pawn, it's a win
|
192 |
+
if (forward_file_bb(strongSide, strongKing) & weakPawn)
|
193 |
+
result = RookValueEg - distance(strongKing, weakPawn);
|
194 |
+
|
195 |
+
// If the weaker side's king is too far from the pawn and the rook,
|
196 |
+
// it's a win.
|
197 |
+
else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide)
|
198 |
+
&& distance(weakKing, strongRook) >= 3)
|
199 |
+
result = RookValueEg - distance(strongKing, weakPawn);
|
200 |
+
|
201 |
+
// If the pawn is far advanced and supported by the defending king,
|
202 |
+
// the position is drawish
|
203 |
+
else if ( relative_rank(strongSide, weakKing) <= RANK_3
|
204 |
+
&& distance(weakKing, weakPawn) == 1
|
205 |
+
&& relative_rank(strongSide, strongKing) >= RANK_4
|
206 |
+
&& distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide))
|
207 |
+
result = Value(80) - 8 * distance(strongKing, weakPawn);
|
208 |
+
|
209 |
+
else
|
210 |
+
result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide))
|
211 |
+
- distance(weakKing, weakPawn + pawn_push(weakSide))
|
212 |
+
- distance(weakPawn, queeningSquare));
|
213 |
+
|
214 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
215 |
+
}
|
216 |
+
|
217 |
+
|
218 |
+
/// KR vs KB. This is very simple, and always returns drawish scores. The
|
219 |
+
/// score is slightly bigger when the defending king is close to the edge.
|
220 |
+
template<>
|
221 |
+
Value Endgame<KRKB>::operator()(const Position& pos) const {
|
222 |
+
|
223 |
+
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
224 |
+
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
225 |
+
|
226 |
+
Value result = Value(push_to_edge(pos.square<KING>(weakSide)));
|
227 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
228 |
+
}
|
229 |
+
|
230 |
+
|
231 |
+
/// KR vs KN. The attacking side has slightly better winning chances than
|
232 |
+
/// in KR vs KB, particularly if the king and the knight are far apart.
|
233 |
+
template<>
|
234 |
+
Value Endgame<KRKN>::operator()(const Position& pos) const {
|
235 |
+
|
236 |
+
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
237 |
+
assert(verify_material(pos, weakSide, KnightValueMg, 0));
|
238 |
+
|
239 |
+
Square weakKing = pos.square<KING>(weakSide);
|
240 |
+
Square weakKnight = pos.square<KNIGHT>(weakSide);
|
241 |
+
Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight));
|
242 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
243 |
+
}
|
244 |
+
|
245 |
+
|
246 |
+
/// KQ vs KP. In general, this is a win for the stronger side, but there are a
|
247 |
+
/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files
|
248 |
+
/// with a king positioned next to it can be a draw, so in that case, we only
|
249 |
+
/// use the distance between the kings.
|
250 |
+
template<>
|
251 |
+
Value Endgame<KQKP>::operator()(const Position& pos) const {
|
252 |
+
|
253 |
+
assert(verify_material(pos, strongSide, QueenValueMg, 0));
|
254 |
+
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
255 |
+
|
256 |
+
Square strongKing = pos.square<KING>(strongSide);
|
257 |
+
Square weakKing = pos.square<KING>(weakSide);
|
258 |
+
Square weakPawn = pos.square<PAWN>(weakSide);
|
259 |
+
|
260 |
+
Value result = Value(push_close(strongKing, weakKing));
|
261 |
+
|
262 |
+
if ( relative_rank(weakSide, weakPawn) != RANK_7
|
263 |
+
|| distance(weakKing, weakPawn) != 1
|
264 |
+
|| ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn))
|
265 |
+
result += QueenValueEg - PawnValueEg;
|
266 |
+
|
267 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
268 |
+
}
|
269 |
+
|
270 |
+
|
271 |
+
/// KQ vs KR. This is almost identical to KX vs K: we give the attacking
|
272 |
+
/// king a bonus for having the kings close together, and for forcing the
|
273 |
+
/// defending king towards the edge. If we also take care to avoid null move for
|
274 |
+
/// the defending side in the search, this is usually sufficient to win KQ vs KR.
|
275 |
+
template<>
|
276 |
+
Value Endgame<KQKR>::operator()(const Position& pos) const {
|
277 |
+
|
278 |
+
assert(verify_material(pos, strongSide, QueenValueMg, 0));
|
279 |
+
assert(verify_material(pos, weakSide, RookValueMg, 0));
|
280 |
+
|
281 |
+
Square strongKing = pos.square<KING>(strongSide);
|
282 |
+
Square weakKing = pos.square<KING>(weakSide);
|
283 |
+
|
284 |
+
Value result = QueenValueEg
|
285 |
+
- RookValueEg
|
286 |
+
+ push_to_edge(weakKing)
|
287 |
+
+ push_close(strongKing, weakKing);
|
288 |
+
|
289 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
290 |
+
}
|
291 |
+
|
292 |
+
|
293 |
+
/// KNN vs KP. Very drawish, but there are some mate opportunities if we can
|
294 |
+
/// press the weakSide King to a corner before the pawn advances too much.
|
295 |
+
template<>
|
296 |
+
Value Endgame<KNNKP>::operator()(const Position& pos) const {
|
297 |
+
|
298 |
+
assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
|
299 |
+
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
300 |
+
|
301 |
+
Square weakKing = pos.square<KING>(weakSide);
|
302 |
+
Square weakPawn = pos.square<PAWN>(weakSide);
|
303 |
+
|
304 |
+
Value result = PawnValueEg
|
305 |
+
+ 2 * push_to_edge(weakKing)
|
306 |
+
- 10 * relative_rank(weakSide, weakPawn);
|
307 |
+
|
308 |
+
return strongSide == pos.side_to_move() ? result : -result;
|
309 |
+
}
|
310 |
+
|
311 |
+
|
312 |
+
/// Some cases of trivial draws
|
313 |
+
template<> Value Endgame<KNNK>::operator()(const Position&) const { return VALUE_DRAW; }
|
314 |
+
|
315 |
+
|
316 |
+
/// KB and one or more pawns vs K. It checks for draws with rook pawns and
|
317 |
+
/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW
|
318 |
+
/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling
|
319 |
+
/// will be used.
|
320 |
+
template<>
|
321 |
+
ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
|
322 |
+
|
323 |
+
assert(pos.non_pawn_material(strongSide) == BishopValueMg);
|
324 |
+
assert(pos.count<PAWN>(strongSide) >= 1);
|
325 |
+
|
326 |
+
// No assertions about the material of weakSide, because we want draws to
|
327 |
+
// be detected even when the weaker side has some pawns.
|
328 |
+
|
329 |
+
Bitboard strongPawns = pos.pieces(strongSide, PAWN);
|
330 |
+
Bitboard allPawns = pos.pieces(PAWN);
|
331 |
+
|
332 |
+
Square strongBishop = pos.square<BISHOP>(strongSide);
|
333 |
+
Square weakKing = pos.square<KING>(weakSide);
|
334 |
+
Square strongKing = pos.square<KING>(strongSide);
|
335 |
+
|
336 |
+
// All strongSide pawns are on a single rook file?
|
337 |
+
if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB))
|
338 |
+
{
|
339 |
+
Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
|
340 |
+
|
341 |
+
if ( opposite_colors(queeningSquare, strongBishop)
|
342 |
+
&& distance(queeningSquare, weakKing) <= 1)
|
343 |
+
return SCALE_FACTOR_DRAW;
|
344 |
+
}
|
345 |
+
|
346 |
+
// If all the pawns are on the same B or G file, then it's potentially a draw
|
347 |
+
if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB))
|
348 |
+
&& pos.non_pawn_material(weakSide) == 0
|
349 |
+
&& pos.count<PAWN>(weakSide) >= 1)
|
350 |
+
{
|
351 |
+
// Get the least advanced weakSide pawn
|
352 |
+
Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
|
353 |
+
|
354 |
+
// There's potential for a draw if our pawn is blocked on the 7th rank,
|
355 |
+
// the bishop cannot attack it or they only have one pawn left.
|
356 |
+
if ( relative_rank(strongSide, weakPawn) == RANK_7
|
357 |
+
&& (strongPawns & (weakPawn + pawn_push(weakSide)))
|
358 |
+
&& (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns)))
|
359 |
+
{
|
360 |
+
int strongKingDist = distance(weakPawn, strongKing);
|
361 |
+
int weakKingDist = distance(weakPawn, weakKing);
|
362 |
+
|
363 |
+
// It's a draw if the weak king is on its back two ranks, within 2
|
364 |
+
// squares of the blocking pawn and the strong king is not
|
365 |
+
// closer. (I think this rule only fails in practically
|
366 |
+
// unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w
|
367 |
+
// and positions where qsearch will immediately correct the
|
368 |
+
// problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w).
|
369 |
+
if ( relative_rank(strongSide, weakKing) >= RANK_7
|
370 |
+
&& weakKingDist <= 2
|
371 |
+
&& weakKingDist <= strongKingDist)
|
372 |
+
return SCALE_FACTOR_DRAW;
|
373 |
+
}
|
374 |
+
}
|
375 |
+
|
376 |
+
return SCALE_FACTOR_NONE;
|
377 |
+
}
|
378 |
+
|
379 |
+
|
380 |
+
/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on
|
381 |
+
/// the third rank defended by a pawn.
|
382 |
+
template<>
|
383 |
+
ScaleFactor Endgame<KQKRPs>::operator()(const Position& pos) const {
|
384 |
+
|
385 |
+
assert(verify_material(pos, strongSide, QueenValueMg, 0));
|
386 |
+
assert(pos.count<ROOK>(weakSide) == 1);
|
387 |
+
assert(pos.count<PAWN>(weakSide) >= 1);
|
388 |
+
|
389 |
+
Square strongKing = pos.square<KING>(strongSide);
|
390 |
+
Square weakKing = pos.square<KING>(weakSide);
|
391 |
+
Square weakRook = pos.square<ROOK>(weakSide);
|
392 |
+
|
393 |
+
if ( relative_rank(weakSide, weakKing) <= RANK_2
|
394 |
+
&& relative_rank(weakSide, strongKing) >= RANK_4
|
395 |
+
&& relative_rank(weakSide, weakRook) == RANK_3
|
396 |
+
&& ( pos.pieces(weakSide, PAWN)
|
397 |
+
& attacks_bb<KING>(weakKing)
|
398 |
+
& pawn_attacks_bb(strongSide, weakRook)))
|
399 |
+
return SCALE_FACTOR_DRAW;
|
400 |
+
|
401 |
+
return SCALE_FACTOR_NONE;
|
402 |
+
}
|
403 |
+
|
404 |
+
|
405 |
+
/// KRP vs KR. This function knows a handful of the most important classes of
|
406 |
+
/// drawn positions, but is far from perfect. It would probably be a good idea
|
407 |
+
/// to add more knowledge in the future.
|
408 |
+
///
|
409 |
+
/// It would also be nice to rewrite the actual code for this function,
|
410 |
+
/// which is mostly copied from Glaurung 1.x, and isn't very pretty.
|
411 |
+
template<>
|
412 |
+
ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
|
413 |
+
|
414 |
+
assert(verify_material(pos, strongSide, RookValueMg, 1));
|
415 |
+
assert(verify_material(pos, weakSide, RookValueMg, 0));
|
416 |
+
|
417 |
+
// Assume strongSide is white and the pawn is on files A-D
|
418 |
+
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
419 |
+
Square strongRook = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
|
420 |
+
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
421 |
+
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
422 |
+
Square weakRook = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
|
423 |
+
|
424 |
+
File pawnFile = file_of(strongPawn);
|
425 |
+
Rank pawnRank = rank_of(strongPawn);
|
426 |
+
Square queeningSquare = make_square(pawnFile, RANK_8);
|
427 |
+
int tempo = (pos.side_to_move() == strongSide);
|
428 |
+
|
429 |
+
// If the pawn is not too far advanced and the defending king defends the
|
430 |
+
// queening square, use the third-rank defence.
|
431 |
+
if ( pawnRank <= RANK_5
|
432 |
+
&& distance(weakKing, queeningSquare) <= 1
|
433 |
+
&& strongKing <= SQ_H5
|
434 |
+
&& (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6)))
|
435 |
+
return SCALE_FACTOR_DRAW;
|
436 |
+
|
437 |
+
// The defending side saves a draw by checking from behind in case the pawn
|
438 |
+
// has advanced to the 6th rank with the king behind.
|
439 |
+
if ( pawnRank == RANK_6
|
440 |
+
&& distance(weakKing, queeningSquare) <= 1
|
441 |
+
&& rank_of(strongKing) + tempo <= RANK_6
|
442 |
+
&& (rank_of(weakRook) == RANK_1 || (!tempo && distance<File>(weakRook, strongPawn) >= 3)))
|
443 |
+
return SCALE_FACTOR_DRAW;
|
444 |
+
|
445 |
+
if ( pawnRank >= RANK_6
|
446 |
+
&& weakKing == queeningSquare
|
447 |
+
&& rank_of(weakRook) == RANK_1
|
448 |
+
&& (!tempo || distance(strongKing, strongPawn) >= 2))
|
449 |
+
return SCALE_FACTOR_DRAW;
|
450 |
+
|
451 |
+
// White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7
|
452 |
+
// and the black rook is behind the pawn.
|
453 |
+
if ( strongPawn == SQ_A7
|
454 |
+
&& strongRook == SQ_A8
|
455 |
+
&& (weakKing == SQ_H7 || weakKing == SQ_G7)
|
456 |
+
&& file_of(weakRook) == FILE_A
|
457 |
+
&& (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5))
|
458 |
+
return SCALE_FACTOR_DRAW;
|
459 |
+
|
460 |
+
// If the defending king blocks the pawn and the attacking king is too far
|
461 |
+
// away, it's a draw.
|
462 |
+
if ( pawnRank <= RANK_5
|
463 |
+
&& weakKing == strongPawn + NORTH
|
464 |
+
&& distance(strongKing, strongPawn) - tempo >= 2
|
465 |
+
&& distance(strongKing, weakRook) - tempo >= 2)
|
466 |
+
return SCALE_FACTOR_DRAW;
|
467 |
+
|
468 |
+
// Pawn on the 7th rank supported by the rook from behind usually wins if the
|
469 |
+
// attacking king is closer to the queening square than the defending king,
|
470 |
+
// and the defending king cannot gain tempi by threatening the attacking rook.
|
471 |
+
if ( pawnRank == RANK_7
|
472 |
+
&& pawnFile != FILE_A
|
473 |
+
&& file_of(strongRook) == pawnFile
|
474 |
+
&& strongRook != queeningSquare
|
475 |
+
&& (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
|
476 |
+
&& (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo))
|
477 |
+
return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare));
|
478 |
+
|
479 |
+
// Similar to the above, but with the pawn further back
|
480 |
+
if ( pawnFile != FILE_A
|
481 |
+
&& file_of(strongRook) == pawnFile
|
482 |
+
&& strongRook < strongPawn
|
483 |
+
&& (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
|
484 |
+
&& (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo)
|
485 |
+
&& ( distance(weakKing, strongRook) + tempo >= 3
|
486 |
+
|| ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo
|
487 |
+
&& (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo))))
|
488 |
+
return ScaleFactor( SCALE_FACTOR_MAX
|
489 |
+
- 8 * distance(strongPawn, queeningSquare)
|
490 |
+
- 2 * distance(strongKing, queeningSquare));
|
491 |
+
|
492 |
+
// If the pawn is not far advanced and the defending king is somewhere in
|
493 |
+
// the pawn's path, it's probably a draw.
|
494 |
+
if (pawnRank <= RANK_4 && weakKing > strongPawn)
|
495 |
+
{
|
496 |
+
if (file_of(weakKing) == file_of(strongPawn))
|
497 |
+
return ScaleFactor(10);
|
498 |
+
if ( distance<File>(weakKing, strongPawn) == 1
|
499 |
+
&& distance(strongKing, weakKing) > 2)
|
500 |
+
return ScaleFactor(24 - 2 * distance(strongKing, weakKing));
|
501 |
+
}
|
502 |
+
return SCALE_FACTOR_NONE;
|
503 |
+
}
|
504 |
+
|
505 |
+
template<>
|
506 |
+
ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
|
507 |
+
|
508 |
+
assert(verify_material(pos, strongSide, RookValueMg, 1));
|
509 |
+
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
510 |
+
|
511 |
+
// Test for a rook pawn
|
512 |
+
if (pos.pieces(PAWN) & (FileABB | FileHBB))
|
513 |
+
{
|
514 |
+
Square weakKing = pos.square<KING>(weakSide);
|
515 |
+
Square weakBishop = pos.square<BISHOP>(weakSide);
|
516 |
+
Square strongKing = pos.square<KING>(strongSide);
|
517 |
+
Square strongPawn = pos.square<PAWN>(strongSide);
|
518 |
+
Rank pawnRank = relative_rank(strongSide, strongPawn);
|
519 |
+
Direction push = pawn_push(strongSide);
|
520 |
+
|
521 |
+
// If the pawn is on the 5th rank and the pawn (currently) is on
|
522 |
+
// the same color square as the bishop then there is a chance of
|
523 |
+
// a fortress. Depending on the king position give a moderate
|
524 |
+
// reduction or a stronger one if the defending king is near the
|
525 |
+
// corner but not trapped there.
|
526 |
+
if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn))
|
527 |
+
{
|
528 |
+
int d = distance(strongPawn + 3 * push, weakKing);
|
529 |
+
|
530 |
+
if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push))
|
531 |
+
return ScaleFactor(24);
|
532 |
+
else
|
533 |
+
return ScaleFactor(48);
|
534 |
+
}
|
535 |
+
|
536 |
+
// When the pawn has moved to the 6th rank we can be fairly sure
|
537 |
+
// it's drawn if the bishop attacks the square in front of the
|
538 |
+
// pawn from a reasonable distance and the defending king is near
|
539 |
+
// the corner
|
540 |
+
if ( pawnRank == RANK_6
|
541 |
+
&& distance(strongPawn + 2 * push, weakKing) <= 1
|
542 |
+
&& (attacks_bb<BISHOP>(weakBishop) & (strongPawn + push))
|
543 |
+
&& distance<File>(weakBishop, strongPawn) >= 2)
|
544 |
+
return ScaleFactor(8);
|
545 |
+
}
|
546 |
+
|
547 |
+
return SCALE_FACTOR_NONE;
|
548 |
+
}
|
549 |
+
|
550 |
+
/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed
|
551 |
+
/// pawns and the defending king is actively placed, the position is drawish.
|
552 |
+
template<>
|
553 |
+
ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
|
554 |
+
|
555 |
+
assert(verify_material(pos, strongSide, RookValueMg, 2));
|
556 |
+
assert(verify_material(pos, weakSide, RookValueMg, 1));
|
557 |
+
|
558 |
+
Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN));
|
559 |
+
Square strongPawn2 = msb(pos.pieces(strongSide, PAWN));
|
560 |
+
Square weakKing = pos.square<KING>(weakSide);
|
561 |
+
|
562 |
+
// Does the stronger side have a passed pawn?
|
563 |
+
if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2))
|
564 |
+
return SCALE_FACTOR_NONE;
|
565 |
+
|
566 |
+
Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2));
|
567 |
+
|
568 |
+
if ( distance<File>(weakKing, strongPawn1) <= 1
|
569 |
+
&& distance<File>(weakKing, strongPawn2) <= 1
|
570 |
+
&& relative_rank(strongSide, weakKing) > pawnRank)
|
571 |
+
{
|
572 |
+
assert(pawnRank > RANK_1 && pawnRank < RANK_7);
|
573 |
+
return ScaleFactor(7 * pawnRank);
|
574 |
+
}
|
575 |
+
return SCALE_FACTOR_NONE;
|
576 |
+
}
|
577 |
+
|
578 |
+
|
579 |
+
/// K and two or more pawns vs K. There is just a single rule here: if all pawns
|
580 |
+
/// are on the same rook file and are blocked by the defending king, it's a draw.
|
581 |
+
template<>
|
582 |
+
ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
|
583 |
+
|
584 |
+
assert(pos.non_pawn_material(strongSide) == VALUE_ZERO);
|
585 |
+
assert(pos.count<PAWN>(strongSide) >= 2);
|
586 |
+
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
587 |
+
|
588 |
+
Square weakKing = pos.square<KING>(weakSide);
|
589 |
+
Bitboard strongPawns = pos.pieces(strongSide, PAWN);
|
590 |
+
|
591 |
+
// If all pawns are ahead of the king on a single rook file, it's a draw.
|
592 |
+
if ( !(strongPawns & ~(FileABB | FileHBB))
|
593 |
+
&& !(strongPawns & ~passed_pawn_span(weakSide, weakKing)))
|
594 |
+
return SCALE_FACTOR_DRAW;
|
595 |
+
|
596 |
+
return SCALE_FACTOR_NONE;
|
597 |
+
}
|
598 |
+
|
599 |
+
|
600 |
+
/// KBP vs KB. There are two rules: if the defending king is somewhere along the
|
601 |
+
/// path of the pawn, and the square of the king is not of the same color as the
|
602 |
+
/// stronger side's bishop, it's a draw. If the two bishops have opposite color,
|
603 |
+
/// it's almost always a draw.
|
604 |
+
template<>
|
605 |
+
ScaleFactor Endgame<KBPKB>::operator()(const Position& pos) const {
|
606 |
+
|
607 |
+
assert(verify_material(pos, strongSide, BishopValueMg, 1));
|
608 |
+
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
609 |
+
|
610 |
+
Square strongPawn = pos.square<PAWN>(strongSide);
|
611 |
+
Square strongBishop = pos.square<BISHOP>(strongSide);
|
612 |
+
Square weakBishop = pos.square<BISHOP>(weakSide);
|
613 |
+
Square weakKing = pos.square<KING>(weakSide);
|
614 |
+
|
615 |
+
// Case 1: Defending king blocks the pawn, and cannot be driven away
|
616 |
+
if ( (forward_file_bb(strongSide, strongPawn) & weakKing)
|
617 |
+
&& ( opposite_colors(weakKing, strongBishop)
|
618 |
+
|| relative_rank(strongSide, weakKing) <= RANK_6))
|
619 |
+
return SCALE_FACTOR_DRAW;
|
620 |
+
|
621 |
+
// Case 2: Opposite colored bishops
|
622 |
+
if (opposite_colors(strongBishop, weakBishop))
|
623 |
+
return SCALE_FACTOR_DRAW;
|
624 |
+
|
625 |
+
return SCALE_FACTOR_NONE;
|
626 |
+
}
|
627 |
+
|
628 |
+
|
629 |
+
/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops
|
630 |
+
template<>
|
631 |
+
ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
|
632 |
+
|
633 |
+
assert(verify_material(pos, strongSide, BishopValueMg, 2));
|
634 |
+
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
635 |
+
|
636 |
+
Square strongBishop = pos.square<BISHOP>(strongSide);
|
637 |
+
Square weakBishop = pos.square<BISHOP>(weakSide);
|
638 |
+
|
639 |
+
if (!opposite_colors(strongBishop, weakBishop))
|
640 |
+
return SCALE_FACTOR_NONE;
|
641 |
+
|
642 |
+
Square weakKing = pos.square<KING>(weakSide);
|
643 |
+
Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN));
|
644 |
+
Square strongPawn2 = msb(pos.pieces(strongSide, PAWN));
|
645 |
+
Square blockSq1, blockSq2;
|
646 |
+
|
647 |
+
if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2))
|
648 |
+
{
|
649 |
+
blockSq1 = strongPawn1 + pawn_push(strongSide);
|
650 |
+
blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1));
|
651 |
+
}
|
652 |
+
else
|
653 |
+
{
|
654 |
+
blockSq1 = strongPawn2 + pawn_push(strongSide);
|
655 |
+
blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2));
|
656 |
+
}
|
657 |
+
|
658 |
+
switch (distance<File>(strongPawn1, strongPawn2))
|
659 |
+
{
|
660 |
+
case 0:
|
661 |
+
// Both pawns are on the same file. It's an easy draw if the defender firmly
|
662 |
+
// controls some square in the frontmost pawn's path.
|
663 |
+
if ( file_of(weakKing) == file_of(blockSq1)
|
664 |
+
&& relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1)
|
665 |
+
&& opposite_colors(weakKing, strongBishop))
|
666 |
+
return SCALE_FACTOR_DRAW;
|
667 |
+
else
|
668 |
+
return SCALE_FACTOR_NONE;
|
669 |
+
|
670 |
+
case 1:
|
671 |
+
// Pawns on adjacent files. It's a draw if the defender firmly controls the
|
672 |
+
// square in front of the frontmost pawn's path, and the square diagonally
|
673 |
+
// behind this square on the file of the other pawn.
|
674 |
+
if ( weakKing == blockSq1
|
675 |
+
&& opposite_colors(weakKing, strongBishop)
|
676 |
+
&& ( weakBishop == blockSq2
|
677 |
+
|| (attacks_bb<BISHOP>(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP))
|
678 |
+
|| distance<Rank>(strongPawn1, strongPawn2) >= 2))
|
679 |
+
return SCALE_FACTOR_DRAW;
|
680 |
+
|
681 |
+
else if ( weakKing == blockSq2
|
682 |
+
&& opposite_colors(weakKing, strongBishop)
|
683 |
+
&& ( weakBishop == blockSq1
|
684 |
+
|| (attacks_bb<BISHOP>(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP))))
|
685 |
+
return SCALE_FACTOR_DRAW;
|
686 |
+
else
|
687 |
+
return SCALE_FACTOR_NONE;
|
688 |
+
|
689 |
+
default:
|
690 |
+
// The pawns are not on the same file or adjacent files. No scaling.
|
691 |
+
return SCALE_FACTOR_NONE;
|
692 |
+
}
|
693 |
+
}
|
694 |
+
|
695 |
+
|
696 |
+
/// KBP vs KN. There is a single rule: if the defending king is somewhere along
|
697 |
+
/// the path of the pawn, and the square of the king is not of the same color as
|
698 |
+
/// the stronger side's bishop, it's a draw.
|
699 |
+
template<>
|
700 |
+
ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
|
701 |
+
|
702 |
+
assert(verify_material(pos, strongSide, BishopValueMg, 1));
|
703 |
+
assert(verify_material(pos, weakSide, KnightValueMg, 0));
|
704 |
+
|
705 |
+
Square strongPawn = pos.square<PAWN>(strongSide);
|
706 |
+
Square strongBishop = pos.square<BISHOP>(strongSide);
|
707 |
+
Square weakKing = pos.square<KING>(weakSide);
|
708 |
+
|
709 |
+
if ( file_of(weakKing) == file_of(strongPawn)
|
710 |
+
&& relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing)
|
711 |
+
&& ( opposite_colors(weakKing, strongBishop)
|
712 |
+
|| relative_rank(strongSide, weakKing) <= RANK_6))
|
713 |
+
return SCALE_FACTOR_DRAW;
|
714 |
+
|
715 |
+
return SCALE_FACTOR_NONE;
|
716 |
+
}
|
717 |
+
|
718 |
+
|
719 |
+
/// KP vs KP. This is done by removing the weakest side's pawn and probing the
|
720 |
+
/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably
|
721 |
+
/// has at least a draw with the pawn as well. The exception is when the stronger
|
722 |
+
/// side's pawn is far advanced and not on a rook file; in this case it is often
|
723 |
+
/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1).
|
724 |
+
template<>
|
725 |
+
ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
|
726 |
+
|
727 |
+
assert(verify_material(pos, strongSide, VALUE_ZERO, 1));
|
728 |
+
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
729 |
+
|
730 |
+
// Assume strongSide is white and the pawn is on files A-D
|
731 |
+
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
732 |
+
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
733 |
+
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
734 |
+
|
735 |
+
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
|
736 |
+
|
737 |
+
// If the pawn has advanced to the fifth rank or further, and is not a
|
738 |
+
// rook pawn, it's too dangerous to assume that it's at least a draw.
|
739 |
+
if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A)
|
740 |
+
return SCALE_FACTOR_NONE;
|
741 |
+
|
742 |
+
// Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
|
743 |
+
// it's probably at least a draw even with the pawn.
|
744 |
+
return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
|
745 |
+
}
|
746 |
+
|
747 |
+
} // namespace Stockfish
|
src/endgame.h
ADDED
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef ENDGAME_H_INCLUDED
|
20 |
+
#define ENDGAME_H_INCLUDED
|
21 |
+
|
22 |
+
#include <memory>
|
23 |
+
#include <string>
|
24 |
+
#include <type_traits>
|
25 |
+
#include <unordered_map>
|
26 |
+
#include <utility>
|
27 |
+
|
28 |
+
#include "position.h"
|
29 |
+
#include "types.h"
|
30 |
+
|
31 |
+
namespace Stockfish {
|
32 |
+
|
33 |
+
/// EndgameCode lists all supported endgame functions by corresponding codes
|
34 |
+
|
35 |
+
enum EndgameCode {
|
36 |
+
|
37 |
+
EVALUATION_FUNCTIONS,
|
38 |
+
KNNK, // KNN vs K
|
39 |
+
KNNKP, // KNN vs KP
|
40 |
+
KXK, // Generic "mate lone king" eval
|
41 |
+
KBNK, // KBN vs K
|
42 |
+
KPK, // KP vs K
|
43 |
+
KRKP, // KR vs KP
|
44 |
+
KRKB, // KR vs KB
|
45 |
+
KRKN, // KR vs KN
|
46 |
+
KQKP, // KQ vs KP
|
47 |
+
KQKR, // KQ vs KR
|
48 |
+
|
49 |
+
SCALING_FUNCTIONS,
|
50 |
+
KBPsK, // KB and pawns vs K
|
51 |
+
KQKRPs, // KQ vs KR and pawns
|
52 |
+
KRPKR, // KRP vs KR
|
53 |
+
KRPKB, // KRP vs KB
|
54 |
+
KRPPKRP, // KRPP vs KRP
|
55 |
+
KPsK, // K and pawns vs K
|
56 |
+
KBPKB, // KBP vs KB
|
57 |
+
KBPPKB, // KBPP vs KB
|
58 |
+
KBPKN, // KBP vs KN
|
59 |
+
KPKP // KP vs KP
|
60 |
+
};
|
61 |
+
|
62 |
+
|
63 |
+
/// Endgame functions can be of two types depending on whether they return a
|
64 |
+
/// Value or a ScaleFactor.
|
65 |
+
|
66 |
+
template<EndgameCode E> using
|
67 |
+
eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type;
|
68 |
+
|
69 |
+
|
70 |
+
/// Base and derived functors for endgame evaluation and scaling functions
|
71 |
+
|
72 |
+
template<typename T>
|
73 |
+
struct EndgameBase {
|
74 |
+
|
75 |
+
explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {}
|
76 |
+
virtual ~EndgameBase() = default;
|
77 |
+
virtual T operator()(const Position&) const = 0;
|
78 |
+
|
79 |
+
const Color strongSide, weakSide;
|
80 |
+
};
|
81 |
+
|
82 |
+
|
83 |
+
template<EndgameCode E, typename T = eg_type<E>>
|
84 |
+
struct Endgame : public EndgameBase<T> {
|
85 |
+
|
86 |
+
explicit Endgame(Color c) : EndgameBase<T>(c) {}
|
87 |
+
T operator()(const Position&) const override;
|
88 |
+
};
|
89 |
+
|
90 |
+
|
91 |
+
/// The Endgames namespace handles the pointers to endgame evaluation and scaling
|
92 |
+
/// base objects in two std::map. We use polymorphism to invoke the actual
|
93 |
+
/// endgame function by calling its virtual operator().
|
94 |
+
|
95 |
+
namespace Endgames {
|
96 |
+
|
97 |
+
template<typename T> using Ptr = std::unique_ptr<EndgameBase<T>>;
|
98 |
+
template<typename T> using Map = std::unordered_map<Key, Ptr<T>>;
|
99 |
+
|
100 |
+
extern std::pair<Map<Value>, Map<ScaleFactor>> maps;
|
101 |
+
|
102 |
+
void init();
|
103 |
+
|
104 |
+
template<typename T>
|
105 |
+
Map<T>& map() {
|
106 |
+
return std::get<std::is_same<T, ScaleFactor>::value>(maps);
|
107 |
+
}
|
108 |
+
|
109 |
+
template<EndgameCode E, typename T = eg_type<E>>
|
110 |
+
void add(const std::string& code) {
|
111 |
+
|
112 |
+
StateInfo st;
|
113 |
+
map<T>()[Position().set(code, WHITE, &st).material_key()] = Ptr<T>(new Endgame<E>(WHITE));
|
114 |
+
map<T>()[Position().set(code, BLACK, &st).material_key()] = Ptr<T>(new Endgame<E>(BLACK));
|
115 |
+
}
|
116 |
+
|
117 |
+
template<typename T>
|
118 |
+
const EndgameBase<T>* probe(Key key) {
|
119 |
+
auto it = map<T>().find(key);
|
120 |
+
return it != map<T>().end() ? it->second.get() : nullptr;
|
121 |
+
}
|
122 |
+
}
|
123 |
+
|
124 |
+
} // namespace Stockfish
|
125 |
+
|
126 |
+
#endif // #ifndef ENDGAME_H_INCLUDED
|
src/evaluate.cpp
ADDED
@@ -0,0 +1,1171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <algorithm>
|
20 |
+
#include <cassert>
|
21 |
+
#include <cstdlib>
|
22 |
+
#include <cstring> // For std::memset
|
23 |
+
#include <fstream>
|
24 |
+
#include <iomanip>
|
25 |
+
#include <sstream>
|
26 |
+
#include <iostream>
|
27 |
+
#include <streambuf>
|
28 |
+
#include <vector>
|
29 |
+
|
30 |
+
#include "bitboard.h"
|
31 |
+
#include "evaluate.h"
|
32 |
+
#include "material.h"
|
33 |
+
#include "misc.h"
|
34 |
+
#include "pawns.h"
|
35 |
+
#include "thread.h"
|
36 |
+
#include "timeman.h"
|
37 |
+
#include "uci.h"
|
38 |
+
#include "incbin/incbin.h"
|
39 |
+
|
40 |
+
|
41 |
+
// Macro to embed the default efficiently updatable neural network (NNUE) file
|
42 |
+
// data in the engine binary (using incbin.h, by Dale Weiler).
|
43 |
+
// This macro invocation will declare the following three variables
|
44 |
+
// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data
|
45 |
+
// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
|
46 |
+
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
|
47 |
+
// Note that this does not work in Microsoft Visual Studio.
|
48 |
+
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
|
49 |
+
INCBIN(EmbeddedNNUE, EvalFileDefaultName);
|
50 |
+
#else
|
51 |
+
const unsigned char gEmbeddedNNUEData[1] = {0x0};
|
52 |
+
const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
|
53 |
+
const unsigned int gEmbeddedNNUESize = 1;
|
54 |
+
#endif
|
55 |
+
|
56 |
+
|
57 |
+
using namespace std;
|
58 |
+
|
59 |
+
namespace Stockfish {
|
60 |
+
|
61 |
+
namespace Eval {
|
62 |
+
|
63 |
+
bool useNNUE;
|
64 |
+
string currentEvalFileName = "None";
|
65 |
+
|
66 |
+
/// NNUE::init() tries to load a NNUE network at startup time, or when the engine
|
67 |
+
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
|
68 |
+
/// The name of the NNUE network is always retrieved from the EvalFile option.
|
69 |
+
/// We search the given network in three locations: internally (the default
|
70 |
+
/// network may be embedded in the binary), in the active working directory and
|
71 |
+
/// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
|
72 |
+
/// variable to have the engine search in a special directory in their distro.
|
73 |
+
|
74 |
+
void NNUE::init() {
|
75 |
+
|
76 |
+
useNNUE = Options["Use NNUE"];
|
77 |
+
if (!useNNUE)
|
78 |
+
return;
|
79 |
+
|
80 |
+
string eval_file = string(Options["EvalFile"]);
|
81 |
+
if (eval_file.empty())
|
82 |
+
eval_file = EvalFileDefaultName;
|
83 |
+
|
84 |
+
#if defined(DEFAULT_NNUE_DIRECTORY)
|
85 |
+
#define stringify2(x) #x
|
86 |
+
#define stringify(x) stringify2(x)
|
87 |
+
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
|
88 |
+
#else
|
89 |
+
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
|
90 |
+
#endif
|
91 |
+
|
92 |
+
for (string directory : dirs)
|
93 |
+
if (currentEvalFileName != eval_file)
|
94 |
+
{
|
95 |
+
if (directory != "<internal>")
|
96 |
+
{
|
97 |
+
ifstream stream(directory + eval_file, ios::binary);
|
98 |
+
if (load_eval(eval_file, stream))
|
99 |
+
currentEvalFileName = eval_file;
|
100 |
+
}
|
101 |
+
|
102 |
+
if (directory == "<internal>" && eval_file == EvalFileDefaultName)
|
103 |
+
{
|
104 |
+
// C++ way to prepare a buffer for a memory stream
|
105 |
+
class MemoryBuffer : public basic_streambuf<char> {
|
106 |
+
public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); }
|
107 |
+
};
|
108 |
+
|
109 |
+
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
|
110 |
+
size_t(gEmbeddedNNUESize));
|
111 |
+
(void) gEmbeddedNNUEEnd; // Silence warning on unused variable
|
112 |
+
|
113 |
+
istream stream(&buffer);
|
114 |
+
if (load_eval(eval_file, stream))
|
115 |
+
currentEvalFileName = eval_file;
|
116 |
+
}
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
/// NNUE::verify() verifies that the last net used was loaded successfully
|
121 |
+
void NNUE::verify() {
|
122 |
+
|
123 |
+
string eval_file = string(Options["EvalFile"]);
|
124 |
+
if (eval_file.empty())
|
125 |
+
eval_file = EvalFileDefaultName;
|
126 |
+
|
127 |
+
if (useNNUE && currentEvalFileName != eval_file)
|
128 |
+
{
|
129 |
+
|
130 |
+
string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available.";
|
131 |
+
string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully.";
|
132 |
+
string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
|
133 |
+
string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName);
|
134 |
+
string msg5 = "The engine will be terminated now.";
|
135 |
+
|
136 |
+
sync_cout << "info string ERROR: " << msg1 << sync_endl;
|
137 |
+
sync_cout << "info string ERROR: " << msg2 << sync_endl;
|
138 |
+
sync_cout << "info string ERROR: " << msg3 << sync_endl;
|
139 |
+
sync_cout << "info string ERROR: " << msg4 << sync_endl;
|
140 |
+
sync_cout << "info string ERROR: " << msg5 << sync_endl;
|
141 |
+
|
142 |
+
exit(EXIT_FAILURE);
|
143 |
+
}
|
144 |
+
|
145 |
+
if (useNNUE)
|
146 |
+
sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl;
|
147 |
+
else
|
148 |
+
sync_cout << "info string classical evaluation enabled" << sync_endl;
|
149 |
+
}
|
150 |
+
}
|
151 |
+
|
152 |
+
namespace Trace {
|
153 |
+
|
154 |
+
enum Tracing { NO_TRACE, TRACE };
|
155 |
+
|
156 |
+
enum Term { // The first 8 entries are reserved for PieceType
|
157 |
+
MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB
|
158 |
+
};
|
159 |
+
|
160 |
+
Score scores[TERM_NB][COLOR_NB];
|
161 |
+
|
162 |
+
double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; }
|
163 |
+
|
164 |
+
void add(int idx, Color c, Score s) {
|
165 |
+
scores[idx][c] = s;
|
166 |
+
}
|
167 |
+
|
168 |
+
void add(int idx, Score w, Score b = SCORE_ZERO) {
|
169 |
+
scores[idx][WHITE] = w;
|
170 |
+
scores[idx][BLACK] = b;
|
171 |
+
}
|
172 |
+
|
173 |
+
std::ostream& operator<<(std::ostream& os, Score s) {
|
174 |
+
os << std::setw(5) << to_cp(mg_value(s)) << " "
|
175 |
+
<< std::setw(5) << to_cp(eg_value(s));
|
176 |
+
return os;
|
177 |
+
}
|
178 |
+
|
179 |
+
std::ostream& operator<<(std::ostream& os, Term t) {
|
180 |
+
|
181 |
+
if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL)
|
182 |
+
os << " ---- ----" << " | " << " ---- ----";
|
183 |
+
else
|
184 |
+
os << scores[t][WHITE] << " | " << scores[t][BLACK];
|
185 |
+
|
186 |
+
os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n";
|
187 |
+
return os;
|
188 |
+
}
|
189 |
+
}
|
190 |
+
|
191 |
+
using namespace Trace;
|
192 |
+
|
193 |
+
namespace {
|
194 |
+
|
195 |
+
// Threshold for lazy and space evaluation
|
196 |
+
constexpr Value LazyThreshold1 = Value(3631);
|
197 |
+
constexpr Value LazyThreshold2 = Value(2084);
|
198 |
+
constexpr Value SpaceThreshold = Value(11551);
|
199 |
+
|
200 |
+
// KingAttackWeights[PieceType] contains king attack weights by piece type
|
201 |
+
constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 76, 46, 45, 14 };
|
202 |
+
|
203 |
+
// SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
|
204 |
+
// higher if multiple safe checks are possible for that piece type.
|
205 |
+
constexpr int SafeCheck[][2] = {
|
206 |
+
{}, {}, {805, 1292}, {650, 984}, {1071, 1886}, {730, 1128}
|
207 |
+
};
|
208 |
+
|
209 |
+
#define S(mg, eg) make_score(mg, eg)
|
210 |
+
|
211 |
+
// MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game,
|
212 |
+
// indexed by piece type and number of attacked squares in the mobility area.
|
213 |
+
constexpr Score MobilityBonus[][32] = {
|
214 |
+
{ S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight
|
215 |
+
S( 21, 16), S( 28, 21), S( 37, 26) },
|
216 |
+
{ S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop
|
217 |
+
S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87),
|
218 |
+
S( 91, 88), S( 96, 98) },
|
219 |
+
{ S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook
|
220 |
+
S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160),
|
221 |
+
S( 57,165), S( 58,170), S( 67,175) },
|
222 |
+
{ S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen
|
223 |
+
S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101),
|
224 |
+
S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140),
|
225 |
+
S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171),
|
226 |
+
S(112,178), S(114,185), S(114,187), S(119,221) }
|
227 |
+
};
|
228 |
+
|
229 |
+
// BishopPawns[distance from edge] contains a file-dependent penalty for pawns on
|
230 |
+
// squares of the same color as our bishop.
|
231 |
+
constexpr Score BishopPawns[int(FILE_NB) / 2] = {
|
232 |
+
S(3, 8), S(3, 9), S(2, 7), S(3, 7)
|
233 |
+
};
|
234 |
+
|
235 |
+
// KingProtector[knight/bishop] contains penalty for each distance unit to own king
|
236 |
+
constexpr Score KingProtector[] = { S(9, 9), S(7, 9) };
|
237 |
+
|
238 |
+
// Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a
|
239 |
+
// pawn protected square on rank 4 to 6 which is also safe from a pawn attack.
|
240 |
+
constexpr Score Outpost[] = { S(54, 34), S(31, 25) };
|
241 |
+
|
242 |
+
// PassedRank[Rank] contains a bonus according to the rank of a passed pawn
|
243 |
+
constexpr Score PassedRank[RANK_NB] = {
|
244 |
+
S(0, 0), S(2, 38), S(15, 36), S(22, 50), S(64, 81), S(166, 184), S(284, 269)
|
245 |
+
};
|
246 |
+
|
247 |
+
constexpr Score RookOnClosedFile = S(10, 5);
|
248 |
+
constexpr Score RookOnOpenFile[] = { S(18, 8), S(49, 26) };
|
249 |
+
|
250 |
+
// ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
|
251 |
+
// which piece type attacks which one. Attacks on lesser pieces which are
|
252 |
+
// pawn-defended are not considered.
|
253 |
+
constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
|
254 |
+
S(0, 0), S(6, 37), S(64, 50), S(82, 57), S(103, 130), S(81, 163)
|
255 |
+
};
|
256 |
+
|
257 |
+
constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
|
258 |
+
S(0, 0), S(3, 44), S(36, 71), S(44, 59), S(0, 39), S(60, 39)
|
259 |
+
};
|
260 |
+
|
261 |
+
constexpr Value CorneredBishop = Value(50);
|
262 |
+
|
263 |
+
// Assorted bonuses and penalties
|
264 |
+
constexpr Score UncontestedOutpost = S( 0, 10);
|
265 |
+
constexpr Score BishopOnKingRing = S( 24, 0);
|
266 |
+
constexpr Score BishopXRayPawns = S( 4, 5);
|
267 |
+
constexpr Score FlankAttacks = S( 8, 0);
|
268 |
+
constexpr Score Hanging = S( 72, 40);
|
269 |
+
constexpr Score KnightOnQueen = S( 16, 11);
|
270 |
+
constexpr Score LongDiagonalBishop = S( 45, 0);
|
271 |
+
constexpr Score MinorBehindPawn = S( 18, 3);
|
272 |
+
constexpr Score PassedFile = S( 13, 8);
|
273 |
+
constexpr Score PawnlessFlank = S( 19, 97);
|
274 |
+
constexpr Score ReachableOutpost = S( 33, 19);
|
275 |
+
constexpr Score RestrictedPiece = S( 6, 7);
|
276 |
+
constexpr Score RookOnKingRing = S( 16, 0);
|
277 |
+
constexpr Score SliderOnQueen = S( 62, 21);
|
278 |
+
constexpr Score ThreatByKing = S( 24, 87);
|
279 |
+
constexpr Score ThreatByPawnPush = S( 48, 39);
|
280 |
+
constexpr Score ThreatBySafePawn = S(167, 99);
|
281 |
+
constexpr Score TrappedRook = S( 55, 13);
|
282 |
+
constexpr Score WeakQueenProtection = S( 14, 0);
|
283 |
+
constexpr Score WeakQueen = S( 57, 19);
|
284 |
+
|
285 |
+
|
286 |
+
#undef S
|
287 |
+
|
288 |
+
// Evaluation class computes and stores attacks tables and other working data
|
289 |
+
template<Tracing T>
|
290 |
+
class Evaluation {
|
291 |
+
|
292 |
+
public:
|
293 |
+
Evaluation() = delete;
|
294 |
+
explicit Evaluation(const Position& p) : pos(p) {}
|
295 |
+
Evaluation& operator=(const Evaluation&) = delete;
|
296 |
+
Value value();
|
297 |
+
|
298 |
+
private:
|
299 |
+
template<Color Us> void initialize();
|
300 |
+
template<Color Us, PieceType Pt> Score pieces();
|
301 |
+
template<Color Us> Score king() const;
|
302 |
+
template<Color Us> Score threats() const;
|
303 |
+
template<Color Us> Score passed() const;
|
304 |
+
template<Color Us> Score space() const;
|
305 |
+
Value winnable(Score score) const;
|
306 |
+
|
307 |
+
const Position& pos;
|
308 |
+
Material::Entry* me;
|
309 |
+
Pawns::Entry* pe;
|
310 |
+
Bitboard mobilityArea[COLOR_NB];
|
311 |
+
Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO };
|
312 |
+
|
313 |
+
// attackedBy[color][piece type] is a bitboard representing all squares
|
314 |
+
// attacked by a given color and piece type. Special "piece types" which
|
315 |
+
// is also calculated is ALL_PIECES.
|
316 |
+
Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB];
|
317 |
+
|
318 |
+
// attackedBy2[color] are the squares attacked by at least 2 units of a given
|
319 |
+
// color, including x-rays. But diagonal x-rays through pawns are not computed.
|
320 |
+
Bitboard attackedBy2[COLOR_NB];
|
321 |
+
|
322 |
+
// kingRing[color] are the squares adjacent to the king plus some other
|
323 |
+
// very near squares, depending on king position.
|
324 |
+
Bitboard kingRing[COLOR_NB];
|
325 |
+
|
326 |
+
// kingAttackersCount[color] is the number of pieces of the given color
|
327 |
+
// which attack a square in the kingRing of the enemy king.
|
328 |
+
int kingAttackersCount[COLOR_NB];
|
329 |
+
|
330 |
+
// kingAttackersWeight[color] is the sum of the "weights" of the pieces of
|
331 |
+
// the given color which attack a square in the kingRing of the enemy king.
|
332 |
+
// The weights of the individual piece types are given by the elements in
|
333 |
+
// the KingAttackWeights array.
|
334 |
+
int kingAttackersWeight[COLOR_NB];
|
335 |
+
|
336 |
+
// kingAttacksCount[color] is the number of attacks by the given color to
|
337 |
+
// squares directly adjacent to the enemy king. Pieces which attack more
|
338 |
+
// than one square are counted multiple times. For instance, if there is
|
339 |
+
// a white knight on g5 and black's king is on g8, this white knight adds 2
|
340 |
+
// to kingAttacksCount[WHITE].
|
341 |
+
int kingAttacksCount[COLOR_NB];
|
342 |
+
};
|
343 |
+
|
344 |
+
|
345 |
+
// Evaluation::initialize() computes king and pawn attacks, and the king ring
|
346 |
+
// bitboard for a given color. This is done at the beginning of the evaluation.
|
347 |
+
|
348 |
+
template<Tracing T> template<Color Us>
|
349 |
+
void Evaluation<T>::initialize() {
|
350 |
+
|
351 |
+
constexpr Color Them = ~Us;
|
352 |
+
constexpr Direction Up = pawn_push(Us);
|
353 |
+
constexpr Direction Down = -Up;
|
354 |
+
constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB);
|
355 |
+
|
356 |
+
const Square ksq = pos.square<KING>(Us);
|
357 |
+
|
358 |
+
Bitboard dblAttackByPawn = pawn_double_attacks_bb<Us>(pos.pieces(Us, PAWN));
|
359 |
+
|
360 |
+
// Find our pawns that are blocked or on the first two ranks
|
361 |
+
Bitboard b = pos.pieces(Us, PAWN) & (shift<Down>(pos.pieces()) | LowRanks);
|
362 |
+
|
363 |
+
// Squares occupied by those pawns, by our king or queen, by blockers to attacks on our king
|
364 |
+
// or controlled by enemy pawns are excluded from the mobility area.
|
365 |
+
mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them));
|
366 |
+
|
367 |
+
// Initialize attackedBy[] for king and pawns
|
368 |
+
attackedBy[Us][KING] = attacks_bb<KING>(ksq);
|
369 |
+
attackedBy[Us][PAWN] = pe->pawn_attacks(Us);
|
370 |
+
attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN];
|
371 |
+
attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
|
372 |
+
|
373 |
+
// Init our king safety tables
|
374 |
+
Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G),
|
375 |
+
std::clamp(rank_of(ksq), RANK_2, RANK_7));
|
376 |
+
kingRing[Us] = attacks_bb<KING>(s) | s;
|
377 |
+
|
378 |
+
kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
|
379 |
+
kingAttacksCount[Them] = kingAttackersWeight[Them] = 0;
|
380 |
+
|
381 |
+
// Remove from kingRing[] the squares defended by two pawns
|
382 |
+
kingRing[Us] &= ~dblAttackByPawn;
|
383 |
+
}
|
384 |
+
|
385 |
+
|
386 |
+
// Evaluation::pieces() scores pieces of a given color and type
|
387 |
+
|
388 |
+
template<Tracing T> template<Color Us, PieceType Pt>
|
389 |
+
Score Evaluation<T>::pieces() {
|
390 |
+
|
391 |
+
constexpr Color Them = ~Us;
|
392 |
+
constexpr Direction Down = -pawn_push(Us);
|
393 |
+
constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
|
394 |
+
: Rank5BB | Rank4BB | Rank3BB);
|
395 |
+
Bitboard b1 = pos.pieces(Us, Pt);
|
396 |
+
Bitboard b, bb;
|
397 |
+
Score score = SCORE_ZERO;
|
398 |
+
|
399 |
+
attackedBy[Us][Pt] = 0;
|
400 |
+
|
401 |
+
while (b1)
|
402 |
+
{
|
403 |
+
Square s = pop_lsb(b1);
|
404 |
+
|
405 |
+
// Find attacked squares, including x-ray attacks for bishops and rooks
|
406 |
+
b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
|
407 |
+
: Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
|
408 |
+
: attacks_bb<Pt>(s, pos.pieces());
|
409 |
+
|
410 |
+
if (pos.blockers_for_king(Us) & s)
|
411 |
+
b &= line_bb(pos.square<KING>(Us), s);
|
412 |
+
|
413 |
+
attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b;
|
414 |
+
attackedBy[Us][Pt] |= b;
|
415 |
+
attackedBy[Us][ALL_PIECES] |= b;
|
416 |
+
|
417 |
+
if (b & kingRing[Them])
|
418 |
+
{
|
419 |
+
kingAttackersCount[Us]++;
|
420 |
+
kingAttackersWeight[Us] += KingAttackWeights[Pt];
|
421 |
+
kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]);
|
422 |
+
}
|
423 |
+
|
424 |
+
else if (Pt == ROOK && (file_bb(s) & kingRing[Them]))
|
425 |
+
score += RookOnKingRing;
|
426 |
+
|
427 |
+
else if (Pt == BISHOP && (attacks_bb<BISHOP>(s, pos.pieces(PAWN)) & kingRing[Them]))
|
428 |
+
score += BishopOnKingRing;
|
429 |
+
|
430 |
+
int mob = popcount(b & mobilityArea[Us]);
|
431 |
+
mobility[Us] += MobilityBonus[Pt - 2][mob];
|
432 |
+
|
433 |
+
if (Pt == BISHOP || Pt == KNIGHT)
|
434 |
+
{
|
435 |
+
// Bonus if the piece is on an outpost square or can reach one
|
436 |
+
// Bonus for knights (UncontestedOutpost) if few relevant targets
|
437 |
+
bb = OutpostRanks & (attackedBy[Us][PAWN] | shift<Down>(pos.pieces(PAWN)))
|
438 |
+
& ~pe->pawn_attacks_span(Them);
|
439 |
+
Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN);
|
440 |
+
|
441 |
+
if ( Pt == KNIGHT
|
442 |
+
&& bb & s & ~CenterFiles // on a side outpost
|
443 |
+
&& !(b & targets) // no relevant attacks
|
444 |
+
&& (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide))))
|
445 |
+
score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide));
|
446 |
+
else if (bb & s)
|
447 |
+
score += Outpost[Pt == BISHOP];
|
448 |
+
else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
|
449 |
+
score += ReachableOutpost;
|
450 |
+
|
451 |
+
// Bonus for a knight or bishop shielded by pawn
|
452 |
+
if (shift<Down>(pos.pieces(PAWN)) & s)
|
453 |
+
score += MinorBehindPawn;
|
454 |
+
|
455 |
+
// Penalty if the piece is far from the king
|
456 |
+
score -= KingProtector[Pt == BISHOP] * distance(pos.square<KING>(Us), s);
|
457 |
+
|
458 |
+
if constexpr (Pt == BISHOP)
|
459 |
+
{
|
460 |
+
// Penalty according to the number of our pawns on the same color square as the
|
461 |
+
// bishop, bigger when the center files are blocked with pawns and smaller
|
462 |
+
// when the bishop is outside the pawn chain.
|
463 |
+
Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
|
464 |
+
|
465 |
+
score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s)
|
466 |
+
* (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles));
|
467 |
+
|
468 |
+
// Penalty for all enemy pawns x-rayed
|
469 |
+
score -= BishopXRayPawns * popcount(attacks_bb<BISHOP>(s) & pos.pieces(Them, PAWN));
|
470 |
+
|
471 |
+
// Bonus for bishop on a long diagonal which can "see" both center squares
|
472 |
+
if (more_than_one(attacks_bb<BISHOP>(s, pos.pieces(PAWN)) & Center))
|
473 |
+
score += LongDiagonalBishop;
|
474 |
+
|
475 |
+
// An important Chess960 pattern: a cornered bishop blocked by a friendly
|
476 |
+
// pawn diagonally in front of it is a very serious problem, especially
|
477 |
+
// when that pawn is also blocked.
|
478 |
+
if ( pos.is_chess960()
|
479 |
+
&& (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1)))
|
480 |
+
{
|
481 |
+
Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST);
|
482 |
+
if (pos.piece_on(s + d) == make_piece(Us, PAWN))
|
483 |
+
score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop)
|
484 |
+
: 3 * make_score(CorneredBishop, CorneredBishop);
|
485 |
+
}
|
486 |
+
}
|
487 |
+
}
|
488 |
+
|
489 |
+
if constexpr (Pt == ROOK)
|
490 |
+
{
|
491 |
+
// Bonuses for rook on a (semi-)open or closed file
|
492 |
+
if (pos.is_on_semiopen_file(Us, s))
|
493 |
+
{
|
494 |
+
score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)];
|
495 |
+
}
|
496 |
+
else
|
497 |
+
{
|
498 |
+
// If our pawn on this file is blocked, increase penalty
|
499 |
+
if ( pos.pieces(Us, PAWN)
|
500 |
+
& shift<Down>(pos.pieces())
|
501 |
+
& file_bb(s))
|
502 |
+
{
|
503 |
+
score -= RookOnClosedFile;
|
504 |
+
}
|
505 |
+
|
506 |
+
// Penalty when trapped by the king, even more if the king cannot castle
|
507 |
+
if (mob <= 3)
|
508 |
+
{
|
509 |
+
File kf = file_of(pos.square<KING>(Us));
|
510 |
+
if ((kf < FILE_E) == (file_of(s) < kf))
|
511 |
+
score -= TrappedRook * (1 + !pos.castling_rights(Us));
|
512 |
+
}
|
513 |
+
}
|
514 |
+
}
|
515 |
+
|
516 |
+
if constexpr (Pt == QUEEN)
|
517 |
+
{
|
518 |
+
// Penalty if any relative pin or discovered attack against the queen
|
519 |
+
Bitboard queenPinners;
|
520 |
+
if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
|
521 |
+
score -= WeakQueen;
|
522 |
+
}
|
523 |
+
}
|
524 |
+
if constexpr (T)
|
525 |
+
Trace::add(Pt, Us, score);
|
526 |
+
|
527 |
+
return score;
|
528 |
+
}
|
529 |
+
|
530 |
+
|
531 |
+
// Evaluation::king() assigns bonuses and penalties to a king of a given color
|
532 |
+
|
533 |
+
template<Tracing T> template<Color Us>
|
534 |
+
Score Evaluation<T>::king() const {
|
535 |
+
|
536 |
+
constexpr Color Them = ~Us;
|
537 |
+
constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB
|
538 |
+
: AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB);
|
539 |
+
|
540 |
+
Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0;
|
541 |
+
Bitboard rookChecks, queenChecks, bishopChecks, knightChecks;
|
542 |
+
int kingDanger = 0;
|
543 |
+
const Square ksq = pos.square<KING>(Us);
|
544 |
+
|
545 |
+
// Init the score with king shelter and enemy pawns storm
|
546 |
+
Score score = pe->king_safety<Us>(pos);
|
547 |
+
|
548 |
+
// Attacked squares defended at most once by our queen or king
|
549 |
+
weak = attackedBy[Them][ALL_PIECES]
|
550 |
+
& ~attackedBy2[Us]
|
551 |
+
& (~attackedBy[Us][ALL_PIECES] | attackedBy[Us][KING] | attackedBy[Us][QUEEN]);
|
552 |
+
|
553 |
+
// Analyse the safe enemy's checks which are possible on next move
|
554 |
+
safe = ~pos.pieces(Them);
|
555 |
+
safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]);
|
556 |
+
|
557 |
+
b1 = attacks_bb<ROOK >(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
|
558 |
+
b2 = attacks_bb<BISHOP>(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
|
559 |
+
|
560 |
+
// Enemy rooks checks
|
561 |
+
rookChecks = b1 & attackedBy[Them][ROOK] & safe;
|
562 |
+
if (rookChecks)
|
563 |
+
kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)];
|
564 |
+
else
|
565 |
+
unsafeChecks |= b1 & attackedBy[Them][ROOK];
|
566 |
+
|
567 |
+
// Enemy queen safe checks: count them only if the checks are from squares from
|
568 |
+
// which opponent cannot give a rook check, because rook checks are more valuable.
|
569 |
+
queenChecks = (b1 | b2) & attackedBy[Them][QUEEN] & safe
|
570 |
+
& ~(attackedBy[Us][QUEEN] | rookChecks);
|
571 |
+
if (queenChecks)
|
572 |
+
kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)];
|
573 |
+
|
574 |
+
// Enemy bishops checks: count them only if they are from squares from which
|
575 |
+
// opponent cannot give a queen check, because queen checks are more valuable.
|
576 |
+
bishopChecks = b2 & attackedBy[Them][BISHOP] & safe
|
577 |
+
& ~queenChecks;
|
578 |
+
if (bishopChecks)
|
579 |
+
kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)];
|
580 |
+
|
581 |
+
else
|
582 |
+
unsafeChecks |= b2 & attackedBy[Them][BISHOP];
|
583 |
+
|
584 |
+
// Enemy knights checks
|
585 |
+
knightChecks = attacks_bb<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
|
586 |
+
if (knightChecks & safe)
|
587 |
+
kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)];
|
588 |
+
else
|
589 |
+
unsafeChecks |= knightChecks;
|
590 |
+
|
591 |
+
// Find the squares that opponent attacks in our king flank, the squares
|
592 |
+
// which they attack twice in that flank, and the squares that we defend.
|
593 |
+
b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
|
594 |
+
b2 = b1 & attackedBy2[Them];
|
595 |
+
b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
|
596 |
+
|
597 |
+
int kingFlankAttack = popcount(b1) + popcount(b2);
|
598 |
+
int kingFlankDefense = popcount(b3);
|
599 |
+
|
600 |
+
kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo)
|
601 |
+
+ 183 * popcount(kingRing[Us] & weak) // (~15 Elo)
|
602 |
+
+ 148 * popcount(unsafeChecks) // (~4 Elo)
|
603 |
+
+ 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo)
|
604 |
+
+ 69 * kingAttacksCount[Them] // (~0.5 Elo)
|
605 |
+
+ 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo)
|
606 |
+
+ mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo)
|
607 |
+
- 873 * !pos.count<QUEEN>(Them) // (~24 Elo)
|
608 |
+
- 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo)
|
609 |
+
- 6 * mg_value(score) / 8 // (~8 Elo)
|
610 |
+
- 4 * kingFlankDefense // (~5 Elo)
|
611 |
+
+ 37; // (~0.5 Elo)
|
612 |
+
|
613 |
+
// Transform the kingDanger units into a Score, and subtract it from the evaluation
|
614 |
+
if (kingDanger > 100)
|
615 |
+
score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16);
|
616 |
+
|
617 |
+
// Penalty when our king is on a pawnless flank
|
618 |
+
if (!(pos.pieces(PAWN) & KingFlank[file_of(ksq)]))
|
619 |
+
score -= PawnlessFlank;
|
620 |
+
|
621 |
+
// Penalty if king flank is under attack, potentially moving toward the king
|
622 |
+
score -= FlankAttacks * kingFlankAttack;
|
623 |
+
|
624 |
+
if constexpr (T)
|
625 |
+
Trace::add(KING, Us, score);
|
626 |
+
|
627 |
+
return score;
|
628 |
+
}
|
629 |
+
|
630 |
+
|
631 |
+
// Evaluation::threats() assigns bonuses according to the types of the
|
632 |
+
// attacking and the attacked pieces.
|
633 |
+
|
634 |
+
template<Tracing T> template<Color Us>
|
635 |
+
Score Evaluation<T>::threats() const {
|
636 |
+
|
637 |
+
constexpr Color Them = ~Us;
|
638 |
+
constexpr Direction Up = pawn_push(Us);
|
639 |
+
constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
|
640 |
+
|
641 |
+
Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe;
|
642 |
+
Score score = SCORE_ZERO;
|
643 |
+
|
644 |
+
// Non-pawn enemies
|
645 |
+
nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN);
|
646 |
+
|
647 |
+
// Squares strongly protected by the enemy, either because they defend the
|
648 |
+
// square with a pawn, or because they defend the square twice and we don't.
|
649 |
+
stronglyProtected = attackedBy[Them][PAWN]
|
650 |
+
| (attackedBy2[Them] & ~attackedBy2[Us]);
|
651 |
+
|
652 |
+
// Non-pawn enemies, strongly protected
|
653 |
+
defended = nonPawnEnemies & stronglyProtected;
|
654 |
+
|
655 |
+
// Enemies not strongly protected and under our attack
|
656 |
+
weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES];
|
657 |
+
|
658 |
+
// Bonus according to the kind of attacking pieces
|
659 |
+
if (defended | weak)
|
660 |
+
{
|
661 |
+
b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]);
|
662 |
+
while (b)
|
663 |
+
score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))];
|
664 |
+
|
665 |
+
b = weak & attackedBy[Us][ROOK];
|
666 |
+
while (b)
|
667 |
+
score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))];
|
668 |
+
|
669 |
+
if (weak & attackedBy[Us][KING])
|
670 |
+
score += ThreatByKing;
|
671 |
+
|
672 |
+
b = ~attackedBy[Them][ALL_PIECES]
|
673 |
+
| (nonPawnEnemies & attackedBy2[Us]);
|
674 |
+
score += Hanging * popcount(weak & b);
|
675 |
+
|
676 |
+
// Additional bonus if weak piece is only protected by a queen
|
677 |
+
score += WeakQueenProtection * popcount(weak & attackedBy[Them][QUEEN]);
|
678 |
+
}
|
679 |
+
|
680 |
+
// Bonus for restricting their piece moves
|
681 |
+
b = attackedBy[Them][ALL_PIECES]
|
682 |
+
& ~stronglyProtected
|
683 |
+
& attackedBy[Us][ALL_PIECES];
|
684 |
+
score += RestrictedPiece * popcount(b);
|
685 |
+
|
686 |
+
// Protected or unattacked squares
|
687 |
+
safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES];
|
688 |
+
|
689 |
+
// Bonus for attacking enemy pieces with our relatively safe pawns
|
690 |
+
b = pos.pieces(Us, PAWN) & safe;
|
691 |
+
b = pawn_attacks_bb<Us>(b) & nonPawnEnemies;
|
692 |
+
score += ThreatBySafePawn * popcount(b);
|
693 |
+
|
694 |
+
// Find squares where our pawns can push on the next move
|
695 |
+
b = shift<Up>(pos.pieces(Us, PAWN)) & ~pos.pieces();
|
696 |
+
b |= shift<Up>(b & TRank3BB) & ~pos.pieces();
|
697 |
+
|
698 |
+
// Keep only the squares which are relatively safe
|
699 |
+
b &= ~attackedBy[Them][PAWN] & safe;
|
700 |
+
|
701 |
+
// Bonus for safe pawn threats on the next move
|
702 |
+
b = pawn_attacks_bb<Us>(b) & nonPawnEnemies;
|
703 |
+
score += ThreatByPawnPush * popcount(b);
|
704 |
+
|
705 |
+
// Bonus for threats on the next moves against enemy queen
|
706 |
+
if (pos.count<QUEEN>(Them) == 1)
|
707 |
+
{
|
708 |
+
bool queenImbalance = pos.count<QUEEN>() == 1;
|
709 |
+
|
710 |
+
Square s = pos.square<QUEEN>(Them);
|
711 |
+
safe = mobilityArea[Us]
|
712 |
+
& ~pos.pieces(Us, PAWN)
|
713 |
+
& ~stronglyProtected;
|
714 |
+
|
715 |
+
b = attackedBy[Us][KNIGHT] & attacks_bb<KNIGHT>(s);
|
716 |
+
|
717 |
+
score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance);
|
718 |
+
|
719 |
+
b = (attackedBy[Us][BISHOP] & attacks_bb<BISHOP>(s, pos.pieces()))
|
720 |
+
| (attackedBy[Us][ROOK ] & attacks_bb<ROOK >(s, pos.pieces()));
|
721 |
+
|
722 |
+
score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance);
|
723 |
+
}
|
724 |
+
|
725 |
+
if constexpr (T)
|
726 |
+
Trace::add(THREAT, Us, score);
|
727 |
+
|
728 |
+
return score;
|
729 |
+
}
|
730 |
+
|
731 |
+
// Evaluation::passed() evaluates the passed pawns and candidate passed
|
732 |
+
// pawns of the given color.
|
733 |
+
|
734 |
+
template<Tracing T> template<Color Us>
|
735 |
+
Score Evaluation<T>::passed() const {
|
736 |
+
|
737 |
+
constexpr Color Them = ~Us;
|
738 |
+
constexpr Direction Up = pawn_push(Us);
|
739 |
+
constexpr Direction Down = -Up;
|
740 |
+
|
741 |
+
auto king_proximity = [&](Color c, Square s) {
|
742 |
+
return std::min(distance(pos.square<KING>(c), s), 5);
|
743 |
+
};
|
744 |
+
|
745 |
+
Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers;
|
746 |
+
Score score = SCORE_ZERO;
|
747 |
+
|
748 |
+
b = pe->passed_pawns(Us);
|
749 |
+
|
750 |
+
blockedPassers = b & shift<Down>(pos.pieces(Them, PAWN));
|
751 |
+
if (blockedPassers)
|
752 |
+
{
|
753 |
+
helpers = shift<Up>(pos.pieces(Us, PAWN))
|
754 |
+
& ~pos.pieces(Them)
|
755 |
+
& (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]);
|
756 |
+
|
757 |
+
// Remove blocked candidate passers that don't have help to pass
|
758 |
+
b &= ~blockedPassers
|
759 |
+
| shift<WEST>(helpers)
|
760 |
+
| shift<EAST>(helpers);
|
761 |
+
}
|
762 |
+
|
763 |
+
while (b)
|
764 |
+
{
|
765 |
+
Square s = pop_lsb(b);
|
766 |
+
|
767 |
+
assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
|
768 |
+
|
769 |
+
int r = relative_rank(Us, s);
|
770 |
+
|
771 |
+
Score bonus = PassedRank[r];
|
772 |
+
|
773 |
+
if (r > RANK_3)
|
774 |
+
{
|
775 |
+
int w = 5 * r - 13;
|
776 |
+
Square blockSq = s + Up;
|
777 |
+
|
778 |
+
// Adjust bonus based on the king's proximity
|
779 |
+
bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4
|
780 |
+
- king_proximity(Us, blockSq) * 2) * w);
|
781 |
+
|
782 |
+
// If blockSq is not the queening square then consider also a second push
|
783 |
+
if (r != RANK_7)
|
784 |
+
bonus -= make_score(0, king_proximity(Us, blockSq + Up) * w);
|
785 |
+
|
786 |
+
// If the pawn is free to advance, then increase the bonus
|
787 |
+
if (pos.empty(blockSq))
|
788 |
+
{
|
789 |
+
squaresToQueen = forward_file_bb(Us, s);
|
790 |
+
unsafeSquares = passed_pawn_span(Us, s);
|
791 |
+
|
792 |
+
bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN);
|
793 |
+
|
794 |
+
if (!(pos.pieces(Them) & bb))
|
795 |
+
unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them);
|
796 |
+
|
797 |
+
// If there are no enemy pieces or attacks on passed pawn span, assign a big bonus.
|
798 |
+
// Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus.
|
799 |
+
// Otherwise assign a smaller bonus if the path to queen is not attacked
|
800 |
+
// and even smaller bonus if it is attacked but block square is not.
|
801 |
+
int k = !unsafeSquares ? 36 :
|
802 |
+
!(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 :
|
803 |
+
!(unsafeSquares & squaresToQueen) ? 17 :
|
804 |
+
!(unsafeSquares & blockSq) ? 7 :
|
805 |
+
0 ;
|
806 |
+
|
807 |
+
// Assign a larger bonus if the block square is defended
|
808 |
+
if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq))
|
809 |
+
k += 5;
|
810 |
+
|
811 |
+
bonus += make_score(k * w, k * w);
|
812 |
+
}
|
813 |
+
} // r > RANK_3
|
814 |
+
|
815 |
+
score += bonus - PassedFile * edge_distance(file_of(s));
|
816 |
+
}
|
817 |
+
|
818 |
+
if constexpr (T)
|
819 |
+
Trace::add(PASSED, Us, score);
|
820 |
+
|
821 |
+
return score;
|
822 |
+
}
|
823 |
+
|
824 |
+
|
825 |
+
// Evaluation::space() computes a space evaluation for a given side, aiming to improve game
|
826 |
+
// play in the opening. It is based on the number of safe squares on the four central files
|
827 |
+
// on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
|
828 |
+
// Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
|
829 |
+
|
830 |
+
template<Tracing T> template<Color Us>
|
831 |
+
Score Evaluation<T>::space() const {
|
832 |
+
|
833 |
+
// Early exit if, for example, both queens or 6 minor pieces have been exchanged
|
834 |
+
if (pos.non_pawn_material() < SpaceThreshold)
|
835 |
+
return SCORE_ZERO;
|
836 |
+
|
837 |
+
constexpr Color Them = ~Us;
|
838 |
+
constexpr Direction Down = -pawn_push(Us);
|
839 |
+
constexpr Bitboard SpaceMask =
|
840 |
+
Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB)
|
841 |
+
: CenterFiles & (Rank7BB | Rank6BB | Rank5BB);
|
842 |
+
|
843 |
+
// Find the available squares for our pieces inside the area defined by SpaceMask
|
844 |
+
Bitboard safe = SpaceMask
|
845 |
+
& ~pos.pieces(Us, PAWN)
|
846 |
+
& ~attackedBy[Them][PAWN];
|
847 |
+
|
848 |
+
// Find all squares which are at most three squares behind some friendly pawn
|
849 |
+
Bitboard behind = pos.pieces(Us, PAWN);
|
850 |
+
behind |= shift<Down>(behind);
|
851 |
+
behind |= shift<Down+Down>(behind);
|
852 |
+
|
853 |
+
// Compute space score based on the number of safe squares and number of our pieces
|
854 |
+
// increased with number of total blocked pawns in position.
|
855 |
+
int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
|
856 |
+
int weight = pos.count<ALL_PIECES>(Us) - 3 + std::min(pe->blocked_count(), 9);
|
857 |
+
Score score = make_score(bonus * weight * weight / 16, 0);
|
858 |
+
|
859 |
+
if constexpr (T)
|
860 |
+
Trace::add(SPACE, Us, score);
|
861 |
+
|
862 |
+
return score;
|
863 |
+
}
|
864 |
+
|
865 |
+
|
866 |
+
// Evaluation::winnable() adjusts the midgame and endgame score components, based on
|
867 |
+
// the known attacking/defending status of the players. The final value is derived
|
868 |
+
// by interpolation from the midgame and endgame values.
|
869 |
+
|
870 |
+
template<Tracing T>
|
871 |
+
Value Evaluation<T>::winnable(Score score) const {
|
872 |
+
|
873 |
+
int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
|
874 |
+
+ int(rank_of(pos.square<KING>(WHITE)) - rank_of(pos.square<KING>(BLACK)));
|
875 |
+
|
876 |
+
bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
|
877 |
+
&& (pos.pieces(PAWN) & KingSide);
|
878 |
+
|
879 |
+
bool almostUnwinnable = outflanking < 0
|
880 |
+
&& !pawnsOnBothFlanks;
|
881 |
+
|
882 |
+
bool infiltration = rank_of(pos.square<KING>(WHITE)) > RANK_4
|
883 |
+
|| rank_of(pos.square<KING>(BLACK)) < RANK_5;
|
884 |
+
|
885 |
+
// Compute the initiative bonus for the attacking side
|
886 |
+
int complexity = 9 * pe->passed_count()
|
887 |
+
+ 12 * pos.count<PAWN>()
|
888 |
+
+ 9 * outflanking
|
889 |
+
+ 21 * pawnsOnBothFlanks
|
890 |
+
+ 24 * infiltration
|
891 |
+
+ 51 * !pos.non_pawn_material()
|
892 |
+
- 43 * almostUnwinnable
|
893 |
+
-110 ;
|
894 |
+
|
895 |
+
Value mg = mg_value(score);
|
896 |
+
Value eg = eg_value(score);
|
897 |
+
|
898 |
+
// Now apply the bonus: note that we find the attacking side by extracting the
|
899 |
+
// sign of the midgame or endgame values, and that we carefully cap the bonus
|
900 |
+
// so that the midgame and endgame scores do not change sign after the bonus.
|
901 |
+
int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0);
|
902 |
+
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
|
903 |
+
|
904 |
+
mg += u;
|
905 |
+
eg += v;
|
906 |
+
|
907 |
+
// Compute the scale factor for the winning side
|
908 |
+
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
|
909 |
+
int sf = me->scale_factor(pos, strongSide);
|
910 |
+
|
911 |
+
// If scale factor is not already specific, scale up/down via general heuristics
|
912 |
+
if (sf == SCALE_FACTOR_NORMAL)
|
913 |
+
{
|
914 |
+
if (pos.opposite_bishops())
|
915 |
+
{
|
916 |
+
// For pure opposite colored bishops endgames use scale factor
|
917 |
+
// based on the number of passed pawns of the strong side.
|
918 |
+
if ( pos.non_pawn_material(WHITE) == BishopValueMg
|
919 |
+
&& pos.non_pawn_material(BLACK) == BishopValueMg)
|
920 |
+
sf = 18 + 4 * popcount(pe->passed_pawns(strongSide));
|
921 |
+
// For every other opposite colored bishops endgames use scale factor
|
922 |
+
// based on the number of all pieces of the strong side.
|
923 |
+
else
|
924 |
+
sf = 22 + 3 * pos.count<ALL_PIECES>(strongSide);
|
925 |
+
}
|
926 |
+
// For rook endgames with strong side not having overwhelming pawn number advantage
|
927 |
+
// and its pawns being on one flank and weak side protecting its pieces with a king
|
928 |
+
// use lower scale factor.
|
929 |
+
else if ( pos.non_pawn_material(WHITE) == RookValueMg
|
930 |
+
&& pos.non_pawn_material(BLACK) == RookValueMg
|
931 |
+
&& pos.count<PAWN>(strongSide) - pos.count<PAWN>(~strongSide) <= 1
|
932 |
+
&& bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN))
|
933 |
+
&& (attacks_bb<KING>(pos.square<KING>(~strongSide)) & pos.pieces(~strongSide, PAWN)))
|
934 |
+
sf = 36;
|
935 |
+
// For queen vs no queen endgames use scale factor
|
936 |
+
// based on number of minors of side that doesn't have queen.
|
937 |
+
else if (pos.count<QUEEN>() == 1)
|
938 |
+
sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
|
939 |
+
: pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE));
|
940 |
+
// In every other case use scale factor based on
|
941 |
+
// the number of pawns of the strong side reduced if pawns are on a single flank.
|
942 |
+
else
|
943 |
+
sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide)) - 4 * !pawnsOnBothFlanks;
|
944 |
+
|
945 |
+
// Reduce scale factor in case of pawns being on a single flank
|
946 |
+
sf -= 4 * !pawnsOnBothFlanks;
|
947 |
+
}
|
948 |
+
|
949 |
+
// Interpolate between the middlegame and (scaled by 'sf') endgame score
|
950 |
+
v = mg * int(me->game_phase())
|
951 |
+
+ eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL;
|
952 |
+
v /= PHASE_MIDGAME;
|
953 |
+
|
954 |
+
if constexpr (T)
|
955 |
+
{
|
956 |
+
Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score)));
|
957 |
+
Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL));
|
958 |
+
}
|
959 |
+
|
960 |
+
return Value(v);
|
961 |
+
}
|
962 |
+
|
963 |
+
|
964 |
+
// Evaluation::value() is the main function of the class. It computes the various
|
965 |
+
// parts of the evaluation and returns the value of the position from the point
|
966 |
+
// of view of the side to move.
|
967 |
+
|
968 |
+
template<Tracing T>
|
969 |
+
Value Evaluation<T>::value() {
|
970 |
+
|
971 |
+
assert(!pos.checkers());
|
972 |
+
|
973 |
+
// Probe the material hash table
|
974 |
+
me = Material::probe(pos);
|
975 |
+
|
976 |
+
// If we have a specialized evaluation function for the current material
|
977 |
+
// configuration, call it and return.
|
978 |
+
if (me->specialized_eval_exists())
|
979 |
+
return me->evaluate(pos);
|
980 |
+
|
981 |
+
// Initialize score by reading the incrementally updated scores included in
|
982 |
+
// the position object (material + piece square tables) and the material
|
983 |
+
// imbalance. Score is computed internally from the white point of view.
|
984 |
+
Score score = pos.psq_score() + me->imbalance();
|
985 |
+
|
986 |
+
// Probe the pawn hash table
|
987 |
+
pe = Pawns::probe(pos);
|
988 |
+
score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK);
|
989 |
+
|
990 |
+
// Early exit if score is high
|
991 |
+
auto lazy_skip = [&](Value lazyThreshold) {
|
992 |
+
return abs(mg_value(score) + eg_value(score)) > lazyThreshold
|
993 |
+
+ std::abs(pos.this_thread()->bestValue) * 5 / 4
|
994 |
+
+ pos.non_pawn_material() / 32;
|
995 |
+
};
|
996 |
+
|
997 |
+
if (lazy_skip(LazyThreshold1))
|
998 |
+
goto make_v;
|
999 |
+
|
1000 |
+
// Main evaluation begins here
|
1001 |
+
initialize<WHITE>();
|
1002 |
+
initialize<BLACK>();
|
1003 |
+
|
1004 |
+
// Pieces evaluated first (also populates attackedBy, attackedBy2).
|
1005 |
+
// Note that the order of evaluation of the terms is left unspecified.
|
1006 |
+
score += pieces<WHITE, KNIGHT>() - pieces<BLACK, KNIGHT>()
|
1007 |
+
+ pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>()
|
1008 |
+
+ pieces<WHITE, ROOK >() - pieces<BLACK, ROOK >()
|
1009 |
+
+ pieces<WHITE, QUEEN >() - pieces<BLACK, QUEEN >();
|
1010 |
+
|
1011 |
+
score += mobility[WHITE] - mobility[BLACK];
|
1012 |
+
|
1013 |
+
// More complex interactions that require fully populated attack bitboards
|
1014 |
+
score += king< WHITE>() - king< BLACK>()
|
1015 |
+
+ passed< WHITE>() - passed< BLACK>();
|
1016 |
+
|
1017 |
+
if (lazy_skip(LazyThreshold2))
|
1018 |
+
goto make_v;
|
1019 |
+
|
1020 |
+
score += threats<WHITE>() - threats<BLACK>()
|
1021 |
+
+ space< WHITE>() - space< BLACK>();
|
1022 |
+
|
1023 |
+
make_v:
|
1024 |
+
// Derive single value from mg and eg parts of score
|
1025 |
+
Value v = winnable(score);
|
1026 |
+
|
1027 |
+
// In case of tracing add all remaining individual evaluation terms
|
1028 |
+
if constexpr (T)
|
1029 |
+
{
|
1030 |
+
Trace::add(MATERIAL, pos.psq_score());
|
1031 |
+
Trace::add(IMBALANCE, me->imbalance());
|
1032 |
+
Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK));
|
1033 |
+
Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
|
1034 |
+
}
|
1035 |
+
|
1036 |
+
// Evaluation grain
|
1037 |
+
v = (v / 16) * 16;
|
1038 |
+
|
1039 |
+
// Side to move point of view
|
1040 |
+
v = (pos.side_to_move() == WHITE ? v : -v);
|
1041 |
+
|
1042 |
+
return v;
|
1043 |
+
}
|
1044 |
+
|
1045 |
+
} // namespace Eval
|
1046 |
+
|
1047 |
+
|
1048 |
+
/// evaluate() is the evaluator for the outer world. It returns a static
|
1049 |
+
/// evaluation of the position from the point of view of the side to move.
|
1050 |
+
|
1051 |
+
Value Eval::evaluate(const Position& pos, int* complexity) {
|
1052 |
+
|
1053 |
+
Value v;
|
1054 |
+
Value psq = pos.psq_eg_stm();
|
1055 |
+
|
1056 |
+
// We use the much less accurate but faster Classical eval when the NNUE
|
1057 |
+
// option is set to false. Otherwise we use the NNUE eval unless the
|
1058 |
+
// PSQ advantage is decisive and several pieces remain. (~3 Elo)
|
1059 |
+
bool useClassical = !useNNUE || (pos.count<ALL_PIECES>() > 7 && abs(psq) > 1760);
|
1060 |
+
|
1061 |
+
if (useClassical)
|
1062 |
+
v = Evaluation<NO_TRACE>(pos).value();
|
1063 |
+
else
|
1064 |
+
{
|
1065 |
+
int nnueComplexity;
|
1066 |
+
int scale = 1064 + 106 * pos.non_pawn_material() / 5120;
|
1067 |
+
|
1068 |
+
Color stm = pos.side_to_move();
|
1069 |
+
Value optimism = pos.this_thread()->optimism[stm];
|
1070 |
+
|
1071 |
+
Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
|
1072 |
+
|
1073 |
+
// Blend nnue complexity with (semi)classical complexity
|
1074 |
+
nnueComplexity = ( 416 * nnueComplexity
|
1075 |
+
+ 424 * abs(psq - nnue)
|
1076 |
+
+ (optimism > 0 ? int(optimism) * int(psq - nnue) : 0)
|
1077 |
+
) / 1024;
|
1078 |
+
|
1079 |
+
// Return hybrid NNUE complexity to caller
|
1080 |
+
if (complexity)
|
1081 |
+
*complexity = nnueComplexity;
|
1082 |
+
|
1083 |
+
optimism = optimism * (269 + nnueComplexity) / 256;
|
1084 |
+
v = (nnue * scale + optimism * (scale - 754)) / 1024;
|
1085 |
+
}
|
1086 |
+
|
1087 |
+
// Damp down the evaluation linearly when shuffling
|
1088 |
+
v = v * (195 - pos.rule50_count()) / 211;
|
1089 |
+
|
1090 |
+
// Guarantee evaluation does not hit the tablebase range
|
1091 |
+
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
1092 |
+
|
1093 |
+
// When not using NNUE, return classical complexity to caller
|
1094 |
+
if (complexity && (!useNNUE || useClassical))
|
1095 |
+
*complexity = abs(v - psq);
|
1096 |
+
|
1097 |
+
return v;
|
1098 |
+
}
|
1099 |
+
|
1100 |
+
/// trace() is like evaluate(), but instead of returning a value, it returns
|
1101 |
+
/// a string (suitable for outputting to stdout) that contains the detailed
|
1102 |
+
/// descriptions and values of each evaluation term. Useful for debugging.
|
1103 |
+
/// Trace scores are from white's point of view
|
1104 |
+
|
1105 |
+
std::string Eval::trace(Position& pos) {
|
1106 |
+
|
1107 |
+
if (pos.checkers())
|
1108 |
+
return "Final evaluation: none (in check)";
|
1109 |
+
|
1110 |
+
std::stringstream ss;
|
1111 |
+
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
|
1112 |
+
|
1113 |
+
Value v;
|
1114 |
+
|
1115 |
+
std::memset(scores, 0, sizeof(scores));
|
1116 |
+
|
1117 |
+
// Reset any global variable used in eval
|
1118 |
+
pos.this_thread()->bestValue = VALUE_ZERO;
|
1119 |
+
pos.this_thread()->optimism[WHITE] = VALUE_ZERO;
|
1120 |
+
pos.this_thread()->optimism[BLACK] = VALUE_ZERO;
|
1121 |
+
|
1122 |
+
v = Evaluation<TRACE>(pos).value();
|
1123 |
+
|
1124 |
+
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
|
1125 |
+
<< " Contributing terms for the classical eval:\n"
|
1126 |
+
<< "+------------+-------------+-------------+-------------+\n"
|
1127 |
+
<< "| Term | White | Black | Total |\n"
|
1128 |
+
<< "| | MG EG | MG EG | MG EG |\n"
|
1129 |
+
<< "+------------+-------------+-------------+-------------+\n"
|
1130 |
+
<< "| Material | " << Term(MATERIAL)
|
1131 |
+
<< "| Imbalance | " << Term(IMBALANCE)
|
1132 |
+
<< "| Pawns | " << Term(PAWN)
|
1133 |
+
<< "| Knights | " << Term(KNIGHT)
|
1134 |
+
<< "| Bishops | " << Term(BISHOP)
|
1135 |
+
<< "| Rooks | " << Term(ROOK)
|
1136 |
+
<< "| Queens | " << Term(QUEEN)
|
1137 |
+
<< "| Mobility | " << Term(MOBILITY)
|
1138 |
+
<< "|King safety | " << Term(KING)
|
1139 |
+
<< "| Threats | " << Term(THREAT)
|
1140 |
+
<< "| Passed | " << Term(PASSED)
|
1141 |
+
<< "| Space | " << Term(SPACE)
|
1142 |
+
<< "| Winnable | " << Term(WINNABLE)
|
1143 |
+
<< "+------------+-------------+-------------+-------------+\n"
|
1144 |
+
<< "| Total | " << Term(TOTAL)
|
1145 |
+
<< "+------------+-------------+-------------+-------------+\n";
|
1146 |
+
|
1147 |
+
if (Eval::useNNUE)
|
1148 |
+
ss << '\n' << NNUE::trace(pos) << '\n';
|
1149 |
+
|
1150 |
+
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
|
1151 |
+
|
1152 |
+
v = pos.side_to_move() == WHITE ? v : -v;
|
1153 |
+
ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n";
|
1154 |
+
if (Eval::useNNUE)
|
1155 |
+
{
|
1156 |
+
v = NNUE::evaluate(pos, false);
|
1157 |
+
v = pos.side_to_move() == WHITE ? v : -v;
|
1158 |
+
ss << "NNUE evaluation " << to_cp(v) << " (white side)\n";
|
1159 |
+
}
|
1160 |
+
|
1161 |
+
v = evaluate(pos);
|
1162 |
+
v = pos.side_to_move() == WHITE ? v : -v;
|
1163 |
+
ss << "Final evaluation " << to_cp(v) << " (white side)";
|
1164 |
+
if (Eval::useNNUE)
|
1165 |
+
ss << " [with scaled NNUE, hybrid, ...]";
|
1166 |
+
ss << "\n";
|
1167 |
+
|
1168 |
+
return ss.str();
|
1169 |
+
}
|
1170 |
+
|
1171 |
+
} // namespace Stockfish
|
src/evaluate.h
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef EVALUATE_H_INCLUDED
|
20 |
+
#define EVALUATE_H_INCLUDED
|
21 |
+
|
22 |
+
#include <string>
|
23 |
+
#include <optional>
|
24 |
+
|
25 |
+
#include "types.h"
|
26 |
+
|
27 |
+
namespace Stockfish {
|
28 |
+
|
29 |
+
class Position;
|
30 |
+
|
31 |
+
namespace Eval {
|
32 |
+
|
33 |
+
std::string trace(Position& pos);
|
34 |
+
Value evaluate(const Position& pos, int* complexity = nullptr);
|
35 |
+
|
36 |
+
extern bool useNNUE;
|
37 |
+
extern std::string currentEvalFileName;
|
38 |
+
|
39 |
+
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
|
40 |
+
// for the build process (profile-build and fishtest) to work. Do not change the
|
41 |
+
// name of the macro, as it is used in the Makefile.
|
42 |
+
#define EvalFileDefaultName "nn-ad9b42354671.nnue"
|
43 |
+
|
44 |
+
namespace NNUE {
|
45 |
+
|
46 |
+
std::string trace(Position& pos);
|
47 |
+
Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
|
48 |
+
|
49 |
+
void init();
|
50 |
+
void verify();
|
51 |
+
|
52 |
+
bool load_eval(std::string name, std::istream& stream);
|
53 |
+
bool save_eval(std::ostream& stream);
|
54 |
+
bool save_eval(const std::optional<std::string>& filename);
|
55 |
+
|
56 |
+
} // namespace NNUE
|
57 |
+
|
58 |
+
} // namespace Eval
|
59 |
+
|
60 |
+
} // namespace Stockfish
|
61 |
+
|
62 |
+
#endif // #ifndef EVALUATE_H_INCLUDED
|
src/incbin/UNLICENCE
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
The file "incbin.h" is free and unencumbered software released into
|
2 |
+
the public domain by Dale Weiler, see:
|
3 |
+
<https://github.com/graphitemaster/incbin>
|
4 |
+
|
5 |
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
6 |
+
distribute this software, either in source code form or as a compiled
|
7 |
+
binary, for any purpose, commercial or non-commercial, and by any
|
8 |
+
means.
|
9 |
+
|
10 |
+
In jurisdictions that recognize copyright laws, the author or authors
|
11 |
+
of this software dedicate any and all copyright interest in the
|
12 |
+
software to the public domain. We make this dedication for the benefit
|
13 |
+
of the public at large and to the detriment of our heirs and
|
14 |
+
successors. We intend this dedication to be an overt act of
|
15 |
+
relinquishment in perpetuity of all present and future rights to this
|
16 |
+
software under copyright law.
|
17 |
+
|
18 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
21 |
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
22 |
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
23 |
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24 |
+
OTHER DEALINGS IN THE SOFTWARE.
|
25 |
+
|
26 |
+
For more information, please refer to <http://unlicense.org/>
|
src/incbin/incbin.h
ADDED
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* @file incbin.h
|
3 |
+
* @author Dale Weiler
|
4 |
+
* @brief Utility for including binary files
|
5 |
+
*
|
6 |
+
* Facilities for including binary files into the current translation unit and
|
7 |
+
* making use from them externally in other translation units.
|
8 |
+
*/
|
9 |
+
#ifndef INCBIN_HDR
|
10 |
+
#define INCBIN_HDR
|
11 |
+
#include <limits.h>
|
12 |
+
#if defined(__AVX512BW__) || \
|
13 |
+
defined(__AVX512CD__) || \
|
14 |
+
defined(__AVX512DQ__) || \
|
15 |
+
defined(__AVX512ER__) || \
|
16 |
+
defined(__AVX512PF__) || \
|
17 |
+
defined(__AVX512VL__) || \
|
18 |
+
defined(__AVX512F__)
|
19 |
+
# define INCBIN_ALIGNMENT_INDEX 6
|
20 |
+
#elif defined(__AVX__) || \
|
21 |
+
defined(__AVX2__)
|
22 |
+
# define INCBIN_ALIGNMENT_INDEX 5
|
23 |
+
#elif defined(__SSE__) || \
|
24 |
+
defined(__SSE2__) || \
|
25 |
+
defined(__SSE3__) || \
|
26 |
+
defined(__SSSE3__) || \
|
27 |
+
defined(__SSE4_1__) || \
|
28 |
+
defined(__SSE4_2__) || \
|
29 |
+
defined(__neon__)
|
30 |
+
# define INCBIN_ALIGNMENT_INDEX 4
|
31 |
+
#elif ULONG_MAX != 0xffffffffu
|
32 |
+
# define INCBIN_ALIGNMENT_INDEX 3
|
33 |
+
# else
|
34 |
+
# define INCBIN_ALIGNMENT_INDEX 2
|
35 |
+
#endif
|
36 |
+
|
37 |
+
/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */
|
38 |
+
#define INCBIN_ALIGN_SHIFT_0 1
|
39 |
+
#define INCBIN_ALIGN_SHIFT_1 2
|
40 |
+
#define INCBIN_ALIGN_SHIFT_2 4
|
41 |
+
#define INCBIN_ALIGN_SHIFT_3 8
|
42 |
+
#define INCBIN_ALIGN_SHIFT_4 16
|
43 |
+
#define INCBIN_ALIGN_SHIFT_5 32
|
44 |
+
#define INCBIN_ALIGN_SHIFT_6 64
|
45 |
+
|
46 |
+
/* Actual alignment value */
|
47 |
+
#define INCBIN_ALIGNMENT \
|
48 |
+
INCBIN_CONCATENATE( \
|
49 |
+
INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \
|
50 |
+
INCBIN_ALIGNMENT_INDEX)
|
51 |
+
|
52 |
+
/* Stringize */
|
53 |
+
#define INCBIN_STR(X) \
|
54 |
+
#X
|
55 |
+
#define INCBIN_STRINGIZE(X) \
|
56 |
+
INCBIN_STR(X)
|
57 |
+
/* Concatenate */
|
58 |
+
#define INCBIN_CAT(X, Y) \
|
59 |
+
X ## Y
|
60 |
+
#define INCBIN_CONCATENATE(X, Y) \
|
61 |
+
INCBIN_CAT(X, Y)
|
62 |
+
/* Deferred macro expansion */
|
63 |
+
#define INCBIN_EVAL(X) \
|
64 |
+
X
|
65 |
+
#define INCBIN_INVOKE(N, ...) \
|
66 |
+
INCBIN_EVAL(N(__VA_ARGS__))
|
67 |
+
|
68 |
+
/* Green Hills uses a different directive for including binary data */
|
69 |
+
#if defined(__ghs__)
|
70 |
+
# if (__ghs_asm == 2)
|
71 |
+
# define INCBIN_MACRO ".file"
|
72 |
+
/* Or consider the ".myrawdata" entry in the ld file */
|
73 |
+
# else
|
74 |
+
# define INCBIN_MACRO "\tINCBIN"
|
75 |
+
# endif
|
76 |
+
#else
|
77 |
+
# define INCBIN_MACRO ".incbin"
|
78 |
+
#endif
|
79 |
+
|
80 |
+
#ifndef _MSC_VER
|
81 |
+
# define INCBIN_ALIGN \
|
82 |
+
__attribute__((aligned(INCBIN_ALIGNMENT)))
|
83 |
+
#else
|
84 |
+
# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT))
|
85 |
+
#endif
|
86 |
+
|
87 |
+
#if defined(__arm__) || /* GNU C and RealView */ \
|
88 |
+
defined(__arm) || /* Diab */ \
|
89 |
+
defined(_ARM) /* ImageCraft */
|
90 |
+
# define INCBIN_ARM
|
91 |
+
#endif
|
92 |
+
|
93 |
+
#ifdef __GNUC__
|
94 |
+
/* Utilize .balign where supported */
|
95 |
+
# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
|
96 |
+
# define INCBIN_ALIGN_BYTE ".balign 1\n"
|
97 |
+
#elif defined(INCBIN_ARM)
|
98 |
+
/*
|
99 |
+
* On arm assemblers, the alignment value is calculated as (1 << n) where `n' is
|
100 |
+
* the shift count. This is the value passed to `.align'
|
101 |
+
*/
|
102 |
+
# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n"
|
103 |
+
# define INCBIN_ALIGN_BYTE ".align 0\n"
|
104 |
+
#else
|
105 |
+
/* We assume other inline assembler's treat `.align' as `.balign' */
|
106 |
+
# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
|
107 |
+
# define INCBIN_ALIGN_BYTE ".align 1\n"
|
108 |
+
#endif
|
109 |
+
|
110 |
+
/* INCBIN_CONST is used by incbin.c generated files */
|
111 |
+
#if defined(__cplusplus)
|
112 |
+
# define INCBIN_EXTERNAL extern "C"
|
113 |
+
# define INCBIN_CONST extern const
|
114 |
+
#else
|
115 |
+
# define INCBIN_EXTERNAL extern
|
116 |
+
# define INCBIN_CONST const
|
117 |
+
#endif
|
118 |
+
|
119 |
+
/**
|
120 |
+
* @brief Optionally override the linker section into which data is emitted.
|
121 |
+
*
|
122 |
+
* @warning If you use this facility, you'll have to deal with platform-specific linker output
|
123 |
+
* section naming on your own
|
124 |
+
*
|
125 |
+
* Overriding the default linker output section, e.g for esp8266/Arduino:
|
126 |
+
* @code
|
127 |
+
* #define INCBIN_OUTPUT_SECTION ".irom.text"
|
128 |
+
* #include "incbin.h"
|
129 |
+
* INCBIN(Foo, "foo.txt");
|
130 |
+
* // Data is emitted into program memory that never gets copied to RAM
|
131 |
+
* @endcode
|
132 |
+
*/
|
133 |
+
#if !defined(INCBIN_OUTPUT_SECTION)
|
134 |
+
# if defined(__APPLE__)
|
135 |
+
# define INCBIN_OUTPUT_SECTION ".const_data"
|
136 |
+
# else
|
137 |
+
# define INCBIN_OUTPUT_SECTION ".rodata"
|
138 |
+
# endif
|
139 |
+
#endif
|
140 |
+
|
141 |
+
#if defined(__APPLE__)
|
142 |
+
/* The directives are different for Apple branded compilers */
|
143 |
+
# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n"
|
144 |
+
# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
|
145 |
+
# define INCBIN_INT ".long "
|
146 |
+
# define INCBIN_MANGLE "_"
|
147 |
+
# define INCBIN_BYTE ".byte "
|
148 |
+
# define INCBIN_TYPE(...)
|
149 |
+
#else
|
150 |
+
# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n"
|
151 |
+
# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
|
152 |
+
# if defined(__ghs__)
|
153 |
+
# define INCBIN_INT ".word "
|
154 |
+
# else
|
155 |
+
# define INCBIN_INT ".int "
|
156 |
+
# endif
|
157 |
+
# if defined(__USER_LABEL_PREFIX__)
|
158 |
+
# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__)
|
159 |
+
# else
|
160 |
+
# define INCBIN_MANGLE ""
|
161 |
+
# endif
|
162 |
+
# if defined(INCBIN_ARM)
|
163 |
+
/* On arm assemblers, `@' is used as a line comment token */
|
164 |
+
# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n"
|
165 |
+
# elif defined(__MINGW32__) || defined(__MINGW64__)
|
166 |
+
/* Mingw doesn't support this directive either */
|
167 |
+
# define INCBIN_TYPE(NAME)
|
168 |
+
# else
|
169 |
+
/* It's safe to use `@' on other architectures */
|
170 |
+
# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n"
|
171 |
+
# endif
|
172 |
+
# define INCBIN_BYTE ".byte "
|
173 |
+
#endif
|
174 |
+
|
175 |
+
/* List of style types used for symbol names */
|
176 |
+
#define INCBIN_STYLE_CAMEL 0
|
177 |
+
#define INCBIN_STYLE_SNAKE 1
|
178 |
+
|
179 |
+
/**
|
180 |
+
* @brief Specify the prefix to use for symbol names.
|
181 |
+
*
|
182 |
+
* By default this is `g', producing symbols of the form:
|
183 |
+
* @code
|
184 |
+
* #include "incbin.h"
|
185 |
+
* INCBIN(Foo, "foo.txt");
|
186 |
+
*
|
187 |
+
* // Now you have the following symbols:
|
188 |
+
* // const unsigned char gFooData[];
|
189 |
+
* // const unsigned char *const gFooEnd;
|
190 |
+
* // const unsigned int gFooSize;
|
191 |
+
* @endcode
|
192 |
+
*
|
193 |
+
* If however you specify a prefix before including: e.g:
|
194 |
+
* @code
|
195 |
+
* #define INCBIN_PREFIX incbin
|
196 |
+
* #include "incbin.h"
|
197 |
+
* INCBIN(Foo, "foo.txt");
|
198 |
+
*
|
199 |
+
* // Now you have the following symbols instead:
|
200 |
+
* // const unsigned char incbinFooData[];
|
201 |
+
* // const unsigned char *const incbinFooEnd;
|
202 |
+
* // const unsigned int incbinFooSize;
|
203 |
+
* @endcode
|
204 |
+
*/
|
205 |
+
#if !defined(INCBIN_PREFIX)
|
206 |
+
# define INCBIN_PREFIX g
|
207 |
+
#endif
|
208 |
+
|
209 |
+
/**
|
210 |
+
* @brief Specify the style used for symbol names.
|
211 |
+
*
|
212 |
+
* Possible options are
|
213 |
+
* - INCBIN_STYLE_CAMEL "CamelCase"
|
214 |
+
* - INCBIN_STYLE_SNAKE "snake_case"
|
215 |
+
*
|
216 |
+
* Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form:
|
217 |
+
* @code
|
218 |
+
* #include "incbin.h"
|
219 |
+
* INCBIN(Foo, "foo.txt");
|
220 |
+
*
|
221 |
+
* // Now you have the following symbols:
|
222 |
+
* // const unsigned char <prefix>FooData[];
|
223 |
+
* // const unsigned char *const <prefix>FooEnd;
|
224 |
+
* // const unsigned int <prefix>FooSize;
|
225 |
+
* @endcode
|
226 |
+
*
|
227 |
+
* If however you specify a style before including: e.g:
|
228 |
+
* @code
|
229 |
+
* #define INCBIN_STYLE INCBIN_STYLE_SNAKE
|
230 |
+
* #include "incbin.h"
|
231 |
+
* INCBIN(foo, "foo.txt");
|
232 |
+
*
|
233 |
+
* // Now you have the following symbols:
|
234 |
+
* // const unsigned char <prefix>foo_data[];
|
235 |
+
* // const unsigned char *const <prefix>foo_end;
|
236 |
+
* // const unsigned int <prefix>foo_size;
|
237 |
+
* @endcode
|
238 |
+
*/
|
239 |
+
#if !defined(INCBIN_STYLE)
|
240 |
+
# define INCBIN_STYLE INCBIN_STYLE_CAMEL
|
241 |
+
#endif
|
242 |
+
|
243 |
+
/* Style lookup tables */
|
244 |
+
#define INCBIN_STYLE_0_DATA Data
|
245 |
+
#define INCBIN_STYLE_0_END End
|
246 |
+
#define INCBIN_STYLE_0_SIZE Size
|
247 |
+
#define INCBIN_STYLE_1_DATA _data
|
248 |
+
#define INCBIN_STYLE_1_END _end
|
249 |
+
#define INCBIN_STYLE_1_SIZE _size
|
250 |
+
|
251 |
+
/* Style lookup: returning identifier */
|
252 |
+
#define INCBIN_STYLE_IDENT(TYPE) \
|
253 |
+
INCBIN_CONCATENATE( \
|
254 |
+
INCBIN_STYLE_, \
|
255 |
+
INCBIN_CONCATENATE( \
|
256 |
+
INCBIN_EVAL(INCBIN_STYLE), \
|
257 |
+
INCBIN_CONCATENATE(_, TYPE)))
|
258 |
+
|
259 |
+
/* Style lookup: returning string literal */
|
260 |
+
#define INCBIN_STYLE_STRING(TYPE) \
|
261 |
+
INCBIN_STRINGIZE( \
|
262 |
+
INCBIN_STYLE_IDENT(TYPE)) \
|
263 |
+
|
264 |
+
/* Generate the global labels by indirectly invoking the macro with our style
|
265 |
+
* type and concatenating the name against them. */
|
266 |
+
#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
|
267 |
+
INCBIN_INVOKE( \
|
268 |
+
INCBIN_GLOBAL, \
|
269 |
+
INCBIN_CONCATENATE( \
|
270 |
+
NAME, \
|
271 |
+
INCBIN_INVOKE( \
|
272 |
+
INCBIN_STYLE_IDENT, \
|
273 |
+
TYPE))) \
|
274 |
+
INCBIN_INVOKE( \
|
275 |
+
INCBIN_TYPE, \
|
276 |
+
INCBIN_CONCATENATE( \
|
277 |
+
NAME, \
|
278 |
+
INCBIN_INVOKE( \
|
279 |
+
INCBIN_STYLE_IDENT, \
|
280 |
+
TYPE)))
|
281 |
+
|
282 |
+
/**
|
283 |
+
* @brief Externally reference binary data included in another translation unit.
|
284 |
+
*
|
285 |
+
* Produces three external symbols that reference the binary data included in
|
286 |
+
* another translation unit.
|
287 |
+
*
|
288 |
+
* The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
|
289 |
+
* "Data", as well as "End" and "Size" after. An example is provided below.
|
290 |
+
*
|
291 |
+
* @param NAME The name given for the binary data
|
292 |
+
*
|
293 |
+
* @code
|
294 |
+
* INCBIN_EXTERN(Foo);
|
295 |
+
*
|
296 |
+
* // Now you have the following symbols:
|
297 |
+
* // extern const unsigned char <prefix>FooData[];
|
298 |
+
* // extern const unsigned char *const <prefix>FooEnd;
|
299 |
+
* // extern const unsigned int <prefix>FooSize;
|
300 |
+
* @endcode
|
301 |
+
*/
|
302 |
+
#define INCBIN_EXTERN(NAME) \
|
303 |
+
INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \
|
304 |
+
INCBIN_CONCATENATE( \
|
305 |
+
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
306 |
+
INCBIN_STYLE_IDENT(DATA))[]; \
|
307 |
+
INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \
|
308 |
+
INCBIN_CONCATENATE( \
|
309 |
+
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
310 |
+
INCBIN_STYLE_IDENT(END)); \
|
311 |
+
INCBIN_EXTERNAL const unsigned int \
|
312 |
+
INCBIN_CONCATENATE( \
|
313 |
+
INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
|
314 |
+
INCBIN_STYLE_IDENT(SIZE))
|
315 |
+
|
316 |
+
/**
|
317 |
+
* @brief Include a binary file into the current translation unit.
|
318 |
+
*
|
319 |
+
* Includes a binary file into the current translation unit, producing three symbols
|
320 |
+
* for objects that encode the data and size respectively.
|
321 |
+
*
|
322 |
+
* The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
|
323 |
+
* "Data", as well as "End" and "Size" after. An example is provided below.
|
324 |
+
*
|
325 |
+
* @param NAME The name to associate with this binary data (as an identifier.)
|
326 |
+
* @param FILENAME The file to include (as a string literal.)
|
327 |
+
*
|
328 |
+
* @code
|
329 |
+
* INCBIN(Icon, "icon.png");
|
330 |
+
*
|
331 |
+
* // Now you have the following symbols:
|
332 |
+
* // const unsigned char <prefix>IconData[];
|
333 |
+
* // const unsigned char *const <prefix>IconEnd;
|
334 |
+
* // const unsigned int <prefix>IconSize;
|
335 |
+
* @endcode
|
336 |
+
*
|
337 |
+
* @warning This must be used in global scope
|
338 |
+
* @warning The identifiers may be different if INCBIN_STYLE is not default
|
339 |
+
*
|
340 |
+
* To externally reference the data included by this in another translation unit
|
341 |
+
* please @see INCBIN_EXTERN.
|
342 |
+
*/
|
343 |
+
#ifdef _MSC_VER
|
344 |
+
#define INCBIN(NAME, FILENAME) \
|
345 |
+
INCBIN_EXTERN(NAME)
|
346 |
+
#else
|
347 |
+
#define INCBIN(NAME, FILENAME) \
|
348 |
+
__asm__(INCBIN_SECTION \
|
349 |
+
INCBIN_GLOBAL_LABELS(NAME, DATA) \
|
350 |
+
INCBIN_ALIGN_HOST \
|
351 |
+
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \
|
352 |
+
INCBIN_MACRO " \"" FILENAME "\"\n" \
|
353 |
+
INCBIN_GLOBAL_LABELS(NAME, END) \
|
354 |
+
INCBIN_ALIGN_BYTE \
|
355 |
+
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \
|
356 |
+
INCBIN_BYTE "1\n" \
|
357 |
+
INCBIN_GLOBAL_LABELS(NAME, SIZE) \
|
358 |
+
INCBIN_ALIGN_HOST \
|
359 |
+
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \
|
360 |
+
INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \
|
361 |
+
INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \
|
362 |
+
INCBIN_ALIGN_HOST \
|
363 |
+
".text\n" \
|
364 |
+
); \
|
365 |
+
INCBIN_EXTERN(NAME)
|
366 |
+
|
367 |
+
#endif
|
368 |
+
#endif
|
src/main.cpp
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <iostream>
|
20 |
+
|
21 |
+
#include "bitboard.h"
|
22 |
+
#include "endgame.h"
|
23 |
+
#include "position.h"
|
24 |
+
#include "psqt.h"
|
25 |
+
#include "search.h"
|
26 |
+
#include "syzygy/tbprobe.h"
|
27 |
+
#include "thread.h"
|
28 |
+
#include "tt.h"
|
29 |
+
#include "uci.h"
|
30 |
+
|
31 |
+
using namespace Stockfish;
|
32 |
+
|
33 |
+
int main(int argc, char* argv[]) {
|
34 |
+
|
35 |
+
std::cout << engine_info() << std::endl;
|
36 |
+
|
37 |
+
CommandLine::init(argc, argv);
|
38 |
+
UCI::init(Options);
|
39 |
+
Tune::init();
|
40 |
+
PSQT::init();
|
41 |
+
Bitboards::init();
|
42 |
+
Position::init();
|
43 |
+
Bitbases::init();
|
44 |
+
Endgames::init();
|
45 |
+
Threads.set(size_t(Options["Threads"]));
|
46 |
+
Search::clear(); // After threads are up
|
47 |
+
Eval::NNUE::init();
|
48 |
+
|
49 |
+
UCI::loop(argc, argv);
|
50 |
+
|
51 |
+
Threads.set(0);
|
52 |
+
return 0;
|
53 |
+
}
|
src/material.cpp
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <cassert>
|
20 |
+
#include <cstring> // For std::memset
|
21 |
+
|
22 |
+
#include "material.h"
|
23 |
+
#include "thread.h"
|
24 |
+
|
25 |
+
using namespace std;
|
26 |
+
|
27 |
+
namespace Stockfish {
|
28 |
+
|
29 |
+
namespace {
|
30 |
+
#define S(mg, eg) make_score(mg, eg)
|
31 |
+
|
32 |
+
// Polynomial material imbalance parameters
|
33 |
+
|
34 |
+
// One Score parameter for each pair (our piece, another of our pieces)
|
35 |
+
constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = {
|
36 |
+
// OUR PIECE 2
|
37 |
+
// bishop pair pawn knight bishop rook queen
|
38 |
+
{S(1419, 1455) }, // Bishop pair
|
39 |
+
{S( 101, 28), S( 37, 39) }, // Pawn
|
40 |
+
{S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1
|
41 |
+
{S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop
|
42 |
+
{S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook
|
43 |
+
{S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen
|
44 |
+
};
|
45 |
+
|
46 |
+
// One Score parameter for each pair (our piece, their piece)
|
47 |
+
constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = {
|
48 |
+
// THEIR PIECE
|
49 |
+
// bishop pair pawn knight bishop rook queen
|
50 |
+
{ }, // Bishop pair
|
51 |
+
{S( 33, 30) }, // Pawn
|
52 |
+
{S( 46, 18), S(106, 84) }, // Knight OUR PIECE
|
53 |
+
{S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop
|
54 |
+
{S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook
|
55 |
+
{S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen
|
56 |
+
};
|
57 |
+
|
58 |
+
#undef S
|
59 |
+
|
60 |
+
// Endgame evaluation and scaling functions are accessed directly and not through
|
61 |
+
// the function maps because they correspond to more than one material hash key.
|
62 |
+
Endgame<KXK> EvaluateKXK[] = { Endgame<KXK>(WHITE), Endgame<KXK>(BLACK) };
|
63 |
+
|
64 |
+
Endgame<KBPsK> ScaleKBPsK[] = { Endgame<KBPsK>(WHITE), Endgame<KBPsK>(BLACK) };
|
65 |
+
Endgame<KQKRPs> ScaleKQKRPs[] = { Endgame<KQKRPs>(WHITE), Endgame<KQKRPs>(BLACK) };
|
66 |
+
Endgame<KPsK> ScaleKPsK[] = { Endgame<KPsK>(WHITE), Endgame<KPsK>(BLACK) };
|
67 |
+
Endgame<KPKP> ScaleKPKP[] = { Endgame<KPKP>(WHITE), Endgame<KPKP>(BLACK) };
|
68 |
+
|
69 |
+
// Helper used to detect a given material distribution
|
70 |
+
bool is_KXK(const Position& pos, Color us) {
|
71 |
+
return !more_than_one(pos.pieces(~us))
|
72 |
+
&& pos.non_pawn_material(us) >= RookValueMg;
|
73 |
+
}
|
74 |
+
|
75 |
+
bool is_KBPsK(const Position& pos, Color us) {
|
76 |
+
return pos.non_pawn_material(us) == BishopValueMg
|
77 |
+
&& pos.count<PAWN>(us) >= 1;
|
78 |
+
}
|
79 |
+
|
80 |
+
bool is_KQKRPs(const Position& pos, Color us) {
|
81 |
+
return !pos.count<PAWN>(us)
|
82 |
+
&& pos.non_pawn_material(us) == QueenValueMg
|
83 |
+
&& pos.count<ROOK>(~us) == 1
|
84 |
+
&& pos.count<PAWN>(~us) >= 1;
|
85 |
+
}
|
86 |
+
|
87 |
+
|
88 |
+
/// imbalance() calculates the imbalance by comparing the piece count of each
|
89 |
+
/// piece type for both colors.
|
90 |
+
|
91 |
+
template<Color Us>
|
92 |
+
Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
|
93 |
+
|
94 |
+
constexpr Color Them = ~Us;
|
95 |
+
|
96 |
+
Score bonus = SCORE_ZERO;
|
97 |
+
|
98 |
+
// Second-degree polynomial material imbalance, by Tord Romstad
|
99 |
+
for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
|
100 |
+
{
|
101 |
+
if (!pieceCount[Us][pt1])
|
102 |
+
continue;
|
103 |
+
|
104 |
+
int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1];
|
105 |
+
|
106 |
+
for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2)
|
107 |
+
v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2]
|
108 |
+
+ QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
|
109 |
+
|
110 |
+
bonus += pieceCount[Us][pt1] * v;
|
111 |
+
}
|
112 |
+
|
113 |
+
return bonus;
|
114 |
+
}
|
115 |
+
|
116 |
+
} // namespace
|
117 |
+
|
118 |
+
namespace Material {
|
119 |
+
|
120 |
+
|
121 |
+
/// Material::probe() looks up the current position's material configuration in
|
122 |
+
/// the material hash table. It returns a pointer to the Entry if the position
|
123 |
+
/// is found. Otherwise a new Entry is computed and stored there, so we don't
|
124 |
+
/// have to recompute all when the same material configuration occurs again.
|
125 |
+
|
126 |
+
Entry* probe(const Position& pos) {
|
127 |
+
|
128 |
+
Key key = pos.material_key();
|
129 |
+
Entry* e = pos.this_thread()->materialTable[key];
|
130 |
+
|
131 |
+
if (e->key == key)
|
132 |
+
return e;
|
133 |
+
|
134 |
+
std::memset(e, 0, sizeof(Entry));
|
135 |
+
e->key = key;
|
136 |
+
e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL;
|
137 |
+
|
138 |
+
Value npm_w = pos.non_pawn_material(WHITE);
|
139 |
+
Value npm_b = pos.non_pawn_material(BLACK);
|
140 |
+
Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
|
141 |
+
|
142 |
+
// Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
|
143 |
+
e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
|
144 |
+
|
145 |
+
// Let's look if we have a specialized evaluation function for this particular
|
146 |
+
// material configuration. Firstly we look for a fixed configuration one, then
|
147 |
+
// for a generic one if the previous search failed.
|
148 |
+
if ((e->evaluationFunction = Endgames::probe<Value>(key)) != nullptr)
|
149 |
+
return e;
|
150 |
+
|
151 |
+
for (Color c : { WHITE, BLACK })
|
152 |
+
if (is_KXK(pos, c))
|
153 |
+
{
|
154 |
+
e->evaluationFunction = &EvaluateKXK[c];
|
155 |
+
return e;
|
156 |
+
}
|
157 |
+
|
158 |
+
// OK, we didn't find any special evaluation function for the current material
|
159 |
+
// configuration. Is there a suitable specialized scaling function?
|
160 |
+
const auto* sf = Endgames::probe<ScaleFactor>(key);
|
161 |
+
|
162 |
+
if (sf)
|
163 |
+
{
|
164 |
+
e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned
|
165 |
+
return e;
|
166 |
+
}
|
167 |
+
|
168 |
+
// We didn't find any specialized scaling function, so fall back on generic
|
169 |
+
// ones that refer to more than one material distribution. Note that in this
|
170 |
+
// case we don't return after setting the function.
|
171 |
+
for (Color c : { WHITE, BLACK })
|
172 |
+
{
|
173 |
+
if (is_KBPsK(pos, c))
|
174 |
+
e->scalingFunction[c] = &ScaleKBPsK[c];
|
175 |
+
|
176 |
+
else if (is_KQKRPs(pos, c))
|
177 |
+
e->scalingFunction[c] = &ScaleKQKRPs[c];
|
178 |
+
}
|
179 |
+
|
180 |
+
if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board
|
181 |
+
{
|
182 |
+
if (!pos.count<PAWN>(BLACK))
|
183 |
+
{
|
184 |
+
assert(pos.count<PAWN>(WHITE) >= 2);
|
185 |
+
|
186 |
+
e->scalingFunction[WHITE] = &ScaleKPsK[WHITE];
|
187 |
+
}
|
188 |
+
else if (!pos.count<PAWN>(WHITE))
|
189 |
+
{
|
190 |
+
assert(pos.count<PAWN>(BLACK) >= 2);
|
191 |
+
|
192 |
+
e->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
|
193 |
+
}
|
194 |
+
else if (pos.count<PAWN>(WHITE) == 1 && pos.count<PAWN>(BLACK) == 1)
|
195 |
+
{
|
196 |
+
// This is a special case because we set scaling functions
|
197 |
+
// for both colors instead of only one.
|
198 |
+
e->scalingFunction[WHITE] = &ScaleKPKP[WHITE];
|
199 |
+
e->scalingFunction[BLACK] = &ScaleKPKP[BLACK];
|
200 |
+
}
|
201 |
+
}
|
202 |
+
|
203 |
+
// Zero or just one pawn makes it difficult to win, even with a small material
|
204 |
+
// advantage. This catches some trivial draws like KK, KBK and KNK and gives a
|
205 |
+
// drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN).
|
206 |
+
if (!pos.count<PAWN>(WHITE) && npm_w - npm_b <= BishopValueMg)
|
207 |
+
e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW :
|
208 |
+
npm_b <= BishopValueMg ? 4 : 14);
|
209 |
+
|
210 |
+
if (!pos.count<PAWN>(BLACK) && npm_b - npm_w <= BishopValueMg)
|
211 |
+
e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW :
|
212 |
+
npm_w <= BishopValueMg ? 4 : 14);
|
213 |
+
|
214 |
+
// Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder
|
215 |
+
// for the bishop pair "extended piece", which allows us to be more flexible
|
216 |
+
// in defining bishop pair bonuses.
|
217 |
+
const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = {
|
218 |
+
{ pos.count<BISHOP>(WHITE) > 1, pos.count<PAWN>(WHITE), pos.count<KNIGHT>(WHITE),
|
219 |
+
pos.count<BISHOP>(WHITE) , pos.count<ROOK>(WHITE), pos.count<QUEEN >(WHITE) },
|
220 |
+
{ pos.count<BISHOP>(BLACK) > 1, pos.count<PAWN>(BLACK), pos.count<KNIGHT>(BLACK),
|
221 |
+
pos.count<BISHOP>(BLACK) , pos.count<ROOK>(BLACK), pos.count<QUEEN >(BLACK) } };
|
222 |
+
|
223 |
+
e->score = (imbalance<WHITE>(pieceCount) - imbalance<BLACK>(pieceCount)) / 16;
|
224 |
+
return e;
|
225 |
+
}
|
226 |
+
|
227 |
+
} // namespace Material
|
228 |
+
|
229 |
+
} // namespace Stockfish
|
src/material.h
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef MATERIAL_H_INCLUDED
|
20 |
+
#define MATERIAL_H_INCLUDED
|
21 |
+
|
22 |
+
#include "endgame.h"
|
23 |
+
#include "misc.h"
|
24 |
+
#include "position.h"
|
25 |
+
#include "types.h"
|
26 |
+
|
27 |
+
namespace Stockfish::Material {
|
28 |
+
|
29 |
+
/// Material::Entry contains various information about a material configuration.
|
30 |
+
/// It contains a material imbalance evaluation, a function pointer to a special
|
31 |
+
/// endgame evaluation function (which in most cases is NULL, meaning that the
|
32 |
+
/// standard evaluation function will be used), and scale factors.
|
33 |
+
///
|
34 |
+
/// The scale factors are used to scale the evaluation score up or down. For
|
35 |
+
/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4,
|
36 |
+
/// which will result in scores of absolute value less than one pawn.
|
37 |
+
|
38 |
+
struct Entry {
|
39 |
+
|
40 |
+
Score imbalance() const { return score; }
|
41 |
+
Phase game_phase() const { return (Phase)gamePhase; }
|
42 |
+
bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
|
43 |
+
Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
|
44 |
+
|
45 |
+
// scale_factor() takes a position and a color as input and returns a scale factor
|
46 |
+
// for the given color. We have to provide the position in addition to the color
|
47 |
+
// because the scale factor may also be a function which should be applied to
|
48 |
+
// the position. For instance, in KBP vs K endgames, the scaling function looks
|
49 |
+
// for rook pawns and wrong-colored bishops.
|
50 |
+
ScaleFactor scale_factor(const Position& pos, Color c) const {
|
51 |
+
ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos)
|
52 |
+
: SCALE_FACTOR_NONE;
|
53 |
+
return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]);
|
54 |
+
}
|
55 |
+
|
56 |
+
Key key;
|
57 |
+
const EndgameBase<Value>* evaluationFunction;
|
58 |
+
const EndgameBase<ScaleFactor>* scalingFunction[COLOR_NB]; // Could be one for each
|
59 |
+
// side (e.g. KPKP, KBPsK)
|
60 |
+
Score score;
|
61 |
+
int16_t gamePhase;
|
62 |
+
uint8_t factor[COLOR_NB];
|
63 |
+
};
|
64 |
+
|
65 |
+
typedef HashTable<Entry, 8192> Table;
|
66 |
+
|
67 |
+
Entry* probe(const Position& pos);
|
68 |
+
|
69 |
+
} // namespace Stockfish::Material
|
70 |
+
|
71 |
+
#endif // #ifndef MATERIAL_H_INCLUDED
|
src/misc.cpp
ADDED
@@ -0,0 +1,687 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifdef _WIN32
|
20 |
+
#if _WIN32_WINNT < 0x0601
|
21 |
+
#undef _WIN32_WINNT
|
22 |
+
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
|
23 |
+
#endif
|
24 |
+
|
25 |
+
#ifndef NOMINMAX
|
26 |
+
#define NOMINMAX
|
27 |
+
#endif
|
28 |
+
|
29 |
+
#include <windows.h>
|
30 |
+
// The needed Windows API for processor groups could be missed from old Windows
|
31 |
+
// versions, so instead of calling them directly (forcing the linker to resolve
|
32 |
+
// the calls at compile time), try to load them at runtime. To do this we need
|
33 |
+
// first to define the corresponding function pointers.
|
34 |
+
extern "C" {
|
35 |
+
typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP,
|
36 |
+
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
|
37 |
+
typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY);
|
38 |
+
typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
39 |
+
typedef bool(*fun4_t)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
|
40 |
+
typedef WORD(*fun5_t)();
|
41 |
+
}
|
42 |
+
#endif
|
43 |
+
|
44 |
+
#include <fstream>
|
45 |
+
#include <iomanip>
|
46 |
+
#include <iostream>
|
47 |
+
#include <sstream>
|
48 |
+
#include <vector>
|
49 |
+
#include <cstdlib>
|
50 |
+
|
51 |
+
#if defined(__linux__) && !defined(__ANDROID__)
|
52 |
+
#include <stdlib.h>
|
53 |
+
#include <sys/mman.h>
|
54 |
+
#endif
|
55 |
+
|
56 |
+
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__)
|
57 |
+
#define POSIXALIGNEDALLOC
|
58 |
+
#include <stdlib.h>
|
59 |
+
#endif
|
60 |
+
|
61 |
+
#include "misc.h"
|
62 |
+
#include "thread.h"
|
63 |
+
|
64 |
+
using namespace std;
|
65 |
+
|
66 |
+
namespace Stockfish {
|
67 |
+
|
68 |
+
namespace {
|
69 |
+
|
70 |
+
/// Version number or dev.
|
71 |
+
const string version = "15.1";
|
72 |
+
|
73 |
+
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
|
74 |
+
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
75 |
+
/// can toggle the logging of std::cout and std:cin at runtime whilst preserving
|
76 |
+
/// usual I/O functionality, all without changing a single line of code!
|
77 |
+
/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
|
78 |
+
|
79 |
+
struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout
|
80 |
+
|
81 |
+
Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {}
|
82 |
+
|
83 |
+
int sync() override { return logBuf->pubsync(), buf->pubsync(); }
|
84 |
+
int overflow(int c) override { return log(buf->sputc((char)c), "<< "); }
|
85 |
+
int underflow() override { return buf->sgetc(); }
|
86 |
+
int uflow() override { return log(buf->sbumpc(), ">> "); }
|
87 |
+
|
88 |
+
streambuf *buf, *logBuf;
|
89 |
+
|
90 |
+
int log(int c, const char* prefix) {
|
91 |
+
|
92 |
+
static int last = '\n'; // Single log file
|
93 |
+
|
94 |
+
if (last == '\n')
|
95 |
+
logBuf->sputn(prefix, 3);
|
96 |
+
|
97 |
+
return last = logBuf->sputc((char)c);
|
98 |
+
}
|
99 |
+
};
|
100 |
+
|
101 |
+
class Logger {
|
102 |
+
|
103 |
+
Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {}
|
104 |
+
~Logger() { start(""); }
|
105 |
+
|
106 |
+
ofstream file;
|
107 |
+
Tie in, out;
|
108 |
+
|
109 |
+
public:
|
110 |
+
static void start(const std::string& fname) {
|
111 |
+
|
112 |
+
static Logger l;
|
113 |
+
|
114 |
+
if (l.file.is_open())
|
115 |
+
{
|
116 |
+
cout.rdbuf(l.out.buf);
|
117 |
+
cin.rdbuf(l.in.buf);
|
118 |
+
l.file.close();
|
119 |
+
}
|
120 |
+
|
121 |
+
if (!fname.empty())
|
122 |
+
{
|
123 |
+
l.file.open(fname, ifstream::out);
|
124 |
+
|
125 |
+
if (!l.file.is_open())
|
126 |
+
{
|
127 |
+
cerr << "Unable to open debug log file " << fname << endl;
|
128 |
+
exit(EXIT_FAILURE);
|
129 |
+
}
|
130 |
+
|
131 |
+
cin.rdbuf(&l.in);
|
132 |
+
cout.rdbuf(&l.out);
|
133 |
+
}
|
134 |
+
}
|
135 |
+
};
|
136 |
+
|
137 |
+
} // namespace
|
138 |
+
|
139 |
+
|
140 |
+
/// engine_info() returns the full name of the current Stockfish version.
|
141 |
+
/// For local dev compiles we try to append the commit sha and commit date
|
142 |
+
/// from git if that fails only the local compilation date is set and "nogit" is specified:
|
143 |
+
/// Stockfish dev-YYYYMMDD-SHA
|
144 |
+
/// or
|
145 |
+
/// Stockfish dev-YYYYMMDD-nogit
|
146 |
+
///
|
147 |
+
/// For releases (non dev builds) we only include the version number:
|
148 |
+
/// Stockfish version
|
149 |
+
|
150 |
+
string engine_info(bool to_uci) {
|
151 |
+
stringstream ss;
|
152 |
+
ss << "Stockfish " << version << setfill('0');
|
153 |
+
|
154 |
+
if (version == "dev")
|
155 |
+
{
|
156 |
+
ss << "-";
|
157 |
+
#ifdef GIT_DATE
|
158 |
+
ss << GIT_DATE;
|
159 |
+
#else
|
160 |
+
const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
|
161 |
+
string month, day, year;
|
162 |
+
stringstream date(__DATE__); // From compiler, format is "Sep 21 2008"
|
163 |
+
|
164 |
+
date >> month >> day >> year;
|
165 |
+
ss << year << setw(2) << setfill('0') << (1 + months.find(month) / 4) << setw(2) << setfill('0') << day;
|
166 |
+
#endif
|
167 |
+
|
168 |
+
ss << "-";
|
169 |
+
|
170 |
+
#ifdef GIT_SHA
|
171 |
+
ss << GIT_SHA;
|
172 |
+
#else
|
173 |
+
ss << "nogit";
|
174 |
+
#endif
|
175 |
+
}
|
176 |
+
|
177 |
+
ss << (to_uci ? "\nid author ": " by ")
|
178 |
+
<< "the Stockfish developers (see AUTHORS file)";
|
179 |
+
|
180 |
+
return ss.str();
|
181 |
+
}
|
182 |
+
|
183 |
+
|
184 |
+
/// compiler_info() returns a string trying to describe the compiler we use
|
185 |
+
|
186 |
+
std::string compiler_info() {
|
187 |
+
|
188 |
+
#define stringify2(x) #x
|
189 |
+
#define stringify(x) stringify2(x)
|
190 |
+
#define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
|
191 |
+
|
192 |
+
/// Predefined macros hell:
|
193 |
+
///
|
194 |
+
/// __GNUC__ Compiler is gcc, Clang or Intel on Linux
|
195 |
+
/// __INTEL_COMPILER Compiler is Intel
|
196 |
+
/// _MSC_VER Compiler is MSVC or Intel on Windows
|
197 |
+
/// _WIN32 Building on Windows (any)
|
198 |
+
/// _WIN64 Building on Windows 64 bit
|
199 |
+
|
200 |
+
std::string compiler = "\nCompiled by ";
|
201 |
+
|
202 |
+
#ifdef __clang__
|
203 |
+
compiler += "clang++ ";
|
204 |
+
compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
|
205 |
+
#elif __INTEL_COMPILER
|
206 |
+
compiler += "Intel compiler ";
|
207 |
+
compiler += "(version ";
|
208 |
+
compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE);
|
209 |
+
compiler += ")";
|
210 |
+
#elif _MSC_VER
|
211 |
+
compiler += "MSVC ";
|
212 |
+
compiler += "(version ";
|
213 |
+
compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
|
214 |
+
compiler += ")";
|
215 |
+
#elif defined(__e2k__) && defined(__LCC__)
|
216 |
+
#define dot_ver2(n) \
|
217 |
+
compiler += (char)'.'; \
|
218 |
+
compiler += (char)('0' + (n) / 10); \
|
219 |
+
compiler += (char)('0' + (n) % 10);
|
220 |
+
|
221 |
+
compiler += "MCST LCC ";
|
222 |
+
compiler += "(version ";
|
223 |
+
compiler += std::to_string(__LCC__ / 100);
|
224 |
+
dot_ver2(__LCC__ % 100)
|
225 |
+
dot_ver2(__LCC_MINOR__)
|
226 |
+
compiler += ")";
|
227 |
+
#elif __GNUC__
|
228 |
+
compiler += "g++ (GNUC) ";
|
229 |
+
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
230 |
+
#else
|
231 |
+
compiler += "Unknown compiler ";
|
232 |
+
compiler += "(unknown version)";
|
233 |
+
#endif
|
234 |
+
|
235 |
+
#if defined(__APPLE__)
|
236 |
+
compiler += " on Apple";
|
237 |
+
#elif defined(__CYGWIN__)
|
238 |
+
compiler += " on Cygwin";
|
239 |
+
#elif defined(__MINGW64__)
|
240 |
+
compiler += " on MinGW64";
|
241 |
+
#elif defined(__MINGW32__)
|
242 |
+
compiler += " on MinGW32";
|
243 |
+
#elif defined(__ANDROID__)
|
244 |
+
compiler += " on Android";
|
245 |
+
#elif defined(__linux__)
|
246 |
+
compiler += " on Linux";
|
247 |
+
#elif defined(_WIN64)
|
248 |
+
compiler += " on Microsoft Windows 64-bit";
|
249 |
+
#elif defined(_WIN32)
|
250 |
+
compiler += " on Microsoft Windows 32-bit";
|
251 |
+
#else
|
252 |
+
compiler += " on unknown system";
|
253 |
+
#endif
|
254 |
+
|
255 |
+
compiler += "\nCompilation settings include: ";
|
256 |
+
compiler += (Is64Bit ? " 64bit" : " 32bit");
|
257 |
+
#if defined(USE_VNNI)
|
258 |
+
compiler += " VNNI";
|
259 |
+
#endif
|
260 |
+
#if defined(USE_AVX512)
|
261 |
+
compiler += " AVX512";
|
262 |
+
#endif
|
263 |
+
compiler += (HasPext ? " BMI2" : "");
|
264 |
+
#if defined(USE_AVX2)
|
265 |
+
compiler += " AVX2";
|
266 |
+
#endif
|
267 |
+
#if defined(USE_SSE41)
|
268 |
+
compiler += " SSE41";
|
269 |
+
#endif
|
270 |
+
#if defined(USE_SSSE3)
|
271 |
+
compiler += " SSSE3";
|
272 |
+
#endif
|
273 |
+
#if defined(USE_SSE2)
|
274 |
+
compiler += " SSE2";
|
275 |
+
#endif
|
276 |
+
compiler += (HasPopCnt ? " POPCNT" : "");
|
277 |
+
#if defined(USE_MMX)
|
278 |
+
compiler += " MMX";
|
279 |
+
#endif
|
280 |
+
#if defined(USE_NEON)
|
281 |
+
compiler += " NEON";
|
282 |
+
#endif
|
283 |
+
|
284 |
+
#if !defined(NDEBUG)
|
285 |
+
compiler += " DEBUG";
|
286 |
+
#endif
|
287 |
+
|
288 |
+
compiler += "\n__VERSION__ macro expands to: ";
|
289 |
+
#ifdef __VERSION__
|
290 |
+
compiler += __VERSION__;
|
291 |
+
#else
|
292 |
+
compiler += "(undefined macro)";
|
293 |
+
#endif
|
294 |
+
compiler += "\n";
|
295 |
+
|
296 |
+
return compiler;
|
297 |
+
}
|
298 |
+
|
299 |
+
|
300 |
+
/// Debug functions used mainly to collect run-time statistics
|
301 |
+
static std::atomic<int64_t> hits[2], means[2];
|
302 |
+
|
303 |
+
void dbg_hit_on(bool b) { ++hits[0]; if (b) ++hits[1]; }
|
304 |
+
void dbg_hit_on(bool c, bool b) { if (c) dbg_hit_on(b); }
|
305 |
+
void dbg_mean_of(int v) { ++means[0]; means[1] += v; }
|
306 |
+
|
307 |
+
void dbg_print() {
|
308 |
+
|
309 |
+
if (hits[0])
|
310 |
+
cerr << "Total " << hits[0] << " Hits " << hits[1]
|
311 |
+
<< " hit rate (%) " << 100 * hits[1] / hits[0] << endl;
|
312 |
+
|
313 |
+
if (means[0])
|
314 |
+
cerr << "Total " << means[0] << " Mean "
|
315 |
+
<< (double)means[1] / means[0] << endl;
|
316 |
+
}
|
317 |
+
|
318 |
+
|
319 |
+
/// Used to serialize access to std::cout to avoid multiple threads writing at
|
320 |
+
/// the same time.
|
321 |
+
|
322 |
+
std::ostream& operator<<(std::ostream& os, SyncCout sc) {
|
323 |
+
|
324 |
+
static std::mutex m;
|
325 |
+
|
326 |
+
if (sc == IO_LOCK)
|
327 |
+
m.lock();
|
328 |
+
|
329 |
+
if (sc == IO_UNLOCK)
|
330 |
+
m.unlock();
|
331 |
+
|
332 |
+
return os;
|
333 |
+
}
|
334 |
+
|
335 |
+
|
336 |
+
/// Trampoline helper to avoid moving Logger to misc.h
|
337 |
+
void start_logger(const std::string& fname) { Logger::start(fname); }
|
338 |
+
|
339 |
+
|
340 |
+
/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking
|
341 |
+
/// function that doesn't stall the CPU waiting for data to be loaded from memory,
|
342 |
+
/// which can be quite slow.
|
343 |
+
#ifdef NO_PREFETCH
|
344 |
+
|
345 |
+
void prefetch(void*) {}
|
346 |
+
|
347 |
+
#else
|
348 |
+
|
349 |
+
void prefetch(void* addr) {
|
350 |
+
|
351 |
+
# if defined(__INTEL_COMPILER)
|
352 |
+
// This hack prevents prefetches from being optimized away by
|
353 |
+
// Intel compiler. Both MSVC and gcc seem not be affected by this.
|
354 |
+
__asm__ ("");
|
355 |
+
# endif
|
356 |
+
|
357 |
+
# if defined(__INTEL_COMPILER) || defined(_MSC_VER)
|
358 |
+
_mm_prefetch((char*)addr, _MM_HINT_T0);
|
359 |
+
# else
|
360 |
+
__builtin_prefetch(addr);
|
361 |
+
# endif
|
362 |
+
}
|
363 |
+
|
364 |
+
#endif
|
365 |
+
|
366 |
+
|
367 |
+
/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation
|
368 |
+
/// does not guarantee the availability of aligned_alloc(). Memory allocated with
|
369 |
+
/// std_aligned_alloc() must be freed with std_aligned_free().
|
370 |
+
|
371 |
+
void* std_aligned_alloc(size_t alignment, size_t size) {
|
372 |
+
|
373 |
+
#if defined(POSIXALIGNEDALLOC)
|
374 |
+
void *mem;
|
375 |
+
return posix_memalign(&mem, alignment, size) ? nullptr : mem;
|
376 |
+
#elif defined(_WIN32)
|
377 |
+
return _mm_malloc(size, alignment);
|
378 |
+
#else
|
379 |
+
return std::aligned_alloc(alignment, size);
|
380 |
+
#endif
|
381 |
+
}
|
382 |
+
|
383 |
+
void std_aligned_free(void* ptr) {
|
384 |
+
|
385 |
+
#if defined(POSIXALIGNEDALLOC)
|
386 |
+
free(ptr);
|
387 |
+
#elif defined(_WIN32)
|
388 |
+
_mm_free(ptr);
|
389 |
+
#else
|
390 |
+
free(ptr);
|
391 |
+
#endif
|
392 |
+
}
|
393 |
+
|
394 |
+
/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages.
|
395 |
+
|
396 |
+
#if defined(_WIN32)
|
397 |
+
|
398 |
+
static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) {
|
399 |
+
|
400 |
+
#if !defined(_WIN64)
|
401 |
+
return nullptr;
|
402 |
+
#else
|
403 |
+
|
404 |
+
HANDLE hProcessToken { };
|
405 |
+
LUID luid { };
|
406 |
+
void* mem = nullptr;
|
407 |
+
|
408 |
+
const size_t largePageSize = GetLargePageMinimum();
|
409 |
+
if (!largePageSize)
|
410 |
+
return nullptr;
|
411 |
+
|
412 |
+
// We need SeLockMemoryPrivilege, so try to enable it for the process
|
413 |
+
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
|
414 |
+
return nullptr;
|
415 |
+
|
416 |
+
if (LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid))
|
417 |
+
{
|
418 |
+
TOKEN_PRIVILEGES tp { };
|
419 |
+
TOKEN_PRIVILEGES prevTp { };
|
420 |
+
DWORD prevTpLen = 0;
|
421 |
+
|
422 |
+
tp.PrivilegeCount = 1;
|
423 |
+
tp.Privileges[0].Luid = luid;
|
424 |
+
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
425 |
+
|
426 |
+
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
|
427 |
+
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
|
428 |
+
if (AdjustTokenPrivileges(
|
429 |
+
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
|
430 |
+
GetLastError() == ERROR_SUCCESS)
|
431 |
+
{
|
432 |
+
// Round up size to full pages and allocate
|
433 |
+
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
|
434 |
+
mem = VirtualAlloc(
|
435 |
+
NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
|
436 |
+
|
437 |
+
// Privilege no longer needed, restore previous state
|
438 |
+
AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL);
|
439 |
+
}
|
440 |
+
}
|
441 |
+
|
442 |
+
CloseHandle(hProcessToken);
|
443 |
+
|
444 |
+
return mem;
|
445 |
+
|
446 |
+
#endif
|
447 |
+
}
|
448 |
+
|
449 |
+
void* aligned_large_pages_alloc(size_t allocSize) {
|
450 |
+
|
451 |
+
// Try to allocate large pages
|
452 |
+
void* mem = aligned_large_pages_alloc_windows(allocSize);
|
453 |
+
|
454 |
+
// Fall back to regular, page aligned, allocation if necessary
|
455 |
+
if (!mem)
|
456 |
+
mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
457 |
+
|
458 |
+
return mem;
|
459 |
+
}
|
460 |
+
|
461 |
+
#else
|
462 |
+
|
463 |
+
void* aligned_large_pages_alloc(size_t allocSize) {
|
464 |
+
|
465 |
+
#if defined(__linux__)
|
466 |
+
constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size
|
467 |
+
#else
|
468 |
+
constexpr size_t alignment = 4096; // assumed small page size
|
469 |
+
#endif
|
470 |
+
|
471 |
+
// round up to multiples of alignment
|
472 |
+
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
|
473 |
+
void *mem = std_aligned_alloc(alignment, size);
|
474 |
+
#if defined(MADV_HUGEPAGE)
|
475 |
+
madvise(mem, size, MADV_HUGEPAGE);
|
476 |
+
#endif
|
477 |
+
return mem;
|
478 |
+
}
|
479 |
+
|
480 |
+
#endif
|
481 |
+
|
482 |
+
|
483 |
+
/// aligned_large_pages_free() will free the previously allocated ttmem
|
484 |
+
|
485 |
+
#if defined(_WIN32)
|
486 |
+
|
487 |
+
void aligned_large_pages_free(void* mem) {
|
488 |
+
|
489 |
+
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
|
490 |
+
{
|
491 |
+
DWORD err = GetLastError();
|
492 |
+
std::cerr << "Failed to free large page memory. Error code: 0x"
|
493 |
+
<< std::hex << err
|
494 |
+
<< std::dec << std::endl;
|
495 |
+
exit(EXIT_FAILURE);
|
496 |
+
}
|
497 |
+
}
|
498 |
+
|
499 |
+
#else
|
500 |
+
|
501 |
+
void aligned_large_pages_free(void *mem) {
|
502 |
+
std_aligned_free(mem);
|
503 |
+
}
|
504 |
+
|
505 |
+
#endif
|
506 |
+
|
507 |
+
|
508 |
+
namespace WinProcGroup {
|
509 |
+
|
510 |
+
#ifndef _WIN32
|
511 |
+
|
512 |
+
void bindThisThread(size_t) {}
|
513 |
+
|
514 |
+
#else
|
515 |
+
|
516 |
+
/// best_node() retrieves logical processor information using Windows specific
|
517 |
+
/// API and returns the best node id for the thread with index idx. Original
|
518 |
+
/// code from Texel by Peter Österlund.
|
519 |
+
|
520 |
+
int best_node(size_t idx) {
|
521 |
+
|
522 |
+
int threads = 0;
|
523 |
+
int nodes = 0;
|
524 |
+
int cores = 0;
|
525 |
+
DWORD returnLength = 0;
|
526 |
+
DWORD byteOffset = 0;
|
527 |
+
|
528 |
+
// Early exit if the needed API is not available at runtime
|
529 |
+
HMODULE k32 = GetModuleHandle("Kernel32.dll");
|
530 |
+
auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx");
|
531 |
+
if (!fun1)
|
532 |
+
return -1;
|
533 |
+
|
534 |
+
// First call to GetLogicalProcessorInformationEx() to get returnLength.
|
535 |
+
// We expect the call to fail due to null buffer.
|
536 |
+
if (fun1(RelationAll, nullptr, &returnLength))
|
537 |
+
return -1;
|
538 |
+
|
539 |
+
// Once we know returnLength, allocate the buffer
|
540 |
+
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr;
|
541 |
+
ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength);
|
542 |
+
|
543 |
+
// Second call to GetLogicalProcessorInformationEx(), now we expect to succeed
|
544 |
+
if (!fun1(RelationAll, buffer, &returnLength))
|
545 |
+
{
|
546 |
+
free(buffer);
|
547 |
+
return -1;
|
548 |
+
}
|
549 |
+
|
550 |
+
while (byteOffset < returnLength)
|
551 |
+
{
|
552 |
+
if (ptr->Relationship == RelationNumaNode)
|
553 |
+
nodes++;
|
554 |
+
|
555 |
+
else if (ptr->Relationship == RelationProcessorCore)
|
556 |
+
{
|
557 |
+
cores++;
|
558 |
+
threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1;
|
559 |
+
}
|
560 |
+
|
561 |
+
assert(ptr->Size);
|
562 |
+
byteOffset += ptr->Size;
|
563 |
+
ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size);
|
564 |
+
}
|
565 |
+
|
566 |
+
free(buffer);
|
567 |
+
|
568 |
+
std::vector<int> groups;
|
569 |
+
|
570 |
+
// Run as many threads as possible on the same node until core limit is
|
571 |
+
// reached, then move on filling the next node.
|
572 |
+
for (int n = 0; n < nodes; n++)
|
573 |
+
for (int i = 0; i < cores / nodes; i++)
|
574 |
+
groups.push_back(n);
|
575 |
+
|
576 |
+
// In case a core has more than one logical processor (we assume 2) and we
|
577 |
+
// have still threads to allocate, then spread them evenly across available
|
578 |
+
// nodes.
|
579 |
+
for (int t = 0; t < threads - cores; t++)
|
580 |
+
groups.push_back(t % nodes);
|
581 |
+
|
582 |
+
// If we still have more threads than the total number of logical processors
|
583 |
+
// then return -1 and let the OS to decide what to do.
|
584 |
+
return idx < groups.size() ? groups[idx] : -1;
|
585 |
+
}
|
586 |
+
|
587 |
+
|
588 |
+
/// bindThisThread() set the group affinity of the current thread
|
589 |
+
|
590 |
+
void bindThisThread(size_t idx) {
|
591 |
+
|
592 |
+
// Use only local variables to be thread-safe
|
593 |
+
int node = best_node(idx);
|
594 |
+
|
595 |
+
if (node == -1)
|
596 |
+
return;
|
597 |
+
|
598 |
+
// Early exit if the needed API are not available at runtime
|
599 |
+
HMODULE k32 = GetModuleHandle("Kernel32.dll");
|
600 |
+
auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
|
601 |
+
auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
|
602 |
+
auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2");
|
603 |
+
auto fun5 = (fun5_t)(void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount");
|
604 |
+
|
605 |
+
if (!fun2 || !fun3)
|
606 |
+
return;
|
607 |
+
|
608 |
+
if (!fun4 || !fun5)
|
609 |
+
{
|
610 |
+
GROUP_AFFINITY affinity;
|
611 |
+
if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx
|
612 |
+
fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity
|
613 |
+
}
|
614 |
+
else
|
615 |
+
{
|
616 |
+
// If a numa node has more than one processor group, we assume they are
|
617 |
+
// sized equal and we spread threads evenly across the groups.
|
618 |
+
USHORT elements, returnedElements;
|
619 |
+
elements = fun5(); // GetMaximumProcessorGroupCount
|
620 |
+
GROUP_AFFINITY *affinity = (GROUP_AFFINITY*)malloc(elements * sizeof(GROUP_AFFINITY));
|
621 |
+
if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2
|
622 |
+
fun3(GetCurrentThread(), &affinity[idx % returnedElements], nullptr); // SetThreadGroupAffinity
|
623 |
+
free(affinity);
|
624 |
+
}
|
625 |
+
}
|
626 |
+
|
627 |
+
#endif
|
628 |
+
|
629 |
+
} // namespace WinProcGroup
|
630 |
+
|
631 |
+
#ifdef _WIN32
|
632 |
+
#include <direct.h>
|
633 |
+
#define GETCWD _getcwd
|
634 |
+
#else
|
635 |
+
#include <unistd.h>
|
636 |
+
#define GETCWD getcwd
|
637 |
+
#endif
|
638 |
+
|
639 |
+
namespace CommandLine {
|
640 |
+
|
641 |
+
string argv0; // path+name of the executable binary, as given by argv[0]
|
642 |
+
string binaryDirectory; // path of the executable directory
|
643 |
+
string workingDirectory; // path of the working directory
|
644 |
+
|
645 |
+
void init([[maybe_unused]] int argc, char* argv[]) {
|
646 |
+
string pathSeparator;
|
647 |
+
|
648 |
+
// extract the path+name of the executable binary
|
649 |
+
argv0 = argv[0];
|
650 |
+
|
651 |
+
#ifdef _WIN32
|
652 |
+
pathSeparator = "\\";
|
653 |
+
#ifdef _MSC_VER
|
654 |
+
// Under windows argv[0] may not have the extension. Also _get_pgmptr() had
|
655 |
+
// issues in some windows 10 versions, so check returned values carefully.
|
656 |
+
char* pgmptr = nullptr;
|
657 |
+
if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
|
658 |
+
argv0 = pgmptr;
|
659 |
+
#endif
|
660 |
+
#else
|
661 |
+
pathSeparator = "/";
|
662 |
+
#endif
|
663 |
+
|
664 |
+
// extract the working directory
|
665 |
+
workingDirectory = "";
|
666 |
+
char buff[40000];
|
667 |
+
char* cwd = GETCWD(buff, 40000);
|
668 |
+
if (cwd)
|
669 |
+
workingDirectory = cwd;
|
670 |
+
|
671 |
+
// extract the binary directory path from argv0
|
672 |
+
binaryDirectory = argv0;
|
673 |
+
size_t pos = binaryDirectory.find_last_of("\\/");
|
674 |
+
if (pos == std::string::npos)
|
675 |
+
binaryDirectory = "." + pathSeparator;
|
676 |
+
else
|
677 |
+
binaryDirectory.resize(pos + 1);
|
678 |
+
|
679 |
+
// pattern replacement: "./" at the start of path is replaced by the working directory
|
680 |
+
if (binaryDirectory.find("." + pathSeparator) == 0)
|
681 |
+
binaryDirectory.replace(0, 1, workingDirectory);
|
682 |
+
}
|
683 |
+
|
684 |
+
|
685 |
+
} // namespace CommandLine
|
686 |
+
|
687 |
+
} // namespace Stockfish
|
src/misc.h
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef MISC_H_INCLUDED
|
20 |
+
#define MISC_H_INCLUDED
|
21 |
+
|
22 |
+
#include <cassert>
|
23 |
+
#include <chrono>
|
24 |
+
#include <ostream>
|
25 |
+
#include <string>
|
26 |
+
#include <vector>
|
27 |
+
#include <cstdint>
|
28 |
+
|
29 |
+
#include "types.h"
|
30 |
+
|
31 |
+
namespace Stockfish {
|
32 |
+
|
33 |
+
std::string engine_info(bool to_uci = false);
|
34 |
+
std::string compiler_info();
|
35 |
+
void prefetch(void* addr);
|
36 |
+
void start_logger(const std::string& fname);
|
37 |
+
void* std_aligned_alloc(size_t alignment, size_t size);
|
38 |
+
void std_aligned_free(void* ptr);
|
39 |
+
void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes
|
40 |
+
void aligned_large_pages_free(void* mem); // nop if mem == nullptr
|
41 |
+
|
42 |
+
void dbg_hit_on(bool b);
|
43 |
+
void dbg_hit_on(bool c, bool b);
|
44 |
+
void dbg_mean_of(int v);
|
45 |
+
void dbg_print();
|
46 |
+
|
47 |
+
typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
|
48 |
+
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
|
49 |
+
inline TimePoint now() {
|
50 |
+
return std::chrono::duration_cast<std::chrono::milliseconds>
|
51 |
+
(std::chrono::steady_clock::now().time_since_epoch()).count();
|
52 |
+
}
|
53 |
+
|
54 |
+
template<class Entry, int Size>
|
55 |
+
struct HashTable {
|
56 |
+
Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; }
|
57 |
+
|
58 |
+
private:
|
59 |
+
std::vector<Entry> table = std::vector<Entry>(Size); // Allocate on the heap
|
60 |
+
};
|
61 |
+
|
62 |
+
|
63 |
+
enum SyncCout { IO_LOCK, IO_UNLOCK };
|
64 |
+
std::ostream& operator<<(std::ostream&, SyncCout);
|
65 |
+
|
66 |
+
#define sync_cout std::cout << IO_LOCK
|
67 |
+
#define sync_endl std::endl << IO_UNLOCK
|
68 |
+
|
69 |
+
|
70 |
+
// align_ptr_up() : get the first aligned element of an array.
|
71 |
+
// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes,
|
72 |
+
// where N is the number of elements in the array.
|
73 |
+
template <uintptr_t Alignment, typename T>
|
74 |
+
T* align_ptr_up(T* ptr)
|
75 |
+
{
|
76 |
+
static_assert(alignof(T) < Alignment);
|
77 |
+
|
78 |
+
const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
|
79 |
+
return reinterpret_cast<T*>(reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
|
80 |
+
}
|
81 |
+
|
82 |
+
|
83 |
+
// IsLittleEndian : true if and only if the binary is compiled on a little endian machine
|
84 |
+
static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
|
85 |
+
static inline const bool IsLittleEndian = (Le.c[0] == 4);
|
86 |
+
|
87 |
+
|
88 |
+
// RunningAverage : a class to calculate a running average of a series of values.
|
89 |
+
// For efficiency, all computations are done with integers.
|
90 |
+
class RunningAverage {
|
91 |
+
public:
|
92 |
+
|
93 |
+
// Reset the running average to rational value p / q
|
94 |
+
void set(int64_t p, int64_t q)
|
95 |
+
{ average = p * PERIOD * RESOLUTION / q; }
|
96 |
+
|
97 |
+
// Update average with value v
|
98 |
+
void update(int64_t v)
|
99 |
+
{ average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; }
|
100 |
+
|
101 |
+
// Test if average is strictly greater than rational a / b
|
102 |
+
bool is_greater(int64_t a, int64_t b) const
|
103 |
+
{ return b * average > a * (PERIOD * RESOLUTION); }
|
104 |
+
|
105 |
+
int64_t value() const
|
106 |
+
{ return average / (PERIOD * RESOLUTION); }
|
107 |
+
|
108 |
+
private :
|
109 |
+
static constexpr int64_t PERIOD = 4096;
|
110 |
+
static constexpr int64_t RESOLUTION = 1024;
|
111 |
+
int64_t average;
|
112 |
+
};
|
113 |
+
|
114 |
+
template <typename T, std::size_t MaxSize>
|
115 |
+
class ValueList {
|
116 |
+
|
117 |
+
public:
|
118 |
+
std::size_t size() const { return size_; }
|
119 |
+
void push_back(const T& value) { values_[size_++] = value; }
|
120 |
+
const T* begin() const { return values_; }
|
121 |
+
const T* end() const { return values_ + size_; }
|
122 |
+
|
123 |
+
private:
|
124 |
+
T values_[MaxSize];
|
125 |
+
std::size_t size_ = 0;
|
126 |
+
};
|
127 |
+
|
128 |
+
|
129 |
+
/// xorshift64star Pseudo-Random Number Generator
|
130 |
+
/// This class is based on original code written and dedicated
|
131 |
+
/// to the public domain by Sebastiano Vigna (2014).
|
132 |
+
/// It has the following characteristics:
|
133 |
+
///
|
134 |
+
/// - Outputs 64-bit numbers
|
135 |
+
/// - Passes Dieharder and SmallCrush test batteries
|
136 |
+
/// - Does not require warm-up, no zeroland to escape
|
137 |
+
/// - Internal state is a single 64-bit integer
|
138 |
+
/// - Period is 2^64 - 1
|
139 |
+
/// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
|
140 |
+
///
|
141 |
+
/// For further analysis see
|
142 |
+
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
|
143 |
+
|
144 |
+
class PRNG {
|
145 |
+
|
146 |
+
uint64_t s;
|
147 |
+
|
148 |
+
uint64_t rand64() {
|
149 |
+
|
150 |
+
s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
|
151 |
+
return s * 2685821657736338717LL;
|
152 |
+
}
|
153 |
+
|
154 |
+
public:
|
155 |
+
PRNG(uint64_t seed) : s(seed) { assert(seed); }
|
156 |
+
|
157 |
+
template<typename T> T rand() { return T(rand64()); }
|
158 |
+
|
159 |
+
/// Special generator used to fast init magic numbers.
|
160 |
+
/// Output values only have 1/8th of their bits set on average.
|
161 |
+
template<typename T> T sparse_rand()
|
162 |
+
{ return T(rand64() & rand64() & rand64()); }
|
163 |
+
};
|
164 |
+
|
165 |
+
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
166 |
+
#if defined(__GNUC__) && defined(IS_64BIT)
|
167 |
+
__extension__ typedef unsigned __int128 uint128;
|
168 |
+
return ((uint128)a * (uint128)b) >> 64;
|
169 |
+
#else
|
170 |
+
uint64_t aL = (uint32_t)a, aH = a >> 32;
|
171 |
+
uint64_t bL = (uint32_t)b, bH = b >> 32;
|
172 |
+
uint64_t c1 = (aL * bL) >> 32;
|
173 |
+
uint64_t c2 = aH * bL + c1;
|
174 |
+
uint64_t c3 = aL * bH + (uint32_t)c2;
|
175 |
+
return aH * bH + (c2 >> 32) + (c3 >> 32);
|
176 |
+
#endif
|
177 |
+
}
|
178 |
+
|
179 |
+
/// Under Windows it is not possible for a process to run on more than one
|
180 |
+
/// logical processor group. This usually means to be limited to use max 64
|
181 |
+
/// cores. To overcome this, some special platform specific API should be
|
182 |
+
/// called to set group affinity for each thread. Original code from Texel by
|
183 |
+
/// Peter Österlund.
|
184 |
+
|
185 |
+
namespace WinProcGroup {
|
186 |
+
void bindThisThread(size_t idx);
|
187 |
+
}
|
188 |
+
|
189 |
+
namespace CommandLine {
|
190 |
+
void init(int argc, char* argv[]);
|
191 |
+
|
192 |
+
extern std::string binaryDirectory; // path of the executable directory
|
193 |
+
extern std::string workingDirectory; // path of the working directory
|
194 |
+
}
|
195 |
+
|
196 |
+
} // namespace Stockfish
|
197 |
+
|
198 |
+
#endif // #ifndef MISC_H_INCLUDED
|
src/movegen.cpp
ADDED
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <cassert>
|
20 |
+
|
21 |
+
#include "movegen.h"
|
22 |
+
#include "position.h"
|
23 |
+
|
24 |
+
namespace Stockfish {
|
25 |
+
|
26 |
+
namespace {
|
27 |
+
|
28 |
+
template<GenType Type, Direction D>
|
29 |
+
ExtMove* make_promotions(ExtMove* moveList, Square to) {
|
30 |
+
|
31 |
+
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
32 |
+
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
|
33 |
+
|
34 |
+
if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
|
35 |
+
{
|
36 |
+
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
|
37 |
+
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
|
38 |
+
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
39 |
+
}
|
40 |
+
|
41 |
+
return moveList;
|
42 |
+
}
|
43 |
+
|
44 |
+
|
45 |
+
template<Color Us, GenType Type>
|
46 |
+
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
|
47 |
+
|
48 |
+
constexpr Color Them = ~Us;
|
49 |
+
constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
|
50 |
+
constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
|
51 |
+
constexpr Direction Up = pawn_push(Us);
|
52 |
+
constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
|
53 |
+
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
|
54 |
+
|
55 |
+
const Bitboard emptySquares = ~pos.pieces();
|
56 |
+
const Bitboard enemies = Type == EVASIONS ? pos.checkers()
|
57 |
+
: pos.pieces(Them);
|
58 |
+
|
59 |
+
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
|
60 |
+
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
|
61 |
+
|
62 |
+
// Single and double pawn pushes, no promotions
|
63 |
+
if (Type != CAPTURES)
|
64 |
+
{
|
65 |
+
Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
|
66 |
+
Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
|
67 |
+
|
68 |
+
if (Type == EVASIONS) // Consider only blocking squares
|
69 |
+
{
|
70 |
+
b1 &= target;
|
71 |
+
b2 &= target;
|
72 |
+
}
|
73 |
+
|
74 |
+
if (Type == QUIET_CHECKS)
|
75 |
+
{
|
76 |
+
// To make a quiet check, you either make a direct check by pushing a pawn
|
77 |
+
// or push a blocker pawn that is not on the same file as the enemy king.
|
78 |
+
// Discovered check promotion has been already generated amongst the captures.
|
79 |
+
Square ksq = pos.square<KING>(Them);
|
80 |
+
Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq);
|
81 |
+
b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns);
|
82 |
+
b2 &= pawn_attacks_bb(Them, ksq) | shift<Up+Up>(dcCandidatePawns);
|
83 |
+
}
|
84 |
+
|
85 |
+
while (b1)
|
86 |
+
{
|
87 |
+
Square to = pop_lsb(b1);
|
88 |
+
*moveList++ = make_move(to - Up, to);
|
89 |
+
}
|
90 |
+
|
91 |
+
while (b2)
|
92 |
+
{
|
93 |
+
Square to = pop_lsb(b2);
|
94 |
+
*moveList++ = make_move(to - Up - Up, to);
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
// Promotions and underpromotions
|
99 |
+
if (pawnsOn7)
|
100 |
+
{
|
101 |
+
Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
|
102 |
+
Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
|
103 |
+
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
|
104 |
+
|
105 |
+
if (Type == EVASIONS)
|
106 |
+
b3 &= target;
|
107 |
+
|
108 |
+
while (b1)
|
109 |
+
moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(b1));
|
110 |
+
|
111 |
+
while (b2)
|
112 |
+
moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(b2));
|
113 |
+
|
114 |
+
while (b3)
|
115 |
+
moveList = make_promotions<Type, Up >(moveList, pop_lsb(b3));
|
116 |
+
}
|
117 |
+
|
118 |
+
// Standard and en passant captures
|
119 |
+
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
120 |
+
{
|
121 |
+
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
|
122 |
+
Bitboard b2 = shift<UpLeft >(pawnsNotOn7) & enemies;
|
123 |
+
|
124 |
+
while (b1)
|
125 |
+
{
|
126 |
+
Square to = pop_lsb(b1);
|
127 |
+
*moveList++ = make_move(to - UpRight, to);
|
128 |
+
}
|
129 |
+
|
130 |
+
while (b2)
|
131 |
+
{
|
132 |
+
Square to = pop_lsb(b2);
|
133 |
+
*moveList++ = make_move(to - UpLeft, to);
|
134 |
+
}
|
135 |
+
|
136 |
+
if (pos.ep_square() != SQ_NONE)
|
137 |
+
{
|
138 |
+
assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6));
|
139 |
+
|
140 |
+
// An en passant capture cannot resolve a discovered check
|
141 |
+
if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
|
142 |
+
return moveList;
|
143 |
+
|
144 |
+
b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
|
145 |
+
|
146 |
+
assert(b1);
|
147 |
+
|
148 |
+
while (b1)
|
149 |
+
*moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
return moveList;
|
154 |
+
}
|
155 |
+
|
156 |
+
|
157 |
+
template<Color Us, PieceType Pt, bool Checks>
|
158 |
+
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
|
159 |
+
|
160 |
+
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
|
161 |
+
|
162 |
+
Bitboard bb = pos.pieces(Us, Pt);
|
163 |
+
|
164 |
+
while (bb)
|
165 |
+
{
|
166 |
+
Square from = pop_lsb(bb);
|
167 |
+
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
|
168 |
+
|
169 |
+
// To check, you either move freely a blocker or make a direct check.
|
170 |
+
if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from)))
|
171 |
+
b &= pos.check_squares(Pt);
|
172 |
+
|
173 |
+
while (b)
|
174 |
+
*moveList++ = make_move(from, pop_lsb(b));
|
175 |
+
}
|
176 |
+
|
177 |
+
return moveList;
|
178 |
+
}
|
179 |
+
|
180 |
+
|
181 |
+
template<Color Us, GenType Type>
|
182 |
+
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
|
183 |
+
|
184 |
+
static_assert(Type != LEGAL, "Unsupported type in generate_all()");
|
185 |
+
|
186 |
+
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations
|
187 |
+
const Square ksq = pos.square<KING>(Us);
|
188 |
+
Bitboard target;
|
189 |
+
|
190 |
+
// Skip generating non-king moves when in double check
|
191 |
+
if (Type != EVASIONS || !more_than_one(pos.checkers()))
|
192 |
+
{
|
193 |
+
target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
|
194 |
+
: Type == NON_EVASIONS ? ~pos.pieces( Us)
|
195 |
+
: Type == CAPTURES ? pos.pieces(~Us)
|
196 |
+
: ~pos.pieces( ); // QUIETS || QUIET_CHECKS
|
197 |
+
|
198 |
+
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
|
199 |
+
moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
|
200 |
+
moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
|
201 |
+
moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
|
202 |
+
moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
|
203 |
+
}
|
204 |
+
|
205 |
+
if (!Checks || pos.blockers_for_king(~Us) & ksq)
|
206 |
+
{
|
207 |
+
Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
|
208 |
+
if (Checks)
|
209 |
+
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
|
210 |
+
|
211 |
+
while (b)
|
212 |
+
*moveList++ = make_move(ksq, pop_lsb(b));
|
213 |
+
|
214 |
+
if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING))
|
215 |
+
for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
|
216 |
+
if (!pos.castling_impeded(cr) && pos.can_castle(cr))
|
217 |
+
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
|
218 |
+
}
|
219 |
+
|
220 |
+
return moveList;
|
221 |
+
}
|
222 |
+
|
223 |
+
} // namespace
|
224 |
+
|
225 |
+
|
226 |
+
/// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
|
227 |
+
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
|
228 |
+
/// <EVASIONS> Generates all pseudo-legal check evasions when the side to move is in check
|
229 |
+
/// <QUIET_CHECKS> Generates all pseudo-legal non-captures giving check, except castling and promotions
|
230 |
+
/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
|
231 |
+
///
|
232 |
+
/// Returns a pointer to the end of the move list.
|
233 |
+
|
234 |
+
template<GenType Type>
|
235 |
+
ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
236 |
+
|
237 |
+
static_assert(Type != LEGAL, "Unsupported type in generate()");
|
238 |
+
assert((Type == EVASIONS) == (bool)pos.checkers());
|
239 |
+
|
240 |
+
Color us = pos.side_to_move();
|
241 |
+
|
242 |
+
return us == WHITE ? generate_all<WHITE, Type>(pos, moveList)
|
243 |
+
: generate_all<BLACK, Type>(pos, moveList);
|
244 |
+
}
|
245 |
+
|
246 |
+
// Explicit template instantiations
|
247 |
+
template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
|
248 |
+
template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
|
249 |
+
template ExtMove* generate<EVASIONS>(const Position&, ExtMove*);
|
250 |
+
template ExtMove* generate<QUIET_CHECKS>(const Position&, ExtMove*);
|
251 |
+
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
|
252 |
+
|
253 |
+
|
254 |
+
/// generate<LEGAL> generates all the legal moves in the given position
|
255 |
+
|
256 |
+
template<>
|
257 |
+
ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
|
258 |
+
|
259 |
+
Color us = pos.side_to_move();
|
260 |
+
Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
|
261 |
+
Square ksq = pos.square<KING>(us);
|
262 |
+
ExtMove* cur = moveList;
|
263 |
+
|
264 |
+
moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
|
265 |
+
: generate<NON_EVASIONS>(pos, moveList);
|
266 |
+
while (cur != moveList)
|
267 |
+
if ( ((pinned && pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT)
|
268 |
+
&& !pos.legal(*cur))
|
269 |
+
*cur = (--moveList)->move;
|
270 |
+
else
|
271 |
+
++cur;
|
272 |
+
|
273 |
+
return moveList;
|
274 |
+
}
|
275 |
+
|
276 |
+
} // namespace Stockfish
|
src/movegen.h
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef MOVEGEN_H_INCLUDED
|
20 |
+
#define MOVEGEN_H_INCLUDED
|
21 |
+
|
22 |
+
#include <algorithm>
|
23 |
+
|
24 |
+
#include "types.h"
|
25 |
+
|
26 |
+
namespace Stockfish {
|
27 |
+
|
28 |
+
class Position;
|
29 |
+
|
30 |
+
enum GenType {
|
31 |
+
CAPTURES,
|
32 |
+
QUIETS,
|
33 |
+
QUIET_CHECKS,
|
34 |
+
EVASIONS,
|
35 |
+
NON_EVASIONS,
|
36 |
+
LEGAL
|
37 |
+
};
|
38 |
+
|
39 |
+
struct ExtMove {
|
40 |
+
Move move;
|
41 |
+
int value;
|
42 |
+
|
43 |
+
operator Move() const { return move; }
|
44 |
+
void operator=(Move m) { move = m; }
|
45 |
+
|
46 |
+
// Inhibit unwanted implicit conversions to Move
|
47 |
+
// with an ambiguity that yields to a compile error.
|
48 |
+
operator float() const = delete;
|
49 |
+
};
|
50 |
+
|
51 |
+
inline bool operator<(const ExtMove& f, const ExtMove& s) {
|
52 |
+
return f.value < s.value;
|
53 |
+
}
|
54 |
+
|
55 |
+
template<GenType>
|
56 |
+
ExtMove* generate(const Position& pos, ExtMove* moveList);
|
57 |
+
|
58 |
+
/// The MoveList struct is a simple wrapper around generate(). It sometimes comes
|
59 |
+
/// in handy to use this class instead of the low level generate() function.
|
60 |
+
template<GenType T>
|
61 |
+
struct MoveList {
|
62 |
+
|
63 |
+
explicit MoveList(const Position& pos) : last(generate<T>(pos, moveList)) {}
|
64 |
+
const ExtMove* begin() const { return moveList; }
|
65 |
+
const ExtMove* end() const { return last; }
|
66 |
+
size_t size() const { return last - moveList; }
|
67 |
+
bool contains(Move move) const {
|
68 |
+
return std::find(begin(), end(), move) != end();
|
69 |
+
}
|
70 |
+
|
71 |
+
private:
|
72 |
+
ExtMove moveList[MAX_MOVES], *last;
|
73 |
+
};
|
74 |
+
|
75 |
+
} // namespace Stockfish
|
76 |
+
|
77 |
+
#endif // #ifndef MOVEGEN_H_INCLUDED
|
src/movepick.cpp
ADDED
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <cassert>
|
20 |
+
|
21 |
+
#include "bitboard.h"
|
22 |
+
#include "movepick.h"
|
23 |
+
|
24 |
+
namespace Stockfish {
|
25 |
+
|
26 |
+
namespace {
|
27 |
+
|
28 |
+
enum Stages {
|
29 |
+
MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE,
|
30 |
+
EVASION_TT, EVASION_INIT, EVASION,
|
31 |
+
PROBCUT_TT, PROBCUT_INIT, PROBCUT,
|
32 |
+
QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK
|
33 |
+
};
|
34 |
+
|
35 |
+
// partial_insertion_sort() sorts moves in descending order up to and including
|
36 |
+
// a given limit. The order of moves smaller than the limit is left unspecified.
|
37 |
+
void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
|
38 |
+
|
39 |
+
for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p)
|
40 |
+
if (p->value >= limit)
|
41 |
+
{
|
42 |
+
ExtMove tmp = *p, *q;
|
43 |
+
*p = *++sortedEnd;
|
44 |
+
for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q)
|
45 |
+
*q = *(q - 1);
|
46 |
+
*q = tmp;
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
} // namespace
|
51 |
+
|
52 |
+
|
53 |
+
/// Constructors of the MovePicker class. As arguments we pass information
|
54 |
+
/// to help it to return the (presumably) good moves first, to decide which
|
55 |
+
/// moves to return (in the quiescence search, for instance, we only want to
|
56 |
+
/// search captures, promotions, and some checks) and how important good move
|
57 |
+
/// ordering is at the current node.
|
58 |
+
|
59 |
+
/// MovePicker constructor for the main search
|
60 |
+
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
61 |
+
const CapturePieceToHistory* cph,
|
62 |
+
const PieceToHistory** ch,
|
63 |
+
Move cm,
|
64 |
+
const Move* killers)
|
65 |
+
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
|
66 |
+
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d)
|
67 |
+
{
|
68 |
+
assert(d > 0);
|
69 |
+
|
70 |
+
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
|
71 |
+
!(ttm && pos.pseudo_legal(ttm));
|
72 |
+
threatenedPieces = 0;
|
73 |
+
}
|
74 |
+
|
75 |
+
/// MovePicker constructor for quiescence search
|
76 |
+
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
77 |
+
const CapturePieceToHistory* cph,
|
78 |
+
const PieceToHistory** ch,
|
79 |
+
Square rs)
|
80 |
+
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d)
|
81 |
+
{
|
82 |
+
assert(d <= 0);
|
83 |
+
|
84 |
+
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) +
|
85 |
+
!( ttm
|
86 |
+
&& pos.pseudo_legal(ttm));
|
87 |
+
}
|
88 |
+
|
89 |
+
/// MovePicker constructor for ProbCut: we generate captures with SEE greater
|
90 |
+
/// than or equal to the given threshold.
|
91 |
+
MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
|
92 |
+
: pos(p), captureHistory(cph), ttMove(ttm), threshold(th)
|
93 |
+
{
|
94 |
+
assert(!pos.checkers());
|
95 |
+
|
96 |
+
stage = PROBCUT_TT + !(ttm && pos.capture(ttm)
|
97 |
+
&& pos.pseudo_legal(ttm)
|
98 |
+
&& pos.see_ge(ttm, threshold));
|
99 |
+
}
|
100 |
+
|
101 |
+
/// MovePicker::score() assigns a numerical value to each move in a list, used
|
102 |
+
/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring
|
103 |
+
/// captures with a good history. Quiets moves are ordered using the histories.
|
104 |
+
template<GenType Type>
|
105 |
+
void MovePicker::score() {
|
106 |
+
|
107 |
+
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
|
108 |
+
|
109 |
+
[[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook;
|
110 |
+
if constexpr (Type == QUIETS)
|
111 |
+
{
|
112 |
+
Color us = pos.side_to_move();
|
113 |
+
|
114 |
+
threatenedByPawn = pos.attacks_by<PAWN>(~us);
|
115 |
+
threatenedByMinor = pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
|
116 |
+
threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
|
117 |
+
|
118 |
+
// Pieces threatened by pieces of lesser material value
|
119 |
+
threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook)
|
120 |
+
| (pos.pieces(us, ROOK) & threatenedByMinor)
|
121 |
+
| (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn);
|
122 |
+
}
|
123 |
+
|
124 |
+
for (auto& m : *this)
|
125 |
+
if constexpr (Type == CAPTURES)
|
126 |
+
m.value = 6 * int(PieceValue[MG][pos.piece_on(to_sq(m))])
|
127 |
+
+ (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
|
128 |
+
|
129 |
+
else if constexpr (Type == QUIETS)
|
130 |
+
m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]
|
131 |
+
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
132 |
+
+ (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
|
133 |
+
+ (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
|
134 |
+
+ (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)]
|
135 |
+
+ (threatenedPieces & from_sq(m) ?
|
136 |
+
(type_of(pos.moved_piece(m)) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000
|
137 |
+
: type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000
|
138 |
+
: !(to_sq(m) & threatenedByPawn) ? 15000
|
139 |
+
: 0)
|
140 |
+
: 0)
|
141 |
+
+ bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384;
|
142 |
+
else // Type == EVASIONS
|
143 |
+
{
|
144 |
+
if (pos.capture(m))
|
145 |
+
m.value = PieceValue[MG][pos.piece_on(to_sq(m))]
|
146 |
+
- Value(type_of(pos.moved_piece(m)))
|
147 |
+
+ (1 << 28);
|
148 |
+
else
|
149 |
+
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
|
150 |
+
+ (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)];
|
151 |
+
}
|
152 |
+
}
|
153 |
+
|
154 |
+
/// MovePicker::select() returns the next move satisfying a predicate function.
|
155 |
+
/// It never returns the TT move.
|
156 |
+
template<MovePicker::PickType T, typename Pred>
|
157 |
+
Move MovePicker::select(Pred filter) {
|
158 |
+
|
159 |
+
while (cur < endMoves)
|
160 |
+
{
|
161 |
+
if (T == Best)
|
162 |
+
std::swap(*cur, *std::max_element(cur, endMoves));
|
163 |
+
|
164 |
+
if (*cur != ttMove && filter())
|
165 |
+
return *cur++;
|
166 |
+
|
167 |
+
cur++;
|
168 |
+
}
|
169 |
+
return MOVE_NONE;
|
170 |
+
}
|
171 |
+
|
172 |
+
/// MovePicker::next_move() is the most important method of the MovePicker class. It
|
173 |
+
/// returns a new pseudo-legal move every time it is called until there are no more
|
174 |
+
/// moves left, picking the move with the highest score from a list of generated moves.
|
175 |
+
Move MovePicker::next_move(bool skipQuiets) {
|
176 |
+
|
177 |
+
top:
|
178 |
+
switch (stage) {
|
179 |
+
|
180 |
+
case MAIN_TT:
|
181 |
+
case EVASION_TT:
|
182 |
+
case QSEARCH_TT:
|
183 |
+
case PROBCUT_TT:
|
184 |
+
++stage;
|
185 |
+
return ttMove;
|
186 |
+
|
187 |
+
case CAPTURE_INIT:
|
188 |
+
case PROBCUT_INIT:
|
189 |
+
case QCAPTURE_INIT:
|
190 |
+
cur = endBadCaptures = moves;
|
191 |
+
endMoves = generate<CAPTURES>(pos, cur);
|
192 |
+
|
193 |
+
score<CAPTURES>();
|
194 |
+
partial_insertion_sort(cur, endMoves, std::numeric_limits<int>::min());
|
195 |
+
++stage;
|
196 |
+
goto top;
|
197 |
+
|
198 |
+
case GOOD_CAPTURE:
|
199 |
+
if (select<Next>([&](){
|
200 |
+
return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ?
|
201 |
+
// Move losing capture to endBadCaptures to be tried later
|
202 |
+
true : (*endBadCaptures++ = *cur, false); }))
|
203 |
+
return *(cur - 1);
|
204 |
+
|
205 |
+
// Prepare the pointers to loop over the refutations array
|
206 |
+
cur = std::begin(refutations);
|
207 |
+
endMoves = std::end(refutations);
|
208 |
+
|
209 |
+
// If the countermove is the same as a killer, skip it
|
210 |
+
if ( refutations[0].move == refutations[2].move
|
211 |
+
|| refutations[1].move == refutations[2].move)
|
212 |
+
--endMoves;
|
213 |
+
|
214 |
+
++stage;
|
215 |
+
[[fallthrough]];
|
216 |
+
|
217 |
+
case REFUTATION:
|
218 |
+
if (select<Next>([&](){ return *cur != MOVE_NONE
|
219 |
+
&& !pos.capture(*cur)
|
220 |
+
&& pos.pseudo_legal(*cur); }))
|
221 |
+
return *(cur - 1);
|
222 |
+
++stage;
|
223 |
+
[[fallthrough]];
|
224 |
+
|
225 |
+
case QUIET_INIT:
|
226 |
+
if (!skipQuiets)
|
227 |
+
{
|
228 |
+
cur = endBadCaptures;
|
229 |
+
endMoves = generate<QUIETS>(pos, cur);
|
230 |
+
|
231 |
+
score<QUIETS>();
|
232 |
+
partial_insertion_sort(cur, endMoves, -3000 * depth);
|
233 |
+
}
|
234 |
+
|
235 |
+
++stage;
|
236 |
+
[[fallthrough]];
|
237 |
+
|
238 |
+
case QUIET:
|
239 |
+
if ( !skipQuiets
|
240 |
+
&& select<Next>([&](){return *cur != refutations[0].move
|
241 |
+
&& *cur != refutations[1].move
|
242 |
+
&& *cur != refutations[2].move;}))
|
243 |
+
return *(cur - 1);
|
244 |
+
|
245 |
+
// Prepare the pointers to loop over the bad captures
|
246 |
+
cur = moves;
|
247 |
+
endMoves = endBadCaptures;
|
248 |
+
|
249 |
+
++stage;
|
250 |
+
[[fallthrough]];
|
251 |
+
|
252 |
+
case BAD_CAPTURE:
|
253 |
+
return select<Next>([](){ return true; });
|
254 |
+
|
255 |
+
case EVASION_INIT:
|
256 |
+
cur = moves;
|
257 |
+
endMoves = generate<EVASIONS>(pos, cur);
|
258 |
+
|
259 |
+
score<EVASIONS>();
|
260 |
+
++stage;
|
261 |
+
[[fallthrough]];
|
262 |
+
|
263 |
+
case EVASION:
|
264 |
+
return select<Best>([](){ return true; });
|
265 |
+
|
266 |
+
case PROBCUT:
|
267 |
+
return select<Next>([&](){ return pos.see_ge(*cur, threshold); });
|
268 |
+
|
269 |
+
case QCAPTURE:
|
270 |
+
if (select<Next>([&](){ return depth > DEPTH_QS_RECAPTURES
|
271 |
+
|| to_sq(*cur) == recaptureSquare; }))
|
272 |
+
return *(cur - 1);
|
273 |
+
|
274 |
+
// If we did not find any move and we do not try checks, we have finished
|
275 |
+
if (depth != DEPTH_QS_CHECKS)
|
276 |
+
return MOVE_NONE;
|
277 |
+
|
278 |
+
++stage;
|
279 |
+
[[fallthrough]];
|
280 |
+
|
281 |
+
case QCHECK_INIT:
|
282 |
+
cur = moves;
|
283 |
+
endMoves = generate<QUIET_CHECKS>(pos, cur);
|
284 |
+
|
285 |
+
++stage;
|
286 |
+
[[fallthrough]];
|
287 |
+
|
288 |
+
case QCHECK:
|
289 |
+
return select<Next>([](){ return true; });
|
290 |
+
}
|
291 |
+
|
292 |
+
assert(false);
|
293 |
+
return MOVE_NONE; // Silence warning
|
294 |
+
}
|
295 |
+
|
296 |
+
} // namespace Stockfish
|
src/movepick.h
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef MOVEPICK_H_INCLUDED
|
20 |
+
#define MOVEPICK_H_INCLUDED
|
21 |
+
|
22 |
+
#include <array>
|
23 |
+
#include <limits>
|
24 |
+
#include <type_traits>
|
25 |
+
|
26 |
+
#include "movegen.h"
|
27 |
+
#include "position.h"
|
28 |
+
#include "types.h"
|
29 |
+
|
30 |
+
namespace Stockfish {
|
31 |
+
|
32 |
+
/// StatsEntry stores the stat table value. It is usually a number but could
|
33 |
+
/// be a move or even a nested history. We use a class instead of naked value
|
34 |
+
/// to directly call history update operator<<() on the entry so to use stats
|
35 |
+
/// tables at caller sites as simple multi-dim arrays.
|
36 |
+
template<typename T, int D>
|
37 |
+
class StatsEntry {
|
38 |
+
|
39 |
+
T entry;
|
40 |
+
|
41 |
+
public:
|
42 |
+
void operator=(const T& v) { entry = v; }
|
43 |
+
T* operator&() { return &entry; }
|
44 |
+
T* operator->() { return &entry; }
|
45 |
+
operator const T&() const { return entry; }
|
46 |
+
|
47 |
+
void operator<<(int bonus) {
|
48 |
+
assert(abs(bonus) <= D); // Ensure range is [-D, D]
|
49 |
+
static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
|
50 |
+
|
51 |
+
entry += bonus - entry * abs(bonus) / D;
|
52 |
+
|
53 |
+
assert(abs(entry) <= D);
|
54 |
+
}
|
55 |
+
};
|
56 |
+
|
57 |
+
/// Stats is a generic N-dimensional array used to store various statistics.
|
58 |
+
/// The first template parameter T is the base type of the array, the second
|
59 |
+
/// template parameter D limits the range of updates in [-D, D] when we update
|
60 |
+
/// values with the << operator, while the last parameters (Size and Sizes)
|
61 |
+
/// encode the dimensions of the array.
|
62 |
+
template <typename T, int D, int Size, int... Sizes>
|
63 |
+
struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
|
64 |
+
{
|
65 |
+
typedef Stats<T, D, Size, Sizes...> stats;
|
66 |
+
|
67 |
+
void fill(const T& v) {
|
68 |
+
|
69 |
+
// For standard-layout 'this' points to first struct member
|
70 |
+
assert(std::is_standard_layout<stats>::value);
|
71 |
+
|
72 |
+
typedef StatsEntry<T, D> entry;
|
73 |
+
entry* p = reinterpret_cast<entry*>(this);
|
74 |
+
std::fill(p, p + sizeof(*this) / sizeof(entry), v);
|
75 |
+
}
|
76 |
+
};
|
77 |
+
|
78 |
+
template <typename T, int D, int Size>
|
79 |
+
struct Stats<T, D, Size> : public std::array<StatsEntry<T, D>, Size> {};
|
80 |
+
|
81 |
+
/// In stats table, D=0 means that the template parameter is not used
|
82 |
+
enum StatsParams { NOT_USED = 0 };
|
83 |
+
enum StatsType { NoCaptures, Captures };
|
84 |
+
|
85 |
+
/// ButterflyHistory records how often quiet moves have been successful or
|
86 |
+
/// unsuccessful during the current search, and is used for reduction and move
|
87 |
+
/// ordering decisions. It uses 2 tables (one for each color) indexed by
|
88 |
+
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
|
89 |
+
/// (~11 elo)
|
90 |
+
typedef Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
|
91 |
+
|
92 |
+
/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
|
93 |
+
/// move, see www.chessprogramming.org/Countermove_Heuristic
|
94 |
+
typedef Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
|
95 |
+
|
96 |
+
/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
|
97 |
+
typedef Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB> CapturePieceToHistory;
|
98 |
+
|
99 |
+
/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
|
100 |
+
typedef Stats<int16_t, 29952, PIECE_NB, SQUARE_NB> PieceToHistory;
|
101 |
+
|
102 |
+
/// ContinuationHistory is the combined history of a given pair of moves, usually
|
103 |
+
/// the current one given a previous one. The nested history table is based on
|
104 |
+
/// PieceToHistory instead of ButterflyBoards.
|
105 |
+
/// (~63 elo)
|
106 |
+
typedef Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHistory;
|
107 |
+
|
108 |
+
|
109 |
+
/// MovePicker class is used to pick one pseudo-legal move at a time from the
|
110 |
+
/// current position. The most important method is next_move(), which returns a
|
111 |
+
/// new pseudo-legal move each time it is called, until there are no moves left,
|
112 |
+
/// when MOVE_NONE is returned. In order to improve the efficiency of the
|
113 |
+
/// alpha-beta algorithm, MovePicker attempts to return the moves which are most
|
114 |
+
/// likely to get a cut-off first.
|
115 |
+
class MovePicker {
|
116 |
+
|
117 |
+
enum PickType { Next, Best };
|
118 |
+
|
119 |
+
public:
|
120 |
+
MovePicker(const MovePicker&) = delete;
|
121 |
+
MovePicker& operator=(const MovePicker&) = delete;
|
122 |
+
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
123 |
+
const CapturePieceToHistory*,
|
124 |
+
const PieceToHistory**,
|
125 |
+
Move,
|
126 |
+
const Move*);
|
127 |
+
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
128 |
+
const CapturePieceToHistory*,
|
129 |
+
const PieceToHistory**,
|
130 |
+
Square);
|
131 |
+
MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
|
132 |
+
Move next_move(bool skipQuiets = false);
|
133 |
+
|
134 |
+
Bitboard threatenedPieces;
|
135 |
+
|
136 |
+
private:
|
137 |
+
template<PickType T, typename Pred> Move select(Pred);
|
138 |
+
template<GenType> void score();
|
139 |
+
ExtMove* begin() { return cur; }
|
140 |
+
ExtMove* end() { return endMoves; }
|
141 |
+
|
142 |
+
const Position& pos;
|
143 |
+
const ButterflyHistory* mainHistory;
|
144 |
+
const CapturePieceToHistory* captureHistory;
|
145 |
+
const PieceToHistory** continuationHistory;
|
146 |
+
Move ttMove;
|
147 |
+
ExtMove refutations[3], *cur, *endMoves, *endBadCaptures;
|
148 |
+
int stage;
|
149 |
+
Square recaptureSquare;
|
150 |
+
Value threshold;
|
151 |
+
Depth depth;
|
152 |
+
ExtMove moves[MAX_MOVES];
|
153 |
+
};
|
154 |
+
|
155 |
+
} // namespace Stockfish
|
156 |
+
|
157 |
+
#endif // #ifndef MOVEPICK_H_INCLUDED
|
src/nnue/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
src/nnue/evaluate_nnue.cpp
ADDED
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// Code for calculating NNUE evaluation function
|
20 |
+
|
21 |
+
#include <iostream>
|
22 |
+
#include <set>
|
23 |
+
#include <sstream>
|
24 |
+
#include <iomanip>
|
25 |
+
#include <fstream>
|
26 |
+
|
27 |
+
#include "../evaluate.h"
|
28 |
+
#include "../position.h"
|
29 |
+
#include "../misc.h"
|
30 |
+
#include "../uci.h"
|
31 |
+
#include "../types.h"
|
32 |
+
|
33 |
+
#include "evaluate_nnue.h"
|
34 |
+
|
35 |
+
namespace Stockfish::Eval::NNUE {
|
36 |
+
|
37 |
+
// Input feature converter
|
38 |
+
LargePagePtr<FeatureTransformer> featureTransformer;
|
39 |
+
|
40 |
+
// Evaluation function
|
41 |
+
AlignedPtr<Network> network[LayerStacks];
|
42 |
+
|
43 |
+
// Evaluation function file name
|
44 |
+
std::string fileName;
|
45 |
+
std::string netDescription;
|
46 |
+
|
47 |
+
namespace Detail {
|
48 |
+
|
49 |
+
// Initialize the evaluation function parameters
|
50 |
+
template <typename T>
|
51 |
+
void initialize(AlignedPtr<T>& pointer) {
|
52 |
+
|
53 |
+
pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
|
54 |
+
std::memset(pointer.get(), 0, sizeof(T));
|
55 |
+
}
|
56 |
+
|
57 |
+
template <typename T>
|
58 |
+
void initialize(LargePagePtr<T>& pointer) {
|
59 |
+
|
60 |
+
static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
|
61 |
+
pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
|
62 |
+
std::memset(pointer.get(), 0, sizeof(T));
|
63 |
+
}
|
64 |
+
|
65 |
+
// Read evaluation function parameters
|
66 |
+
template <typename T>
|
67 |
+
bool read_parameters(std::istream& stream, T& reference) {
|
68 |
+
|
69 |
+
std::uint32_t header;
|
70 |
+
header = read_little_endian<std::uint32_t>(stream);
|
71 |
+
if (!stream || header != T::get_hash_value()) return false;
|
72 |
+
return reference.read_parameters(stream);
|
73 |
+
}
|
74 |
+
|
75 |
+
// Write evaluation function parameters
|
76 |
+
template <typename T>
|
77 |
+
bool write_parameters(std::ostream& stream, const T& reference) {
|
78 |
+
|
79 |
+
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
|
80 |
+
return reference.write_parameters(stream);
|
81 |
+
}
|
82 |
+
|
83 |
+
} // namespace Detail
|
84 |
+
|
85 |
+
// Initialize the evaluation function parameters
|
86 |
+
void initialize() {
|
87 |
+
|
88 |
+
Detail::initialize(featureTransformer);
|
89 |
+
for (std::size_t i = 0; i < LayerStacks; ++i)
|
90 |
+
Detail::initialize(network[i]);
|
91 |
+
}
|
92 |
+
|
93 |
+
// Read network header
|
94 |
+
bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc)
|
95 |
+
{
|
96 |
+
std::uint32_t version, size;
|
97 |
+
|
98 |
+
version = read_little_endian<std::uint32_t>(stream);
|
99 |
+
*hashValue = read_little_endian<std::uint32_t>(stream);
|
100 |
+
size = read_little_endian<std::uint32_t>(stream);
|
101 |
+
if (!stream || version != Version) return false;
|
102 |
+
desc->resize(size);
|
103 |
+
stream.read(&(*desc)[0], size);
|
104 |
+
return !stream.fail();
|
105 |
+
}
|
106 |
+
|
107 |
+
// Write network header
|
108 |
+
bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc)
|
109 |
+
{
|
110 |
+
write_little_endian<std::uint32_t>(stream, Version);
|
111 |
+
write_little_endian<std::uint32_t>(stream, hashValue);
|
112 |
+
write_little_endian<std::uint32_t>(stream, (std::uint32_t)desc.size());
|
113 |
+
stream.write(&desc[0], desc.size());
|
114 |
+
return !stream.fail();
|
115 |
+
}
|
116 |
+
|
117 |
+
// Read network parameters
|
118 |
+
bool read_parameters(std::istream& stream) {
|
119 |
+
|
120 |
+
std::uint32_t hashValue;
|
121 |
+
if (!read_header(stream, &hashValue, &netDescription)) return false;
|
122 |
+
if (hashValue != HashValue) return false;
|
123 |
+
if (!Detail::read_parameters(stream, *featureTransformer)) return false;
|
124 |
+
for (std::size_t i = 0; i < LayerStacks; ++i)
|
125 |
+
if (!Detail::read_parameters(stream, *(network[i]))) return false;
|
126 |
+
return stream && stream.peek() == std::ios::traits_type::eof();
|
127 |
+
}
|
128 |
+
|
129 |
+
// Write network parameters
|
130 |
+
bool write_parameters(std::ostream& stream) {
|
131 |
+
|
132 |
+
if (!write_header(stream, HashValue, netDescription)) return false;
|
133 |
+
if (!Detail::write_parameters(stream, *featureTransformer)) return false;
|
134 |
+
for (std::size_t i = 0; i < LayerStacks; ++i)
|
135 |
+
if (!Detail::write_parameters(stream, *(network[i]))) return false;
|
136 |
+
return (bool)stream;
|
137 |
+
}
|
138 |
+
|
139 |
+
// Evaluation function. Perform differential calculation.
|
140 |
+
Value evaluate(const Position& pos, bool adjusted, int* complexity) {
|
141 |
+
|
142 |
+
// We manually align the arrays on the stack because with gcc < 9.3
|
143 |
+
// overaligning stack variables with alignas() doesn't work correctly.
|
144 |
+
|
145 |
+
constexpr uint64_t alignment = CacheLineSize;
|
146 |
+
int delta = 24 - pos.non_pawn_material() / 9560;
|
147 |
+
|
148 |
+
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
|
149 |
+
TransformedFeatureType transformedFeaturesUnaligned[
|
150 |
+
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
|
151 |
+
|
152 |
+
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
153 |
+
#else
|
154 |
+
alignas(alignment)
|
155 |
+
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
|
156 |
+
#endif
|
157 |
+
|
158 |
+
ASSERT_ALIGNED(transformedFeatures, alignment);
|
159 |
+
|
160 |
+
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
161 |
+
const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket);
|
162 |
+
const auto positional = network[bucket]->propagate(transformedFeatures);
|
163 |
+
|
164 |
+
if (complexity)
|
165 |
+
*complexity = abs(psqt - positional) / OutputScale;
|
166 |
+
|
167 |
+
// Give more value to positional evaluation when adjusted flag is set
|
168 |
+
if (adjusted)
|
169 |
+
return static_cast<Value>(((1024 - delta) * psqt + (1024 + delta) * positional) / (1024 * OutputScale));
|
170 |
+
else
|
171 |
+
return static_cast<Value>((psqt + positional) / OutputScale);
|
172 |
+
}
|
173 |
+
|
174 |
+
struct NnueEvalTrace {
|
175 |
+
static_assert(LayerStacks == PSQTBuckets);
|
176 |
+
|
177 |
+
Value psqt[LayerStacks];
|
178 |
+
Value positional[LayerStacks];
|
179 |
+
std::size_t correctBucket;
|
180 |
+
};
|
181 |
+
|
182 |
+
static NnueEvalTrace trace_evaluate(const Position& pos) {
|
183 |
+
|
184 |
+
// We manually align the arrays on the stack because with gcc < 9.3
|
185 |
+
// overaligning stack variables with alignas() doesn't work correctly.
|
186 |
+
|
187 |
+
constexpr uint64_t alignment = CacheLineSize;
|
188 |
+
|
189 |
+
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
|
190 |
+
TransformedFeatureType transformedFeaturesUnaligned[
|
191 |
+
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
|
192 |
+
|
193 |
+
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
194 |
+
#else
|
195 |
+
alignas(alignment)
|
196 |
+
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
|
197 |
+
#endif
|
198 |
+
|
199 |
+
ASSERT_ALIGNED(transformedFeatures, alignment);
|
200 |
+
|
201 |
+
NnueEvalTrace t{};
|
202 |
+
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
203 |
+
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) {
|
204 |
+
const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket);
|
205 |
+
const auto positional = network[bucket]->propagate(transformedFeatures);
|
206 |
+
|
207 |
+
t.psqt[bucket] = static_cast<Value>( materialist / OutputScale );
|
208 |
+
t.positional[bucket] = static_cast<Value>( positional / OutputScale );
|
209 |
+
}
|
210 |
+
|
211 |
+
return t;
|
212 |
+
}
|
213 |
+
|
214 |
+
static const std::string PieceToChar(" PNBRQK pnbrqk");
|
215 |
+
|
216 |
+
|
217 |
+
// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer.
|
218 |
+
// The buffer must have capacity for at least 5 chars.
|
219 |
+
static void format_cp_compact(Value v, char* buffer) {
|
220 |
+
|
221 |
+
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
|
222 |
+
|
223 |
+
int cp = std::abs(100 * v / UCI::NormalizeToPawnValue);
|
224 |
+
if (cp >= 10000)
|
225 |
+
{
|
226 |
+
buffer[1] = '0' + cp / 10000; cp %= 10000;
|
227 |
+
buffer[2] = '0' + cp / 1000; cp %= 1000;
|
228 |
+
buffer[3] = '0' + cp / 100;
|
229 |
+
buffer[4] = ' ';
|
230 |
+
}
|
231 |
+
else if (cp >= 1000)
|
232 |
+
{
|
233 |
+
buffer[1] = '0' + cp / 1000; cp %= 1000;
|
234 |
+
buffer[2] = '0' + cp / 100; cp %= 100;
|
235 |
+
buffer[3] = '.';
|
236 |
+
buffer[4] = '0' + cp / 10;
|
237 |
+
}
|
238 |
+
else
|
239 |
+
{
|
240 |
+
buffer[1] = '0' + cp / 100; cp %= 100;
|
241 |
+
buffer[2] = '.';
|
242 |
+
buffer[3] = '0' + cp / 10; cp %= 10;
|
243 |
+
buffer[4] = '0' + cp / 1;
|
244 |
+
}
|
245 |
+
}
|
246 |
+
|
247 |
+
|
248 |
+
// format_cp_aligned_dot() converts a Value into (centi)pawns and writes it in a buffer,
|
249 |
+
// always keeping two decimals. The buffer must have capacity for at least 7 chars.
|
250 |
+
static void format_cp_aligned_dot(Value v, char* buffer) {
|
251 |
+
|
252 |
+
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
|
253 |
+
|
254 |
+
double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue;
|
255 |
+
sprintf(&buffer[1], "%6.2f", cp);
|
256 |
+
}
|
257 |
+
|
258 |
+
|
259 |
+
// trace() returns a string with the value of each piece on a board,
|
260 |
+
// and a table for (PSQT, Layers) values bucket by bucket.
|
261 |
+
|
262 |
+
std::string trace(Position& pos) {
|
263 |
+
|
264 |
+
std::stringstream ss;
|
265 |
+
|
266 |
+
char board[3*8+1][8*8+2];
|
267 |
+
std::memset(board, ' ', sizeof(board));
|
268 |
+
for (int row = 0; row < 3*8+1; ++row)
|
269 |
+
board[row][8*8+1] = '\0';
|
270 |
+
|
271 |
+
// A lambda to output one box of the board
|
272 |
+
auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) {
|
273 |
+
|
274 |
+
const int x = ((int)file) * 8;
|
275 |
+
const int y = (7 - (int)rank) * 3;
|
276 |
+
for (int i = 1; i < 8; ++i)
|
277 |
+
board[y][x+i] = board[y+3][x+i] = '-';
|
278 |
+
for (int i = 1; i < 3; ++i)
|
279 |
+
board[y+i][x] = board[y+i][x+8] = '|';
|
280 |
+
board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+';
|
281 |
+
if (pc != NO_PIECE)
|
282 |
+
board[y+1][x+4] = PieceToChar[pc];
|
283 |
+
if (value != VALUE_NONE)
|
284 |
+
format_cp_compact(value, &board[y+2][x+2]);
|
285 |
+
};
|
286 |
+
|
287 |
+
// We estimate the value of each piece by doing a differential evaluation from
|
288 |
+
// the current base eval, simulating the removal of the piece from its square.
|
289 |
+
Value base = evaluate(pos);
|
290 |
+
base = pos.side_to_move() == WHITE ? base : -base;
|
291 |
+
|
292 |
+
for (File f = FILE_A; f <= FILE_H; ++f)
|
293 |
+
for (Rank r = RANK_1; r <= RANK_8; ++r)
|
294 |
+
{
|
295 |
+
Square sq = make_square(f, r);
|
296 |
+
Piece pc = pos.piece_on(sq);
|
297 |
+
Value v = VALUE_NONE;
|
298 |
+
|
299 |
+
if (pc != NO_PIECE && type_of(pc) != KING)
|
300 |
+
{
|
301 |
+
auto st = pos.state();
|
302 |
+
|
303 |
+
pos.remove_piece(sq);
|
304 |
+
st->accumulator.computed[WHITE] = false;
|
305 |
+
st->accumulator.computed[BLACK] = false;
|
306 |
+
|
307 |
+
Value eval = evaluate(pos);
|
308 |
+
eval = pos.side_to_move() == WHITE ? eval : -eval;
|
309 |
+
v = base - eval;
|
310 |
+
|
311 |
+
pos.put_piece(pc, sq);
|
312 |
+
st->accumulator.computed[WHITE] = false;
|
313 |
+
st->accumulator.computed[BLACK] = false;
|
314 |
+
}
|
315 |
+
|
316 |
+
writeSquare(f, r, pc, v);
|
317 |
+
}
|
318 |
+
|
319 |
+
ss << " NNUE derived piece values:\n";
|
320 |
+
for (int row = 0; row < 3*8+1; ++row)
|
321 |
+
ss << board[row] << '\n';
|
322 |
+
ss << '\n';
|
323 |
+
|
324 |
+
auto t = trace_evaluate(pos);
|
325 |
+
|
326 |
+
ss << " NNUE network contributions "
|
327 |
+
<< (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl
|
328 |
+
<< "+------------+------------+------------+------------+\n"
|
329 |
+
<< "| Bucket | Material | Positional | Total |\n"
|
330 |
+
<< "| | (PSQT) | (Layers) | |\n"
|
331 |
+
<< "+------------+------------+------------+------------+\n";
|
332 |
+
|
333 |
+
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)
|
334 |
+
{
|
335 |
+
char buffer[3][8];
|
336 |
+
std::memset(buffer, '\0', sizeof(buffer));
|
337 |
+
|
338 |
+
format_cp_aligned_dot(t.psqt[bucket], buffer[0]);
|
339 |
+
format_cp_aligned_dot(t.positional[bucket], buffer[1]);
|
340 |
+
format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]);
|
341 |
+
|
342 |
+
ss << "| " << bucket << " "
|
343 |
+
<< " | " << buffer[0] << " "
|
344 |
+
<< " | " << buffer[1] << " "
|
345 |
+
<< " | " << buffer[2] << " "
|
346 |
+
<< " |";
|
347 |
+
if (bucket == t.correctBucket)
|
348 |
+
ss << " <-- this bucket is used";
|
349 |
+
ss << '\n';
|
350 |
+
}
|
351 |
+
|
352 |
+
ss << "+------------+------------+------------+------------+\n";
|
353 |
+
|
354 |
+
return ss.str();
|
355 |
+
}
|
356 |
+
|
357 |
+
|
358 |
+
// Load eval, from a file stream or a memory stream
|
359 |
+
bool load_eval(std::string name, std::istream& stream) {
|
360 |
+
|
361 |
+
initialize();
|
362 |
+
fileName = name;
|
363 |
+
return read_parameters(stream);
|
364 |
+
}
|
365 |
+
|
366 |
+
// Save eval, to a file stream or a memory stream
|
367 |
+
bool save_eval(std::ostream& stream) {
|
368 |
+
|
369 |
+
if (fileName.empty())
|
370 |
+
return false;
|
371 |
+
|
372 |
+
return write_parameters(stream);
|
373 |
+
}
|
374 |
+
|
375 |
+
/// Save eval, to a file given by its name
|
376 |
+
bool save_eval(const std::optional<std::string>& filename) {
|
377 |
+
|
378 |
+
std::string actualFilename;
|
379 |
+
std::string msg;
|
380 |
+
|
381 |
+
if (filename.has_value())
|
382 |
+
actualFilename = filename.value();
|
383 |
+
else
|
384 |
+
{
|
385 |
+
if (currentEvalFileName != EvalFileDefaultName)
|
386 |
+
{
|
387 |
+
msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified";
|
388 |
+
|
389 |
+
sync_cout << msg << sync_endl;
|
390 |
+
return false;
|
391 |
+
}
|
392 |
+
actualFilename = EvalFileDefaultName;
|
393 |
+
}
|
394 |
+
|
395 |
+
std::ofstream stream(actualFilename, std::ios_base::binary);
|
396 |
+
bool saved = save_eval(stream);
|
397 |
+
|
398 |
+
msg = saved ? "Network saved successfully to " + actualFilename
|
399 |
+
: "Failed to export a net";
|
400 |
+
|
401 |
+
sync_cout << msg << sync_endl;
|
402 |
+
return saved;
|
403 |
+
}
|
404 |
+
|
405 |
+
|
406 |
+
} // namespace Stockfish::Eval::NNUE
|
src/nnue/evaluate_nnue.h
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// header used in NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
|
22 |
+
#define NNUE_EVALUATE_NNUE_H_INCLUDED
|
23 |
+
|
24 |
+
#include "nnue_feature_transformer.h"
|
25 |
+
|
26 |
+
#include <memory>
|
27 |
+
|
28 |
+
namespace Stockfish::Eval::NNUE {
|
29 |
+
|
30 |
+
// Hash value of evaluation function structure
|
31 |
+
constexpr std::uint32_t HashValue =
|
32 |
+
FeatureTransformer::get_hash_value() ^ Network::get_hash_value();
|
33 |
+
|
34 |
+
// Deleter for automating release of memory area
|
35 |
+
template <typename T>
|
36 |
+
struct AlignedDeleter {
|
37 |
+
void operator()(T* ptr) const {
|
38 |
+
ptr->~T();
|
39 |
+
std_aligned_free(ptr);
|
40 |
+
}
|
41 |
+
};
|
42 |
+
|
43 |
+
template <typename T>
|
44 |
+
struct LargePageDeleter {
|
45 |
+
void operator()(T* ptr) const {
|
46 |
+
ptr->~T();
|
47 |
+
aligned_large_pages_free(ptr);
|
48 |
+
}
|
49 |
+
};
|
50 |
+
|
51 |
+
template <typename T>
|
52 |
+
using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
|
53 |
+
|
54 |
+
template <typename T>
|
55 |
+
using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
|
56 |
+
|
57 |
+
} // namespace Stockfish::Eval::NNUE
|
58 |
+
|
59 |
+
#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
|
src/nnue/features/half_ka_v2_hm.cpp
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
//Definition of input features HalfKAv2_hm of NNUE evaluation function
|
20 |
+
|
21 |
+
#include "half_ka_v2_hm.h"
|
22 |
+
|
23 |
+
#include "../../position.h"
|
24 |
+
|
25 |
+
namespace Stockfish::Eval::NNUE::Features {
|
26 |
+
|
27 |
+
// Index of a feature for a given king position and another piece on some square
|
28 |
+
template<Color Perspective>
|
29 |
+
inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) {
|
30 |
+
return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + KingBuckets[Perspective][ksq]);
|
31 |
+
}
|
32 |
+
|
33 |
+
// Get a list of indices for active features
|
34 |
+
template<Color Perspective>
|
35 |
+
void HalfKAv2_hm::append_active_indices(
|
36 |
+
const Position& pos,
|
37 |
+
IndexList& active
|
38 |
+
) {
|
39 |
+
Square ksq = pos.square<KING>(Perspective);
|
40 |
+
Bitboard bb = pos.pieces();
|
41 |
+
while (bb)
|
42 |
+
{
|
43 |
+
Square s = pop_lsb(bb);
|
44 |
+
active.push_back(make_index<Perspective>(s, pos.piece_on(s), ksq));
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
// Explicit template instantiations
|
49 |
+
template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
|
50 |
+
template void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
|
51 |
+
|
52 |
+
// append_changed_indices() : get a list of indices for recently changed features
|
53 |
+
template<Color Perspective>
|
54 |
+
void HalfKAv2_hm::append_changed_indices(
|
55 |
+
Square ksq,
|
56 |
+
const DirtyPiece& dp,
|
57 |
+
IndexList& removed,
|
58 |
+
IndexList& added
|
59 |
+
) {
|
60 |
+
for (int i = 0; i < dp.dirty_num; ++i) {
|
61 |
+
if (dp.from[i] != SQ_NONE)
|
62 |
+
removed.push_back(make_index<Perspective>(dp.from[i], dp.piece[i], ksq));
|
63 |
+
if (dp.to[i] != SQ_NONE)
|
64 |
+
added.push_back(make_index<Perspective>(dp.to[i], dp.piece[i], ksq));
|
65 |
+
}
|
66 |
+
}
|
67 |
+
|
68 |
+
// Explicit template instantiations
|
69 |
+
template void HalfKAv2_hm::append_changed_indices<WHITE>(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
|
70 |
+
template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
|
71 |
+
|
72 |
+
int HalfKAv2_hm::update_cost(const StateInfo* st) {
|
73 |
+
return st->dirtyPiece.dirty_num;
|
74 |
+
}
|
75 |
+
|
76 |
+
int HalfKAv2_hm::refresh_cost(const Position& pos) {
|
77 |
+
return pos.count<ALL_PIECES>();
|
78 |
+
}
|
79 |
+
|
80 |
+
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
|
81 |
+
return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
|
82 |
+
}
|
83 |
+
|
84 |
+
} // namespace Stockfish::Eval::NNUE::Features
|
src/nnue/features/half_ka_v2_hm.h
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
//Definition of input features HalfKP of NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
22 |
+
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
23 |
+
|
24 |
+
#include "../nnue_common.h"
|
25 |
+
|
26 |
+
#include "../../evaluate.h"
|
27 |
+
#include "../../misc.h"
|
28 |
+
|
29 |
+
namespace Stockfish {
|
30 |
+
struct StateInfo;
|
31 |
+
}
|
32 |
+
|
33 |
+
namespace Stockfish::Eval::NNUE::Features {
|
34 |
+
|
35 |
+
// Feature HalfKAv2_hm: Combination of the position of own king
|
36 |
+
// and the position of pieces. Position mirrored such that king always on e..h files.
|
37 |
+
class HalfKAv2_hm {
|
38 |
+
|
39 |
+
// unique number for each piece type on each square
|
40 |
+
enum {
|
41 |
+
PS_NONE = 0,
|
42 |
+
PS_W_PAWN = 0,
|
43 |
+
PS_B_PAWN = 1 * SQUARE_NB,
|
44 |
+
PS_W_KNIGHT = 2 * SQUARE_NB,
|
45 |
+
PS_B_KNIGHT = 3 * SQUARE_NB,
|
46 |
+
PS_W_BISHOP = 4 * SQUARE_NB,
|
47 |
+
PS_B_BISHOP = 5 * SQUARE_NB,
|
48 |
+
PS_W_ROOK = 6 * SQUARE_NB,
|
49 |
+
PS_B_ROOK = 7 * SQUARE_NB,
|
50 |
+
PS_W_QUEEN = 8 * SQUARE_NB,
|
51 |
+
PS_B_QUEEN = 9 * SQUARE_NB,
|
52 |
+
PS_KING = 10 * SQUARE_NB,
|
53 |
+
PS_NB = 11 * SQUARE_NB
|
54 |
+
};
|
55 |
+
|
56 |
+
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
|
57 |
+
// convention: W - us, B - them
|
58 |
+
// viewed from other side, W and B are reversed
|
59 |
+
{ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,
|
60 |
+
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE },
|
61 |
+
{ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
|
62 |
+
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE }
|
63 |
+
};
|
64 |
+
|
65 |
+
// Index of a feature for a given king position and another piece on some square
|
66 |
+
template<Color Perspective>
|
67 |
+
static IndexType make_index(Square s, Piece pc, Square ksq);
|
68 |
+
|
69 |
+
public:
|
70 |
+
// Feature name
|
71 |
+
static constexpr const char* Name = "HalfKAv2_hm(Friend)";
|
72 |
+
|
73 |
+
// Hash value embedded in the evaluation file
|
74 |
+
static constexpr std::uint32_t HashValue = 0x7f234cb8u;
|
75 |
+
|
76 |
+
// Number of feature dimensions
|
77 |
+
static constexpr IndexType Dimensions =
|
78 |
+
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
|
79 |
+
|
80 |
+
#define B(v) (v * PS_NB)
|
81 |
+
static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = {
|
82 |
+
{ B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28),
|
83 |
+
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
|
84 |
+
B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
|
85 |
+
B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
|
86 |
+
B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
|
87 |
+
B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
|
88 |
+
B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
|
89 |
+
B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) },
|
90 |
+
{ B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0),
|
91 |
+
B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
|
92 |
+
B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
|
93 |
+
B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
|
94 |
+
B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
|
95 |
+
B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
|
96 |
+
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
|
97 |
+
B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) }
|
98 |
+
};
|
99 |
+
#undef B
|
100 |
+
|
101 |
+
// Orient a square according to perspective (rotates by 180 for black)
|
102 |
+
static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = {
|
103 |
+
{ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
104 |
+
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
105 |
+
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
106 |
+
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
107 |
+
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
108 |
+
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
109 |
+
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
110 |
+
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 },
|
111 |
+
{ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
112 |
+
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
113 |
+
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
114 |
+
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
115 |
+
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
116 |
+
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
117 |
+
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
118 |
+
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 }
|
119 |
+
};
|
120 |
+
|
121 |
+
// Maximum number of simultaneously active features.
|
122 |
+
static constexpr IndexType MaxActiveDimensions = 32;
|
123 |
+
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
|
124 |
+
|
125 |
+
// Get a list of indices for active features
|
126 |
+
template<Color Perspective>
|
127 |
+
static void append_active_indices(
|
128 |
+
const Position& pos,
|
129 |
+
IndexList& active);
|
130 |
+
|
131 |
+
// Get a list of indices for recently changed features
|
132 |
+
template<Color Perspective>
|
133 |
+
static void append_changed_indices(
|
134 |
+
Square ksq,
|
135 |
+
const DirtyPiece& dp,
|
136 |
+
IndexList& removed,
|
137 |
+
IndexList& added
|
138 |
+
);
|
139 |
+
|
140 |
+
// Returns the cost of updating one perspective, the most costly one.
|
141 |
+
// Assumes no refresh needed.
|
142 |
+
static int update_cost(const StateInfo* st);
|
143 |
+
static int refresh_cost(const Position& pos);
|
144 |
+
|
145 |
+
// Returns whether the change stored in this StateInfo means that
|
146 |
+
// a full accumulator refresh is required.
|
147 |
+
static bool requires_refresh(const StateInfo* st, Color perspective);
|
148 |
+
};
|
149 |
+
|
150 |
+
} // namespace Stockfish::Eval::NNUE::Features
|
151 |
+
|
152 |
+
#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
src/nnue/layers/affine_transform.h
ADDED
@@ -0,0 +1,545 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// Definition of layer AffineTransform of NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
22 |
+
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
23 |
+
|
24 |
+
#include <iostream>
|
25 |
+
#include <algorithm>
|
26 |
+
#include <type_traits>
|
27 |
+
#include "../nnue_common.h"
|
28 |
+
#include "simd.h"
|
29 |
+
|
30 |
+
/*
|
31 |
+
This file contains the definition for a fully connected layer (aka affine transform).
|
32 |
+
Two approaches are employed, depending on the sizes of the transform.
|
33 |
+
|
34 |
+
Approach 1:
|
35 |
+
- used when the PaddedInputDimensions >= 128
|
36 |
+
- uses AVX512 if possible
|
37 |
+
- processes inputs in batches of 2*InputSimdWidth
|
38 |
+
- so in batches of 128 for AVX512
|
39 |
+
- the weight blocks of size InputSimdWidth are transposed such that
|
40 |
+
access is sequential
|
41 |
+
- N columns of the weight matrix are processed a time, where N
|
42 |
+
depends on the architecture (the amount of registers)
|
43 |
+
- accumulate + hadd is used
|
44 |
+
|
45 |
+
Approach 2:
|
46 |
+
- used when the PaddedInputDimensions < 128
|
47 |
+
- does not use AVX512
|
48 |
+
- expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32.
|
49 |
+
- that's why AVX512 is hard to implement
|
50 |
+
- expected use-case is small layers
|
51 |
+
- not optimized as well as the approach 1
|
52 |
+
- inputs are processed in chunks of 4, weights are respectively transposed
|
53 |
+
- accumulation happens directly to int32s
|
54 |
+
*/
|
55 |
+
|
56 |
+
namespace Stockfish::Eval::NNUE::Layers {
|
57 |
+
|
58 |
+
// Fallback implementation for older/other architectures.
|
59 |
+
// Identical for both approaches. Requires the input to be padded to at least 16 values.
|
60 |
+
#if !defined(USE_SSSE3)
|
61 |
+
template <IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
|
62 |
+
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)
|
63 |
+
{
|
64 |
+
# if defined(USE_SSE2)
|
65 |
+
// At least a multiple of 16, with SSE2.
|
66 |
+
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
|
67 |
+
const __m128i Zeros = _mm_setzero_si128();
|
68 |
+
const auto inputVector = reinterpret_cast<const __m128i*>(input);
|
69 |
+
|
70 |
+
# elif defined(USE_MMX)
|
71 |
+
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 8;
|
72 |
+
const __m64 Zeros = _mm_setzero_si64();
|
73 |
+
const auto inputVector = reinterpret_cast<const __m64*>(input);
|
74 |
+
|
75 |
+
# elif defined(USE_NEON)
|
76 |
+
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
|
77 |
+
const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
|
78 |
+
# endif
|
79 |
+
|
80 |
+
for (IndexType i = 0; i < OutputDimensions; ++i) {
|
81 |
+
const IndexType offset = i * PaddedInputDimensions;
|
82 |
+
|
83 |
+
# if defined(USE_SSE2)
|
84 |
+
__m128i sumLo = _mm_cvtsi32_si128(biases[i]);
|
85 |
+
__m128i sumHi = Zeros;
|
86 |
+
const auto row = reinterpret_cast<const __m128i*>(&weights[offset]);
|
87 |
+
for (IndexType j = 0; j < NumChunks; ++j) {
|
88 |
+
__m128i row_j = _mm_load_si128(&row[j]);
|
89 |
+
__m128i input_j = _mm_load_si128(&inputVector[j]);
|
90 |
+
__m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
|
91 |
+
__m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8);
|
92 |
+
__m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros);
|
93 |
+
__m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros);
|
94 |
+
__m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo);
|
95 |
+
__m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi);
|
96 |
+
sumLo = _mm_add_epi32(sumLo, productLo);
|
97 |
+
sumHi = _mm_add_epi32(sumHi, productHi);
|
98 |
+
}
|
99 |
+
__m128i sum = _mm_add_epi32(sumLo, sumHi);
|
100 |
+
__m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2));
|
101 |
+
sum = _mm_add_epi32(sum, sumHigh_64);
|
102 |
+
__m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2));
|
103 |
+
sum = _mm_add_epi32(sum, sum_second_32);
|
104 |
+
output[i] = _mm_cvtsi128_si32(sum);
|
105 |
+
|
106 |
+
# elif defined(USE_MMX)
|
107 |
+
__m64 sumLo = _mm_cvtsi32_si64(biases[i]);
|
108 |
+
__m64 sumHi = Zeros;
|
109 |
+
const auto row = reinterpret_cast<const __m64*>(&weights[offset]);
|
110 |
+
for (IndexType j = 0; j < NumChunks; ++j) {
|
111 |
+
__m64 row_j = row[j];
|
112 |
+
__m64 input_j = inputVector[j];
|
113 |
+
__m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8);
|
114 |
+
__m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8);
|
115 |
+
__m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros);
|
116 |
+
__m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros);
|
117 |
+
__m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo);
|
118 |
+
__m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi);
|
119 |
+
sumLo = _mm_add_pi32(sumLo, productLo);
|
120 |
+
sumHi = _mm_add_pi32(sumHi, productHi);
|
121 |
+
}
|
122 |
+
__m64 sum = _mm_add_pi32(sumLo, sumHi);
|
123 |
+
sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum));
|
124 |
+
output[i] = _mm_cvtsi64_si32(sum);
|
125 |
+
|
126 |
+
# elif defined(USE_NEON)
|
127 |
+
int32x4_t sum = {biases[i]};
|
128 |
+
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
|
129 |
+
for (IndexType j = 0; j < NumChunks; ++j) {
|
130 |
+
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
|
131 |
+
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
|
132 |
+
sum = vpadalq_s16(sum, product);
|
133 |
+
}
|
134 |
+
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
|
135 |
+
|
136 |
+
# else
|
137 |
+
std::int32_t sum = biases[i];
|
138 |
+
for (IndexType j = 0; j < InputDimensions; ++j) {
|
139 |
+
sum += weights[offset + j] * input[j];
|
140 |
+
}
|
141 |
+
output[i] = sum;
|
142 |
+
# endif
|
143 |
+
}
|
144 |
+
|
145 |
+
# if defined(USE_MMX)
|
146 |
+
_mm_empty();
|
147 |
+
# endif
|
148 |
+
}
|
149 |
+
#endif
|
150 |
+
|
151 |
+
template <IndexType InDims, IndexType OutDims, typename Enabled = void>
|
152 |
+
class AffineTransform;
|
153 |
+
|
154 |
+
#if defined (USE_AVX512)
|
155 |
+
constexpr IndexType LargeInputSize = 2 * 64;
|
156 |
+
#else
|
157 |
+
constexpr IndexType LargeInputSize = std::numeric_limits<IndexType>::max();
|
158 |
+
#endif
|
159 |
+
|
160 |
+
// A specialization for large inputs.
|
161 |
+
template <IndexType InDims, IndexType OutDims>
|
162 |
+
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) >= LargeInputSize)>> {
|
163 |
+
public:
|
164 |
+
// Input/output type
|
165 |
+
using InputType = std::uint8_t;
|
166 |
+
using OutputType = std::int32_t;
|
167 |
+
|
168 |
+
// Number of input/output dimensions
|
169 |
+
static constexpr IndexType InputDimensions = InDims;
|
170 |
+
static constexpr IndexType OutputDimensions = OutDims;
|
171 |
+
|
172 |
+
static constexpr IndexType PaddedInputDimensions =
|
173 |
+
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
|
174 |
+
static constexpr IndexType PaddedOutputDimensions =
|
175 |
+
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
|
176 |
+
|
177 |
+
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
178 |
+
|
179 |
+
static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization should not have been chosen.");
|
180 |
+
|
181 |
+
#if defined (USE_AVX512)
|
182 |
+
static constexpr const IndexType InputSimdWidth = 64;
|
183 |
+
static constexpr const IndexType MaxNumOutputRegs = 16;
|
184 |
+
#elif defined (USE_AVX2)
|
185 |
+
static constexpr const IndexType InputSimdWidth = 32;
|
186 |
+
static constexpr const IndexType MaxNumOutputRegs = 8;
|
187 |
+
#elif defined (USE_SSSE3)
|
188 |
+
static constexpr const IndexType InputSimdWidth = 16;
|
189 |
+
static constexpr const IndexType MaxNumOutputRegs = 8;
|
190 |
+
#elif defined (USE_NEON)
|
191 |
+
static constexpr const IndexType InputSimdWidth = 8;
|
192 |
+
static constexpr const IndexType MaxNumOutputRegs = 8;
|
193 |
+
#else
|
194 |
+
// The fallback implementation will not have permuted weights.
|
195 |
+
// We define these to avoid a lot of ifdefs later.
|
196 |
+
static constexpr const IndexType InputSimdWidth = 1;
|
197 |
+
static constexpr const IndexType MaxNumOutputRegs = 1;
|
198 |
+
#endif
|
199 |
+
|
200 |
+
// A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs].
|
201 |
+
// A small block is a region of size [InputSimdWidth, 1]
|
202 |
+
|
203 |
+
static constexpr const IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions);
|
204 |
+
static constexpr const IndexType SmallBlockSize = InputSimdWidth;
|
205 |
+
static constexpr const IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions;
|
206 |
+
static constexpr const IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize;
|
207 |
+
static constexpr const IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize;
|
208 |
+
static constexpr const IndexType NumBigBlocks = OutputDimensions / NumOutputRegs;
|
209 |
+
|
210 |
+
static_assert(OutputDimensions % NumOutputRegs == 0);
|
211 |
+
|
212 |
+
// Hash value embedded in the evaluation file
|
213 |
+
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
214 |
+
std::uint32_t hashValue = 0xCC03DAE4u;
|
215 |
+
hashValue += OutputDimensions;
|
216 |
+
hashValue ^= prevHash >> 1;
|
217 |
+
hashValue ^= prevHash << 31;
|
218 |
+
return hashValue;
|
219 |
+
}
|
220 |
+
|
221 |
+
/*
|
222 |
+
Transposes the small blocks within a block.
|
223 |
+
Effectively means that weights can be traversed sequentially during inference.
|
224 |
+
*/
|
225 |
+
static IndexType get_weight_index(IndexType i)
|
226 |
+
{
|
227 |
+
const IndexType smallBlock = (i / SmallBlockSize) % NumSmallBlocksInBigBlock;
|
228 |
+
const IndexType smallBlockCol = smallBlock / NumSmallBlocksPerOutput;
|
229 |
+
const IndexType smallBlockRow = smallBlock % NumSmallBlocksPerOutput;
|
230 |
+
const IndexType bigBlock = i / BigBlockSize;
|
231 |
+
const IndexType rest = i % SmallBlockSize;
|
232 |
+
|
233 |
+
const IndexType idx =
|
234 |
+
bigBlock * BigBlockSize
|
235 |
+
+ smallBlockRow * SmallBlockSize * NumOutputRegs
|
236 |
+
+ smallBlockCol * SmallBlockSize
|
237 |
+
+ rest;
|
238 |
+
|
239 |
+
return idx;
|
240 |
+
}
|
241 |
+
|
242 |
+
// Read network parameters
|
243 |
+
bool read_parameters(std::istream& stream) {
|
244 |
+
for (IndexType i = 0; i < OutputDimensions; ++i)
|
245 |
+
biases[i] = read_little_endian<BiasType>(stream);
|
246 |
+
|
247 |
+
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
|
248 |
+
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
|
249 |
+
|
250 |
+
return !stream.fail();
|
251 |
+
}
|
252 |
+
|
253 |
+
// Write network parameters
|
254 |
+
bool write_parameters(std::ostream& stream) const {
|
255 |
+
for (IndexType i = 0; i < OutputDimensions; ++i)
|
256 |
+
write_little_endian<BiasType>(stream, biases[i]);
|
257 |
+
|
258 |
+
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
|
259 |
+
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
|
260 |
+
|
261 |
+
return !stream.fail();
|
262 |
+
}
|
263 |
+
|
264 |
+
// Forward propagation
|
265 |
+
const OutputType* propagate(
|
266 |
+
const InputType* input, OutputType* output) const {
|
267 |
+
|
268 |
+
#if defined (USE_AVX512)
|
269 |
+
using acc_vec_t = __m512i;
|
270 |
+
using bias_vec_t = __m128i;
|
271 |
+
using weight_vec_t = __m512i;
|
272 |
+
using in_vec_t = __m512i;
|
273 |
+
#define vec_zero _mm512_setzero_si512()
|
274 |
+
#define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2
|
275 |
+
#define vec_hadd Simd::m512_hadd
|
276 |
+
#define vec_haddx4 Simd::m512_haddx4
|
277 |
+
#elif defined (USE_AVX2)
|
278 |
+
using acc_vec_t = __m256i;
|
279 |
+
using bias_vec_t = __m128i;
|
280 |
+
using weight_vec_t = __m256i;
|
281 |
+
using in_vec_t = __m256i;
|
282 |
+
#define vec_zero _mm256_setzero_si256()
|
283 |
+
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
|
284 |
+
#define vec_hadd Simd::m256_hadd
|
285 |
+
#define vec_haddx4 Simd::m256_haddx4
|
286 |
+
#elif defined (USE_SSSE3)
|
287 |
+
using acc_vec_t = __m128i;
|
288 |
+
using bias_vec_t = __m128i;
|
289 |
+
using weight_vec_t = __m128i;
|
290 |
+
using in_vec_t = __m128i;
|
291 |
+
#define vec_zero _mm_setzero_si128()
|
292 |
+
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
|
293 |
+
#define vec_hadd Simd::m128_hadd
|
294 |
+
#define vec_haddx4 Simd::m128_haddx4
|
295 |
+
#elif defined (USE_NEON)
|
296 |
+
using acc_vec_t = int32x4_t;
|
297 |
+
using bias_vec_t = int32x4_t;
|
298 |
+
using weight_vec_t = int8x8_t;
|
299 |
+
using in_vec_t = int8x8_t;
|
300 |
+
#define vec_zero {0}
|
301 |
+
#define vec_add_dpbusd_32x2 Simd::neon_m128_add_dpbusd_epi32x2
|
302 |
+
#define vec_hadd Simd::neon_m128_hadd
|
303 |
+
#define vec_haddx4 Simd::neon_m128_haddx4
|
304 |
+
#endif
|
305 |
+
|
306 |
+
#if defined (USE_SSSE3) || defined (USE_NEON)
|
307 |
+
const in_vec_t* invec = reinterpret_cast<const in_vec_t*>(input);
|
308 |
+
|
309 |
+
// Perform accumulation to registers for each big block
|
310 |
+
for (IndexType bigBlock = 0; bigBlock < NumBigBlocks; ++bigBlock)
|
311 |
+
{
|
312 |
+
acc_vec_t acc[NumOutputRegs] = { vec_zero };
|
313 |
+
|
314 |
+
// Each big block has NumOutputRegs small blocks in each "row", one per register.
|
315 |
+
// We process two small blocks at a time to save on one addition without VNNI.
|
316 |
+
for (IndexType smallBlock = 0; smallBlock < NumSmallBlocksPerOutput; smallBlock += 2)
|
317 |
+
{
|
318 |
+
const weight_vec_t* weightvec =
|
319 |
+
reinterpret_cast<const weight_vec_t*>(
|
320 |
+
weights
|
321 |
+
+ bigBlock * BigBlockSize
|
322 |
+
+ smallBlock * SmallBlockSize * NumOutputRegs);
|
323 |
+
|
324 |
+
const in_vec_t in0 = invec[smallBlock + 0];
|
325 |
+
const in_vec_t in1 = invec[smallBlock + 1];
|
326 |
+
|
327 |
+
for (IndexType k = 0; k < NumOutputRegs; ++k)
|
328 |
+
vec_add_dpbusd_32x2(acc[k], in0, weightvec[k], in1, weightvec[k + NumOutputRegs]);
|
329 |
+
}
|
330 |
+
|
331 |
+
// Horizontally add all accumulators.
|
332 |
+
if constexpr (NumOutputRegs % 4 == 0)
|
333 |
+
{
|
334 |
+
bias_vec_t* outputvec = reinterpret_cast<bias_vec_t*>(output);
|
335 |
+
const bias_vec_t* biasvec = reinterpret_cast<const bias_vec_t*>(biases);
|
336 |
+
|
337 |
+
for (IndexType k = 0; k < NumOutputRegs; k += 4)
|
338 |
+
{
|
339 |
+
const IndexType idx = (bigBlock * NumOutputRegs + k) / 4;
|
340 |
+
outputvec[idx] = vec_haddx4(acc[k+0], acc[k+1], acc[k+2], acc[k+3], biasvec[idx]);
|
341 |
+
}
|
342 |
+
}
|
343 |
+
else
|
344 |
+
{
|
345 |
+
for (IndexType k = 0; k < NumOutputRegs; ++k)
|
346 |
+
{
|
347 |
+
const IndexType idx = (bigBlock * NumOutputRegs + k);
|
348 |
+
output[idx] = vec_hadd(acc[k], biases[idx]);
|
349 |
+
}
|
350 |
+
}
|
351 |
+
}
|
352 |
+
|
353 |
+
# undef vec_zero
|
354 |
+
# undef vec_add_dpbusd_32x2
|
355 |
+
# undef vec_hadd
|
356 |
+
# undef vec_haddx4
|
357 |
+
#else
|
358 |
+
// Use old implementation for the other architectures.
|
359 |
+
affine_transform_non_ssse3<
|
360 |
+
InputDimensions,
|
361 |
+
PaddedInputDimensions,
|
362 |
+
OutputDimensions>(output, weights, biases, input);
|
363 |
+
|
364 |
+
#endif
|
365 |
+
|
366 |
+
return output;
|
367 |
+
}
|
368 |
+
|
369 |
+
private:
|
370 |
+
using BiasType = OutputType;
|
371 |
+
using WeightType = std::int8_t;
|
372 |
+
|
373 |
+
alignas(CacheLineSize) BiasType biases[OutputDimensions];
|
374 |
+
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
|
375 |
+
};
|
376 |
+
|
377 |
+
template <IndexType InDims, IndexType OutDims>
|
378 |
+
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) < LargeInputSize)>> {
|
379 |
+
public:
|
380 |
+
// Input/output type
|
381 |
+
// Input/output type
|
382 |
+
using InputType = std::uint8_t;
|
383 |
+
using OutputType = std::int32_t;
|
384 |
+
|
385 |
+
// Number of input/output dimensions
|
386 |
+
static constexpr IndexType InputDimensions = InDims;
|
387 |
+
static constexpr IndexType OutputDimensions = OutDims;
|
388 |
+
|
389 |
+
static constexpr IndexType PaddedInputDimensions =
|
390 |
+
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
|
391 |
+
static constexpr IndexType PaddedOutputDimensions =
|
392 |
+
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
|
393 |
+
|
394 |
+
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
395 |
+
|
396 |
+
static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization should not have been chosen.");
|
397 |
+
|
398 |
+
#if defined (USE_SSSE3)
|
399 |
+
static constexpr const IndexType OutputSimdWidth = SimdWidth / 4;
|
400 |
+
static constexpr const IndexType InputSimdWidth = SimdWidth;
|
401 |
+
#endif
|
402 |
+
|
403 |
+
// Hash value embedded in the evaluation file
|
404 |
+
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
405 |
+
std::uint32_t hashValue = 0xCC03DAE4u;
|
406 |
+
hashValue += OutputDimensions;
|
407 |
+
hashValue ^= prevHash >> 1;
|
408 |
+
hashValue ^= prevHash << 31;
|
409 |
+
return hashValue;
|
410 |
+
}
|
411 |
+
|
412 |
+
static IndexType get_weight_index_scrambled(IndexType i)
|
413 |
+
{
|
414 |
+
return
|
415 |
+
(i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 +
|
416 |
+
i / PaddedInputDimensions * 4 +
|
417 |
+
i % 4;
|
418 |
+
}
|
419 |
+
|
420 |
+
static IndexType get_weight_index(IndexType i)
|
421 |
+
{
|
422 |
+
#if defined (USE_SSSE3)
|
423 |
+
return get_weight_index_scrambled(i);
|
424 |
+
#else
|
425 |
+
return i;
|
426 |
+
#endif
|
427 |
+
}
|
428 |
+
|
429 |
+
// Read network parameters
|
430 |
+
bool read_parameters(std::istream& stream) {
|
431 |
+
for (IndexType i = 0; i < OutputDimensions; ++i)
|
432 |
+
biases[i] = read_little_endian<BiasType>(stream);
|
433 |
+
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
|
434 |
+
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
|
435 |
+
|
436 |
+
return !stream.fail();
|
437 |
+
}
|
438 |
+
|
439 |
+
// Write network parameters
|
440 |
+
bool write_parameters(std::ostream& stream) const {
|
441 |
+
for (IndexType i = 0; i < OutputDimensions; ++i)
|
442 |
+
write_little_endian<BiasType>(stream, biases[i]);
|
443 |
+
|
444 |
+
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
|
445 |
+
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
|
446 |
+
|
447 |
+
return !stream.fail();
|
448 |
+
}
|
449 |
+
// Forward propagation
|
450 |
+
const OutputType* propagate(
|
451 |
+
const InputType* input, OutputType* output) const {
|
452 |
+
|
453 |
+
#if defined (USE_AVX2)
|
454 |
+
using vec_t = __m256i;
|
455 |
+
#define vec_setzero _mm256_setzero_si256
|
456 |
+
#define vec_set_32 _mm256_set1_epi32
|
457 |
+
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
458 |
+
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
|
459 |
+
#define vec_add_dpbusd_32x4 Simd::m256_add_dpbusd_epi32x4
|
460 |
+
#define vec_hadd Simd::m256_hadd
|
461 |
+
#define vec_haddx4 Simd::m256_haddx4
|
462 |
+
#elif defined (USE_SSSE3)
|
463 |
+
using vec_t = __m128i;
|
464 |
+
#define vec_setzero _mm_setzero_si128
|
465 |
+
#define vec_set_32 _mm_set1_epi32
|
466 |
+
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
467 |
+
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
|
468 |
+
#define vec_add_dpbusd_32x4 Simd::m128_add_dpbusd_epi32x4
|
469 |
+
#define vec_hadd Simd::m128_hadd
|
470 |
+
#define vec_haddx4 Simd::m128_haddx4
|
471 |
+
#endif
|
472 |
+
|
473 |
+
#if defined (USE_SSSE3)
|
474 |
+
const auto inputVector = reinterpret_cast<const vec_t*>(input);
|
475 |
+
|
476 |
+
static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1);
|
477 |
+
|
478 |
+
if constexpr (OutputDimensions % OutputSimdWidth == 0)
|
479 |
+
{
|
480 |
+
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
|
481 |
+
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
|
482 |
+
|
483 |
+
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
|
484 |
+
const vec_t* biasvec = reinterpret_cast<const vec_t*>(biases);
|
485 |
+
vec_t acc[NumRegs];
|
486 |
+
for (IndexType k = 0; k < NumRegs; ++k)
|
487 |
+
acc[k] = biasvec[k];
|
488 |
+
|
489 |
+
for (IndexType i = 0; i < NumChunks; i += 2)
|
490 |
+
{
|
491 |
+
const vec_t in0 = vec_set_32(input32[i + 0]);
|
492 |
+
const vec_t in1 = vec_set_32(input32[i + 1]);
|
493 |
+
const auto col0 = reinterpret_cast<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
|
494 |
+
const auto col1 = reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
|
495 |
+
for (IndexType k = 0; k < NumRegs; ++k)
|
496 |
+
vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]);
|
497 |
+
}
|
498 |
+
|
499 |
+
vec_t* outptr = reinterpret_cast<vec_t*>(output);
|
500 |
+
for (IndexType k = 0; k < NumRegs; ++k)
|
501 |
+
outptr[k] = acc[k];
|
502 |
+
}
|
503 |
+
else if constexpr (OutputDimensions == 1)
|
504 |
+
{
|
505 |
+
constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth;
|
506 |
+
vec_t sum0 = vec_setzero();
|
507 |
+
const auto row0 = reinterpret_cast<const vec_t*>(&weights[0]);
|
508 |
+
|
509 |
+
for (int j = 0; j < (int)NumChunks; ++j)
|
510 |
+
{
|
511 |
+
const vec_t in = inputVector[j];
|
512 |
+
vec_add_dpbusd_32(sum0, in, row0[j]);
|
513 |
+
}
|
514 |
+
output[0] = vec_hadd(sum0, biases[0]);
|
515 |
+
}
|
516 |
+
|
517 |
+
# undef vec_setzero
|
518 |
+
# undef vec_set_32
|
519 |
+
# undef vec_add_dpbusd_32
|
520 |
+
# undef vec_add_dpbusd_32x2
|
521 |
+
# undef vec_add_dpbusd_32x4
|
522 |
+
# undef vec_hadd
|
523 |
+
# undef vec_haddx4
|
524 |
+
#else
|
525 |
+
// Use old implementation for the other architectures.
|
526 |
+
affine_transform_non_ssse3<
|
527 |
+
InputDimensions,
|
528 |
+
PaddedInputDimensions,
|
529 |
+
OutputDimensions>(output, weights, biases, input);
|
530 |
+
#endif
|
531 |
+
|
532 |
+
return output;
|
533 |
+
}
|
534 |
+
|
535 |
+
private:
|
536 |
+
using BiasType = OutputType;
|
537 |
+
using WeightType = std::int8_t;
|
538 |
+
|
539 |
+
alignas(CacheLineSize) BiasType biases[OutputDimensions];
|
540 |
+
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
|
541 |
+
};
|
542 |
+
|
543 |
+
} // namespace Stockfish::Eval::NNUE::Layers
|
544 |
+
|
545 |
+
#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
src/nnue/layers/clipped_relu.h
ADDED
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// Definition of layer ClippedReLU of NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
22 |
+
#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
23 |
+
|
24 |
+
#include "../nnue_common.h"
|
25 |
+
|
26 |
+
namespace Stockfish::Eval::NNUE::Layers {
|
27 |
+
|
28 |
+
// Clipped ReLU
|
29 |
+
template <IndexType InDims>
|
30 |
+
class ClippedReLU {
|
31 |
+
public:
|
32 |
+
// Input/output type
|
33 |
+
using InputType = std::int32_t;
|
34 |
+
using OutputType = std::uint8_t;
|
35 |
+
|
36 |
+
// Number of input/output dimensions
|
37 |
+
static constexpr IndexType InputDimensions = InDims;
|
38 |
+
static constexpr IndexType OutputDimensions = InputDimensions;
|
39 |
+
static constexpr IndexType PaddedOutputDimensions =
|
40 |
+
ceil_to_multiple<IndexType>(OutputDimensions, 32);
|
41 |
+
|
42 |
+
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
43 |
+
|
44 |
+
// Hash value embedded in the evaluation file
|
45 |
+
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
46 |
+
std::uint32_t hashValue = 0x538D24C7u;
|
47 |
+
hashValue += prevHash;
|
48 |
+
return hashValue;
|
49 |
+
}
|
50 |
+
|
51 |
+
// Read network parameters
|
52 |
+
bool read_parameters(std::istream&) {
|
53 |
+
return true;
|
54 |
+
}
|
55 |
+
|
56 |
+
// Write network parameters
|
57 |
+
bool write_parameters(std::ostream&) const {
|
58 |
+
return true;
|
59 |
+
}
|
60 |
+
|
61 |
+
// Forward propagation
|
62 |
+
const OutputType* propagate(
|
63 |
+
const InputType* input, OutputType* output) const {
|
64 |
+
|
65 |
+
#if defined(USE_AVX2)
|
66 |
+
if constexpr (InputDimensions % SimdWidth == 0) {
|
67 |
+
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
68 |
+
const __m256i Zero = _mm256_setzero_si256();
|
69 |
+
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
70 |
+
const auto in = reinterpret_cast<const __m256i*>(input);
|
71 |
+
const auto out = reinterpret_cast<__m256i*>(output);
|
72 |
+
for (IndexType i = 0; i < NumChunks; ++i) {
|
73 |
+
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
|
74 |
+
_mm256_load_si256(&in[i * 4 + 0]),
|
75 |
+
_mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits);
|
76 |
+
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
|
77 |
+
_mm256_load_si256(&in[i * 4 + 2]),
|
78 |
+
_mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits);
|
79 |
+
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
|
80 |
+
_mm256_packs_epi16(words0, words1), Zero), Offsets));
|
81 |
+
}
|
82 |
+
} else {
|
83 |
+
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
84 |
+
const __m128i Zero = _mm_setzero_si128();
|
85 |
+
const auto in = reinterpret_cast<const __m128i*>(input);
|
86 |
+
const auto out = reinterpret_cast<__m128i*>(output);
|
87 |
+
for (IndexType i = 0; i < NumChunks; ++i) {
|
88 |
+
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
|
89 |
+
_mm_load_si128(&in[i * 4 + 0]),
|
90 |
+
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
|
91 |
+
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
|
92 |
+
_mm_load_si128(&in[i * 4 + 2]),
|
93 |
+
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
|
94 |
+
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
95 |
+
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
|
96 |
+
}
|
97 |
+
}
|
98 |
+
constexpr IndexType Start =
|
99 |
+
InputDimensions % SimdWidth == 0
|
100 |
+
? InputDimensions / SimdWidth * SimdWidth
|
101 |
+
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
|
102 |
+
|
103 |
+
#elif defined(USE_SSE2)
|
104 |
+
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
105 |
+
|
106 |
+
#ifdef USE_SSE41
|
107 |
+
const __m128i Zero = _mm_setzero_si128();
|
108 |
+
#else
|
109 |
+
const __m128i k0x80s = _mm_set1_epi8(-128);
|
110 |
+
#endif
|
111 |
+
|
112 |
+
const auto in = reinterpret_cast<const __m128i*>(input);
|
113 |
+
const auto out = reinterpret_cast<__m128i*>(output);
|
114 |
+
for (IndexType i = 0; i < NumChunks; ++i) {
|
115 |
+
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
|
116 |
+
_mm_load_si128(&in[i * 4 + 0]),
|
117 |
+
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
|
118 |
+
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
|
119 |
+
_mm_load_si128(&in[i * 4 + 2]),
|
120 |
+
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
|
121 |
+
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
122 |
+
_mm_store_si128(&out[i],
|
123 |
+
|
124 |
+
#ifdef USE_SSE41
|
125 |
+
_mm_max_epi8(packedbytes, Zero)
|
126 |
+
#else
|
127 |
+
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
128 |
+
#endif
|
129 |
+
|
130 |
+
);
|
131 |
+
}
|
132 |
+
constexpr IndexType Start = NumChunks * SimdWidth;
|
133 |
+
|
134 |
+
#elif defined(USE_MMX)
|
135 |
+
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
136 |
+
const __m64 k0x80s = _mm_set1_pi8(-128);
|
137 |
+
const auto in = reinterpret_cast<const __m64*>(input);
|
138 |
+
const auto out = reinterpret_cast<__m64*>(output);
|
139 |
+
for (IndexType i = 0; i < NumChunks; ++i) {
|
140 |
+
const __m64 words0 = _mm_srai_pi16(
|
141 |
+
_mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]),
|
142 |
+
WeightScaleBits);
|
143 |
+
const __m64 words1 = _mm_srai_pi16(
|
144 |
+
_mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]),
|
145 |
+
WeightScaleBits);
|
146 |
+
const __m64 packedbytes = _mm_packs_pi16(words0, words1);
|
147 |
+
out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
|
148 |
+
}
|
149 |
+
_mm_empty();
|
150 |
+
constexpr IndexType Start = NumChunks * SimdWidth;
|
151 |
+
|
152 |
+
#elif defined(USE_NEON)
|
153 |
+
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
154 |
+
const int8x8_t Zero = {0};
|
155 |
+
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
156 |
+
const auto out = reinterpret_cast<int8x8_t*>(output);
|
157 |
+
for (IndexType i = 0; i < NumChunks; ++i) {
|
158 |
+
int16x8_t shifted;
|
159 |
+
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
|
160 |
+
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);
|
161 |
+
pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits);
|
162 |
+
out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
|
163 |
+
}
|
164 |
+
constexpr IndexType Start = NumChunks * (SimdWidth / 2);
|
165 |
+
#else
|
166 |
+
constexpr IndexType Start = 0;
|
167 |
+
#endif
|
168 |
+
|
169 |
+
for (IndexType i = Start; i < InputDimensions; ++i) {
|
170 |
+
output[i] = static_cast<OutputType>(
|
171 |
+
std::max(0, std::min(127, input[i] >> WeightScaleBits)));
|
172 |
+
}
|
173 |
+
|
174 |
+
return output;
|
175 |
+
}
|
176 |
+
};
|
177 |
+
|
178 |
+
} // namespace Stockfish::Eval::NNUE::Layers
|
179 |
+
|
180 |
+
#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
src/nnue/layers/simd.h
ADDED
@@ -0,0 +1,387 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef STOCKFISH_SIMD_H_INCLUDED
|
20 |
+
#define STOCKFISH_SIMD_H_INCLUDED
|
21 |
+
|
22 |
+
#if defined(USE_AVX2)
|
23 |
+
# include <immintrin.h>
|
24 |
+
|
25 |
+
#elif defined(USE_SSE41)
|
26 |
+
# include <smmintrin.h>
|
27 |
+
|
28 |
+
#elif defined(USE_SSSE3)
|
29 |
+
# include <tmmintrin.h>
|
30 |
+
|
31 |
+
#elif defined(USE_SSE2)
|
32 |
+
# include <emmintrin.h>
|
33 |
+
|
34 |
+
#elif defined(USE_MMX)
|
35 |
+
# include <mmintrin.h>
|
36 |
+
|
37 |
+
#elif defined(USE_NEON)
|
38 |
+
# include <arm_neon.h>
|
39 |
+
#endif
|
40 |
+
|
41 |
+
// The inline asm is only safe for GCC, where it is necessary to get good codegen.
|
42 |
+
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101693
|
43 |
+
// Clang does fine without it.
|
44 |
+
// Play around here: https://godbolt.org/z/7EWqrYq51
|
45 |
+
#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER))
|
46 |
+
#define USE_INLINE_ASM
|
47 |
+
#endif
|
48 |
+
|
49 |
+
// Use either the AVX512 or AVX-VNNI version of the VNNI instructions.
|
50 |
+
#if defined(USE_AVXVNNI)
|
51 |
+
#define VNNI_PREFIX "%{vex%} "
|
52 |
+
#else
|
53 |
+
#define VNNI_PREFIX ""
|
54 |
+
#endif
|
55 |
+
|
56 |
+
namespace Stockfish::Simd {
|
57 |
+
|
58 |
+
#if defined (USE_AVX512)
|
59 |
+
|
60 |
+
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
|
61 |
+
return _mm512_reduce_add_epi32(sum) + bias;
|
62 |
+
}
|
63 |
+
|
64 |
+
/*
|
65 |
+
Parameters:
|
66 |
+
sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]]
|
67 |
+
sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]]
|
68 |
+
sum2 = [zmm2.i128[0], zmm2.i128[1], zmm2.i128[2], zmm2.i128[3]]
|
69 |
+
sum3 = [zmm3.i128[0], zmm3.i128[1], zmm3.i128[2], zmm3.i128[3]]
|
70 |
+
|
71 |
+
Returns:
|
72 |
+
ret = [
|
73 |
+
reduce_add_epi32(zmm0.i128[0]), reduce_add_epi32(zmm1.i128[0]), reduce_add_epi32(zmm2.i128[0]), reduce_add_epi32(zmm3.i128[0]),
|
74 |
+
reduce_add_epi32(zmm0.i128[1]), reduce_add_epi32(zmm1.i128[1]), reduce_add_epi32(zmm2.i128[1]), reduce_add_epi32(zmm3.i128[1]),
|
75 |
+
reduce_add_epi32(zmm0.i128[2]), reduce_add_epi32(zmm1.i128[2]), reduce_add_epi32(zmm2.i128[2]), reduce_add_epi32(zmm3.i128[2]),
|
76 |
+
reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3])
|
77 |
+
]
|
78 |
+
*/
|
79 |
+
[[maybe_unused]] static __m512i m512_hadd128x16_interleave(
|
80 |
+
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
|
81 |
+
|
82 |
+
__m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
|
83 |
+
__m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
|
84 |
+
|
85 |
+
__m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3);
|
86 |
+
__m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3);
|
87 |
+
|
88 |
+
__m512i sum01 = _mm512_add_epi32(sum01a, sum01b);
|
89 |
+
__m512i sum23 = _mm512_add_epi32(sum23a, sum23b);
|
90 |
+
|
91 |
+
__m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23);
|
92 |
+
__m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23);
|
93 |
+
|
94 |
+
return _mm512_add_epi32(sum0123a, sum0123b);
|
95 |
+
}
|
96 |
+
|
97 |
+
[[maybe_unused]] static __m128i m512_haddx4(
|
98 |
+
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3,
|
99 |
+
__m128i bias) {
|
100 |
+
|
101 |
+
__m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3);
|
102 |
+
|
103 |
+
__m256i sum256lo = _mm512_castsi512_si256(sum);
|
104 |
+
__m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1);
|
105 |
+
|
106 |
+
sum256lo = _mm256_add_epi32(sum256lo, sum256hi);
|
107 |
+
|
108 |
+
__m128i sum128lo = _mm256_castsi256_si128(sum256lo);
|
109 |
+
__m128i sum128hi = _mm256_extracti128_si256(sum256lo, 1);
|
110 |
+
|
111 |
+
return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias);
|
112 |
+
}
|
113 |
+
|
114 |
+
[[maybe_unused]] static void m512_add_dpbusd_epi32(
|
115 |
+
__m512i& acc,
|
116 |
+
__m512i a,
|
117 |
+
__m512i b) {
|
118 |
+
|
119 |
+
# if defined (USE_VNNI)
|
120 |
+
# if defined (USE_INLINE_ASM)
|
121 |
+
asm(
|
122 |
+
"vpdpbusd %[b], %[a], %[acc]\n\t"
|
123 |
+
: [acc]"+v"(acc)
|
124 |
+
: [a]"v"(a), [b]"vm"(b)
|
125 |
+
);
|
126 |
+
# else
|
127 |
+
acc = _mm512_dpbusd_epi32(acc, a, b);
|
128 |
+
# endif
|
129 |
+
# else
|
130 |
+
# if defined (USE_INLINE_ASM)
|
131 |
+
__m512i tmp = _mm512_maddubs_epi16(a, b);
|
132 |
+
asm(
|
133 |
+
"vpmaddwd %[tmp], %[ones], %[tmp]\n\t"
|
134 |
+
"vpaddd %[acc], %[tmp], %[acc]\n\t"
|
135 |
+
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
|
136 |
+
: [ones]"v"(_mm512_set1_epi16(1))
|
137 |
+
);
|
138 |
+
# else
|
139 |
+
__m512i product0 = _mm512_maddubs_epi16(a, b);
|
140 |
+
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
|
141 |
+
acc = _mm512_add_epi32(acc, product0);
|
142 |
+
# endif
|
143 |
+
# endif
|
144 |
+
}
|
145 |
+
|
146 |
+
[[maybe_unused]] static void m512_add_dpbusd_epi32x2(
|
147 |
+
__m512i& acc,
|
148 |
+
__m512i a0, __m512i b0,
|
149 |
+
__m512i a1, __m512i b1) {
|
150 |
+
|
151 |
+
# if defined (USE_VNNI)
|
152 |
+
# if defined (USE_INLINE_ASM)
|
153 |
+
asm(
|
154 |
+
"vpdpbusd %[b0], %[a0], %[acc]\n\t"
|
155 |
+
"vpdpbusd %[b1], %[a1], %[acc]\n\t"
|
156 |
+
: [acc]"+v"(acc)
|
157 |
+
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
|
158 |
+
);
|
159 |
+
# else
|
160 |
+
acc = _mm512_dpbusd_epi32(acc, a0, b0);
|
161 |
+
acc = _mm512_dpbusd_epi32(acc, a1, b1);
|
162 |
+
# endif
|
163 |
+
# else
|
164 |
+
# if defined (USE_INLINE_ASM)
|
165 |
+
__m512i tmp0 = _mm512_maddubs_epi16(a0, b0);
|
166 |
+
__m512i tmp1 = _mm512_maddubs_epi16(a1, b1);
|
167 |
+
asm(
|
168 |
+
"vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
|
169 |
+
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
|
170 |
+
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
|
171 |
+
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
|
172 |
+
: [tmp1]"v"(tmp1), [ones]"v"(_mm512_set1_epi16(1))
|
173 |
+
);
|
174 |
+
# else
|
175 |
+
__m512i product0 = _mm512_maddubs_epi16(a0, b0);
|
176 |
+
__m512i product1 = _mm512_maddubs_epi16(a1, b1);
|
177 |
+
product0 = _mm512_adds_epi16(product0, product1);
|
178 |
+
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
|
179 |
+
acc = _mm512_add_epi32(acc, product0);
|
180 |
+
# endif
|
181 |
+
# endif
|
182 |
+
}
|
183 |
+
|
184 |
+
#endif
|
185 |
+
|
186 |
+
#if defined (USE_AVX2)
|
187 |
+
|
188 |
+
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
|
189 |
+
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
|
190 |
+
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
|
191 |
+
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
|
192 |
+
return _mm_cvtsi128_si32(sum128) + bias;
|
193 |
+
}
|
194 |
+
|
195 |
+
[[maybe_unused]] static __m128i m256_haddx4(
|
196 |
+
__m256i sum0, __m256i sum1, __m256i sum2, __m256i sum3,
|
197 |
+
__m128i bias) {
|
198 |
+
|
199 |
+
sum0 = _mm256_hadd_epi32(sum0, sum1);
|
200 |
+
sum2 = _mm256_hadd_epi32(sum2, sum3);
|
201 |
+
|
202 |
+
sum0 = _mm256_hadd_epi32(sum0, sum2);
|
203 |
+
|
204 |
+
__m128i sum128lo = _mm256_castsi256_si128(sum0);
|
205 |
+
__m128i sum128hi = _mm256_extracti128_si256(sum0, 1);
|
206 |
+
|
207 |
+
return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias);
|
208 |
+
}
|
209 |
+
|
210 |
+
[[maybe_unused]] static void m256_add_dpbusd_epi32(
|
211 |
+
__m256i& acc,
|
212 |
+
__m256i a,
|
213 |
+
__m256i b) {
|
214 |
+
|
215 |
+
# if defined (USE_VNNI)
|
216 |
+
# if defined (USE_INLINE_ASM)
|
217 |
+
asm(
|
218 |
+
VNNI_PREFIX "vpdpbusd %[b], %[a], %[acc]\n\t"
|
219 |
+
: [acc]"+v"(acc)
|
220 |
+
: [a]"v"(a), [b]"vm"(b)
|
221 |
+
);
|
222 |
+
# else
|
223 |
+
acc = _mm256_dpbusd_epi32(acc, a, b);
|
224 |
+
# endif
|
225 |
+
# else
|
226 |
+
# if defined (USE_INLINE_ASM)
|
227 |
+
__m256i tmp = _mm256_maddubs_epi16(a, b);
|
228 |
+
asm(
|
229 |
+
"vpmaddwd %[tmp], %[ones], %[tmp]\n\t"
|
230 |
+
"vpaddd %[acc], %[tmp], %[acc]\n\t"
|
231 |
+
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
|
232 |
+
: [ones]"v"(_mm256_set1_epi16(1))
|
233 |
+
);
|
234 |
+
# else
|
235 |
+
__m256i product0 = _mm256_maddubs_epi16(a, b);
|
236 |
+
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
|
237 |
+
acc = _mm256_add_epi32(acc, product0);
|
238 |
+
# endif
|
239 |
+
# endif
|
240 |
+
}
|
241 |
+
|
242 |
+
[[maybe_unused]] static void m256_add_dpbusd_epi32x2(
|
243 |
+
__m256i& acc,
|
244 |
+
__m256i a0, __m256i b0,
|
245 |
+
__m256i a1, __m256i b1) {
|
246 |
+
|
247 |
+
# if defined (USE_VNNI)
|
248 |
+
# if defined (USE_INLINE_ASM)
|
249 |
+
asm(
|
250 |
+
VNNI_PREFIX "vpdpbusd %[b0], %[a0], %[acc]\n\t"
|
251 |
+
VNNI_PREFIX "vpdpbusd %[b1], %[a1], %[acc]\n\t"
|
252 |
+
: [acc]"+v"(acc)
|
253 |
+
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
|
254 |
+
);
|
255 |
+
# else
|
256 |
+
acc = _mm256_dpbusd_epi32(acc, a0, b0);
|
257 |
+
acc = _mm256_dpbusd_epi32(acc, a1, b1);
|
258 |
+
# endif
|
259 |
+
# else
|
260 |
+
# if defined (USE_INLINE_ASM)
|
261 |
+
__m256i tmp0 = _mm256_maddubs_epi16(a0, b0);
|
262 |
+
__m256i tmp1 = _mm256_maddubs_epi16(a1, b1);
|
263 |
+
asm(
|
264 |
+
"vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
|
265 |
+
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
|
266 |
+
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
|
267 |
+
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
|
268 |
+
: [tmp1]"v"(tmp1), [ones]"v"(_mm256_set1_epi16(1))
|
269 |
+
);
|
270 |
+
# else
|
271 |
+
__m256i product0 = _mm256_maddubs_epi16(a0, b0);
|
272 |
+
__m256i product1 = _mm256_maddubs_epi16(a1, b1);
|
273 |
+
product0 = _mm256_adds_epi16(product0, product1);
|
274 |
+
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
|
275 |
+
acc = _mm256_add_epi32(acc, product0);
|
276 |
+
# endif
|
277 |
+
# endif
|
278 |
+
}
|
279 |
+
|
280 |
+
#endif
|
281 |
+
|
282 |
+
#if defined (USE_SSSE3)
|
283 |
+
|
284 |
+
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
|
285 |
+
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
|
286 |
+
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
|
287 |
+
return _mm_cvtsi128_si32(sum) + bias;
|
288 |
+
}
|
289 |
+
|
290 |
+
[[maybe_unused]] static __m128i m128_haddx4(
|
291 |
+
__m128i sum0, __m128i sum1, __m128i sum2, __m128i sum3,
|
292 |
+
__m128i bias) {
|
293 |
+
|
294 |
+
sum0 = _mm_hadd_epi32(sum0, sum1);
|
295 |
+
sum2 = _mm_hadd_epi32(sum2, sum3);
|
296 |
+
sum0 = _mm_hadd_epi32(sum0, sum2);
|
297 |
+
return _mm_add_epi32(sum0, bias);
|
298 |
+
}
|
299 |
+
|
300 |
+
[[maybe_unused]] static void m128_add_dpbusd_epi32(
|
301 |
+
__m128i& acc,
|
302 |
+
__m128i a,
|
303 |
+
__m128i b) {
|
304 |
+
|
305 |
+
# if defined (USE_INLINE_ASM)
|
306 |
+
__m128i tmp = _mm_maddubs_epi16(a, b);
|
307 |
+
asm(
|
308 |
+
"pmaddwd %[ones], %[tmp]\n\t"
|
309 |
+
"paddd %[tmp], %[acc]\n\t"
|
310 |
+
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
|
311 |
+
: [ones]"v"(_mm_set1_epi16(1))
|
312 |
+
);
|
313 |
+
# else
|
314 |
+
__m128i product0 = _mm_maddubs_epi16(a, b);
|
315 |
+
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
316 |
+
acc = _mm_add_epi32(acc, product0);
|
317 |
+
# endif
|
318 |
+
}
|
319 |
+
|
320 |
+
[[maybe_unused]] static void m128_add_dpbusd_epi32x2(
|
321 |
+
__m128i& acc,
|
322 |
+
__m128i a0, __m128i b0,
|
323 |
+
__m128i a1, __m128i b1) {
|
324 |
+
|
325 |
+
# if defined (USE_INLINE_ASM)
|
326 |
+
__m128i tmp0 = _mm_maddubs_epi16(a0, b0);
|
327 |
+
__m128i tmp1 = _mm_maddubs_epi16(a1, b1);
|
328 |
+
asm(
|
329 |
+
"paddsw %[tmp1], %[tmp0]\n\t"
|
330 |
+
"pmaddwd %[ones], %[tmp0]\n\t"
|
331 |
+
"paddd %[tmp0], %[acc]\n\t"
|
332 |
+
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
|
333 |
+
: [tmp1]"v"(tmp1), [ones]"v"(_mm_set1_epi16(1))
|
334 |
+
);
|
335 |
+
# else
|
336 |
+
__m128i product0 = _mm_maddubs_epi16(a0, b0);
|
337 |
+
__m128i product1 = _mm_maddubs_epi16(a1, b1);
|
338 |
+
product0 = _mm_adds_epi16(product0, product1);
|
339 |
+
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
340 |
+
acc = _mm_add_epi32(acc, product0);
|
341 |
+
# endif
|
342 |
+
}
|
343 |
+
|
344 |
+
#endif
|
345 |
+
|
346 |
+
#if defined (USE_NEON)
|
347 |
+
|
348 |
+
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
|
349 |
+
# if USE_NEON >= 8
|
350 |
+
return vaddvq_s32(s);
|
351 |
+
# else
|
352 |
+
return s[0] + s[1] + s[2] + s[3];
|
353 |
+
# endif
|
354 |
+
}
|
355 |
+
|
356 |
+
[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
|
357 |
+
return neon_m128_reduce_add_epi32(sum) + bias;
|
358 |
+
}
|
359 |
+
|
360 |
+
[[maybe_unused]] static int32x4_t neon_m128_haddx4(
|
361 |
+
int32x4_t sum0, int32x4_t sum1, int32x4_t sum2, int32x4_t sum3,
|
362 |
+
int32x4_t bias) {
|
363 |
+
|
364 |
+
int32x4_t hsums {
|
365 |
+
neon_m128_reduce_add_epi32(sum0),
|
366 |
+
neon_m128_reduce_add_epi32(sum1),
|
367 |
+
neon_m128_reduce_add_epi32(sum2),
|
368 |
+
neon_m128_reduce_add_epi32(sum3)
|
369 |
+
};
|
370 |
+
return vaddq_s32(hsums, bias);
|
371 |
+
}
|
372 |
+
|
373 |
+
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2(
|
374 |
+
int32x4_t& acc,
|
375 |
+
int8x8_t a0, int8x8_t b0,
|
376 |
+
int8x8_t a1, int8x8_t b1) {
|
377 |
+
|
378 |
+
int16x8_t product = vmull_s8(a0, b0);
|
379 |
+
product = vmlal_s8(product, a1, b1);
|
380 |
+
acc = vpadalq_s16(acc, product);
|
381 |
+
}
|
382 |
+
|
383 |
+
#endif
|
384 |
+
|
385 |
+
}
|
386 |
+
|
387 |
+
#endif // STOCKFISH_SIMD_H_INCLUDED
|
src/nnue/layers/sqr_clipped_relu.h
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// Definition of layer ClippedReLU of NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
|
22 |
+
#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
|
23 |
+
|
24 |
+
#include "../nnue_common.h"
|
25 |
+
|
26 |
+
namespace Stockfish::Eval::NNUE::Layers {
|
27 |
+
|
28 |
+
// Clipped ReLU
|
29 |
+
template <IndexType InDims>
|
30 |
+
class SqrClippedReLU {
|
31 |
+
public:
|
32 |
+
// Input/output type
|
33 |
+
using InputType = std::int32_t;
|
34 |
+
using OutputType = std::uint8_t;
|
35 |
+
|
36 |
+
// Number of input/output dimensions
|
37 |
+
static constexpr IndexType InputDimensions = InDims;
|
38 |
+
static constexpr IndexType OutputDimensions = InputDimensions;
|
39 |
+
static constexpr IndexType PaddedOutputDimensions =
|
40 |
+
ceil_to_multiple<IndexType>(OutputDimensions, 32);
|
41 |
+
|
42 |
+
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
43 |
+
|
44 |
+
// Hash value embedded in the evaluation file
|
45 |
+
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
46 |
+
std::uint32_t hashValue = 0x538D24C7u;
|
47 |
+
hashValue += prevHash;
|
48 |
+
return hashValue;
|
49 |
+
}
|
50 |
+
|
51 |
+
// Read network parameters
|
52 |
+
bool read_parameters(std::istream&) {
|
53 |
+
return true;
|
54 |
+
}
|
55 |
+
|
56 |
+
// Write network parameters
|
57 |
+
bool write_parameters(std::ostream&) const {
|
58 |
+
return true;
|
59 |
+
}
|
60 |
+
|
61 |
+
// Forward propagation
|
62 |
+
const OutputType* propagate(
|
63 |
+
const InputType* input, OutputType* output) const {
|
64 |
+
|
65 |
+
#if defined(USE_SSE2)
|
66 |
+
constexpr IndexType NumChunks = InputDimensions / 16;
|
67 |
+
|
68 |
+
#ifdef USE_SSE41
|
69 |
+
const __m128i Zero = _mm_setzero_si128();
|
70 |
+
#else
|
71 |
+
const __m128i k0x80s = _mm_set1_epi8(-128);
|
72 |
+
#endif
|
73 |
+
|
74 |
+
static_assert(WeightScaleBits == 6);
|
75 |
+
const auto in = reinterpret_cast<const __m128i*>(input);
|
76 |
+
const auto out = reinterpret_cast<__m128i*>(output);
|
77 |
+
for (IndexType i = 0; i < NumChunks; ++i) {
|
78 |
+
__m128i words0 = _mm_packs_epi32(
|
79 |
+
_mm_load_si128(&in[i * 4 + 0]),
|
80 |
+
_mm_load_si128(&in[i * 4 + 1]));
|
81 |
+
__m128i words1 = _mm_packs_epi32(
|
82 |
+
_mm_load_si128(&in[i * 4 + 2]),
|
83 |
+
_mm_load_si128(&in[i * 4 + 3]));
|
84 |
+
|
85 |
+
// Not sure if
|
86 |
+
words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3);
|
87 |
+
words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3);
|
88 |
+
|
89 |
+
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
90 |
+
|
91 |
+
_mm_store_si128(&out[i],
|
92 |
+
|
93 |
+
#ifdef USE_SSE41
|
94 |
+
_mm_max_epi8(packedbytes, Zero)
|
95 |
+
#else
|
96 |
+
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
97 |
+
#endif
|
98 |
+
|
99 |
+
);
|
100 |
+
}
|
101 |
+
constexpr IndexType Start = NumChunks * 16;
|
102 |
+
|
103 |
+
#else
|
104 |
+
constexpr IndexType Start = 0;
|
105 |
+
#endif
|
106 |
+
|
107 |
+
for (IndexType i = Start; i < InputDimensions; ++i) {
|
108 |
+
output[i] = static_cast<OutputType>(
|
109 |
+
// realy should be /127 but we need to make it fast
|
110 |
+
// needs to be accounted for in the trainer
|
111 |
+
std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128)));
|
112 |
+
}
|
113 |
+
|
114 |
+
return output;
|
115 |
+
}
|
116 |
+
};
|
117 |
+
|
118 |
+
} // namespace Stockfish::Eval::NNUE::Layers
|
119 |
+
|
120 |
+
#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
|
src/nnue/nnue_accumulator.h
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// Class for difference calculation of NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_ACCUMULATOR_H_INCLUDED
|
22 |
+
#define NNUE_ACCUMULATOR_H_INCLUDED
|
23 |
+
|
24 |
+
#include "nnue_architecture.h"
|
25 |
+
|
26 |
+
namespace Stockfish::Eval::NNUE {
|
27 |
+
|
28 |
+
// Class that holds the result of affine transformation of input features
|
29 |
+
struct alignas(CacheLineSize) Accumulator {
|
30 |
+
std::int16_t accumulation[2][TransformedFeatureDimensions];
|
31 |
+
std::int32_t psqtAccumulation[2][PSQTBuckets];
|
32 |
+
bool computed[2];
|
33 |
+
};
|
34 |
+
|
35 |
+
} // namespace Stockfish::Eval::NNUE
|
36 |
+
|
37 |
+
#endif // NNUE_ACCUMULATOR_H_INCLUDED
|
src/nnue/nnue_architecture.h
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// Input features and network structure used in NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
22 |
+
#define NNUE_ARCHITECTURE_H_INCLUDED
|
23 |
+
|
24 |
+
#include <memory>
|
25 |
+
|
26 |
+
#include "nnue_common.h"
|
27 |
+
|
28 |
+
#include "features/half_ka_v2_hm.h"
|
29 |
+
|
30 |
+
#include "layers/affine_transform.h"
|
31 |
+
#include "layers/clipped_relu.h"
|
32 |
+
#include "layers/sqr_clipped_relu.h"
|
33 |
+
|
34 |
+
#include "../misc.h"
|
35 |
+
|
36 |
+
namespace Stockfish::Eval::NNUE {
|
37 |
+
|
38 |
+
// Input features used in evaluation function
|
39 |
+
using FeatureSet = Features::HalfKAv2_hm;
|
40 |
+
|
41 |
+
// Number of input feature dimensions after conversion
|
42 |
+
constexpr IndexType TransformedFeatureDimensions = 1024;
|
43 |
+
constexpr IndexType PSQTBuckets = 8;
|
44 |
+
constexpr IndexType LayerStacks = 8;
|
45 |
+
|
46 |
+
struct Network
|
47 |
+
{
|
48 |
+
static constexpr int FC_0_OUTPUTS = 15;
|
49 |
+
static constexpr int FC_1_OUTPUTS = 32;
|
50 |
+
|
51 |
+
Layers::AffineTransform<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
|
52 |
+
Layers::SqrClippedReLU<FC_0_OUTPUTS + 1> ac_sqr_0;
|
53 |
+
Layers::ClippedReLU<FC_0_OUTPUTS + 1> ac_0;
|
54 |
+
Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS> fc_1;
|
55 |
+
Layers::ClippedReLU<FC_1_OUTPUTS> ac_1;
|
56 |
+
Layers::AffineTransform<FC_1_OUTPUTS, 1> fc_2;
|
57 |
+
|
58 |
+
// Hash value embedded in the evaluation file
|
59 |
+
static constexpr std::uint32_t get_hash_value() {
|
60 |
+
// input slice hash
|
61 |
+
std::uint32_t hashValue = 0xEC42E90Du;
|
62 |
+
hashValue ^= TransformedFeatureDimensions * 2;
|
63 |
+
|
64 |
+
hashValue = decltype(fc_0)::get_hash_value(hashValue);
|
65 |
+
hashValue = decltype(ac_0)::get_hash_value(hashValue);
|
66 |
+
hashValue = decltype(fc_1)::get_hash_value(hashValue);
|
67 |
+
hashValue = decltype(ac_1)::get_hash_value(hashValue);
|
68 |
+
hashValue = decltype(fc_2)::get_hash_value(hashValue);
|
69 |
+
|
70 |
+
return hashValue;
|
71 |
+
}
|
72 |
+
|
73 |
+
// Read network parameters
|
74 |
+
bool read_parameters(std::istream& stream) {
|
75 |
+
if (!fc_0.read_parameters(stream)) return false;
|
76 |
+
if (!ac_0.read_parameters(stream)) return false;
|
77 |
+
if (!fc_1.read_parameters(stream)) return false;
|
78 |
+
if (!ac_1.read_parameters(stream)) return false;
|
79 |
+
if (!fc_2.read_parameters(stream)) return false;
|
80 |
+
return true;
|
81 |
+
}
|
82 |
+
|
83 |
+
// Read network parameters
|
84 |
+
bool write_parameters(std::ostream& stream) const {
|
85 |
+
if (!fc_0.write_parameters(stream)) return false;
|
86 |
+
if (!ac_0.write_parameters(stream)) return false;
|
87 |
+
if (!fc_1.write_parameters(stream)) return false;
|
88 |
+
if (!ac_1.write_parameters(stream)) return false;
|
89 |
+
if (!fc_2.write_parameters(stream)) return false;
|
90 |
+
return true;
|
91 |
+
}
|
92 |
+
|
93 |
+
std::int32_t propagate(const TransformedFeatureType* transformedFeatures)
|
94 |
+
{
|
95 |
+
struct alignas(CacheLineSize) Buffer
|
96 |
+
{
|
97 |
+
alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out;
|
98 |
+
alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];
|
99 |
+
alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out;
|
100 |
+
alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out;
|
101 |
+
alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out;
|
102 |
+
alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out;
|
103 |
+
|
104 |
+
Buffer()
|
105 |
+
{
|
106 |
+
std::memset(this, 0, sizeof(*this));
|
107 |
+
}
|
108 |
+
};
|
109 |
+
|
110 |
+
#if defined(__clang__) && (__APPLE__)
|
111 |
+
// workaround for a bug reported with xcode 12
|
112 |
+
static thread_local auto tlsBuffer = std::make_unique<Buffer>();
|
113 |
+
// Access TLS only once, cache result.
|
114 |
+
Buffer& buffer = *tlsBuffer;
|
115 |
+
#else
|
116 |
+
alignas(CacheLineSize) static thread_local Buffer buffer;
|
117 |
+
#endif
|
118 |
+
|
119 |
+
fc_0.propagate(transformedFeatures, buffer.fc_0_out);
|
120 |
+
ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out);
|
121 |
+
ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out);
|
122 |
+
std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType));
|
123 |
+
fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out);
|
124 |
+
ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out);
|
125 |
+
fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out);
|
126 |
+
|
127 |
+
// buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in quantized form
|
128 |
+
// but we want 1.0 to be equal to 600*OutputScale
|
129 |
+
std::int32_t fwdOut = int(buffer.fc_0_out[FC_0_OUTPUTS]) * (600*OutputScale) / (127*(1<<WeightScaleBits));
|
130 |
+
std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
|
131 |
+
|
132 |
+
return outputValue;
|
133 |
+
}
|
134 |
+
};
|
135 |
+
|
136 |
+
} // namespace Stockfish::Eval::NNUE
|
137 |
+
|
138 |
+
#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
src/nnue/nnue_common.h
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// Constants used in NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_COMMON_H_INCLUDED
|
22 |
+
#define NNUE_COMMON_H_INCLUDED
|
23 |
+
|
24 |
+
#include <cstring>
|
25 |
+
#include <iostream>
|
26 |
+
|
27 |
+
#include "../misc.h" // for IsLittleEndian
|
28 |
+
|
29 |
+
#if defined(USE_AVX2)
|
30 |
+
#include <immintrin.h>
|
31 |
+
|
32 |
+
#elif defined(USE_SSE41)
|
33 |
+
#include <smmintrin.h>
|
34 |
+
|
35 |
+
#elif defined(USE_SSSE3)
|
36 |
+
#include <tmmintrin.h>
|
37 |
+
|
38 |
+
#elif defined(USE_SSE2)
|
39 |
+
#include <emmintrin.h>
|
40 |
+
|
41 |
+
#elif defined(USE_MMX)
|
42 |
+
#include <mmintrin.h>
|
43 |
+
|
44 |
+
#elif defined(USE_NEON)
|
45 |
+
#include <arm_neon.h>
|
46 |
+
#endif
|
47 |
+
|
48 |
+
namespace Stockfish::Eval::NNUE {
|
49 |
+
|
50 |
+
// Version of the evaluation file
|
51 |
+
constexpr std::uint32_t Version = 0x7AF32F20u;
|
52 |
+
|
53 |
+
// Constant used in evaluation value calculation
|
54 |
+
constexpr int OutputScale = 16;
|
55 |
+
constexpr int WeightScaleBits = 6;
|
56 |
+
|
57 |
+
// Size of cache line (in bytes)
|
58 |
+
constexpr std::size_t CacheLineSize = 64;
|
59 |
+
|
60 |
+
// SIMD width (in bytes)
|
61 |
+
#if defined(USE_AVX2)
|
62 |
+
constexpr std::size_t SimdWidth = 32;
|
63 |
+
|
64 |
+
#elif defined(USE_SSE2)
|
65 |
+
constexpr std::size_t SimdWidth = 16;
|
66 |
+
|
67 |
+
#elif defined(USE_MMX)
|
68 |
+
constexpr std::size_t SimdWidth = 8;
|
69 |
+
|
70 |
+
#elif defined(USE_NEON)
|
71 |
+
constexpr std::size_t SimdWidth = 16;
|
72 |
+
#endif
|
73 |
+
|
74 |
+
constexpr std::size_t MaxSimdWidth = 32;
|
75 |
+
|
76 |
+
// Type of input feature after conversion
|
77 |
+
using TransformedFeatureType = std::uint8_t;
|
78 |
+
using IndexType = std::uint32_t;
|
79 |
+
|
80 |
+
// Round n up to be a multiple of base
|
81 |
+
template <typename IntType>
|
82 |
+
constexpr IntType ceil_to_multiple(IntType n, IntType base) {
|
83 |
+
return (n + base - 1) / base * base;
|
84 |
+
}
|
85 |
+
|
86 |
+
// read_little_endian() is our utility to read an integer (signed or unsigned, any size)
|
87 |
+
// from a stream in little-endian order. We swap the byte order after the read if
|
88 |
+
// necessary to return a result with the byte ordering of the compiling machine.
|
89 |
+
template <typename IntType>
|
90 |
+
inline IntType read_little_endian(std::istream& stream) {
|
91 |
+
IntType result;
|
92 |
+
|
93 |
+
if (IsLittleEndian)
|
94 |
+
stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
|
95 |
+
else
|
96 |
+
{
|
97 |
+
std::uint8_t u[sizeof(IntType)];
|
98 |
+
typename std::make_unsigned<IntType>::type v = 0;
|
99 |
+
|
100 |
+
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
|
101 |
+
for (std::size_t i = 0; i < sizeof(IntType); ++i)
|
102 |
+
v = (v << 8) | u[sizeof(IntType) - i - 1];
|
103 |
+
|
104 |
+
std::memcpy(&result, &v, sizeof(IntType));
|
105 |
+
}
|
106 |
+
|
107 |
+
return result;
|
108 |
+
}
|
109 |
+
|
110 |
+
// write_little_endian() is our utility to write an integer (signed or unsigned, any size)
|
111 |
+
// to a stream in little-endian order. We swap the byte order before the write if
|
112 |
+
// necessary to always write in little endian order, independently of the byte
|
113 |
+
// ordering of the compiling machine.
|
114 |
+
template <typename IntType>
|
115 |
+
inline void write_little_endian(std::ostream& stream, IntType value) {
|
116 |
+
|
117 |
+
if (IsLittleEndian)
|
118 |
+
stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
|
119 |
+
else
|
120 |
+
{
|
121 |
+
std::uint8_t u[sizeof(IntType)];
|
122 |
+
typename std::make_unsigned<IntType>::type v = value;
|
123 |
+
|
124 |
+
std::size_t i = 0;
|
125 |
+
// if constexpr to silence the warning about shift by 8
|
126 |
+
if constexpr (sizeof(IntType) > 1)
|
127 |
+
{
|
128 |
+
for (; i + 1 < sizeof(IntType); ++i)
|
129 |
+
{
|
130 |
+
u[i] = (std::uint8_t)v;
|
131 |
+
v >>= 8;
|
132 |
+
}
|
133 |
+
}
|
134 |
+
u[i] = (std::uint8_t)v;
|
135 |
+
|
136 |
+
stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
// read_little_endian(s, out, N) : read integers in bulk from a little indian stream.
|
141 |
+
// This reads N integers from stream s and put them in array out.
|
142 |
+
template <typename IntType>
|
143 |
+
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
|
144 |
+
if (IsLittleEndian)
|
145 |
+
stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
|
146 |
+
else
|
147 |
+
for (std::size_t i = 0; i < count; ++i)
|
148 |
+
out[i] = read_little_endian<IntType>(stream);
|
149 |
+
}
|
150 |
+
|
151 |
+
// write_little_endian(s, values, N) : write integers in bulk to a little indian stream.
|
152 |
+
// This takes N integers from array values and writes them on stream s.
|
153 |
+
template <typename IntType>
|
154 |
+
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
|
155 |
+
if (IsLittleEndian)
|
156 |
+
stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
|
157 |
+
else
|
158 |
+
for (std::size_t i = 0; i < count; ++i)
|
159 |
+
write_little_endian<IntType>(stream, values[i]);
|
160 |
+
}
|
161 |
+
|
162 |
+
} // namespace Stockfish::Eval::NNUE
|
163 |
+
|
164 |
+
#endif // #ifndef NNUE_COMMON_H_INCLUDED
|
src/nnue/nnue_feature_transformer.h
ADDED
@@ -0,0 +1,589 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
// A class that converts the input features of the NNUE evaluation function
|
20 |
+
|
21 |
+
#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
22 |
+
#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
23 |
+
|
24 |
+
#include "nnue_common.h"
|
25 |
+
#include "nnue_architecture.h"
|
26 |
+
|
27 |
+
#include <cstring> // std::memset()
|
28 |
+
|
29 |
+
namespace Stockfish::Eval::NNUE {
|
30 |
+
|
31 |
+
using BiasType = std::int16_t;
|
32 |
+
using WeightType = std::int16_t;
|
33 |
+
using PSQTWeightType = std::int32_t;
|
34 |
+
|
35 |
+
// If vector instructions are enabled, we update and refresh the
|
36 |
+
// accumulator tile by tile such that each tile fits in the CPU's
|
37 |
+
// vector registers.
|
38 |
+
#define VECTOR
|
39 |
+
|
40 |
+
static_assert(PSQTBuckets % 8 == 0,
|
41 |
+
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
|
42 |
+
|
43 |
+
#ifdef USE_AVX512
|
44 |
+
typedef __m512i vec_t;
|
45 |
+
typedef __m256i psqt_vec_t;
|
46 |
+
#define vec_load(a) _mm512_load_si512(a)
|
47 |
+
#define vec_store(a,b) _mm512_store_si512(a,b)
|
48 |
+
#define vec_add_16(a,b) _mm512_add_epi16(a,b)
|
49 |
+
#define vec_sub_16(a,b) _mm512_sub_epi16(a,b)
|
50 |
+
#define vec_mul_16(a,b) _mm512_mullo_epi16(a,b)
|
51 |
+
#define vec_zero() _mm512_setzero_epi32()
|
52 |
+
#define vec_set_16(a) _mm512_set1_epi16(a)
|
53 |
+
#define vec_max_16(a,b) _mm512_max_epi16(a,b)
|
54 |
+
#define vec_min_16(a,b) _mm512_min_epi16(a,b)
|
55 |
+
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
|
56 |
+
vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a,7),_mm512_srli_epi16(b,7));
|
57 |
+
return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted);
|
58 |
+
}
|
59 |
+
#define vec_load_psqt(a) _mm256_load_si256(a)
|
60 |
+
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
|
61 |
+
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
|
62 |
+
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
|
63 |
+
#define vec_zero_psqt() _mm256_setzero_si256()
|
64 |
+
#define NumRegistersSIMD 32
|
65 |
+
#define MaxChunkSize 64
|
66 |
+
|
67 |
+
#elif USE_AVX2
|
68 |
+
typedef __m256i vec_t;
|
69 |
+
typedef __m256i psqt_vec_t;
|
70 |
+
#define vec_load(a) _mm256_load_si256(a)
|
71 |
+
#define vec_store(a,b) _mm256_store_si256(a,b)
|
72 |
+
#define vec_add_16(a,b) _mm256_add_epi16(a,b)
|
73 |
+
#define vec_sub_16(a,b) _mm256_sub_epi16(a,b)
|
74 |
+
#define vec_mul_16(a,b) _mm256_mullo_epi16(a,b)
|
75 |
+
#define vec_zero() _mm256_setzero_si256()
|
76 |
+
#define vec_set_16(a) _mm256_set1_epi16(a)
|
77 |
+
#define vec_max_16(a,b) _mm256_max_epi16(a,b)
|
78 |
+
#define vec_min_16(a,b) _mm256_min_epi16(a,b)
|
79 |
+
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
|
80 |
+
vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a,7), _mm256_srli_epi16(b,7));
|
81 |
+
return _mm256_permute4x64_epi64(compacted, 0b11011000);
|
82 |
+
}
|
83 |
+
#define vec_load_psqt(a) _mm256_load_si256(a)
|
84 |
+
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
|
85 |
+
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
|
86 |
+
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
|
87 |
+
#define vec_zero_psqt() _mm256_setzero_si256()
|
88 |
+
#define NumRegistersSIMD 16
|
89 |
+
#define MaxChunkSize 32
|
90 |
+
|
91 |
+
#elif USE_SSE2
|
92 |
+
typedef __m128i vec_t;
|
93 |
+
typedef __m128i psqt_vec_t;
|
94 |
+
#define vec_load(a) (*(a))
|
95 |
+
#define vec_store(a,b) *(a)=(b)
|
96 |
+
#define vec_add_16(a,b) _mm_add_epi16(a,b)
|
97 |
+
#define vec_sub_16(a,b) _mm_sub_epi16(a,b)
|
98 |
+
#define vec_mul_16(a,b) _mm_mullo_epi16(a,b)
|
99 |
+
#define vec_zero() _mm_setzero_si128()
|
100 |
+
#define vec_set_16(a) _mm_set1_epi16(a)
|
101 |
+
#define vec_max_16(a,b) _mm_max_epi16(a,b)
|
102 |
+
#define vec_min_16(a,b) _mm_min_epi16(a,b)
|
103 |
+
#define vec_msb_pack_16(a,b) _mm_packs_epi16(_mm_srli_epi16(a,7),_mm_srli_epi16(b,7))
|
104 |
+
#define vec_load_psqt(a) (*(a))
|
105 |
+
#define vec_store_psqt(a,b) *(a)=(b)
|
106 |
+
#define vec_add_psqt_32(a,b) _mm_add_epi32(a,b)
|
107 |
+
#define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b)
|
108 |
+
#define vec_zero_psqt() _mm_setzero_si128()
|
109 |
+
#define NumRegistersSIMD (Is64Bit ? 16 : 8)
|
110 |
+
#define MaxChunkSize 16
|
111 |
+
|
112 |
+
#elif USE_MMX
|
113 |
+
typedef __m64 vec_t;
|
114 |
+
typedef __m64 psqt_vec_t;
|
115 |
+
#define vec_load(a) (*(a))
|
116 |
+
#define vec_store(a,b) *(a)=(b)
|
117 |
+
#define vec_add_16(a,b) _mm_add_pi16(a,b)
|
118 |
+
#define vec_sub_16(a,b) _mm_sub_pi16(a,b)
|
119 |
+
#define vec_mul_16(a,b) _mm_mullo_pi16(a,b)
|
120 |
+
#define vec_zero() _mm_setzero_si64()
|
121 |
+
#define vec_set_16(a) _mm_set1_pi16(a)
|
122 |
+
inline vec_t vec_max_16(vec_t a,vec_t b){
|
123 |
+
vec_t comparison = _mm_cmpgt_pi16(a,b);
|
124 |
+
return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b));
|
125 |
+
}
|
126 |
+
inline vec_t vec_min_16(vec_t a,vec_t b){
|
127 |
+
vec_t comparison = _mm_cmpgt_pi16(a,b);
|
128 |
+
return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a));
|
129 |
+
}
|
130 |
+
#define vec_msb_pack_16(a,b) _mm_packs_pi16(_mm_srli_pi16(a,7),_mm_srli_pi16(b,7))
|
131 |
+
#define vec_load_psqt(a) (*(a))
|
132 |
+
#define vec_store_psqt(a,b) *(a)=(b)
|
133 |
+
#define vec_add_psqt_32(a,b) _mm_add_pi32(a,b)
|
134 |
+
#define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b)
|
135 |
+
#define vec_zero_psqt() _mm_setzero_si64()
|
136 |
+
#define vec_cleanup() _mm_empty()
|
137 |
+
#define NumRegistersSIMD 8
|
138 |
+
#define MaxChunkSize 8
|
139 |
+
|
140 |
+
#elif USE_NEON
|
141 |
+
typedef int16x8_t vec_t;
|
142 |
+
typedef int32x4_t psqt_vec_t;
|
143 |
+
#define vec_load(a) (*(a))
|
144 |
+
#define vec_store(a,b) *(a)=(b)
|
145 |
+
#define vec_add_16(a,b) vaddq_s16(a,b)
|
146 |
+
#define vec_sub_16(a,b) vsubq_s16(a,b)
|
147 |
+
#define vec_mul_16(a,b) vmulq_s16(a,b)
|
148 |
+
#define vec_zero() vec_t{0}
|
149 |
+
#define vec_set_16(a) vdupq_n_s16(a)
|
150 |
+
#define vec_max_16(a,b) vmaxq_s16(a,b)
|
151 |
+
#define vec_min_16(a,b) vminq_s16(a,b)
|
152 |
+
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
|
153 |
+
const int8x8_t shifta = vshrn_n_s16(a, 7);
|
154 |
+
const int8x8_t shiftb = vshrn_n_s16(b, 7);
|
155 |
+
const int8x16_t compacted = vcombine_s8(shifta,shiftb);
|
156 |
+
return *reinterpret_cast<const vec_t*> (&compacted);
|
157 |
+
}
|
158 |
+
#define vec_load_psqt(a) (*(a))
|
159 |
+
#define vec_store_psqt(a,b) *(a)=(b)
|
160 |
+
#define vec_add_psqt_32(a,b) vaddq_s32(a,b)
|
161 |
+
#define vec_sub_psqt_32(a,b) vsubq_s32(a,b)
|
162 |
+
#define vec_zero_psqt() psqt_vec_t{0}
|
163 |
+
#define NumRegistersSIMD 16
|
164 |
+
#define MaxChunkSize 16
|
165 |
+
|
166 |
+
#else
|
167 |
+
#undef VECTOR
|
168 |
+
|
169 |
+
#endif
|
170 |
+
|
171 |
+
|
172 |
+
#ifdef VECTOR
|
173 |
+
|
174 |
+
// Compute optimal SIMD register count for feature transformer accumulation.
|
175 |
+
|
176 |
+
// We use __m* types as template arguments, which causes GCC to emit warnings
|
177 |
+
// about losing some attribute information. This is irrelevant to us as we
|
178 |
+
// only take their size, so the following pragma are harmless.
|
179 |
+
#if defined(__GNUC__)
|
180 |
+
#pragma GCC diagnostic push
|
181 |
+
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
182 |
+
#endif
|
183 |
+
|
184 |
+
template <typename SIMDRegisterType,
|
185 |
+
typename LaneType,
|
186 |
+
int NumLanes,
|
187 |
+
int MaxRegisters>
|
188 |
+
static constexpr int BestRegisterCount()
|
189 |
+
{
|
190 |
+
#define RegisterSize sizeof(SIMDRegisterType)
|
191 |
+
#define LaneSize sizeof(LaneType)
|
192 |
+
|
193 |
+
static_assert(RegisterSize >= LaneSize);
|
194 |
+
static_assert(MaxRegisters <= NumRegistersSIMD);
|
195 |
+
static_assert(MaxRegisters > 0);
|
196 |
+
static_assert(NumRegistersSIMD > 0);
|
197 |
+
static_assert(RegisterSize % LaneSize == 0);
|
198 |
+
static_assert((NumLanes * LaneSize) % RegisterSize == 0);
|
199 |
+
|
200 |
+
const int ideal = (NumLanes * LaneSize) / RegisterSize;
|
201 |
+
if (ideal <= MaxRegisters)
|
202 |
+
return ideal;
|
203 |
+
|
204 |
+
// Look for the largest divisor of the ideal register count that is smaller than MaxRegisters
|
205 |
+
for (int divisor = MaxRegisters; divisor > 1; --divisor)
|
206 |
+
if (ideal % divisor == 0)
|
207 |
+
return divisor;
|
208 |
+
|
209 |
+
return 1;
|
210 |
+
}
|
211 |
+
|
212 |
+
static constexpr int NumRegs = BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
|
213 |
+
static constexpr int NumPsqtRegs = BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
|
214 |
+
#if defined(__GNUC__)
|
215 |
+
#pragma GCC diagnostic pop
|
216 |
+
#endif
|
217 |
+
#endif
|
218 |
+
|
219 |
+
|
220 |
+
|
221 |
+
// Input feature converter
|
222 |
+
class FeatureTransformer {
|
223 |
+
|
224 |
+
private:
|
225 |
+
// Number of output dimensions for one side
|
226 |
+
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
227 |
+
|
228 |
+
#ifdef VECTOR
|
229 |
+
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
|
230 |
+
static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
|
231 |
+
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
|
232 |
+
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
|
233 |
+
#endif
|
234 |
+
|
235 |
+
public:
|
236 |
+
// Output type
|
237 |
+
using OutputType = TransformedFeatureType;
|
238 |
+
|
239 |
+
// Number of input/output dimensions
|
240 |
+
static constexpr IndexType InputDimensions = FeatureSet::Dimensions;
|
241 |
+
static constexpr IndexType OutputDimensions = HalfDimensions;
|
242 |
+
|
243 |
+
// Size of forward propagation buffer
|
244 |
+
static constexpr std::size_t BufferSize =
|
245 |
+
OutputDimensions * sizeof(OutputType);
|
246 |
+
|
247 |
+
// Hash value embedded in the evaluation file
|
248 |
+
static constexpr std::uint32_t get_hash_value() {
|
249 |
+
return FeatureSet::HashValue ^ (OutputDimensions * 2);
|
250 |
+
}
|
251 |
+
|
252 |
+
// Read network parameters
|
253 |
+
bool read_parameters(std::istream& stream) {
|
254 |
+
|
255 |
+
read_little_endian<BiasType >(stream, biases , HalfDimensions );
|
256 |
+
read_little_endian<WeightType >(stream, weights , HalfDimensions * InputDimensions);
|
257 |
+
read_little_endian<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
|
258 |
+
|
259 |
+
return !stream.fail();
|
260 |
+
}
|
261 |
+
|
262 |
+
// Write network parameters
|
263 |
+
bool write_parameters(std::ostream& stream) const {
|
264 |
+
|
265 |
+
write_little_endian<BiasType >(stream, biases , HalfDimensions );
|
266 |
+
write_little_endian<WeightType >(stream, weights , HalfDimensions * InputDimensions);
|
267 |
+
write_little_endian<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
|
268 |
+
|
269 |
+
return !stream.fail();
|
270 |
+
}
|
271 |
+
|
272 |
+
// Convert input features
|
273 |
+
std::int32_t transform(const Position& pos, OutputType* output, int bucket) const {
|
274 |
+
update_accumulator<WHITE>(pos);
|
275 |
+
update_accumulator<BLACK>(pos);
|
276 |
+
|
277 |
+
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
278 |
+
const auto& accumulation = pos.state()->accumulator.accumulation;
|
279 |
+
const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation;
|
280 |
+
|
281 |
+
const auto psqt = (
|
282 |
+
psqtAccumulation[perspectives[0]][bucket]
|
283 |
+
- psqtAccumulation[perspectives[1]][bucket]
|
284 |
+
) / 2;
|
285 |
+
|
286 |
+
|
287 |
+
for (IndexType p = 0; p < 2; ++p)
|
288 |
+
{
|
289 |
+
const IndexType offset = (HalfDimensions / 2) * p;
|
290 |
+
|
291 |
+
#if defined(VECTOR)
|
292 |
+
|
293 |
+
constexpr IndexType OutputChunkSize = MaxChunkSize;
|
294 |
+
static_assert((HalfDimensions / 2) % OutputChunkSize == 0);
|
295 |
+
constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize;
|
296 |
+
|
297 |
+
vec_t Zero = vec_zero();
|
298 |
+
vec_t One = vec_set_16(127);
|
299 |
+
|
300 |
+
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
|
301 |
+
const vec_t* in1 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));
|
302 |
+
vec_t* out = reinterpret_cast< vec_t*>(output + offset);
|
303 |
+
|
304 |
+
for (IndexType j = 0; j < NumOutputChunks; j += 1)
|
305 |
+
{
|
306 |
+
const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero);
|
307 |
+
const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero);
|
308 |
+
const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero);
|
309 |
+
const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero);
|
310 |
+
|
311 |
+
const vec_t pa = vec_mul_16(sum0a, sum1a);
|
312 |
+
const vec_t pb = vec_mul_16(sum0b, sum1b);
|
313 |
+
|
314 |
+
out[j] = vec_msb_pack_16(pa, pb);
|
315 |
+
}
|
316 |
+
|
317 |
+
#else
|
318 |
+
|
319 |
+
for (IndexType j = 0; j < HalfDimensions / 2; ++j) {
|
320 |
+
BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
|
321 |
+
BiasType sum1 = accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
|
322 |
+
sum0 = std::max<int>(0, std::min<int>(127, sum0));
|
323 |
+
sum1 = std::max<int>(0, std::min<int>(127, sum1));
|
324 |
+
output[offset + j] = static_cast<OutputType>(sum0 * sum1 / 128);
|
325 |
+
}
|
326 |
+
|
327 |
+
#endif
|
328 |
+
}
|
329 |
+
|
330 |
+
#if defined(vec_cleanup)
|
331 |
+
vec_cleanup();
|
332 |
+
#endif
|
333 |
+
|
334 |
+
return psqt;
|
335 |
+
|
336 |
+
} // end of function transform()
|
337 |
+
|
338 |
+
|
339 |
+
|
340 |
+
private:
|
341 |
+
template<Color Perspective>
|
342 |
+
void update_accumulator(const Position& pos) const {
|
343 |
+
|
344 |
+
// The size must be enough to contain the largest possible update.
|
345 |
+
// That might depend on the feature set and generally relies on the
|
346 |
+
// feature set's update cost calculation to be correct and never
|
347 |
+
// allow updates with more added/removed features than MaxActiveDimensions.
|
348 |
+
|
349 |
+
#ifdef VECTOR
|
350 |
+
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
|
351 |
+
// is defined in the VECTOR code below, once in each branch
|
352 |
+
vec_t acc[NumRegs];
|
353 |
+
psqt_vec_t psqt[NumPsqtRegs];
|
354 |
+
#endif
|
355 |
+
|
356 |
+
// Look for a usable accumulator of an earlier position. We keep track
|
357 |
+
// of the estimated gain in terms of features to be added/subtracted.
|
358 |
+
StateInfo *st = pos.state(), *next = nullptr;
|
359 |
+
int gain = FeatureSet::refresh_cost(pos);
|
360 |
+
while (st->previous && !st->accumulator.computed[Perspective])
|
361 |
+
{
|
362 |
+
// This governs when a full feature refresh is needed and how many
|
363 |
+
// updates are better than just one full refresh.
|
364 |
+
if ( FeatureSet::requires_refresh(st, Perspective)
|
365 |
+
|| (gain -= FeatureSet::update_cost(st) + 1) < 0)
|
366 |
+
break;
|
367 |
+
next = st;
|
368 |
+
st = st->previous;
|
369 |
+
}
|
370 |
+
|
371 |
+
if (st->accumulator.computed[Perspective])
|
372 |
+
{
|
373 |
+
if (next == nullptr)
|
374 |
+
return;
|
375 |
+
|
376 |
+
// Update incrementally in two steps. First, we update the "next"
|
377 |
+
// accumulator. Then, we update the current accumulator (pos.state()).
|
378 |
+
|
379 |
+
// Gather all features to be updated.
|
380 |
+
const Square ksq = pos.square<KING>(Perspective);
|
381 |
+
FeatureSet::IndexList removed[2], added[2];
|
382 |
+
FeatureSet::append_changed_indices<Perspective>(
|
383 |
+
ksq, next->dirtyPiece, removed[0], added[0]);
|
384 |
+
for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous)
|
385 |
+
FeatureSet::append_changed_indices<Perspective>(
|
386 |
+
ksq, st2->dirtyPiece, removed[1], added[1]);
|
387 |
+
|
388 |
+
// Mark the accumulators as computed.
|
389 |
+
next->accumulator.computed[Perspective] = true;
|
390 |
+
pos.state()->accumulator.computed[Perspective] = true;
|
391 |
+
|
392 |
+
// Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
|
393 |
+
StateInfo *states_to_update[3] =
|
394 |
+
{ next, next == pos.state() ? nullptr : pos.state(), nullptr };
|
395 |
+
#ifdef VECTOR
|
396 |
+
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
397 |
+
{
|
398 |
+
// Load accumulator
|
399 |
+
auto accTile = reinterpret_cast<vec_t*>(
|
400 |
+
&st->accumulator.accumulation[Perspective][j * TileHeight]);
|
401 |
+
for (IndexType k = 0; k < NumRegs; ++k)
|
402 |
+
acc[k] = vec_load(&accTile[k]);
|
403 |
+
|
404 |
+
for (IndexType i = 0; states_to_update[i]; ++i)
|
405 |
+
{
|
406 |
+
// Difference calculation for the deactivated features
|
407 |
+
for (const auto index : removed[i])
|
408 |
+
{
|
409 |
+
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
410 |
+
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
411 |
+
for (IndexType k = 0; k < NumRegs; ++k)
|
412 |
+
acc[k] = vec_sub_16(acc[k], column[k]);
|
413 |
+
}
|
414 |
+
|
415 |
+
// Difference calculation for the activated features
|
416 |
+
for (const auto index : added[i])
|
417 |
+
{
|
418 |
+
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
419 |
+
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
420 |
+
for (IndexType k = 0; k < NumRegs; ++k)
|
421 |
+
acc[k] = vec_add_16(acc[k], column[k]);
|
422 |
+
}
|
423 |
+
|
424 |
+
// Store accumulator
|
425 |
+
accTile = reinterpret_cast<vec_t*>(
|
426 |
+
&states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]);
|
427 |
+
for (IndexType k = 0; k < NumRegs; ++k)
|
428 |
+
vec_store(&accTile[k], acc[k]);
|
429 |
+
}
|
430 |
+
}
|
431 |
+
|
432 |
+
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
|
433 |
+
{
|
434 |
+
// Load accumulator
|
435 |
+
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
436 |
+
&st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
|
437 |
+
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
438 |
+
psqt[k] = vec_load_psqt(&accTilePsqt[k]);
|
439 |
+
|
440 |
+
for (IndexType i = 0; states_to_update[i]; ++i)
|
441 |
+
{
|
442 |
+
// Difference calculation for the deactivated features
|
443 |
+
for (const auto index : removed[i])
|
444 |
+
{
|
445 |
+
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
|
446 |
+
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
447 |
+
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
448 |
+
psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);
|
449 |
+
}
|
450 |
+
|
451 |
+
// Difference calculation for the activated features
|
452 |
+
for (const auto index : added[i])
|
453 |
+
{
|
454 |
+
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
|
455 |
+
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
456 |
+
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
457 |
+
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
|
458 |
+
}
|
459 |
+
|
460 |
+
// Store accumulator
|
461 |
+
accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
462 |
+
&states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
|
463 |
+
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
464 |
+
vec_store_psqt(&accTilePsqt[k], psqt[k]);
|
465 |
+
}
|
466 |
+
}
|
467 |
+
|
468 |
+
#else
|
469 |
+
for (IndexType i = 0; states_to_update[i]; ++i)
|
470 |
+
{
|
471 |
+
std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective],
|
472 |
+
st->accumulator.accumulation[Perspective],
|
473 |
+
HalfDimensions * sizeof(BiasType));
|
474 |
+
|
475 |
+
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
476 |
+
states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k];
|
477 |
+
|
478 |
+
st = states_to_update[i];
|
479 |
+
|
480 |
+
// Difference calculation for the deactivated features
|
481 |
+
for (const auto index : removed[i])
|
482 |
+
{
|
483 |
+
const IndexType offset = HalfDimensions * index;
|
484 |
+
|
485 |
+
for (IndexType j = 0; j < HalfDimensions; ++j)
|
486 |
+
st->accumulator.accumulation[Perspective][j] -= weights[offset + j];
|
487 |
+
|
488 |
+
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
489 |
+
st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k];
|
490 |
+
}
|
491 |
+
|
492 |
+
// Difference calculation for the activated features
|
493 |
+
for (const auto index : added[i])
|
494 |
+
{
|
495 |
+
const IndexType offset = HalfDimensions * index;
|
496 |
+
|
497 |
+
for (IndexType j = 0; j < HalfDimensions; ++j)
|
498 |
+
st->accumulator.accumulation[Perspective][j] += weights[offset + j];
|
499 |
+
|
500 |
+
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
501 |
+
st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
|
502 |
+
}
|
503 |
+
}
|
504 |
+
#endif
|
505 |
+
}
|
506 |
+
else
|
507 |
+
{
|
508 |
+
// Refresh the accumulator
|
509 |
+
auto& accumulator = pos.state()->accumulator;
|
510 |
+
accumulator.computed[Perspective] = true;
|
511 |
+
FeatureSet::IndexList active;
|
512 |
+
FeatureSet::append_active_indices<Perspective>(pos, active);
|
513 |
+
|
514 |
+
#ifdef VECTOR
|
515 |
+
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
516 |
+
{
|
517 |
+
auto biasesTile = reinterpret_cast<const vec_t*>(
|
518 |
+
&biases[j * TileHeight]);
|
519 |
+
for (IndexType k = 0; k < NumRegs; ++k)
|
520 |
+
acc[k] = biasesTile[k];
|
521 |
+
|
522 |
+
for (const auto index : active)
|
523 |
+
{
|
524 |
+
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
525 |
+
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
526 |
+
|
527 |
+
for (unsigned k = 0; k < NumRegs; ++k)
|
528 |
+
acc[k] = vec_add_16(acc[k], column[k]);
|
529 |
+
}
|
530 |
+
|
531 |
+
auto accTile = reinterpret_cast<vec_t*>(
|
532 |
+
&accumulator.accumulation[Perspective][j * TileHeight]);
|
533 |
+
for (unsigned k = 0; k < NumRegs; k++)
|
534 |
+
vec_store(&accTile[k], acc[k]);
|
535 |
+
}
|
536 |
+
|
537 |
+
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
|
538 |
+
{
|
539 |
+
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
540 |
+
psqt[k] = vec_zero_psqt();
|
541 |
+
|
542 |
+
for (const auto index : active)
|
543 |
+
{
|
544 |
+
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
|
545 |
+
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
546 |
+
|
547 |
+
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
548 |
+
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
|
549 |
+
}
|
550 |
+
|
551 |
+
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
552 |
+
&accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
|
553 |
+
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
554 |
+
vec_store_psqt(&accTilePsqt[k], psqt[k]);
|
555 |
+
}
|
556 |
+
|
557 |
+
#else
|
558 |
+
std::memcpy(accumulator.accumulation[Perspective], biases,
|
559 |
+
HalfDimensions * sizeof(BiasType));
|
560 |
+
|
561 |
+
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
562 |
+
accumulator.psqtAccumulation[Perspective][k] = 0;
|
563 |
+
|
564 |
+
for (const auto index : active)
|
565 |
+
{
|
566 |
+
const IndexType offset = HalfDimensions * index;
|
567 |
+
|
568 |
+
for (IndexType j = 0; j < HalfDimensions; ++j)
|
569 |
+
accumulator.accumulation[Perspective][j] += weights[offset + j];
|
570 |
+
|
571 |
+
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
572 |
+
accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
|
573 |
+
}
|
574 |
+
#endif
|
575 |
+
}
|
576 |
+
|
577 |
+
#if defined(USE_MMX)
|
578 |
+
_mm_empty();
|
579 |
+
#endif
|
580 |
+
}
|
581 |
+
|
582 |
+
alignas(CacheLineSize) BiasType biases[HalfDimensions];
|
583 |
+
alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
|
584 |
+
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
|
585 |
+
};
|
586 |
+
|
587 |
+
} // namespace Stockfish::Eval::NNUE
|
588 |
+
|
589 |
+
#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
src/pawns.cpp
ADDED
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <algorithm>
|
20 |
+
#include <cassert>
|
21 |
+
|
22 |
+
#include "bitboard.h"
|
23 |
+
#include "pawns.h"
|
24 |
+
#include "position.h"
|
25 |
+
#include "thread.h"
|
26 |
+
|
27 |
+
namespace Stockfish {
|
28 |
+
|
29 |
+
namespace {
|
30 |
+
|
31 |
+
#define V Value
|
32 |
+
#define S(mg, eg) make_score(mg, eg)
|
33 |
+
|
34 |
+
// Pawn penalties
|
35 |
+
constexpr Score Backward = S( 6, 19);
|
36 |
+
constexpr Score Doubled = S(11, 51);
|
37 |
+
constexpr Score DoubledEarly = S(17, 7);
|
38 |
+
constexpr Score Isolated = S( 1, 20);
|
39 |
+
constexpr Score WeakLever = S( 2, 57);
|
40 |
+
constexpr Score WeakUnopposed = S(15, 18);
|
41 |
+
|
42 |
+
// Bonus for blocked pawns at 5th or 6th rank
|
43 |
+
constexpr Score BlockedPawn[2] = { S(-19, -8), S(-7, 3) };
|
44 |
+
|
45 |
+
constexpr Score BlockedStorm[RANK_NB] = {
|
46 |
+
S(0, 0), S(0, 0), S(64, 75), S(-3, 14), S(-12, 19), S(-7, 4), S(-10, 5)
|
47 |
+
};
|
48 |
+
|
49 |
+
// Connected pawn bonus
|
50 |
+
constexpr int Connected[RANK_NB] = { 0, 3, 7, 7, 15, 54, 86 };
|
51 |
+
|
52 |
+
// Strength of pawn shelter for our king by [distance from edge][rank].
|
53 |
+
// RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king.
|
54 |
+
constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = {
|
55 |
+
{ V(-2), V(85), V(95), V(53), V(39), V(23), V(25) },
|
56 |
+
{ V(-55), V(64), V(32), V(-55), V(-30), V(-11), V(-61) },
|
57 |
+
{ V(-11), V(75), V(19), V(-6), V(26), V(9), V(-47) },
|
58 |
+
{ V(-41), V(-11), V(-27), V(-58), V(-42), V(-66), V(-163) }
|
59 |
+
};
|
60 |
+
|
61 |
+
// Danger of enemy pawns moving toward our king by [distance from edge][rank].
|
62 |
+
// RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn
|
63 |
+
// is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn
|
64 |
+
// on edge, likely blocked by our king.
|
65 |
+
constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = {
|
66 |
+
{ V(94), V(-280), V(-170), V(90), V(59), V(47), V(53) },
|
67 |
+
{ V(43), V(-17), V(128), V(39), V(26), V(-17), V(15) },
|
68 |
+
{ V(-9), V(62), V(170), V(34), V(-5), V(-20), V(-11) },
|
69 |
+
{ V(-27), V(-19), V(106), V(10), V(2), V(-13), V(-24) }
|
70 |
+
};
|
71 |
+
|
72 |
+
|
73 |
+
// KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties
|
74 |
+
// for king when the king is on a semi-open or open file.
|
75 |
+
constexpr Score KingOnFile[2][2] = {{ S(-18,11), S(-6,-3) },
|
76 |
+
{ S( 0, 0), S( 5,-4) }};
|
77 |
+
|
78 |
+
#undef S
|
79 |
+
#undef V
|
80 |
+
|
81 |
+
|
82 |
+
/// evaluate() calculates a score for the static pawn structure of the given position.
|
83 |
+
/// We cannot use the location of pieces or king in this function, as the evaluation
|
84 |
+
/// of the pawn structure will be stored in a small cache for speed reasons, and will
|
85 |
+
/// be re-used even when the pieces have moved.
|
86 |
+
|
87 |
+
template<Color Us>
|
88 |
+
Score evaluate(const Position& pos, Pawns::Entry* e) {
|
89 |
+
|
90 |
+
constexpr Color Them = ~Us;
|
91 |
+
constexpr Direction Up = pawn_push(Us);
|
92 |
+
constexpr Direction Down = -Up;
|
93 |
+
|
94 |
+
Bitboard neighbours, stoppers, support, phalanx, opposed;
|
95 |
+
Bitboard lever, leverPush, blocked;
|
96 |
+
Square s;
|
97 |
+
bool backward, passed, doubled;
|
98 |
+
Score score = SCORE_ZERO;
|
99 |
+
Bitboard b = pos.pieces(Us, PAWN);
|
100 |
+
|
101 |
+
Bitboard ourPawns = pos.pieces( Us, PAWN);
|
102 |
+
Bitboard theirPawns = pos.pieces(Them, PAWN);
|
103 |
+
|
104 |
+
Bitboard doubleAttackThem = pawn_double_attacks_bb<Them>(theirPawns);
|
105 |
+
|
106 |
+
e->passedPawns[Us] = 0;
|
107 |
+
e->kingSquares[Us] = SQ_NONE;
|
108 |
+
e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
|
109 |
+
e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
|
110 |
+
|
111 |
+
// Loop through all pawns of the current color and score each pawn
|
112 |
+
while (b)
|
113 |
+
{
|
114 |
+
s = pop_lsb(b);
|
115 |
+
|
116 |
+
assert(pos.piece_on(s) == make_piece(Us, PAWN));
|
117 |
+
|
118 |
+
Rank r = relative_rank(Us, s);
|
119 |
+
|
120 |
+
// Flag the pawn
|
121 |
+
opposed = theirPawns & forward_file_bb(Us, s);
|
122 |
+
blocked = theirPawns & (s + Up);
|
123 |
+
stoppers = theirPawns & passed_pawn_span(Us, s);
|
124 |
+
lever = theirPawns & pawn_attacks_bb(Us, s);
|
125 |
+
leverPush = theirPawns & pawn_attacks_bb(Us, s + Up);
|
126 |
+
doubled = ourPawns & (s - Up);
|
127 |
+
neighbours = ourPawns & adjacent_files_bb(s);
|
128 |
+
phalanx = neighbours & rank_bb(s);
|
129 |
+
support = neighbours & rank_bb(s - Up);
|
130 |
+
|
131 |
+
if (doubled)
|
132 |
+
{
|
133 |
+
// Additional doubled penalty if none of their pawns is fixed
|
134 |
+
if (!(ourPawns & shift<Down>(theirPawns | pawn_attacks_bb<Them>(theirPawns))))
|
135 |
+
score -= DoubledEarly;
|
136 |
+
}
|
137 |
+
|
138 |
+
// A pawn is backward when it is behind all pawns of the same color on
|
139 |
+
// the adjacent files and cannot safely advance.
|
140 |
+
backward = !(neighbours & forward_ranks_bb(Them, s + Up))
|
141 |
+
&& (leverPush | blocked);
|
142 |
+
|
143 |
+
// Compute additional span if pawn is not backward nor blocked
|
144 |
+
if (!backward && !blocked)
|
145 |
+
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
|
146 |
+
|
147 |
+
// A pawn is passed if one of the three following conditions is true:
|
148 |
+
// (a) there is no stoppers except some levers
|
149 |
+
// (b) the only stoppers are the leverPush, but we outnumber them
|
150 |
+
// (c) there is only one front stopper which can be levered.
|
151 |
+
// (Refined in Evaluation::passed)
|
152 |
+
passed = !(stoppers ^ lever)
|
153 |
+
|| ( !(stoppers ^ leverPush)
|
154 |
+
&& popcount(phalanx) >= popcount(leverPush))
|
155 |
+
|| ( stoppers == blocked && r >= RANK_5
|
156 |
+
&& (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
|
157 |
+
|
158 |
+
passed &= !(forward_file_bb(Us, s) & ourPawns);
|
159 |
+
|
160 |
+
// Passed pawns will be properly scored later in evaluation when we have
|
161 |
+
// full attack info.
|
162 |
+
if (passed)
|
163 |
+
e->passedPawns[Us] |= s;
|
164 |
+
|
165 |
+
// Score this pawn
|
166 |
+
if (support | phalanx)
|
167 |
+
{
|
168 |
+
int v = Connected[r] * (2 + bool(phalanx) - bool(opposed))
|
169 |
+
+ 22 * popcount(support);
|
170 |
+
|
171 |
+
score += make_score(v, v * (r - 2) / 4);
|
172 |
+
}
|
173 |
+
|
174 |
+
else if (!neighbours)
|
175 |
+
{
|
176 |
+
if ( opposed
|
177 |
+
&& (ourPawns & forward_file_bb(Them, s))
|
178 |
+
&& !(theirPawns & adjacent_files_bb(s)))
|
179 |
+
score -= Doubled;
|
180 |
+
else
|
181 |
+
score -= Isolated
|
182 |
+
+ WeakUnopposed * !opposed;
|
183 |
+
}
|
184 |
+
|
185 |
+
else if (backward)
|
186 |
+
score -= Backward
|
187 |
+
+ WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s);
|
188 |
+
|
189 |
+
if (!support)
|
190 |
+
score -= Doubled * doubled
|
191 |
+
+ WeakLever * more_than_one(lever);
|
192 |
+
|
193 |
+
if (blocked && r >= RANK_5)
|
194 |
+
score += BlockedPawn[r - RANK_5];
|
195 |
+
}
|
196 |
+
|
197 |
+
return score;
|
198 |
+
}
|
199 |
+
|
200 |
+
} // namespace
|
201 |
+
|
202 |
+
namespace Pawns {
|
203 |
+
|
204 |
+
|
205 |
+
/// Pawns::probe() looks up the current position's pawns configuration in
|
206 |
+
/// the pawns hash table. It returns a pointer to the Entry if the position
|
207 |
+
/// is found. Otherwise a new Entry is computed and stored there, so we don't
|
208 |
+
/// have to recompute all when the same pawns configuration occurs again.
|
209 |
+
|
210 |
+
Entry* probe(const Position& pos) {
|
211 |
+
|
212 |
+
Key key = pos.pawn_key();
|
213 |
+
Entry* e = pos.this_thread()->pawnsTable[key];
|
214 |
+
|
215 |
+
if (e->key == key)
|
216 |
+
return e;
|
217 |
+
|
218 |
+
e->key = key;
|
219 |
+
e->blockedCount = 0;
|
220 |
+
e->scores[WHITE] = evaluate<WHITE>(pos, e);
|
221 |
+
e->scores[BLACK] = evaluate<BLACK>(pos, e);
|
222 |
+
|
223 |
+
return e;
|
224 |
+
}
|
225 |
+
|
226 |
+
|
227 |
+
/// Entry::evaluate_shelter() calculates the shelter bonus and the storm
|
228 |
+
/// penalty for a king, looking at the king file and the two closest files.
|
229 |
+
|
230 |
+
template<Color Us>
|
231 |
+
Score Entry::evaluate_shelter(const Position& pos, Square ksq) const {
|
232 |
+
|
233 |
+
constexpr Color Them = ~Us;
|
234 |
+
|
235 |
+
Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
|
236 |
+
Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them];
|
237 |
+
Bitboard theirPawns = b & pos.pieces(Them);
|
238 |
+
|
239 |
+
Score bonus = make_score(5, 5);
|
240 |
+
|
241 |
+
File center = std::clamp(file_of(ksq), FILE_B, FILE_G);
|
242 |
+
for (File f = File(center - 1); f <= File(center + 1); ++f)
|
243 |
+
{
|
244 |
+
b = ourPawns & file_bb(f);
|
245 |
+
int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
|
246 |
+
|
247 |
+
b = theirPawns & file_bb(f);
|
248 |
+
int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
|
249 |
+
|
250 |
+
int d = edge_distance(f);
|
251 |
+
bonus += make_score(ShelterStrength[d][ourRank], 0);
|
252 |
+
|
253 |
+
if (ourRank && (ourRank == theirRank - 1))
|
254 |
+
bonus -= BlockedStorm[theirRank];
|
255 |
+
else
|
256 |
+
bonus -= make_score(UnblockedStorm[d][theirRank], 0);
|
257 |
+
}
|
258 |
+
|
259 |
+
// King On File
|
260 |
+
bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)];
|
261 |
+
|
262 |
+
return bonus;
|
263 |
+
}
|
264 |
+
|
265 |
+
|
266 |
+
/// Entry::do_king_safety() calculates a bonus for king safety. It is called only
|
267 |
+
/// when king square changes, which is about 20% of total king_safety() calls.
|
268 |
+
|
269 |
+
template<Color Us>
|
270 |
+
Score Entry::do_king_safety(const Position& pos) {
|
271 |
+
|
272 |
+
Square ksq = pos.square<KING>(Us);
|
273 |
+
kingSquares[Us] = ksq;
|
274 |
+
castlingRights[Us] = pos.castling_rights(Us);
|
275 |
+
auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); };
|
276 |
+
|
277 |
+
Score shelter = evaluate_shelter<Us>(pos, ksq);
|
278 |
+
|
279 |
+
// If we can castle use the bonus after castling if it is bigger
|
280 |
+
|
281 |
+
if (pos.can_castle(Us & KING_SIDE))
|
282 |
+
shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_G1)), compare);
|
283 |
+
|
284 |
+
if (pos.can_castle(Us & QUEEN_SIDE))
|
285 |
+
shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_C1)), compare);
|
286 |
+
|
287 |
+
// In endgame we like to bring our king near our closest pawn
|
288 |
+
Bitboard pawns = pos.pieces(Us, PAWN);
|
289 |
+
int minPawnDist = 6;
|
290 |
+
|
291 |
+
if (pawns & attacks_bb<KING>(ksq))
|
292 |
+
minPawnDist = 1;
|
293 |
+
else while (pawns)
|
294 |
+
minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns)));
|
295 |
+
|
296 |
+
return shelter - make_score(0, 16 * minPawnDist);
|
297 |
+
}
|
298 |
+
|
299 |
+
// Explicit template instantiation
|
300 |
+
template Score Entry::do_king_safety<WHITE>(const Position& pos);
|
301 |
+
template Score Entry::do_king_safety<BLACK>(const Position& pos);
|
302 |
+
|
303 |
+
} // namespace Pawns
|
304 |
+
|
305 |
+
} // namespace Stockfish
|
src/pawns.h
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef PAWNS_H_INCLUDED
|
20 |
+
#define PAWNS_H_INCLUDED
|
21 |
+
|
22 |
+
#include "misc.h"
|
23 |
+
#include "position.h"
|
24 |
+
#include "types.h"
|
25 |
+
|
26 |
+
namespace Stockfish::Pawns {
|
27 |
+
|
28 |
+
/// Pawns::Entry contains various information about a pawn structure. A lookup
|
29 |
+
/// to the pawn hash table (performed by calling the probe function) returns a
|
30 |
+
/// pointer to an Entry object.
|
31 |
+
|
32 |
+
struct Entry {
|
33 |
+
|
34 |
+
Score pawn_score(Color c) const { return scores[c]; }
|
35 |
+
Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
|
36 |
+
Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
|
37 |
+
Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; }
|
38 |
+
int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); }
|
39 |
+
int blocked_count() const { return blockedCount; }
|
40 |
+
|
41 |
+
template<Color Us>
|
42 |
+
Score king_safety(const Position& pos) {
|
43 |
+
return kingSquares[Us] == pos.square<KING>(Us) && castlingRights[Us] == pos.castling_rights(Us)
|
44 |
+
? kingSafety[Us] : (kingSafety[Us] = do_king_safety<Us>(pos));
|
45 |
+
}
|
46 |
+
|
47 |
+
template<Color Us>
|
48 |
+
Score do_king_safety(const Position& pos);
|
49 |
+
|
50 |
+
template<Color Us>
|
51 |
+
Score evaluate_shelter(const Position& pos, Square ksq) const;
|
52 |
+
|
53 |
+
Key key;
|
54 |
+
Score scores[COLOR_NB];
|
55 |
+
Bitboard passedPawns[COLOR_NB];
|
56 |
+
Bitboard pawnAttacks[COLOR_NB];
|
57 |
+
Bitboard pawnAttacksSpan[COLOR_NB];
|
58 |
+
Square kingSquares[COLOR_NB];
|
59 |
+
Score kingSafety[COLOR_NB];
|
60 |
+
int castlingRights[COLOR_NB];
|
61 |
+
int blockedCount;
|
62 |
+
};
|
63 |
+
|
64 |
+
typedef HashTable<Entry, 131072> Table;
|
65 |
+
|
66 |
+
Entry* probe(const Position& pos);
|
67 |
+
|
68 |
+
} // namespace Stockfish::Pawns
|
69 |
+
|
70 |
+
#endif // #ifndef PAWNS_H_INCLUDED
|
src/position.cpp
ADDED
@@ -0,0 +1,1353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <algorithm>
|
20 |
+
#include <cassert>
|
21 |
+
#include <cstddef> // For offsetof()
|
22 |
+
#include <cstring> // For std::memset, std::memcmp
|
23 |
+
#include <iomanip>
|
24 |
+
#include <sstream>
|
25 |
+
|
26 |
+
#include "bitboard.h"
|
27 |
+
#include "misc.h"
|
28 |
+
#include "movegen.h"
|
29 |
+
#include "position.h"
|
30 |
+
#include "thread.h"
|
31 |
+
#include "tt.h"
|
32 |
+
#include "uci.h"
|
33 |
+
#include "syzygy/tbprobe.h"
|
34 |
+
|
35 |
+
using std::string;
|
36 |
+
|
37 |
+
namespace Stockfish {
|
38 |
+
|
39 |
+
namespace Zobrist {
|
40 |
+
|
41 |
+
Key psq[PIECE_NB][SQUARE_NB];
|
42 |
+
Key enpassant[FILE_NB];
|
43 |
+
Key castling[CASTLING_RIGHT_NB];
|
44 |
+
Key side, noPawns;
|
45 |
+
}
|
46 |
+
|
47 |
+
namespace {
|
48 |
+
|
49 |
+
const string PieceToChar(" PNBRQK pnbrqk");
|
50 |
+
|
51 |
+
constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
|
52 |
+
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING };
|
53 |
+
} // namespace
|
54 |
+
|
55 |
+
|
56 |
+
/// operator<<(Position) returns an ASCII representation of the position
|
57 |
+
|
58 |
+
std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
59 |
+
|
60 |
+
os << "\n +---+---+---+---+---+---+---+---+\n";
|
61 |
+
|
62 |
+
for (Rank r = RANK_8; r >= RANK_1; --r)
|
63 |
+
{
|
64 |
+
for (File f = FILE_A; f <= FILE_H; ++f)
|
65 |
+
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
|
66 |
+
|
67 |
+
os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
|
68 |
+
}
|
69 |
+
|
70 |
+
os << " a b c d e f g h\n"
|
71 |
+
<< "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
|
72 |
+
<< std::setfill('0') << std::setw(16) << pos.key()
|
73 |
+
<< std::setfill(' ') << std::dec << "\nCheckers: ";
|
74 |
+
|
75 |
+
for (Bitboard b = pos.checkers(); b; )
|
76 |
+
os << UCI::square(pop_lsb(b)) << " ";
|
77 |
+
|
78 |
+
if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
|
79 |
+
&& !pos.can_castle(ANY_CASTLING))
|
80 |
+
{
|
81 |
+
StateInfo st;
|
82 |
+
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
83 |
+
|
84 |
+
Position p;
|
85 |
+
p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread());
|
86 |
+
Tablebases::ProbeState s1, s2;
|
87 |
+
Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1);
|
88 |
+
int dtz = Tablebases::probe_dtz(p, &s2);
|
89 |
+
os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")"
|
90 |
+
<< "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")";
|
91 |
+
}
|
92 |
+
|
93 |
+
return os;
|
94 |
+
}
|
95 |
+
|
96 |
+
|
97 |
+
// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition"
|
98 |
+
// situations. Description of the algorithm in the following paper:
|
99 |
+
// https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
|
100 |
+
|
101 |
+
// First and second hash functions for indexing the cuckoo tables
|
102 |
+
inline int H1(Key h) { return h & 0x1fff; }
|
103 |
+
inline int H2(Key h) { return (h >> 16) & 0x1fff; }
|
104 |
+
|
105 |
+
// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves
|
106 |
+
Key cuckoo[8192];
|
107 |
+
Move cuckooMove[8192];
|
108 |
+
|
109 |
+
|
110 |
+
/// Position::init() initializes at startup the various arrays used to compute hash keys
|
111 |
+
|
112 |
+
void Position::init() {
|
113 |
+
|
114 |
+
PRNG rng(1070372);
|
115 |
+
|
116 |
+
for (Piece pc : Pieces)
|
117 |
+
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
118 |
+
Zobrist::psq[pc][s] = rng.rand<Key>();
|
119 |
+
|
120 |
+
for (File f = FILE_A; f <= FILE_H; ++f)
|
121 |
+
Zobrist::enpassant[f] = rng.rand<Key>();
|
122 |
+
|
123 |
+
for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
|
124 |
+
Zobrist::castling[cr] = rng.rand<Key>();
|
125 |
+
|
126 |
+
Zobrist::side = rng.rand<Key>();
|
127 |
+
Zobrist::noPawns = rng.rand<Key>();
|
128 |
+
|
129 |
+
// Prepare the cuckoo tables
|
130 |
+
std::memset(cuckoo, 0, sizeof(cuckoo));
|
131 |
+
std::memset(cuckooMove, 0, sizeof(cuckooMove));
|
132 |
+
[[maybe_unused]] int count = 0;
|
133 |
+
for (Piece pc : Pieces)
|
134 |
+
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
135 |
+
for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
|
136 |
+
if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2))
|
137 |
+
{
|
138 |
+
Move move = make_move(s1, s2);
|
139 |
+
Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side;
|
140 |
+
int i = H1(key);
|
141 |
+
while (true)
|
142 |
+
{
|
143 |
+
std::swap(cuckoo[i], key);
|
144 |
+
std::swap(cuckooMove[i], move);
|
145 |
+
if (move == MOVE_NONE) // Arrived at empty slot?
|
146 |
+
break;
|
147 |
+
i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot
|
148 |
+
}
|
149 |
+
count++;
|
150 |
+
}
|
151 |
+
assert(count == 3668);
|
152 |
+
}
|
153 |
+
|
154 |
+
|
155 |
+
/// Position::set() initializes the position object with the given FEN string.
|
156 |
+
/// This function is not very robust - make sure that input FENs are correct,
|
157 |
+
/// this is assumed to be the responsibility of the GUI.
|
158 |
+
|
159 |
+
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
|
160 |
+
/*
|
161 |
+
A FEN string defines a particular position using only the ASCII character set.
|
162 |
+
|
163 |
+
A FEN string contains six fields separated by a space. The fields are:
|
164 |
+
|
165 |
+
1) Piece placement (from white's perspective). Each rank is described, starting
|
166 |
+
with rank 8 and ending with rank 1. Within each rank, the contents of each
|
167 |
+
square are described from file A through file H. Following the Standard
|
168 |
+
Algebraic Notation (SAN), each piece is identified by a single letter taken
|
169 |
+
from the standard English names. White pieces are designated using upper-case
|
170 |
+
letters ("PNBRQK") whilst Black uses lowercase ("pnbrqk"). Blank squares are
|
171 |
+
noted using digits 1 through 8 (the number of blank squares), and "/"
|
172 |
+
separates ranks.
|
173 |
+
|
174 |
+
2) Active color. "w" means white moves next, "b" means black.
|
175 |
+
|
176 |
+
3) Castling availability. If neither side can castle, this is "-". Otherwise,
|
177 |
+
this has one or more letters: "K" (White can castle kingside), "Q" (White
|
178 |
+
can castle queenside), "k" (Black can castle kingside), and/or "q" (Black
|
179 |
+
can castle queenside).
|
180 |
+
|
181 |
+
4) En passant target square (in algebraic notation). If there's no en passant
|
182 |
+
target square, this is "-". If a pawn has just made a 2-square move, this
|
183 |
+
is the position "behind" the pawn. Following X-FEN standard, this is recorded only
|
184 |
+
if there is a pawn in position to make an en passant capture, and if there really
|
185 |
+
is a pawn that might have advanced two squares.
|
186 |
+
|
187 |
+
5) Halfmove clock. This is the number of halfmoves since the last pawn advance
|
188 |
+
or capture. This is used to determine if a draw can be claimed under the
|
189 |
+
fifty-move rule.
|
190 |
+
|
191 |
+
6) Fullmove number. The number of the full move. It starts at 1, and is
|
192 |
+
incremented after Black's move.
|
193 |
+
*/
|
194 |
+
|
195 |
+
unsigned char col, row, token;
|
196 |
+
size_t idx;
|
197 |
+
Square sq = SQ_A8;
|
198 |
+
std::istringstream ss(fenStr);
|
199 |
+
|
200 |
+
std::memset(this, 0, sizeof(Position));
|
201 |
+
std::memset(si, 0, sizeof(StateInfo));
|
202 |
+
st = si;
|
203 |
+
|
204 |
+
ss >> std::noskipws;
|
205 |
+
|
206 |
+
// 1. Piece placement
|
207 |
+
while ((ss >> token) && !isspace(token))
|
208 |
+
{
|
209 |
+
if (isdigit(token))
|
210 |
+
sq += (token - '0') * EAST; // Advance the given number of files
|
211 |
+
|
212 |
+
else if (token == '/')
|
213 |
+
sq += 2 * SOUTH;
|
214 |
+
|
215 |
+
else if ((idx = PieceToChar.find(token)) != string::npos) {
|
216 |
+
put_piece(Piece(idx), sq);
|
217 |
+
++sq;
|
218 |
+
}
|
219 |
+
}
|
220 |
+
|
221 |
+
// 2. Active color
|
222 |
+
ss >> token;
|
223 |
+
sideToMove = (token == 'w' ? WHITE : BLACK);
|
224 |
+
ss >> token;
|
225 |
+
|
226 |
+
// 3. Castling availability. Compatible with 3 standards: Normal FEN standard,
|
227 |
+
// Shredder-FEN that uses the letters of the columns on which the rooks began
|
228 |
+
// the game instead of KQkq and also X-FEN standard that, in case of Chess960,
|
229 |
+
// if an inner rook is associated with the castling right, the castling tag is
|
230 |
+
// replaced by the file letter of the involved rook, as for the Shredder-FEN.
|
231 |
+
while ((ss >> token) && !isspace(token))
|
232 |
+
{
|
233 |
+
Square rsq;
|
234 |
+
Color c = islower(token) ? BLACK : WHITE;
|
235 |
+
Piece rook = make_piece(c, ROOK);
|
236 |
+
|
237 |
+
token = char(toupper(token));
|
238 |
+
|
239 |
+
if (token == 'K')
|
240 |
+
for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {}
|
241 |
+
|
242 |
+
else if (token == 'Q')
|
243 |
+
for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {}
|
244 |
+
|
245 |
+
else if (token >= 'A' && token <= 'H')
|
246 |
+
rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
|
247 |
+
|
248 |
+
else
|
249 |
+
continue;
|
250 |
+
|
251 |
+
set_castling_right(c, rsq);
|
252 |
+
}
|
253 |
+
|
254 |
+
// 4. En passant square.
|
255 |
+
// Ignore if square is invalid or not on side to move relative rank 6.
|
256 |
+
bool enpassant = false;
|
257 |
+
|
258 |
+
if ( ((ss >> col) && (col >= 'a' && col <= 'h'))
|
259 |
+
&& ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
|
260 |
+
{
|
261 |
+
st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
|
262 |
+
|
263 |
+
// En passant square will be considered only if
|
264 |
+
// a) side to move have a pawn threatening epSquare
|
265 |
+
// b) there is an enemy pawn in front of epSquare
|
266 |
+
// c) there is no piece on epSquare or behind epSquare
|
267 |
+
enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
|
268 |
+
&& (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
|
269 |
+
&& !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
|
270 |
+
}
|
271 |
+
|
272 |
+
if (!enpassant)
|
273 |
+
st->epSquare = SQ_NONE;
|
274 |
+
|
275 |
+
// 5-6. Halfmove clock and fullmove number
|
276 |
+
ss >> std::skipws >> st->rule50 >> gamePly;
|
277 |
+
|
278 |
+
// Convert from fullmove starting from 1 to gamePly starting from 0,
|
279 |
+
// handle also common incorrect FEN with fullmove = 0.
|
280 |
+
gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);
|
281 |
+
|
282 |
+
chess960 = isChess960;
|
283 |
+
thisThread = th;
|
284 |
+
set_state(st);
|
285 |
+
|
286 |
+
assert(pos_is_ok());
|
287 |
+
|
288 |
+
return *this;
|
289 |
+
}
|
290 |
+
|
291 |
+
|
292 |
+
/// Position::set_castling_right() is a helper function used to set castling
|
293 |
+
/// rights given the corresponding color and the rook starting square.
|
294 |
+
|
295 |
+
void Position::set_castling_right(Color c, Square rfrom) {
|
296 |
+
|
297 |
+
Square kfrom = square<KING>(c);
|
298 |
+
CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE);
|
299 |
+
|
300 |
+
st->castlingRights |= cr;
|
301 |
+
castlingRightsMask[kfrom] |= cr;
|
302 |
+
castlingRightsMask[rfrom] |= cr;
|
303 |
+
castlingRookSquare[cr] = rfrom;
|
304 |
+
|
305 |
+
Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
|
306 |
+
Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
|
307 |
+
|
308 |
+
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto))
|
309 |
+
& ~(kfrom | rfrom);
|
310 |
+
}
|
311 |
+
|
312 |
+
|
313 |
+
/// Position::set_check_info() sets king attacks to detect if a move gives check
|
314 |
+
|
315 |
+
void Position::set_check_info(StateInfo* si) const {
|
316 |
+
|
317 |
+
si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), si->pinners[BLACK]);
|
318 |
+
si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK), si->pinners[WHITE]);
|
319 |
+
|
320 |
+
Square ksq = square<KING>(~sideToMove);
|
321 |
+
|
322 |
+
si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq);
|
323 |
+
si->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
|
324 |
+
si->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
|
325 |
+
si->checkSquares[ROOK] = attacks_bb<ROOK>(ksq, pieces());
|
326 |
+
si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
|
327 |
+
si->checkSquares[KING] = 0;
|
328 |
+
}
|
329 |
+
|
330 |
+
|
331 |
+
/// Position::set_state() computes the hash keys of the position, and other
|
332 |
+
/// data that once computed is updated incrementally as moves are made.
|
333 |
+
/// The function is only used when a new position is set up, and to verify
|
334 |
+
/// the correctness of the StateInfo data when running in debug mode.
|
335 |
+
|
336 |
+
void Position::set_state(StateInfo* si) const {
|
337 |
+
|
338 |
+
si->key = si->materialKey = 0;
|
339 |
+
si->pawnKey = Zobrist::noPawns;
|
340 |
+
si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO;
|
341 |
+
si->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
|
342 |
+
|
343 |
+
set_check_info(si);
|
344 |
+
|
345 |
+
for (Bitboard b = pieces(); b; )
|
346 |
+
{
|
347 |
+
Square s = pop_lsb(b);
|
348 |
+
Piece pc = piece_on(s);
|
349 |
+
si->key ^= Zobrist::psq[pc][s];
|
350 |
+
|
351 |
+
if (type_of(pc) == PAWN)
|
352 |
+
si->pawnKey ^= Zobrist::psq[pc][s];
|
353 |
+
|
354 |
+
else if (type_of(pc) != KING)
|
355 |
+
si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc];
|
356 |
+
}
|
357 |
+
|
358 |
+
if (si->epSquare != SQ_NONE)
|
359 |
+
si->key ^= Zobrist::enpassant[file_of(si->epSquare)];
|
360 |
+
|
361 |
+
if (sideToMove == BLACK)
|
362 |
+
si->key ^= Zobrist::side;
|
363 |
+
|
364 |
+
si->key ^= Zobrist::castling[si->castlingRights];
|
365 |
+
|
366 |
+
for (Piece pc : Pieces)
|
367 |
+
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
|
368 |
+
si->materialKey ^= Zobrist::psq[pc][cnt];
|
369 |
+
}
|
370 |
+
|
371 |
+
|
372 |
+
/// Position::set() is an overload to initialize the position object with
|
373 |
+
/// the given endgame code string like "KBPKN". It is mainly a helper to
|
374 |
+
/// get the material key out of an endgame code.
|
375 |
+
|
376 |
+
Position& Position::set(const string& code, Color c, StateInfo* si) {
|
377 |
+
|
378 |
+
assert(code[0] == 'K');
|
379 |
+
|
380 |
+
string sides[] = { code.substr(code.find('K', 1)), // Weak
|
381 |
+
code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong
|
382 |
+
|
383 |
+
assert(sides[0].length() > 0 && sides[0].length() < 8);
|
384 |
+
assert(sides[1].length() > 0 && sides[1].length() < 8);
|
385 |
+
|
386 |
+
std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
|
387 |
+
|
388 |
+
string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/"
|
389 |
+
+ sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
|
390 |
+
|
391 |
+
return set(fenStr, false, si, nullptr);
|
392 |
+
}
|
393 |
+
|
394 |
+
|
395 |
+
/// Position::fen() returns a FEN representation of the position. In case of
|
396 |
+
/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function.
|
397 |
+
|
398 |
+
string Position::fen() const {
|
399 |
+
|
400 |
+
int emptyCnt;
|
401 |
+
std::ostringstream ss;
|
402 |
+
|
403 |
+
for (Rank r = RANK_8; r >= RANK_1; --r)
|
404 |
+
{
|
405 |
+
for (File f = FILE_A; f <= FILE_H; ++f)
|
406 |
+
{
|
407 |
+
for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f)
|
408 |
+
++emptyCnt;
|
409 |
+
|
410 |
+
if (emptyCnt)
|
411 |
+
ss << emptyCnt;
|
412 |
+
|
413 |
+
if (f <= FILE_H)
|
414 |
+
ss << PieceToChar[piece_on(make_square(f, r))];
|
415 |
+
}
|
416 |
+
|
417 |
+
if (r > RANK_1)
|
418 |
+
ss << '/';
|
419 |
+
}
|
420 |
+
|
421 |
+
ss << (sideToMove == WHITE ? " w " : " b ");
|
422 |
+
|
423 |
+
if (can_castle(WHITE_OO))
|
424 |
+
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K');
|
425 |
+
|
426 |
+
if (can_castle(WHITE_OOO))
|
427 |
+
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
|
428 |
+
|
429 |
+
if (can_castle(BLACK_OO))
|
430 |
+
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k');
|
431 |
+
|
432 |
+
if (can_castle(BLACK_OOO))
|
433 |
+
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
|
434 |
+
|
435 |
+
if (!can_castle(ANY_CASTLING))
|
436 |
+
ss << '-';
|
437 |
+
|
438 |
+
ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ")
|
439 |
+
<< st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
|
440 |
+
|
441 |
+
return ss.str();
|
442 |
+
}
|
443 |
+
|
444 |
+
|
445 |
+
/// Position::slider_blockers() returns a bitboard of all the pieces (both colors)
|
446 |
+
/// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a
|
447 |
+
/// slider if removing that piece from the board would result in a position where
|
448 |
+
/// square 's' is attacked. For example, a king-attack blocking piece can be either
|
449 |
+
/// a pinned or a discovered check piece, according if its color is the opposite
|
450 |
+
/// or the same of the color of the slider.
|
451 |
+
|
452 |
+
Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const {
|
453 |
+
|
454 |
+
Bitboard blockers = 0;
|
455 |
+
pinners = 0;
|
456 |
+
|
457 |
+
// Snipers are sliders that attack 's' when a piece and other snipers are removed
|
458 |
+
Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK))
|
459 |
+
| (attacks_bb<BISHOP>(s) & pieces(QUEEN, BISHOP))) & sliders;
|
460 |
+
Bitboard occupancy = pieces() ^ snipers;
|
461 |
+
|
462 |
+
while (snipers)
|
463 |
+
{
|
464 |
+
Square sniperSq = pop_lsb(snipers);
|
465 |
+
Bitboard b = between_bb(s, sniperSq) & occupancy;
|
466 |
+
|
467 |
+
if (b && !more_than_one(b))
|
468 |
+
{
|
469 |
+
blockers |= b;
|
470 |
+
if (b & pieces(color_of(piece_on(s))))
|
471 |
+
pinners |= sniperSq;
|
472 |
+
}
|
473 |
+
}
|
474 |
+
return blockers;
|
475 |
+
}
|
476 |
+
|
477 |
+
|
478 |
+
/// Position::attackers_to() computes a bitboard of all pieces which attack a
|
479 |
+
/// given square. Slider attacks use the occupied bitboard to indicate occupancy.
|
480 |
+
|
481 |
+
Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
|
482 |
+
|
483 |
+
return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN))
|
484 |
+
| (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN))
|
485 |
+
| (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
|
486 |
+
| (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN))
|
487 |
+
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
|
488 |
+
| (attacks_bb<KING>(s) & pieces(KING));
|
489 |
+
}
|
490 |
+
|
491 |
+
|
492 |
+
/// Position::legal() tests whether a pseudo-legal move is legal
|
493 |
+
|
494 |
+
bool Position::legal(Move m) const {
|
495 |
+
|
496 |
+
assert(is_ok(m));
|
497 |
+
|
498 |
+
Color us = sideToMove;
|
499 |
+
Square from = from_sq(m);
|
500 |
+
Square to = to_sq(m);
|
501 |
+
|
502 |
+
assert(color_of(moved_piece(m)) == us);
|
503 |
+
assert(piece_on(square<KING>(us)) == make_piece(us, KING));
|
504 |
+
|
505 |
+
// En passant captures are a tricky special case. Because they are rather
|
506 |
+
// uncommon, we do it simply by testing whether the king is attacked after
|
507 |
+
// the move is made.
|
508 |
+
if (type_of(m) == EN_PASSANT)
|
509 |
+
{
|
510 |
+
Square ksq = square<KING>(us);
|
511 |
+
Square capsq = to - pawn_push(us);
|
512 |
+
Bitboard occupied = (pieces() ^ from ^ capsq) | to;
|
513 |
+
|
514 |
+
assert(to == ep_square());
|
515 |
+
assert(moved_piece(m) == make_piece(us, PAWN));
|
516 |
+
assert(piece_on(capsq) == make_piece(~us, PAWN));
|
517 |
+
assert(piece_on(to) == NO_PIECE);
|
518 |
+
|
519 |
+
return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
|
520 |
+
&& !(attacks_bb<BISHOP>(ksq, occupied) & pieces(~us, QUEEN, BISHOP));
|
521 |
+
}
|
522 |
+
|
523 |
+
// Castling moves generation does not check if the castling path is clear of
|
524 |
+
// enemy attacks, it is delayed at a later time: now!
|
525 |
+
if (type_of(m) == CASTLING)
|
526 |
+
{
|
527 |
+
// After castling, the rook and king final positions are the same in
|
528 |
+
// Chess960 as they would be in standard chess.
|
529 |
+
to = relative_square(us, to > from ? SQ_G1 : SQ_C1);
|
530 |
+
Direction step = to > from ? WEST : EAST;
|
531 |
+
|
532 |
+
for (Square s = to; s != from; s += step)
|
533 |
+
if (attackers_to(s) & pieces(~us))
|
534 |
+
return false;
|
535 |
+
|
536 |
+
// In case of Chess960, verify if the Rook blocks some checks
|
537 |
+
// For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
|
538 |
+
return !chess960 || !(blockers_for_king(us) & to_sq(m));
|
539 |
+
}
|
540 |
+
|
541 |
+
// If the moving piece is a king, check whether the destination square is
|
542 |
+
// attacked by the opponent.
|
543 |
+
if (type_of(piece_on(from)) == KING)
|
544 |
+
return !(attackers_to(to, pieces() ^ from) & pieces(~us));
|
545 |
+
|
546 |
+
// A non-king move is legal if and only if it is not pinned or it
|
547 |
+
// is moving along the ray towards or away from the king.
|
548 |
+
return !(blockers_for_king(us) & from)
|
549 |
+
|| aligned(from, to, square<KING>(us));
|
550 |
+
}
|
551 |
+
|
552 |
+
|
553 |
+
/// Position::pseudo_legal() takes a random move and tests whether the move is
|
554 |
+
/// pseudo legal. It is used to validate moves from TT that can be corrupted
|
555 |
+
/// due to SMP concurrent access or hash position key aliasing.
|
556 |
+
|
557 |
+
bool Position::pseudo_legal(const Move m) const {
|
558 |
+
|
559 |
+
Color us = sideToMove;
|
560 |
+
Square from = from_sq(m);
|
561 |
+
Square to = to_sq(m);
|
562 |
+
Piece pc = moved_piece(m);
|
563 |
+
|
564 |
+
// Use a slower but simpler function for uncommon cases
|
565 |
+
// yet we skip the legality check of MoveList<LEGAL>().
|
566 |
+
if (type_of(m) != NORMAL)
|
567 |
+
return checkers() ? MoveList< EVASIONS>(*this).contains(m)
|
568 |
+
: MoveList<NON_EVASIONS>(*this).contains(m);
|
569 |
+
|
570 |
+
// Is not a promotion, so promotion piece must be empty
|
571 |
+
if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE)
|
572 |
+
return false;
|
573 |
+
|
574 |
+
// If the 'from' square is not occupied by a piece belonging to the side to
|
575 |
+
// move, the move is obviously not legal.
|
576 |
+
if (pc == NO_PIECE || color_of(pc) != us)
|
577 |
+
return false;
|
578 |
+
|
579 |
+
// The destination square cannot be occupied by a friendly piece
|
580 |
+
if (pieces(us) & to)
|
581 |
+
return false;
|
582 |
+
|
583 |
+
// Handle the special case of a pawn move
|
584 |
+
if (type_of(pc) == PAWN)
|
585 |
+
{
|
586 |
+
// We have already handled promotion moves, so destination
|
587 |
+
// cannot be on the 8th/1st rank.
|
588 |
+
if ((Rank8BB | Rank1BB) & to)
|
589 |
+
return false;
|
590 |
+
|
591 |
+
if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
|
592 |
+
&& !((from + pawn_push(us) == to) && empty(to)) // Not a single push
|
593 |
+
&& !( (from + 2 * pawn_push(us) == to) // Not a double push
|
594 |
+
&& (relative_rank(us, from) == RANK_2)
|
595 |
+
&& empty(to)
|
596 |
+
&& empty(to - pawn_push(us))))
|
597 |
+
return false;
|
598 |
+
}
|
599 |
+
else if (!(attacks_bb(type_of(pc), from, pieces()) & to))
|
600 |
+
return false;
|
601 |
+
|
602 |
+
// Evasions generator already takes care to avoid some kind of illegal moves
|
603 |
+
// and legal() relies on this. We therefore have to take care that the same
|
604 |
+
// kind of moves are filtered out here.
|
605 |
+
if (checkers())
|
606 |
+
{
|
607 |
+
if (type_of(pc) != KING)
|
608 |
+
{
|
609 |
+
// Double check? In this case a king move is required
|
610 |
+
if (more_than_one(checkers()))
|
611 |
+
return false;
|
612 |
+
|
613 |
+
// Our move must be a blocking interposition or a capture of the checking piece
|
614 |
+
if (!(between_bb(square<KING>(us), lsb(checkers())) & to))
|
615 |
+
return false;
|
616 |
+
}
|
617 |
+
// In case of king moves under check we have to remove king so as to catch
|
618 |
+
// invalid moves like b1a1 when opposite queen is on c1.
|
619 |
+
else if (attackers_to(to, pieces() ^ from) & pieces(~us))
|
620 |
+
return false;
|
621 |
+
}
|
622 |
+
|
623 |
+
return true;
|
624 |
+
}
|
625 |
+
|
626 |
+
|
627 |
+
/// Position::gives_check() tests whether a pseudo-legal move gives a check
|
628 |
+
|
629 |
+
bool Position::gives_check(Move m) const {
|
630 |
+
|
631 |
+
assert(is_ok(m));
|
632 |
+
assert(color_of(moved_piece(m)) == sideToMove);
|
633 |
+
|
634 |
+
Square from = from_sq(m);
|
635 |
+
Square to = to_sq(m);
|
636 |
+
|
637 |
+
// Is there a direct check?
|
638 |
+
if (check_squares(type_of(piece_on(from))) & to)
|
639 |
+
return true;
|
640 |
+
|
641 |
+
// Is there a discovered check?
|
642 |
+
if ( (blockers_for_king(~sideToMove) & from)
|
643 |
+
&& !aligned(from, to, square<KING>(~sideToMove)))
|
644 |
+
return true;
|
645 |
+
|
646 |
+
switch (type_of(m))
|
647 |
+
{
|
648 |
+
case NORMAL:
|
649 |
+
return false;
|
650 |
+
|
651 |
+
case PROMOTION:
|
652 |
+
return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove);
|
653 |
+
|
654 |
+
// En passant capture with check? We have already handled the case
|
655 |
+
// of direct checks and ordinary discovered check, so the only case we
|
656 |
+
// need to handle is the unusual case of a discovered check through
|
657 |
+
// the captured pawn.
|
658 |
+
case EN_PASSANT:
|
659 |
+
{
|
660 |
+
Square capsq = make_square(file_of(to), rank_of(from));
|
661 |
+
Bitboard b = (pieces() ^ from ^ capsq) | to;
|
662 |
+
|
663 |
+
return (attacks_bb< ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
|
664 |
+
| (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
|
665 |
+
}
|
666 |
+
default: //CASTLING
|
667 |
+
{
|
668 |
+
// Castling is encoded as 'king captures the rook'
|
669 |
+
Square ksq = square<KING>(~sideToMove);
|
670 |
+
Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1);
|
671 |
+
|
672 |
+
return (attacks_bb<ROOK>(rto) & ksq)
|
673 |
+
&& (attacks_bb<ROOK>(rto, pieces() ^ from ^ to) & ksq);
|
674 |
+
}
|
675 |
+
}
|
676 |
+
}
|
677 |
+
|
678 |
+
|
679 |
+
/// Position::do_move() makes a move, and saves all information necessary
|
680 |
+
/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal
|
681 |
+
/// moves should be filtered out before this function is called.
|
682 |
+
|
683 |
+
void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
684 |
+
|
685 |
+
assert(is_ok(m));
|
686 |
+
assert(&newSt != st);
|
687 |
+
|
688 |
+
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
|
689 |
+
Key k = st->key ^ Zobrist::side;
|
690 |
+
|
691 |
+
// Copy some fields of the old state to our new StateInfo object except the
|
692 |
+
// ones which are going to be recalculated from scratch anyway and then switch
|
693 |
+
// our state pointer to point to the new (ready to be updated) state.
|
694 |
+
std::memcpy(&newSt, st, offsetof(StateInfo, key));
|
695 |
+
newSt.previous = st;
|
696 |
+
st = &newSt;
|
697 |
+
|
698 |
+
// Increment ply counters. In particular, rule50 will be reset to zero later on
|
699 |
+
// in case of a capture or a pawn move.
|
700 |
+
++gamePly;
|
701 |
+
++st->rule50;
|
702 |
+
++st->pliesFromNull;
|
703 |
+
|
704 |
+
// Used by NNUE
|
705 |
+
st->accumulator.computed[WHITE] = false;
|
706 |
+
st->accumulator.computed[BLACK] = false;
|
707 |
+
auto& dp = st->dirtyPiece;
|
708 |
+
dp.dirty_num = 1;
|
709 |
+
|
710 |
+
Color us = sideToMove;
|
711 |
+
Color them = ~us;
|
712 |
+
Square from = from_sq(m);
|
713 |
+
Square to = to_sq(m);
|
714 |
+
Piece pc = piece_on(from);
|
715 |
+
Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to);
|
716 |
+
|
717 |
+
assert(color_of(pc) == us);
|
718 |
+
assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us));
|
719 |
+
assert(type_of(captured) != KING);
|
720 |
+
|
721 |
+
if (type_of(m) == CASTLING)
|
722 |
+
{
|
723 |
+
assert(pc == make_piece(us, KING));
|
724 |
+
assert(captured == make_piece(us, ROOK));
|
725 |
+
|
726 |
+
Square rfrom, rto;
|
727 |
+
do_castling<true>(us, from, to, rfrom, rto);
|
728 |
+
|
729 |
+
k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
|
730 |
+
captured = NO_PIECE;
|
731 |
+
}
|
732 |
+
|
733 |
+
if (captured)
|
734 |
+
{
|
735 |
+
Square capsq = to;
|
736 |
+
|
737 |
+
// If the captured piece is a pawn, update pawn hash key, otherwise
|
738 |
+
// update non-pawn material.
|
739 |
+
if (type_of(captured) == PAWN)
|
740 |
+
{
|
741 |
+
if (type_of(m) == EN_PASSANT)
|
742 |
+
{
|
743 |
+
capsq -= pawn_push(us);
|
744 |
+
|
745 |
+
assert(pc == make_piece(us, PAWN));
|
746 |
+
assert(to == st->epSquare);
|
747 |
+
assert(relative_rank(us, to) == RANK_6);
|
748 |
+
assert(piece_on(to) == NO_PIECE);
|
749 |
+
assert(piece_on(capsq) == make_piece(them, PAWN));
|
750 |
+
}
|
751 |
+
|
752 |
+
st->pawnKey ^= Zobrist::psq[captured][capsq];
|
753 |
+
}
|
754 |
+
else
|
755 |
+
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
|
756 |
+
|
757 |
+
if (Eval::useNNUE)
|
758 |
+
{
|
759 |
+
dp.dirty_num = 2; // 1 piece moved, 1 piece captured
|
760 |
+
dp.piece[1] = captured;
|
761 |
+
dp.from[1] = capsq;
|
762 |
+
dp.to[1] = SQ_NONE;
|
763 |
+
}
|
764 |
+
|
765 |
+
// Update board and piece lists
|
766 |
+
remove_piece(capsq);
|
767 |
+
|
768 |
+
if (type_of(m) == EN_PASSANT)
|
769 |
+
board[capsq] = NO_PIECE;
|
770 |
+
|
771 |
+
// Update material hash key and prefetch access to materialTable
|
772 |
+
k ^= Zobrist::psq[captured][capsq];
|
773 |
+
st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]];
|
774 |
+
prefetch(thisThread->materialTable[st->materialKey]);
|
775 |
+
|
776 |
+
// Reset rule 50 counter
|
777 |
+
st->rule50 = 0;
|
778 |
+
}
|
779 |
+
|
780 |
+
// Update hash key
|
781 |
+
k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
782 |
+
|
783 |
+
// Reset en passant square
|
784 |
+
if (st->epSquare != SQ_NONE)
|
785 |
+
{
|
786 |
+
k ^= Zobrist::enpassant[file_of(st->epSquare)];
|
787 |
+
st->epSquare = SQ_NONE;
|
788 |
+
}
|
789 |
+
|
790 |
+
// Update castling rights if needed
|
791 |
+
if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
|
792 |
+
{
|
793 |
+
k ^= Zobrist::castling[st->castlingRights];
|
794 |
+
st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]);
|
795 |
+
k ^= Zobrist::castling[st->castlingRights];
|
796 |
+
}
|
797 |
+
|
798 |
+
// Move the piece. The tricky Chess960 castling is handled earlier
|
799 |
+
if (type_of(m) != CASTLING)
|
800 |
+
{
|
801 |
+
if (Eval::useNNUE)
|
802 |
+
{
|
803 |
+
dp.piece[0] = pc;
|
804 |
+
dp.from[0] = from;
|
805 |
+
dp.to[0] = to;
|
806 |
+
}
|
807 |
+
|
808 |
+
move_piece(from, to);
|
809 |
+
}
|
810 |
+
|
811 |
+
// If the moving piece is a pawn do some special extra work
|
812 |
+
if (type_of(pc) == PAWN)
|
813 |
+
{
|
814 |
+
// Set en passant square if the moved pawn can be captured
|
815 |
+
if ( (int(to) ^ int(from)) == 16
|
816 |
+
&& (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
|
817 |
+
{
|
818 |
+
st->epSquare = to - pawn_push(us);
|
819 |
+
k ^= Zobrist::enpassant[file_of(st->epSquare)];
|
820 |
+
}
|
821 |
+
|
822 |
+
else if (type_of(m) == PROMOTION)
|
823 |
+
{
|
824 |
+
Piece promotion = make_piece(us, promotion_type(m));
|
825 |
+
|
826 |
+
assert(relative_rank(us, to) == RANK_8);
|
827 |
+
assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
|
828 |
+
|
829 |
+
remove_piece(to);
|
830 |
+
put_piece(promotion, to);
|
831 |
+
|
832 |
+
if (Eval::useNNUE)
|
833 |
+
{
|
834 |
+
// Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
|
835 |
+
dp.to[0] = SQ_NONE;
|
836 |
+
dp.piece[dp.dirty_num] = promotion;
|
837 |
+
dp.from[dp.dirty_num] = SQ_NONE;
|
838 |
+
dp.to[dp.dirty_num] = to;
|
839 |
+
dp.dirty_num++;
|
840 |
+
}
|
841 |
+
|
842 |
+
// Update hash keys
|
843 |
+
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
|
844 |
+
st->pawnKey ^= Zobrist::psq[pc][to];
|
845 |
+
st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1]
|
846 |
+
^ Zobrist::psq[pc][pieceCount[pc]];
|
847 |
+
|
848 |
+
// Update material
|
849 |
+
st->nonPawnMaterial[us] += PieceValue[MG][promotion];
|
850 |
+
}
|
851 |
+
|
852 |
+
// Update pawn hash key
|
853 |
+
st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
854 |
+
|
855 |
+
// Reset rule 50 draw counter
|
856 |
+
st->rule50 = 0;
|
857 |
+
}
|
858 |
+
|
859 |
+
// Set capture piece
|
860 |
+
st->capturedPiece = captured;
|
861 |
+
|
862 |
+
// Update the key with the final value
|
863 |
+
st->key = k;
|
864 |
+
|
865 |
+
// Calculate checkers bitboard (if move gives check)
|
866 |
+
st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
|
867 |
+
|
868 |
+
sideToMove = ~sideToMove;
|
869 |
+
|
870 |
+
// Update king attacks used for fast check detection
|
871 |
+
set_check_info(st);
|
872 |
+
|
873 |
+
// Calculate the repetition info. It is the ply distance from the previous
|
874 |
+
// occurrence of the same position, negative in the 3-fold case, or zero
|
875 |
+
// if the position was not repeated.
|
876 |
+
st->repetition = 0;
|
877 |
+
int end = std::min(st->rule50, st->pliesFromNull);
|
878 |
+
if (end >= 4)
|
879 |
+
{
|
880 |
+
StateInfo* stp = st->previous->previous;
|
881 |
+
for (int i = 4; i <= end; i += 2)
|
882 |
+
{
|
883 |
+
stp = stp->previous->previous;
|
884 |
+
if (stp->key == st->key)
|
885 |
+
{
|
886 |
+
st->repetition = stp->repetition ? -i : i;
|
887 |
+
break;
|
888 |
+
}
|
889 |
+
}
|
890 |
+
}
|
891 |
+
|
892 |
+
assert(pos_is_ok());
|
893 |
+
}
|
894 |
+
|
895 |
+
|
896 |
+
/// Position::undo_move() unmakes a move. When it returns, the position should
|
897 |
+
/// be restored to exactly the same state as before the move was made.
|
898 |
+
|
899 |
+
void Position::undo_move(Move m) {
|
900 |
+
|
901 |
+
assert(is_ok(m));
|
902 |
+
|
903 |
+
sideToMove = ~sideToMove;
|
904 |
+
|
905 |
+
Color us = sideToMove;
|
906 |
+
Square from = from_sq(m);
|
907 |
+
Square to = to_sq(m);
|
908 |
+
Piece pc = piece_on(to);
|
909 |
+
|
910 |
+
assert(empty(from) || type_of(m) == CASTLING);
|
911 |
+
assert(type_of(st->capturedPiece) != KING);
|
912 |
+
|
913 |
+
if (type_of(m) == PROMOTION)
|
914 |
+
{
|
915 |
+
assert(relative_rank(us, to) == RANK_8);
|
916 |
+
assert(type_of(pc) == promotion_type(m));
|
917 |
+
assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
|
918 |
+
|
919 |
+
remove_piece(to);
|
920 |
+
pc = make_piece(us, PAWN);
|
921 |
+
put_piece(pc, to);
|
922 |
+
}
|
923 |
+
|
924 |
+
if (type_of(m) == CASTLING)
|
925 |
+
{
|
926 |
+
Square rfrom, rto;
|
927 |
+
do_castling<false>(us, from, to, rfrom, rto);
|
928 |
+
}
|
929 |
+
else
|
930 |
+
{
|
931 |
+
move_piece(to, from); // Put the piece back at the source square
|
932 |
+
|
933 |
+
if (st->capturedPiece)
|
934 |
+
{
|
935 |
+
Square capsq = to;
|
936 |
+
|
937 |
+
if (type_of(m) == EN_PASSANT)
|
938 |
+
{
|
939 |
+
capsq -= pawn_push(us);
|
940 |
+
|
941 |
+
assert(type_of(pc) == PAWN);
|
942 |
+
assert(to == st->previous->epSquare);
|
943 |
+
assert(relative_rank(us, to) == RANK_6);
|
944 |
+
assert(piece_on(capsq) == NO_PIECE);
|
945 |
+
assert(st->capturedPiece == make_piece(~us, PAWN));
|
946 |
+
}
|
947 |
+
|
948 |
+
put_piece(st->capturedPiece, capsq); // Restore the captured piece
|
949 |
+
}
|
950 |
+
}
|
951 |
+
|
952 |
+
// Finally point our state pointer back to the previous state
|
953 |
+
st = st->previous;
|
954 |
+
--gamePly;
|
955 |
+
|
956 |
+
assert(pos_is_ok());
|
957 |
+
}
|
958 |
+
|
959 |
+
|
960 |
+
/// Position::do_castling() is a helper used to do/undo a castling move. This
|
961 |
+
/// is a bit tricky in Chess960 where from/to squares can overlap.
|
962 |
+
template<bool Do>
|
963 |
+
void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) {
|
964 |
+
|
965 |
+
bool kingSide = to > from;
|
966 |
+
rfrom = to; // Castling is encoded as "king captures friendly rook"
|
967 |
+
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
|
968 |
+
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
|
969 |
+
|
970 |
+
if (Do && Eval::useNNUE)
|
971 |
+
{
|
972 |
+
auto& dp = st->dirtyPiece;
|
973 |
+
dp.piece[0] = make_piece(us, KING);
|
974 |
+
dp.from[0] = from;
|
975 |
+
dp.to[0] = to;
|
976 |
+
dp.piece[1] = make_piece(us, ROOK);
|
977 |
+
dp.from[1] = rfrom;
|
978 |
+
dp.to[1] = rto;
|
979 |
+
dp.dirty_num = 2;
|
980 |
+
}
|
981 |
+
|
982 |
+
// Remove both pieces first since squares could overlap in Chess960
|
983 |
+
remove_piece(Do ? from : to);
|
984 |
+
remove_piece(Do ? rfrom : rto);
|
985 |
+
board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us
|
986 |
+
put_piece(make_piece(us, KING), Do ? to : from);
|
987 |
+
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
|
988 |
+
}
|
989 |
+
|
990 |
+
|
991 |
+
/// Position::do_null_move() is used to do a "null move": it flips
|
992 |
+
/// the side to move without executing any move on the board.
|
993 |
+
|
994 |
+
void Position::do_null_move(StateInfo& newSt) {
|
995 |
+
|
996 |
+
assert(!checkers());
|
997 |
+
assert(&newSt != st);
|
998 |
+
|
999 |
+
std::memcpy(&newSt, st, offsetof(StateInfo, accumulator));
|
1000 |
+
|
1001 |
+
newSt.previous = st;
|
1002 |
+
st = &newSt;
|
1003 |
+
|
1004 |
+
st->dirtyPiece.dirty_num = 0;
|
1005 |
+
st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator()
|
1006 |
+
st->accumulator.computed[WHITE] = false;
|
1007 |
+
st->accumulator.computed[BLACK] = false;
|
1008 |
+
|
1009 |
+
if (st->epSquare != SQ_NONE)
|
1010 |
+
{
|
1011 |
+
st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
|
1012 |
+
st->epSquare = SQ_NONE;
|
1013 |
+
}
|
1014 |
+
|
1015 |
+
st->key ^= Zobrist::side;
|
1016 |
+
++st->rule50;
|
1017 |
+
prefetch(TT.first_entry(key()));
|
1018 |
+
|
1019 |
+
st->pliesFromNull = 0;
|
1020 |
+
|
1021 |
+
sideToMove = ~sideToMove;
|
1022 |
+
|
1023 |
+
set_check_info(st);
|
1024 |
+
|
1025 |
+
st->repetition = 0;
|
1026 |
+
|
1027 |
+
assert(pos_is_ok());
|
1028 |
+
}
|
1029 |
+
|
1030 |
+
|
1031 |
+
/// Position::undo_null_move() must be used to undo a "null move"
|
1032 |
+
|
1033 |
+
void Position::undo_null_move() {
|
1034 |
+
|
1035 |
+
assert(!checkers());
|
1036 |
+
|
1037 |
+
st = st->previous;
|
1038 |
+
sideToMove = ~sideToMove;
|
1039 |
+
}
|
1040 |
+
|
1041 |
+
|
1042 |
+
/// Position::key_after() computes the new hash key after the given move. Needed
|
1043 |
+
/// for speculative prefetch. It doesn't recognize special moves like castling,
|
1044 |
+
/// en passant and promotions.
|
1045 |
+
|
1046 |
+
Key Position::key_after(Move m) const {
|
1047 |
+
|
1048 |
+
Square from = from_sq(m);
|
1049 |
+
Square to = to_sq(m);
|
1050 |
+
Piece pc = piece_on(from);
|
1051 |
+
Piece captured = piece_on(to);
|
1052 |
+
Key k = st->key ^ Zobrist::side;
|
1053 |
+
|
1054 |
+
if (captured)
|
1055 |
+
k ^= Zobrist::psq[captured][to];
|
1056 |
+
|
1057 |
+
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
|
1058 |
+
|
1059 |
+
return (captured || type_of(pc) == PAWN)
|
1060 |
+
? k : adjust_key50<true>(k);
|
1061 |
+
}
|
1062 |
+
|
1063 |
+
|
1064 |
+
/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the
|
1065 |
+
/// SEE value of move is greater or equal to the given threshold. We'll use an
|
1066 |
+
/// algorithm similar to alpha-beta pruning with a null window.
|
1067 |
+
|
1068 |
+
bool Position::see_ge(Move m, Value threshold) const {
|
1069 |
+
|
1070 |
+
assert(is_ok(m));
|
1071 |
+
|
1072 |
+
// Only deal with normal moves, assume others pass a simple SEE
|
1073 |
+
if (type_of(m) != NORMAL)
|
1074 |
+
return VALUE_ZERO >= threshold;
|
1075 |
+
|
1076 |
+
Square from = from_sq(m), to = to_sq(m);
|
1077 |
+
|
1078 |
+
int swap = PieceValue[MG][piece_on(to)] - threshold;
|
1079 |
+
if (swap < 0)
|
1080 |
+
return false;
|
1081 |
+
|
1082 |
+
swap = PieceValue[MG][piece_on(from)] - swap;
|
1083 |
+
if (swap <= 0)
|
1084 |
+
return true;
|
1085 |
+
|
1086 |
+
assert(color_of(piece_on(from)) == sideToMove);
|
1087 |
+
Bitboard occupied = pieces() ^ from ^ to;
|
1088 |
+
Color stm = sideToMove;
|
1089 |
+
Bitboard attackers = attackers_to(to, occupied);
|
1090 |
+
Bitboard stmAttackers, bb;
|
1091 |
+
int res = 1;
|
1092 |
+
|
1093 |
+
while (true)
|
1094 |
+
{
|
1095 |
+
stm = ~stm;
|
1096 |
+
attackers &= occupied;
|
1097 |
+
|
1098 |
+
// If stm has no more attackers then give up: stm loses
|
1099 |
+
if (!(stmAttackers = attackers & pieces(stm)))
|
1100 |
+
break;
|
1101 |
+
|
1102 |
+
// Don't allow pinned pieces to attack as long as there are
|
1103 |
+
// pinners on their original square.
|
1104 |
+
if (pinners(~stm) & occupied)
|
1105 |
+
{
|
1106 |
+
stmAttackers &= ~blockers_for_king(stm);
|
1107 |
+
|
1108 |
+
if (!stmAttackers)
|
1109 |
+
break;
|
1110 |
+
}
|
1111 |
+
|
1112 |
+
res ^= 1;
|
1113 |
+
|
1114 |
+
// Locate and remove the next least valuable attacker, and add to
|
1115 |
+
// the bitboard 'attackers' any X-ray attackers behind it.
|
1116 |
+
if ((bb = stmAttackers & pieces(PAWN)))
|
1117 |
+
{
|
1118 |
+
if ((swap = PawnValueMg - swap) < res)
|
1119 |
+
break;
|
1120 |
+
|
1121 |
+
occupied ^= least_significant_square_bb(bb);
|
1122 |
+
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
|
1123 |
+
}
|
1124 |
+
|
1125 |
+
else if ((bb = stmAttackers & pieces(KNIGHT)))
|
1126 |
+
{
|
1127 |
+
if ((swap = KnightValueMg - swap) < res)
|
1128 |
+
break;
|
1129 |
+
|
1130 |
+
occupied ^= least_significant_square_bb(bb);
|
1131 |
+
}
|
1132 |
+
|
1133 |
+
else if ((bb = stmAttackers & pieces(BISHOP)))
|
1134 |
+
{
|
1135 |
+
if ((swap = BishopValueMg - swap) < res)
|
1136 |
+
break;
|
1137 |
+
|
1138 |
+
occupied ^= least_significant_square_bb(bb);
|
1139 |
+
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
|
1140 |
+
}
|
1141 |
+
|
1142 |
+
else if ((bb = stmAttackers & pieces(ROOK)))
|
1143 |
+
{
|
1144 |
+
if ((swap = RookValueMg - swap) < res)
|
1145 |
+
break;
|
1146 |
+
|
1147 |
+
occupied ^= least_significant_square_bb(bb);
|
1148 |
+
attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
|
1149 |
+
}
|
1150 |
+
|
1151 |
+
else if ((bb = stmAttackers & pieces(QUEEN)))
|
1152 |
+
{
|
1153 |
+
if ((swap = QueenValueMg - swap) < res)
|
1154 |
+
break;
|
1155 |
+
|
1156 |
+
occupied ^= least_significant_square_bb(bb);
|
1157 |
+
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
|
1158 |
+
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
|
1159 |
+
}
|
1160 |
+
|
1161 |
+
else // KING
|
1162 |
+
// If we "capture" with the king but opponent still has attackers,
|
1163 |
+
// reverse the result.
|
1164 |
+
return (attackers & ~pieces(stm)) ? res ^ 1 : res;
|
1165 |
+
}
|
1166 |
+
|
1167 |
+
return bool(res);
|
1168 |
+
}
|
1169 |
+
|
1170 |
+
|
1171 |
+
/// Position::is_draw() tests whether the position is drawn by 50-move rule
|
1172 |
+
/// or by repetition. It does not detect stalemates.
|
1173 |
+
|
1174 |
+
bool Position::is_draw(int ply) const {
|
1175 |
+
|
1176 |
+
if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
|
1177 |
+
return true;
|
1178 |
+
|
1179 |
+
// Return a draw score if a position repeats once earlier but strictly
|
1180 |
+
// after the root, or repeats twice before or at the root.
|
1181 |
+
return st->repetition && st->repetition < ply;
|
1182 |
+
}
|
1183 |
+
|
1184 |
+
|
1185 |
+
// Position::has_repeated() tests whether there has been at least one repetition
|
1186 |
+
// of positions since the last capture or pawn move.
|
1187 |
+
|
1188 |
+
bool Position::has_repeated() const {
|
1189 |
+
|
1190 |
+
StateInfo* stc = st;
|
1191 |
+
int end = std::min(st->rule50, st->pliesFromNull);
|
1192 |
+
while (end-- >= 4)
|
1193 |
+
{
|
1194 |
+
if (stc->repetition)
|
1195 |
+
return true;
|
1196 |
+
|
1197 |
+
stc = stc->previous;
|
1198 |
+
}
|
1199 |
+
return false;
|
1200 |
+
}
|
1201 |
+
|
1202 |
+
|
1203 |
+
/// Position::has_game_cycle() tests if the position has a move which draws by repetition,
|
1204 |
+
/// or an earlier position has a move that directly reaches the current position.
|
1205 |
+
|
1206 |
+
bool Position::has_game_cycle(int ply) const {
|
1207 |
+
|
1208 |
+
int j;
|
1209 |
+
|
1210 |
+
int end = std::min(st->rule50, st->pliesFromNull);
|
1211 |
+
|
1212 |
+
if (end < 3)
|
1213 |
+
return false;
|
1214 |
+
|
1215 |
+
Key originalKey = st->key;
|
1216 |
+
StateInfo* stp = st->previous;
|
1217 |
+
|
1218 |
+
for (int i = 3; i <= end; i += 2)
|
1219 |
+
{
|
1220 |
+
stp = stp->previous->previous;
|
1221 |
+
|
1222 |
+
Key moveKey = originalKey ^ stp->key;
|
1223 |
+
if ( (j = H1(moveKey), cuckoo[j] == moveKey)
|
1224 |
+
|| (j = H2(moveKey), cuckoo[j] == moveKey))
|
1225 |
+
{
|
1226 |
+
Move move = cuckooMove[j];
|
1227 |
+
Square s1 = from_sq(move);
|
1228 |
+
Square s2 = to_sq(move);
|
1229 |
+
|
1230 |
+
if (!((between_bb(s1, s2) ^ s2) & pieces()))
|
1231 |
+
{
|
1232 |
+
if (ply > i)
|
1233 |
+
return true;
|
1234 |
+
|
1235 |
+
// For nodes before or at the root, check that the move is a
|
1236 |
+
// repetition rather than a move to the current position.
|
1237 |
+
// In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in
|
1238 |
+
// the same location, so we have to select which square to check.
|
1239 |
+
if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move())
|
1240 |
+
continue;
|
1241 |
+
|
1242 |
+
// For repetitions before or at the root, require one more
|
1243 |
+
if (stp->repetition)
|
1244 |
+
return true;
|
1245 |
+
}
|
1246 |
+
}
|
1247 |
+
}
|
1248 |
+
return false;
|
1249 |
+
}
|
1250 |
+
|
1251 |
+
|
1252 |
+
/// Position::flip() flips position with the white and black sides reversed. This
|
1253 |
+
/// is only useful for debugging e.g. for finding evaluation symmetry bugs.
|
1254 |
+
|
1255 |
+
void Position::flip() {
|
1256 |
+
|
1257 |
+
string f, token;
|
1258 |
+
std::stringstream ss(fen());
|
1259 |
+
|
1260 |
+
for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement
|
1261 |
+
{
|
1262 |
+
std::getline(ss, token, r > RANK_1 ? '/' : ' ');
|
1263 |
+
f.insert(0, token + (f.empty() ? " " : "/"));
|
1264 |
+
}
|
1265 |
+
|
1266 |
+
ss >> token; // Active color
|
1267 |
+
f += (token == "w" ? "B " : "W "); // Will be lowercased later
|
1268 |
+
|
1269 |
+
ss >> token; // Castling availability
|
1270 |
+
f += token + " ";
|
1271 |
+
|
1272 |
+
std::transform(f.begin(), f.end(), f.begin(),
|
1273 |
+
[](char c) { return char(islower(c) ? toupper(c) : tolower(c)); });
|
1274 |
+
|
1275 |
+
ss >> token; // En passant square
|
1276 |
+
f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3"));
|
1277 |
+
|
1278 |
+
std::getline(ss, token); // Half and full moves
|
1279 |
+
f += token;
|
1280 |
+
|
1281 |
+
set(f, is_chess960(), st, this_thread());
|
1282 |
+
|
1283 |
+
assert(pos_is_ok());
|
1284 |
+
}
|
1285 |
+
|
1286 |
+
|
1287 |
+
/// Position::pos_is_ok() performs some consistency checks for the
|
1288 |
+
/// position object and raises an asserts if something wrong is detected.
|
1289 |
+
/// This is meant to be helpful when debugging.
|
1290 |
+
|
1291 |
+
bool Position::pos_is_ok() const {
|
1292 |
+
|
1293 |
+
constexpr bool Fast = true; // Quick (default) or full check?
|
1294 |
+
|
1295 |
+
if ( (sideToMove != WHITE && sideToMove != BLACK)
|
1296 |
+
|| piece_on(square<KING>(WHITE)) != W_KING
|
1297 |
+
|| piece_on(square<KING>(BLACK)) != B_KING
|
1298 |
+
|| ( ep_square() != SQ_NONE
|
1299 |
+
&& relative_rank(sideToMove, ep_square()) != RANK_6))
|
1300 |
+
assert(0 && "pos_is_ok: Default");
|
1301 |
+
|
1302 |
+
if (Fast)
|
1303 |
+
return true;
|
1304 |
+
|
1305 |
+
if ( pieceCount[W_KING] != 1
|
1306 |
+
|| pieceCount[B_KING] != 1
|
1307 |
+
|| attackers_to(square<KING>(~sideToMove)) & pieces(sideToMove))
|
1308 |
+
assert(0 && "pos_is_ok: Kings");
|
1309 |
+
|
1310 |
+
if ( (pieces(PAWN) & (Rank1BB | Rank8BB))
|
1311 |
+
|| pieceCount[W_PAWN] > 8
|
1312 |
+
|| pieceCount[B_PAWN] > 8)
|
1313 |
+
assert(0 && "pos_is_ok: Pawns");
|
1314 |
+
|
1315 |
+
if ( (pieces(WHITE) & pieces(BLACK))
|
1316 |
+
|| (pieces(WHITE) | pieces(BLACK)) != pieces()
|
1317 |
+
|| popcount(pieces(WHITE)) > 16
|
1318 |
+
|| popcount(pieces(BLACK)) > 16)
|
1319 |
+
assert(0 && "pos_is_ok: Bitboards");
|
1320 |
+
|
1321 |
+
for (PieceType p1 = PAWN; p1 <= KING; ++p1)
|
1322 |
+
for (PieceType p2 = PAWN; p2 <= KING; ++p2)
|
1323 |
+
if (p1 != p2 && (pieces(p1) & pieces(p2)))
|
1324 |
+
assert(0 && "pos_is_ok: Bitboards");
|
1325 |
+
|
1326 |
+
StateInfo si = *st;
|
1327 |
+
ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize);
|
1328 |
+
|
1329 |
+
set_state(&si);
|
1330 |
+
if (std::memcmp(&si, st, sizeof(StateInfo)))
|
1331 |
+
assert(0 && "pos_is_ok: State");
|
1332 |
+
|
1333 |
+
for (Piece pc : Pieces)
|
1334 |
+
if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
|
1335 |
+
|| pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
|
1336 |
+
assert(0 && "pos_is_ok: Pieces");
|
1337 |
+
|
1338 |
+
for (Color c : { WHITE, BLACK })
|
1339 |
+
for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})
|
1340 |
+
{
|
1341 |
+
if (!can_castle(cr))
|
1342 |
+
continue;
|
1343 |
+
|
1344 |
+
if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
|
1345 |
+
|| castlingRightsMask[castlingRookSquare[cr]] != cr
|
1346 |
+
|| (castlingRightsMask[square<KING>(c)] & cr) != cr)
|
1347 |
+
assert(0 && "pos_is_ok: Castling");
|
1348 |
+
}
|
1349 |
+
|
1350 |
+
return true;
|
1351 |
+
}
|
1352 |
+
|
1353 |
+
} // namespace Stockfish
|
src/position.h
ADDED
@@ -0,0 +1,443 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef POSITION_H_INCLUDED
|
20 |
+
#define POSITION_H_INCLUDED
|
21 |
+
|
22 |
+
#include <cassert>
|
23 |
+
#include <deque>
|
24 |
+
#include <memory> // For std::unique_ptr
|
25 |
+
#include <string>
|
26 |
+
|
27 |
+
#include "bitboard.h"
|
28 |
+
#include "evaluate.h"
|
29 |
+
#include "psqt.h"
|
30 |
+
#include "types.h"
|
31 |
+
|
32 |
+
#include "nnue/nnue_accumulator.h"
|
33 |
+
|
34 |
+
namespace Stockfish {
|
35 |
+
|
36 |
+
/// StateInfo struct stores information needed to restore a Position object to
|
37 |
+
/// its previous state when we retract a move. Whenever a move is made on the
|
38 |
+
/// board (by calling Position::do_move), a StateInfo object must be passed.
|
39 |
+
|
40 |
+
struct StateInfo {
|
41 |
+
|
42 |
+
// Copied when making a move
|
43 |
+
Key pawnKey;
|
44 |
+
Key materialKey;
|
45 |
+
Value nonPawnMaterial[COLOR_NB];
|
46 |
+
int castlingRights;
|
47 |
+
int rule50;
|
48 |
+
int pliesFromNull;
|
49 |
+
Square epSquare;
|
50 |
+
|
51 |
+
// Not copied when making a move (will be recomputed anyhow)
|
52 |
+
Key key;
|
53 |
+
Bitboard checkersBB;
|
54 |
+
StateInfo* previous;
|
55 |
+
Bitboard blockersForKing[COLOR_NB];
|
56 |
+
Bitboard pinners[COLOR_NB];
|
57 |
+
Bitboard checkSquares[PIECE_TYPE_NB];
|
58 |
+
Piece capturedPiece;
|
59 |
+
int repetition;
|
60 |
+
|
61 |
+
// Used by NNUE
|
62 |
+
Eval::NNUE::Accumulator accumulator;
|
63 |
+
DirtyPiece dirtyPiece;
|
64 |
+
};
|
65 |
+
|
66 |
+
|
67 |
+
/// A list to keep track of the position states along the setup moves (from the
|
68 |
+
/// start position to the position just before the search starts). Needed by
|
69 |
+
/// 'draw by repetition' detection. Use a std::deque because pointers to
|
70 |
+
/// elements are not invalidated upon list resizing.
|
71 |
+
typedef std::unique_ptr<std::deque<StateInfo>> StateListPtr;
|
72 |
+
|
73 |
+
|
74 |
+
/// Position class stores information regarding the board representation as
|
75 |
+
/// pieces, side to move, hash keys, castling info, etc. Important methods are
|
76 |
+
/// do_move() and undo_move(), used by the search to update node info when
|
77 |
+
/// traversing the search tree.
|
78 |
+
class Thread;
|
79 |
+
|
80 |
+
class Position {
|
81 |
+
public:
|
82 |
+
static void init();
|
83 |
+
|
84 |
+
Position() = default;
|
85 |
+
Position(const Position&) = delete;
|
86 |
+
Position& operator=(const Position&) = delete;
|
87 |
+
|
88 |
+
// FEN string input/output
|
89 |
+
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
|
90 |
+
Position& set(const std::string& code, Color c, StateInfo* si);
|
91 |
+
std::string fen() const;
|
92 |
+
|
93 |
+
// Position representation
|
94 |
+
Bitboard pieces(PieceType pt) const;
|
95 |
+
Bitboard pieces(PieceType pt1, PieceType pt2) const;
|
96 |
+
Bitboard pieces(Color c) const;
|
97 |
+
Bitboard pieces(Color c, PieceType pt) const;
|
98 |
+
Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const;
|
99 |
+
Piece piece_on(Square s) const;
|
100 |
+
Square ep_square() const;
|
101 |
+
bool empty(Square s) const;
|
102 |
+
template<PieceType Pt> int count(Color c) const;
|
103 |
+
template<PieceType Pt> int count() const;
|
104 |
+
template<PieceType Pt> Square square(Color c) const;
|
105 |
+
bool is_on_semiopen_file(Color c, Square s) const;
|
106 |
+
|
107 |
+
// Castling
|
108 |
+
CastlingRights castling_rights(Color c) const;
|
109 |
+
bool can_castle(CastlingRights cr) const;
|
110 |
+
bool castling_impeded(CastlingRights cr) const;
|
111 |
+
Square castling_rook_square(CastlingRights cr) const;
|
112 |
+
|
113 |
+
// Checking
|
114 |
+
Bitboard checkers() const;
|
115 |
+
Bitboard blockers_for_king(Color c) const;
|
116 |
+
Bitboard check_squares(PieceType pt) const;
|
117 |
+
Bitboard pinners(Color c) const;
|
118 |
+
|
119 |
+
// Attacks to/from a given square
|
120 |
+
Bitboard attackers_to(Square s) const;
|
121 |
+
Bitboard attackers_to(Square s, Bitboard occupied) const;
|
122 |
+
Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
|
123 |
+
template<PieceType Pt> Bitboard attacks_by(Color c) const;
|
124 |
+
|
125 |
+
// Properties of moves
|
126 |
+
bool legal(Move m) const;
|
127 |
+
bool pseudo_legal(const Move m) const;
|
128 |
+
bool capture(Move m) const;
|
129 |
+
bool gives_check(Move m) const;
|
130 |
+
Piece moved_piece(Move m) const;
|
131 |
+
Piece captured_piece() const;
|
132 |
+
|
133 |
+
// Piece specific
|
134 |
+
bool pawn_passed(Color c, Square s) const;
|
135 |
+
bool opposite_bishops() const;
|
136 |
+
int pawns_on_same_color_squares(Color c, Square s) const;
|
137 |
+
|
138 |
+
// Doing and undoing moves
|
139 |
+
void do_move(Move m, StateInfo& newSt);
|
140 |
+
void do_move(Move m, StateInfo& newSt, bool givesCheck);
|
141 |
+
void undo_move(Move m);
|
142 |
+
void do_null_move(StateInfo& newSt);
|
143 |
+
void undo_null_move();
|
144 |
+
|
145 |
+
// Static Exchange Evaluation
|
146 |
+
bool see_ge(Move m, Value threshold = VALUE_ZERO) const;
|
147 |
+
|
148 |
+
// Accessing hash keys
|
149 |
+
Key key() const;
|
150 |
+
Key key_after(Move m) const;
|
151 |
+
Key material_key() const;
|
152 |
+
Key pawn_key() const;
|
153 |
+
|
154 |
+
// Other properties of the position
|
155 |
+
Color side_to_move() const;
|
156 |
+
int game_ply() const;
|
157 |
+
bool is_chess960() const;
|
158 |
+
Thread* this_thread() const;
|
159 |
+
bool is_draw(int ply) const;
|
160 |
+
bool has_game_cycle(int ply) const;
|
161 |
+
bool has_repeated() const;
|
162 |
+
int rule50_count() const;
|
163 |
+
Score psq_score() const;
|
164 |
+
Value psq_eg_stm() const;
|
165 |
+
Value non_pawn_material(Color c) const;
|
166 |
+
Value non_pawn_material() const;
|
167 |
+
|
168 |
+
// Position consistency check, for debugging
|
169 |
+
bool pos_is_ok() const;
|
170 |
+
void flip();
|
171 |
+
|
172 |
+
// Used by NNUE
|
173 |
+
StateInfo* state() const;
|
174 |
+
|
175 |
+
void put_piece(Piece pc, Square s);
|
176 |
+
void remove_piece(Square s);
|
177 |
+
|
178 |
+
private:
|
179 |
+
// Initialization helpers (used while setting up a position)
|
180 |
+
void set_castling_right(Color c, Square rfrom);
|
181 |
+
void set_state(StateInfo* si) const;
|
182 |
+
void set_check_info(StateInfo* si) const;
|
183 |
+
|
184 |
+
// Other helpers
|
185 |
+
void move_piece(Square from, Square to);
|
186 |
+
template<bool Do>
|
187 |
+
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
|
188 |
+
template<bool AfterMove>
|
189 |
+
Key adjust_key50(Key k) const;
|
190 |
+
|
191 |
+
// Data members
|
192 |
+
Piece board[SQUARE_NB];
|
193 |
+
Bitboard byTypeBB[PIECE_TYPE_NB];
|
194 |
+
Bitboard byColorBB[COLOR_NB];
|
195 |
+
int pieceCount[PIECE_NB];
|
196 |
+
int castlingRightsMask[SQUARE_NB];
|
197 |
+
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
198 |
+
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
199 |
+
Thread* thisThread;
|
200 |
+
StateInfo* st;
|
201 |
+
int gamePly;
|
202 |
+
Color sideToMove;
|
203 |
+
Score psq;
|
204 |
+
bool chess960;
|
205 |
+
};
|
206 |
+
|
207 |
+
extern std::ostream& operator<<(std::ostream& os, const Position& pos);
|
208 |
+
|
209 |
+
inline Color Position::side_to_move() const {
|
210 |
+
return sideToMove;
|
211 |
+
}
|
212 |
+
|
213 |
+
inline Piece Position::piece_on(Square s) const {
|
214 |
+
assert(is_ok(s));
|
215 |
+
return board[s];
|
216 |
+
}
|
217 |
+
|
218 |
+
inline bool Position::empty(Square s) const {
|
219 |
+
return piece_on(s) == NO_PIECE;
|
220 |
+
}
|
221 |
+
|
222 |
+
inline Piece Position::moved_piece(Move m) const {
|
223 |
+
return piece_on(from_sq(m));
|
224 |
+
}
|
225 |
+
|
226 |
+
inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const {
|
227 |
+
return byTypeBB[pt];
|
228 |
+
}
|
229 |
+
|
230 |
+
inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const {
|
231 |
+
return pieces(pt1) | pieces(pt2);
|
232 |
+
}
|
233 |
+
|
234 |
+
inline Bitboard Position::pieces(Color c) const {
|
235 |
+
return byColorBB[c];
|
236 |
+
}
|
237 |
+
|
238 |
+
inline Bitboard Position::pieces(Color c, PieceType pt) const {
|
239 |
+
return pieces(c) & pieces(pt);
|
240 |
+
}
|
241 |
+
|
242 |
+
inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const {
|
243 |
+
return pieces(c) & (pieces(pt1) | pieces(pt2));
|
244 |
+
}
|
245 |
+
|
246 |
+
template<PieceType Pt> inline int Position::count(Color c) const {
|
247 |
+
return pieceCount[make_piece(c, Pt)];
|
248 |
+
}
|
249 |
+
|
250 |
+
template<PieceType Pt> inline int Position::count() const {
|
251 |
+
return count<Pt>(WHITE) + count<Pt>(BLACK);
|
252 |
+
}
|
253 |
+
|
254 |
+
template<PieceType Pt> inline Square Position::square(Color c) const {
|
255 |
+
assert(count<Pt>(c) == 1);
|
256 |
+
return lsb(pieces(c, Pt));
|
257 |
+
}
|
258 |
+
|
259 |
+
inline Square Position::ep_square() const {
|
260 |
+
return st->epSquare;
|
261 |
+
}
|
262 |
+
|
263 |
+
inline bool Position::is_on_semiopen_file(Color c, Square s) const {
|
264 |
+
return !(pieces(c, PAWN) & file_bb(s));
|
265 |
+
}
|
266 |
+
|
267 |
+
inline bool Position::can_castle(CastlingRights cr) const {
|
268 |
+
return st->castlingRights & cr;
|
269 |
+
}
|
270 |
+
|
271 |
+
inline CastlingRights Position::castling_rights(Color c) const {
|
272 |
+
return c & CastlingRights(st->castlingRights);
|
273 |
+
}
|
274 |
+
|
275 |
+
inline bool Position::castling_impeded(CastlingRights cr) const {
|
276 |
+
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
277 |
+
|
278 |
+
return pieces() & castlingPath[cr];
|
279 |
+
}
|
280 |
+
|
281 |
+
inline Square Position::castling_rook_square(CastlingRights cr) const {
|
282 |
+
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
283 |
+
|
284 |
+
return castlingRookSquare[cr];
|
285 |
+
}
|
286 |
+
|
287 |
+
inline Bitboard Position::attackers_to(Square s) const {
|
288 |
+
return attackers_to(s, pieces());
|
289 |
+
}
|
290 |
+
|
291 |
+
template<PieceType Pt>
|
292 |
+
inline Bitboard Position::attacks_by(Color c) const {
|
293 |
+
|
294 |
+
if constexpr (Pt == PAWN)
|
295 |
+
return c == WHITE ? pawn_attacks_bb<WHITE>(pieces(WHITE, PAWN))
|
296 |
+
: pawn_attacks_bb<BLACK>(pieces(BLACK, PAWN));
|
297 |
+
else
|
298 |
+
{
|
299 |
+
Bitboard threats = 0;
|
300 |
+
Bitboard attackers = pieces(c, Pt);
|
301 |
+
while (attackers)
|
302 |
+
threats |= attacks_bb<Pt>(pop_lsb(attackers), pieces());
|
303 |
+
return threats;
|
304 |
+
}
|
305 |
+
}
|
306 |
+
|
307 |
+
inline Bitboard Position::checkers() const {
|
308 |
+
return st->checkersBB;
|
309 |
+
}
|
310 |
+
|
311 |
+
inline Bitboard Position::blockers_for_king(Color c) const {
|
312 |
+
return st->blockersForKing[c];
|
313 |
+
}
|
314 |
+
|
315 |
+
inline Bitboard Position::pinners(Color c) const {
|
316 |
+
return st->pinners[c];
|
317 |
+
}
|
318 |
+
|
319 |
+
inline Bitboard Position::check_squares(PieceType pt) const {
|
320 |
+
return st->checkSquares[pt];
|
321 |
+
}
|
322 |
+
|
323 |
+
inline bool Position::pawn_passed(Color c, Square s) const {
|
324 |
+
return !(pieces(~c, PAWN) & passed_pawn_span(c, s));
|
325 |
+
}
|
326 |
+
|
327 |
+
inline int Position::pawns_on_same_color_squares(Color c, Square s) const {
|
328 |
+
return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares));
|
329 |
+
}
|
330 |
+
|
331 |
+
inline Key Position::key() const {
|
332 |
+
return adjust_key50<false>(st->key);
|
333 |
+
}
|
334 |
+
|
335 |
+
template<bool AfterMove>
|
336 |
+
inline Key Position::adjust_key50(Key k) const
|
337 |
+
{
|
338 |
+
return st->rule50 < 14 - AfterMove
|
339 |
+
? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
|
340 |
+
}
|
341 |
+
|
342 |
+
inline Key Position::pawn_key() const {
|
343 |
+
return st->pawnKey;
|
344 |
+
}
|
345 |
+
|
346 |
+
inline Key Position::material_key() const {
|
347 |
+
return st->materialKey;
|
348 |
+
}
|
349 |
+
|
350 |
+
inline Score Position::psq_score() const {
|
351 |
+
return psq;
|
352 |
+
}
|
353 |
+
|
354 |
+
inline Value Position::psq_eg_stm() const {
|
355 |
+
return (sideToMove == WHITE ? 1 : -1) * eg_value(psq);
|
356 |
+
}
|
357 |
+
|
358 |
+
inline Value Position::non_pawn_material(Color c) const {
|
359 |
+
return st->nonPawnMaterial[c];
|
360 |
+
}
|
361 |
+
|
362 |
+
inline Value Position::non_pawn_material() const {
|
363 |
+
return non_pawn_material(WHITE) + non_pawn_material(BLACK);
|
364 |
+
}
|
365 |
+
|
366 |
+
inline int Position::game_ply() const {
|
367 |
+
return gamePly;
|
368 |
+
}
|
369 |
+
|
370 |
+
inline int Position::rule50_count() const {
|
371 |
+
return st->rule50;
|
372 |
+
}
|
373 |
+
|
374 |
+
inline bool Position::opposite_bishops() const {
|
375 |
+
return count<BISHOP>(WHITE) == 1
|
376 |
+
&& count<BISHOP>(BLACK) == 1
|
377 |
+
&& opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
|
378 |
+
}
|
379 |
+
|
380 |
+
inline bool Position::is_chess960() const {
|
381 |
+
return chess960;
|
382 |
+
}
|
383 |
+
|
384 |
+
inline bool Position::capture(Move m) const {
|
385 |
+
assert(is_ok(m));
|
386 |
+
// Castling is encoded as "king captures rook"
|
387 |
+
return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT;
|
388 |
+
}
|
389 |
+
|
390 |
+
inline Piece Position::captured_piece() const {
|
391 |
+
return st->capturedPiece;
|
392 |
+
}
|
393 |
+
|
394 |
+
inline Thread* Position::this_thread() const {
|
395 |
+
return thisThread;
|
396 |
+
}
|
397 |
+
|
398 |
+
inline void Position::put_piece(Piece pc, Square s) {
|
399 |
+
|
400 |
+
board[s] = pc;
|
401 |
+
byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
|
402 |
+
byColorBB[color_of(pc)] |= s;
|
403 |
+
pieceCount[pc]++;
|
404 |
+
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
|
405 |
+
psq += PSQT::psq[pc][s];
|
406 |
+
}
|
407 |
+
|
408 |
+
inline void Position::remove_piece(Square s) {
|
409 |
+
|
410 |
+
Piece pc = board[s];
|
411 |
+
byTypeBB[ALL_PIECES] ^= s;
|
412 |
+
byTypeBB[type_of(pc)] ^= s;
|
413 |
+
byColorBB[color_of(pc)] ^= s;
|
414 |
+
board[s] = NO_PIECE;
|
415 |
+
pieceCount[pc]--;
|
416 |
+
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
|
417 |
+
psq -= PSQT::psq[pc][s];
|
418 |
+
}
|
419 |
+
|
420 |
+
inline void Position::move_piece(Square from, Square to) {
|
421 |
+
|
422 |
+
Piece pc = board[from];
|
423 |
+
Bitboard fromTo = from | to;
|
424 |
+
byTypeBB[ALL_PIECES] ^= fromTo;
|
425 |
+
byTypeBB[type_of(pc)] ^= fromTo;
|
426 |
+
byColorBB[color_of(pc)] ^= fromTo;
|
427 |
+
board[from] = NO_PIECE;
|
428 |
+
board[to] = pc;
|
429 |
+
psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
|
430 |
+
}
|
431 |
+
|
432 |
+
inline void Position::do_move(Move m, StateInfo& newSt) {
|
433 |
+
do_move(m, newSt, gives_check(m));
|
434 |
+
}
|
435 |
+
|
436 |
+
inline StateInfo* Position::state() const {
|
437 |
+
|
438 |
+
return st;
|
439 |
+
}
|
440 |
+
|
441 |
+
} // namespace Stockfish
|
442 |
+
|
443 |
+
#endif // #ifndef POSITION_H_INCLUDED
|
src/psqt.cpp
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
|
20 |
+
#include "psqt.h"
|
21 |
+
|
22 |
+
#include <algorithm>
|
23 |
+
|
24 |
+
#include "bitboard.h"
|
25 |
+
#include "types.h"
|
26 |
+
|
27 |
+
namespace Stockfish {
|
28 |
+
|
29 |
+
namespace
|
30 |
+
{
|
31 |
+
|
32 |
+
auto constexpr S = make_score;
|
33 |
+
|
34 |
+
// 'Bonus' contains Piece-Square parameters.
|
35 |
+
// Scores are explicit for files A to D, implicitly mirrored for E to H.
|
36 |
+
constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
|
37 |
+
{ },
|
38 |
+
{ },
|
39 |
+
{ // Knight
|
40 |
+
{ S(-175, -96), S(-92,-65), S(-74,-49), S(-73,-21) },
|
41 |
+
{ S( -77, -67), S(-41,-54), S(-27,-18), S(-15, 8) },
|
42 |
+
{ S( -61, -40), S(-17,-27), S( 6, -8), S( 12, 29) },
|
43 |
+
{ S( -35, -35), S( 8, -2), S( 40, 13), S( 49, 28) },
|
44 |
+
{ S( -34, -45), S( 13,-16), S( 44, 9), S( 51, 39) },
|
45 |
+
{ S( -9, -51), S( 22,-44), S( 58,-16), S( 53, 17) },
|
46 |
+
{ S( -67, -69), S(-27,-50), S( 4,-51), S( 37, 12) },
|
47 |
+
{ S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) }
|
48 |
+
},
|
49 |
+
{ // Bishop
|
50 |
+
{ S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) },
|
51 |
+
{ S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) },
|
52 |
+
{ S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) },
|
53 |
+
{ S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) },
|
54 |
+
{ S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) },
|
55 |
+
{ S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) },
|
56 |
+
{ S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) },
|
57 |
+
{ S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) }
|
58 |
+
},
|
59 |
+
{ // Rook
|
60 |
+
{ S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) },
|
61 |
+
{ S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) },
|
62 |
+
{ S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) },
|
63 |
+
{ S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) },
|
64 |
+
{ S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) },
|
65 |
+
{ S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) },
|
66 |
+
{ S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) },
|
67 |
+
{ S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) }
|
68 |
+
},
|
69 |
+
{ // Queen
|
70 |
+
{ S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) },
|
71 |
+
{ S(-3,-54), S( 5,-31), S( 8,-22), S(12, -4) },
|
72 |
+
{ S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) },
|
73 |
+
{ S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) },
|
74 |
+
{ S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) },
|
75 |
+
{ S(-4,-38), S(10,-18), S( 6,-11), S( 8, 1) },
|
76 |
+
{ S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) },
|
77 |
+
{ S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) }
|
78 |
+
},
|
79 |
+
{ // King
|
80 |
+
{ S(271, 1), S(327, 45), S(271, 85), S(198, 76) },
|
81 |
+
{ S(278, 53), S(303,100), S(234,133), S(179,135) },
|
82 |
+
{ S(195, 88), S(258,130), S(169,169), S(120,175) },
|
83 |
+
{ S(164,103), S(190,156), S(138,172), S( 98,172) },
|
84 |
+
{ S(154, 96), S(179,166), S(105,199), S( 70,199) },
|
85 |
+
{ S(123, 92), S(145,172), S( 81,184), S( 31,191) },
|
86 |
+
{ S( 88, 47), S(120,121), S( 65,116), S( 33,131) },
|
87 |
+
{ S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) }
|
88 |
+
}
|
89 |
+
};
|
90 |
+
|
91 |
+
constexpr Score PBonus[RANK_NB][FILE_NB] =
|
92 |
+
{ // Pawn (asymmetric distribution)
|
93 |
+
{ },
|
94 |
+
{ S( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) },
|
95 |
+
{ S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) },
|
96 |
+
{ S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) },
|
97 |
+
{ S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) },
|
98 |
+
{ S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) },
|
99 |
+
{ S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) }
|
100 |
+
};
|
101 |
+
|
102 |
+
} // namespace
|
103 |
+
|
104 |
+
|
105 |
+
namespace PSQT
|
106 |
+
{
|
107 |
+
|
108 |
+
Score psq[PIECE_NB][SQUARE_NB];
|
109 |
+
|
110 |
+
// PSQT::init() initializes piece-square tables: the white halves of the tables are
|
111 |
+
// copied from Bonus[] and PBonus[], adding the piece value, then the black halves of
|
112 |
+
// the tables are initialized by flipping and changing the sign of the white scores.
|
113 |
+
void init() {
|
114 |
+
|
115 |
+
for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING})
|
116 |
+
{
|
117 |
+
Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
|
118 |
+
|
119 |
+
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
120 |
+
{
|
121 |
+
File f = File(edge_distance(file_of(s)));
|
122 |
+
psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
|
123 |
+
: Bonus[pc][rank_of(s)][f]);
|
124 |
+
psq[~pc][flip_rank(s)] = -psq[pc][s];
|
125 |
+
}
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
} // namespace PSQT
|
130 |
+
|
131 |
+
} // namespace Stockfish
|
src/psqt.h
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
|
20 |
+
#ifndef PSQT_H_INCLUDED
|
21 |
+
#define PSQT_H_INCLUDED
|
22 |
+
|
23 |
+
|
24 |
+
#include "types.h"
|
25 |
+
|
26 |
+
|
27 |
+
namespace Stockfish::PSQT
|
28 |
+
{
|
29 |
+
|
30 |
+
extern Score psq[PIECE_NB][SQUARE_NB];
|
31 |
+
|
32 |
+
// Fill psqt array from a set of internally linked parameters
|
33 |
+
extern void init();
|
34 |
+
|
35 |
+
} // namespace Stockfish::PSQT
|
36 |
+
|
37 |
+
|
38 |
+
#endif // PSQT_H_INCLUDED
|
src/search.cpp
ADDED
@@ -0,0 +1,1959 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <algorithm>
|
20 |
+
#include <cassert>
|
21 |
+
#include <cmath>
|
22 |
+
#include <cstring> // For std::memset
|
23 |
+
#include <iostream>
|
24 |
+
#include <sstream>
|
25 |
+
|
26 |
+
#include "evaluate.h"
|
27 |
+
#include "misc.h"
|
28 |
+
#include "movegen.h"
|
29 |
+
#include "movepick.h"
|
30 |
+
#include "position.h"
|
31 |
+
#include "search.h"
|
32 |
+
#include "thread.h"
|
33 |
+
#include "timeman.h"
|
34 |
+
#include "tt.h"
|
35 |
+
#include "uci.h"
|
36 |
+
#include "syzygy/tbprobe.h"
|
37 |
+
|
38 |
+
namespace Stockfish {
|
39 |
+
|
40 |
+
namespace Search {
|
41 |
+
|
42 |
+
LimitsType Limits;
|
43 |
+
}
|
44 |
+
|
45 |
+
namespace Tablebases {
|
46 |
+
|
47 |
+
int Cardinality;
|
48 |
+
bool RootInTB;
|
49 |
+
bool UseRule50;
|
50 |
+
Depth ProbeDepth;
|
51 |
+
}
|
52 |
+
|
53 |
+
namespace TB = Tablebases;
|
54 |
+
|
55 |
+
using std::string;
|
56 |
+
using Eval::evaluate;
|
57 |
+
using namespace Search;
|
58 |
+
|
59 |
+
namespace {
|
60 |
+
|
61 |
+
// Different node types, used as a template parameter
|
62 |
+
enum NodeType { NonPV, PV, Root };
|
63 |
+
|
64 |
+
// Futility margin
|
65 |
+
Value futility_margin(Depth d, bool improving) {
|
66 |
+
return Value(165 * (d - improving));
|
67 |
+
}
|
68 |
+
|
69 |
+
// Reductions lookup table, initialized at startup
|
70 |
+
int Reductions[MAX_MOVES]; // [depth or moveNumber]
|
71 |
+
|
72 |
+
Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) {
|
73 |
+
int r = Reductions[d] * Reductions[mn];
|
74 |
+
return (r + 1642 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 916);
|
75 |
+
}
|
76 |
+
|
77 |
+
constexpr int futility_move_count(bool improving, Depth depth) {
|
78 |
+
return improving ? (3 + depth * depth)
|
79 |
+
: (3 + depth * depth) / 2;
|
80 |
+
}
|
81 |
+
|
82 |
+
// History and stats update bonus, based on depth
|
83 |
+
int stat_bonus(Depth d) {
|
84 |
+
return std::min((12 * d + 282) * d - 349 , 1594);
|
85 |
+
}
|
86 |
+
|
87 |
+
// Add a small random component to draw evaluations to avoid 3-fold blindness
|
88 |
+
Value value_draw(const Thread* thisThread) {
|
89 |
+
return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2);
|
90 |
+
}
|
91 |
+
|
92 |
+
// Skill structure is used to implement strength limit. If we have an uci_elo then
|
93 |
+
// we convert it to a suitable fractional skill level using anchoring to CCRL Elo
|
94 |
+
// (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for match (TC 60+0.6)
|
95 |
+
// results spanning a wide range of k values.
|
96 |
+
struct Skill {
|
97 |
+
Skill(int skill_level, int uci_elo) {
|
98 |
+
if (uci_elo)
|
99 |
+
level = std::clamp(std::pow((uci_elo - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0);
|
100 |
+
else
|
101 |
+
level = double(skill_level);
|
102 |
+
}
|
103 |
+
bool enabled() const { return level < 20.0; }
|
104 |
+
bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }
|
105 |
+
Move pick_best(size_t multiPV);
|
106 |
+
|
107 |
+
double level;
|
108 |
+
Move best = MOVE_NONE;
|
109 |
+
};
|
110 |
+
|
111 |
+
template <NodeType nodeType>
|
112 |
+
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
|
113 |
+
|
114 |
+
template <NodeType nodeType>
|
115 |
+
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
|
116 |
+
|
117 |
+
Value value_to_tt(Value v, int ply);
|
118 |
+
Value value_from_tt(Value v, int ply, int r50c);
|
119 |
+
void update_pv(Move* pv, Move move, const Move* childPv);
|
120 |
+
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
|
121 |
+
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus);
|
122 |
+
void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq,
|
123 |
+
Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth);
|
124 |
+
|
125 |
+
// perft() is our utility to verify move generation. All the leaf nodes up
|
126 |
+
// to the given depth are generated and counted, and the sum is returned.
|
127 |
+
template<bool Root>
|
128 |
+
uint64_t perft(Position& pos, Depth depth) {
|
129 |
+
|
130 |
+
StateInfo st;
|
131 |
+
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
132 |
+
|
133 |
+
uint64_t cnt, nodes = 0;
|
134 |
+
const bool leaf = (depth == 2);
|
135 |
+
|
136 |
+
for (const auto& m : MoveList<LEGAL>(pos))
|
137 |
+
{
|
138 |
+
if (Root && depth <= 1)
|
139 |
+
cnt = 1, nodes++;
|
140 |
+
else
|
141 |
+
{
|
142 |
+
pos.do_move(m, st);
|
143 |
+
cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - 1);
|
144 |
+
nodes += cnt;
|
145 |
+
pos.undo_move(m);
|
146 |
+
}
|
147 |
+
if (Root)
|
148 |
+
sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
|
149 |
+
}
|
150 |
+
return nodes;
|
151 |
+
}
|
152 |
+
|
153 |
+
} // namespace
|
154 |
+
|
155 |
+
|
156 |
+
/// Search::init() is called at startup to initialize various lookup tables
|
157 |
+
|
158 |
+
void Search::init() {
|
159 |
+
|
160 |
+
for (int i = 1; i < MAX_MOVES; ++i)
|
161 |
+
Reductions[i] = int((20.26 + std::log(Threads.size()) / 2) * std::log(i));
|
162 |
+
}
|
163 |
+
|
164 |
+
|
165 |
+
/// Search::clear() resets search state to its initial value
|
166 |
+
|
167 |
+
void Search::clear() {
|
168 |
+
|
169 |
+
Threads.main()->wait_for_search_finished();
|
170 |
+
|
171 |
+
Time.availableNodes = 0;
|
172 |
+
TT.clear();
|
173 |
+
Threads.clear();
|
174 |
+
Tablebases::init(Options["SyzygyPath"]); // Free mapped files
|
175 |
+
}
|
176 |
+
|
177 |
+
|
178 |
+
/// MainThread::search() is started when the program receives the UCI 'go'
|
179 |
+
/// command. It searches from the root position and outputs the "bestmove".
|
180 |
+
|
181 |
+
void MainThread::search() {
|
182 |
+
|
183 |
+
if (Limits.perft)
|
184 |
+
{
|
185 |
+
nodes = perft<true>(rootPos, Limits.perft);
|
186 |
+
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
|
187 |
+
return;
|
188 |
+
}
|
189 |
+
|
190 |
+
Color us = rootPos.side_to_move();
|
191 |
+
Time.init(Limits, us, rootPos.game_ply());
|
192 |
+
TT.new_search();
|
193 |
+
|
194 |
+
Eval::NNUE::verify();
|
195 |
+
|
196 |
+
if (rootMoves.empty())
|
197 |
+
{
|
198 |
+
rootMoves.emplace_back(MOVE_NONE);
|
199 |
+
sync_cout << "info depth 0 score "
|
200 |
+
<< UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW)
|
201 |
+
<< sync_endl;
|
202 |
+
}
|
203 |
+
else
|
204 |
+
{
|
205 |
+
Threads.start_searching(); // start non-main threads
|
206 |
+
Thread::search(); // main thread start searching
|
207 |
+
}
|
208 |
+
|
209 |
+
// When we reach the maximum depth, we can arrive here without a raise of
|
210 |
+
// Threads.stop. However, if we are pondering or in an infinite search,
|
211 |
+
// the UCI protocol states that we shouldn't print the best move before the
|
212 |
+
// GUI sends a "stop" or "ponderhit" command. We therefore simply wait here
|
213 |
+
// until the GUI sends one of those commands.
|
214 |
+
|
215 |
+
while (!Threads.stop && (ponder || Limits.infinite))
|
216 |
+
{} // Busy wait for a stop or a ponder reset
|
217 |
+
|
218 |
+
// Stop the threads if not already stopped (also raise the stop if
|
219 |
+
// "ponderhit" just reset Threads.ponder).
|
220 |
+
Threads.stop = true;
|
221 |
+
|
222 |
+
// Wait until all threads have finished
|
223 |
+
Threads.wait_for_search_finished();
|
224 |
+
|
225 |
+
// When playing in 'nodes as time' mode, subtract the searched nodes from
|
226 |
+
// the available ones before exiting.
|
227 |
+
if (Limits.npmsec)
|
228 |
+
Time.availableNodes += Limits.inc[us] - Threads.nodes_searched();
|
229 |
+
|
230 |
+
Thread* bestThread = this;
|
231 |
+
Skill skill = Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0);
|
232 |
+
|
233 |
+
if ( int(Options["MultiPV"]) == 1
|
234 |
+
&& !Limits.depth
|
235 |
+
&& !skill.enabled()
|
236 |
+
&& rootMoves[0].pv[0] != MOVE_NONE)
|
237 |
+
bestThread = Threads.get_best_thread();
|
238 |
+
|
239 |
+
bestPreviousScore = bestThread->rootMoves[0].score;
|
240 |
+
bestPreviousAverageScore = bestThread->rootMoves[0].averageScore;
|
241 |
+
|
242 |
+
for (Thread* th : Threads)
|
243 |
+
th->previousDepth = bestThread->completedDepth;
|
244 |
+
|
245 |
+
// Send again PV info if we have a new best thread
|
246 |
+
if (bestThread != this)
|
247 |
+
sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl;
|
248 |
+
|
249 |
+
sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
|
250 |
+
|
251 |
+
if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
|
252 |
+
std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
|
253 |
+
|
254 |
+
std::cout << sync_endl;
|
255 |
+
}
|
256 |
+
|
257 |
+
|
258 |
+
/// Thread::search() is the main iterative deepening loop. It calls search()
|
259 |
+
/// repeatedly with increasing depth until the allocated thinking time has been
|
260 |
+
/// consumed, the user stops the search, or the maximum search depth is reached.
|
261 |
+
|
262 |
+
void Thread::search() {
|
263 |
+
|
264 |
+
// To allow access to (ss-7) up to (ss+2), the stack must be oversized.
|
265 |
+
// The former is needed to allow update_continuation_histories(ss-1, ...),
|
266 |
+
// which accesses its argument at ss-6, also near the root.
|
267 |
+
// The latter is needed for statScore and killer initialization.
|
268 |
+
Stack stack[MAX_PLY+10], *ss = stack+7;
|
269 |
+
Move pv[MAX_PLY+1];
|
270 |
+
Value alpha, beta, delta;
|
271 |
+
Move lastBestMove = MOVE_NONE;
|
272 |
+
Depth lastBestMoveDepth = 0;
|
273 |
+
MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
|
274 |
+
double timeReduction = 1, totBestMoveChanges = 0;
|
275 |
+
Color us = rootPos.side_to_move();
|
276 |
+
int iterIdx = 0;
|
277 |
+
|
278 |
+
std::memset(ss-7, 0, 10 * sizeof(Stack));
|
279 |
+
for (int i = 7; i > 0; i--)
|
280 |
+
(ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
|
281 |
+
|
282 |
+
for (int i = 0; i <= MAX_PLY + 2; ++i)
|
283 |
+
(ss+i)->ply = i;
|
284 |
+
|
285 |
+
ss->pv = pv;
|
286 |
+
|
287 |
+
bestValue = delta = alpha = -VALUE_INFINITE;
|
288 |
+
beta = VALUE_INFINITE;
|
289 |
+
|
290 |
+
if (mainThread)
|
291 |
+
{
|
292 |
+
if (mainThread->bestPreviousScore == VALUE_INFINITE)
|
293 |
+
for (int i = 0; i < 4; ++i)
|
294 |
+
mainThread->iterValue[i] = VALUE_ZERO;
|
295 |
+
else
|
296 |
+
for (int i = 0; i < 4; ++i)
|
297 |
+
mainThread->iterValue[i] = mainThread->bestPreviousScore;
|
298 |
+
}
|
299 |
+
|
300 |
+
size_t multiPV = size_t(Options["MultiPV"]);
|
301 |
+
Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0);
|
302 |
+
|
303 |
+
// When playing with strength handicap enable MultiPV search that we will
|
304 |
+
// use behind the scenes to retrieve a set of possible moves.
|
305 |
+
if (skill.enabled())
|
306 |
+
multiPV = std::max(multiPV, (size_t)4);
|
307 |
+
|
308 |
+
multiPV = std::min(multiPV, rootMoves.size());
|
309 |
+
|
310 |
+
complexityAverage.set(155, 1);
|
311 |
+
|
312 |
+
optimism[us] = optimism[~us] = VALUE_ZERO;
|
313 |
+
|
314 |
+
int searchAgainCounter = 0;
|
315 |
+
|
316 |
+
// Iterative deepening loop until requested to stop or the target depth is reached
|
317 |
+
while ( ++rootDepth < MAX_PLY
|
318 |
+
&& !Threads.stop
|
319 |
+
&& !(Limits.depth && mainThread && rootDepth > Limits.depth))
|
320 |
+
{
|
321 |
+
// Age out PV variability metric
|
322 |
+
if (mainThread)
|
323 |
+
totBestMoveChanges /= 2;
|
324 |
+
|
325 |
+
// Save the last iteration's scores before first PV line is searched and
|
326 |
+
// all the move scores except the (new) PV are set to -VALUE_INFINITE.
|
327 |
+
for (RootMove& rm : rootMoves)
|
328 |
+
rm.previousScore = rm.score;
|
329 |
+
|
330 |
+
size_t pvFirst = 0;
|
331 |
+
pvLast = 0;
|
332 |
+
|
333 |
+
if (!Threads.increaseDepth)
|
334 |
+
searchAgainCounter++;
|
335 |
+
|
336 |
+
// MultiPV loop. We perform a full root search for each PV line
|
337 |
+
for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx)
|
338 |
+
{
|
339 |
+
if (pvIdx == pvLast)
|
340 |
+
{
|
341 |
+
pvFirst = pvLast;
|
342 |
+
for (pvLast++; pvLast < rootMoves.size(); pvLast++)
|
343 |
+
if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank)
|
344 |
+
break;
|
345 |
+
}
|
346 |
+
|
347 |
+
// Reset UCI info selDepth for each depth and each PV line
|
348 |
+
selDepth = 0;
|
349 |
+
|
350 |
+
// Reset aspiration window starting size
|
351 |
+
if (rootDepth >= 4)
|
352 |
+
{
|
353 |
+
Value prev = rootMoves[pvIdx].averageScore;
|
354 |
+
delta = Value(10) + int(prev) * prev / 15620;
|
355 |
+
alpha = std::max(prev - delta,-VALUE_INFINITE);
|
356 |
+
beta = std::min(prev + delta, VALUE_INFINITE);
|
357 |
+
|
358 |
+
// Adjust optimism based on root move's previousScore
|
359 |
+
int opt = 118 * prev / (std::abs(prev) + 169);
|
360 |
+
optimism[ us] = Value(opt);
|
361 |
+
optimism[~us] = -optimism[us];
|
362 |
+
}
|
363 |
+
|
364 |
+
// Start with a small aspiration window and, in the case of a fail
|
365 |
+
// high/low, re-search with a bigger window until we don't fail
|
366 |
+
// high/low anymore.
|
367 |
+
int failedHighCnt = 0;
|
368 |
+
while (true)
|
369 |
+
{
|
370 |
+
// Adjust the effective depth searched, but ensuring at least one effective increment for every
|
371 |
+
// four searchAgain steps (see issue #2717).
|
372 |
+
Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
|
373 |
+
bestValue = Stockfish::search<Root>(rootPos, ss, alpha, beta, adjustedDepth, false);
|
374 |
+
|
375 |
+
// Bring the best move to the front. It is critical that sorting
|
376 |
+
// is done with a stable algorithm because all the values but the
|
377 |
+
// first and eventually the new best one are set to -VALUE_INFINITE
|
378 |
+
// and we want to keep the same order for all the moves except the
|
379 |
+
// new PV that goes to the front. Note that in case of MultiPV
|
380 |
+
// search the already searched PV lines are preserved.
|
381 |
+
std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast);
|
382 |
+
|
383 |
+
// If search has been stopped, we break immediately. Sorting is
|
384 |
+
// safe because RootMoves is still valid, although it refers to
|
385 |
+
// the previous iteration.
|
386 |
+
if (Threads.stop)
|
387 |
+
break;
|
388 |
+
|
389 |
+
// When failing high/low give some update (without cluttering
|
390 |
+
// the UI) before a re-search.
|
391 |
+
if ( mainThread
|
392 |
+
&& multiPV == 1
|
393 |
+
&& (bestValue <= alpha || bestValue >= beta)
|
394 |
+
&& Time.elapsed() > 3000)
|
395 |
+
sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl;
|
396 |
+
|
397 |
+
// In case of failing low/high increase aspiration window and
|
398 |
+
// re-search, otherwise exit the loop.
|
399 |
+
if (bestValue <= alpha)
|
400 |
+
{
|
401 |
+
beta = (alpha + beta) / 2;
|
402 |
+
alpha = std::max(bestValue - delta, -VALUE_INFINITE);
|
403 |
+
|
404 |
+
failedHighCnt = 0;
|
405 |
+
if (mainThread)
|
406 |
+
mainThread->stopOnPonderhit = false;
|
407 |
+
}
|
408 |
+
else if (bestValue >= beta)
|
409 |
+
{
|
410 |
+
beta = std::min(bestValue + delta, VALUE_INFINITE);
|
411 |
+
++failedHighCnt;
|
412 |
+
}
|
413 |
+
else
|
414 |
+
break;
|
415 |
+
|
416 |
+
delta += delta / 4 + 2;
|
417 |
+
|
418 |
+
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
|
419 |
+
}
|
420 |
+
|
421 |
+
// Sort the PV lines searched so far and update the GUI
|
422 |
+
std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);
|
423 |
+
|
424 |
+
if ( mainThread
|
425 |
+
&& (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000))
|
426 |
+
sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl;
|
427 |
+
}
|
428 |
+
|
429 |
+
if (!Threads.stop)
|
430 |
+
completedDepth = rootDepth;
|
431 |
+
|
432 |
+
if (rootMoves[0].pv[0] != lastBestMove) {
|
433 |
+
lastBestMove = rootMoves[0].pv[0];
|
434 |
+
lastBestMoveDepth = rootDepth;
|
435 |
+
}
|
436 |
+
|
437 |
+
// Have we found a "mate in x"?
|
438 |
+
if ( Limits.mate
|
439 |
+
&& bestValue >= VALUE_MATE_IN_MAX_PLY
|
440 |
+
&& VALUE_MATE - bestValue <= 2 * Limits.mate)
|
441 |
+
Threads.stop = true;
|
442 |
+
|
443 |
+
if (!mainThread)
|
444 |
+
continue;
|
445 |
+
|
446 |
+
// If skill level is enabled and time is up, pick a sub-optimal best move
|
447 |
+
if (skill.enabled() && skill.time_to_pick(rootDepth))
|
448 |
+
skill.pick_best(multiPV);
|
449 |
+
|
450 |
+
// Use part of the gained time from a previous stable move for the current move
|
451 |
+
for (Thread* th : Threads)
|
452 |
+
{
|
453 |
+
totBestMoveChanges += th->bestMoveChanges;
|
454 |
+
th->bestMoveChanges = 0;
|
455 |
+
}
|
456 |
+
|
457 |
+
// Do we have time for the next iteration? Can we stop searching now?
|
458 |
+
if ( Limits.use_time_management()
|
459 |
+
&& !Threads.stop
|
460 |
+
&& !mainThread->stopOnPonderhit)
|
461 |
+
{
|
462 |
+
double fallingEval = (71 + 12 * (mainThread->bestPreviousAverageScore - bestValue)
|
463 |
+
+ 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 656.7;
|
464 |
+
fallingEval = std::clamp(fallingEval, 0.5, 1.5);
|
465 |
+
|
466 |
+
// If the bestMove is stable over several iterations, reduce time accordingly
|
467 |
+
timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.37 : 0.65;
|
468 |
+
double reduction = (1.4 + mainThread->previousTimeReduction) / (2.15 * timeReduction);
|
469 |
+
double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size();
|
470 |
+
int complexity = mainThread->complexityAverage.value();
|
471 |
+
double complexPosition = std::min(1.0 + (complexity - 261) / 1738.7, 1.5);
|
472 |
+
|
473 |
+
double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition;
|
474 |
+
|
475 |
+
// Cap used time in case of a single legal move for a better viewer experience in tournaments
|
476 |
+
// yielding correct scores and sufficiently fast moves.
|
477 |
+
if (rootMoves.size() == 1)
|
478 |
+
totalTime = std::min(500.0, totalTime);
|
479 |
+
|
480 |
+
// Stop the search if we have exceeded the totalTime
|
481 |
+
if (Time.elapsed() > totalTime)
|
482 |
+
{
|
483 |
+
// If we are allowed to ponder do not stop the search now but
|
484 |
+
// keep pondering until the GUI sends "ponderhit" or "stop".
|
485 |
+
if (mainThread->ponder)
|
486 |
+
mainThread->stopOnPonderhit = true;
|
487 |
+
else
|
488 |
+
Threads.stop = true;
|
489 |
+
}
|
490 |
+
else if ( Threads.increaseDepth
|
491 |
+
&& !mainThread->ponder
|
492 |
+
&& Time.elapsed() > totalTime * 0.53)
|
493 |
+
Threads.increaseDepth = false;
|
494 |
+
else
|
495 |
+
Threads.increaseDepth = true;
|
496 |
+
}
|
497 |
+
|
498 |
+
mainThread->iterValue[iterIdx] = bestValue;
|
499 |
+
iterIdx = (iterIdx + 1) & 3;
|
500 |
+
}
|
501 |
+
|
502 |
+
if (!mainThread)
|
503 |
+
return;
|
504 |
+
|
505 |
+
mainThread->previousTimeReduction = timeReduction;
|
506 |
+
|
507 |
+
// If skill level is enabled, swap best PV line with the sub-optimal one
|
508 |
+
if (skill.enabled())
|
509 |
+
std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(),
|
510 |
+
skill.best ? skill.best : skill.pick_best(multiPV)));
|
511 |
+
}
|
512 |
+
|
513 |
+
|
514 |
+
namespace {
|
515 |
+
|
516 |
+
// search<>() is the main search function for both PV and non-PV nodes
|
517 |
+
|
518 |
+
template <NodeType nodeType>
|
519 |
+
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
|
520 |
+
|
521 |
+
constexpr bool PvNode = nodeType != NonPV;
|
522 |
+
constexpr bool rootNode = nodeType == Root;
|
523 |
+
const Depth maxNextDepth = rootNode ? depth : depth + 1;
|
524 |
+
|
525 |
+
// Check if we have an upcoming move which draws by repetition, or
|
526 |
+
// if the opponent had an alternative move earlier to this position.
|
527 |
+
if ( !rootNode
|
528 |
+
&& pos.rule50_count() >= 3
|
529 |
+
&& alpha < VALUE_DRAW
|
530 |
+
&& pos.has_game_cycle(ss->ply))
|
531 |
+
{
|
532 |
+
alpha = value_draw(pos.this_thread());
|
533 |
+
if (alpha >= beta)
|
534 |
+
return alpha;
|
535 |
+
}
|
536 |
+
|
537 |
+
// Dive into quiescence search when the depth reaches zero
|
538 |
+
if (depth <= 0)
|
539 |
+
return qsearch<PvNode ? PV : NonPV>(pos, ss, alpha, beta);
|
540 |
+
|
541 |
+
assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
|
542 |
+
assert(PvNode || (alpha == beta - 1));
|
543 |
+
assert(0 < depth && depth < MAX_PLY);
|
544 |
+
assert(!(PvNode && cutNode));
|
545 |
+
|
546 |
+
Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64];
|
547 |
+
StateInfo st;
|
548 |
+
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
549 |
+
|
550 |
+
TTEntry* tte;
|
551 |
+
Key posKey;
|
552 |
+
Move ttMove, move, excludedMove, bestMove;
|
553 |
+
Depth extension, newDepth;
|
554 |
+
Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
|
555 |
+
bool givesCheck, improving, priorCapture, singularQuietLMR;
|
556 |
+
bool capture, moveCountPruning, ttCapture;
|
557 |
+
Piece movedPiece;
|
558 |
+
int moveCount, captureCount, quietCount, improvement, complexity;
|
559 |
+
|
560 |
+
// Step 1. Initialize node
|
561 |
+
Thread* thisThread = pos.this_thread();
|
562 |
+
ss->inCheck = pos.checkers();
|
563 |
+
priorCapture = pos.captured_piece();
|
564 |
+
Color us = pos.side_to_move();
|
565 |
+
moveCount = captureCount = quietCount = ss->moveCount = 0;
|
566 |
+
bestValue = -VALUE_INFINITE;
|
567 |
+
maxValue = VALUE_INFINITE;
|
568 |
+
|
569 |
+
// Check for the available remaining time
|
570 |
+
if (thisThread == Threads.main())
|
571 |
+
static_cast<MainThread*>(thisThread)->check_time();
|
572 |
+
|
573 |
+
// Used to send selDepth info to GUI (selDepth counts from 1, ply from 0)
|
574 |
+
if (PvNode && thisThread->selDepth < ss->ply + 1)
|
575 |
+
thisThread->selDepth = ss->ply + 1;
|
576 |
+
|
577 |
+
if (!rootNode)
|
578 |
+
{
|
579 |
+
// Step 2. Check for aborted search and immediate draw
|
580 |
+
if ( Threads.stop.load(std::memory_order_relaxed)
|
581 |
+
|| pos.is_draw(ss->ply)
|
582 |
+
|| ss->ply >= MAX_PLY)
|
583 |
+
return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos)
|
584 |
+
: value_draw(pos.this_thread());
|
585 |
+
|
586 |
+
// Step 3. Mate distance pruning. Even if we mate at the next move our score
|
587 |
+
// would be at best mate_in(ss->ply+1), but if alpha is already bigger because
|
588 |
+
// a shorter mate was found upward in the tree then there is no need to search
|
589 |
+
// because we will never beat the current alpha. Same logic but with reversed
|
590 |
+
// signs applies also in the opposite condition of being mated instead of giving
|
591 |
+
// mate. In this case return a fail-high score.
|
592 |
+
alpha = std::max(mated_in(ss->ply), alpha);
|
593 |
+
beta = std::min(mate_in(ss->ply+1), beta);
|
594 |
+
if (alpha >= beta)
|
595 |
+
return alpha;
|
596 |
+
}
|
597 |
+
else
|
598 |
+
thisThread->rootDelta = beta - alpha;
|
599 |
+
|
600 |
+
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
601 |
+
|
602 |
+
(ss+1)->ttPv = false;
|
603 |
+
(ss+1)->excludedMove = bestMove = MOVE_NONE;
|
604 |
+
(ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
|
605 |
+
(ss+2)->cutoffCnt = 0;
|
606 |
+
ss->doubleExtensions = (ss-1)->doubleExtensions;
|
607 |
+
Square prevSq = to_sq((ss-1)->currentMove);
|
608 |
+
|
609 |
+
// Initialize statScore to zero for the grandchildren of the current position.
|
610 |
+
// So statScore is shared between all grandchildren and only the first grandchild
|
611 |
+
// starts with statScore = 0. Later grandchildren start with the last calculated
|
612 |
+
// statScore of the previous grandchild. This influences the reduction rules in
|
613 |
+
// LMR which are based on the statScore of parent position.
|
614 |
+
if (!rootNode)
|
615 |
+
(ss+2)->statScore = 0;
|
616 |
+
|
617 |
+
// Step 4. Transposition table lookup. We don't want the score of a partial
|
618 |
+
// search to overwrite a previous full search TT value, so we use a different
|
619 |
+
// position key in case of an excluded move.
|
620 |
+
excludedMove = ss->excludedMove;
|
621 |
+
posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove);
|
622 |
+
tte = TT.probe(posKey, ss->ttHit);
|
623 |
+
ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
|
624 |
+
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
|
625 |
+
: ss->ttHit ? tte->move() : MOVE_NONE;
|
626 |
+
ttCapture = ttMove && pos.capture(ttMove);
|
627 |
+
if (!excludedMove)
|
628 |
+
ss->ttPv = PvNode || (ss->ttHit && tte->is_pv());
|
629 |
+
|
630 |
+
// At non-PV nodes we check for an early TT cutoff
|
631 |
+
if ( !PvNode
|
632 |
+
&& ss->ttHit
|
633 |
+
&& tte->depth() > depth - (tte->bound() == BOUND_EXACT)
|
634 |
+
&& ttValue != VALUE_NONE // Possible in case of TT access race
|
635 |
+
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
|
636 |
+
{
|
637 |
+
// If ttMove is quiet, update move sorting heuristics on TT hit (~1 Elo)
|
638 |
+
if (ttMove)
|
639 |
+
{
|
640 |
+
if (ttValue >= beta)
|
641 |
+
{
|
642 |
+
// Bonus for a quiet ttMove that fails high (~3 Elo)
|
643 |
+
if (!ttCapture)
|
644 |
+
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth));
|
645 |
+
|
646 |
+
// Extra penalty for early quiet moves of the previous ply (~0 Elo)
|
647 |
+
if ((ss-1)->moveCount <= 2 && !priorCapture)
|
648 |
+
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1));
|
649 |
+
}
|
650 |
+
// Penalty for a quiet ttMove that fails low (~1 Elo)
|
651 |
+
else if (!ttCapture)
|
652 |
+
{
|
653 |
+
int penalty = -stat_bonus(depth);
|
654 |
+
thisThread->mainHistory[us][from_to(ttMove)] << penalty;
|
655 |
+
update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty);
|
656 |
+
}
|
657 |
+
}
|
658 |
+
|
659 |
+
// Partial workaround for the graph history interaction problem
|
660 |
+
// For high rule50 counts don't produce transposition table cutoffs.
|
661 |
+
if (pos.rule50_count() < 90)
|
662 |
+
return ttValue;
|
663 |
+
}
|
664 |
+
|
665 |
+
// Step 5. Tablebases probe
|
666 |
+
if (!rootNode && TB::Cardinality)
|
667 |
+
{
|
668 |
+
int piecesCount = pos.count<ALL_PIECES>();
|
669 |
+
|
670 |
+
if ( piecesCount <= TB::Cardinality
|
671 |
+
&& (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth)
|
672 |
+
&& pos.rule50_count() == 0
|
673 |
+
&& !pos.can_castle(ANY_CASTLING))
|
674 |
+
{
|
675 |
+
TB::ProbeState err;
|
676 |
+
TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
|
677 |
+
|
678 |
+
// Force check of time on the next occasion
|
679 |
+
if (thisThread == Threads.main())
|
680 |
+
static_cast<MainThread*>(thisThread)->callsCnt = 0;
|
681 |
+
|
682 |
+
if (err != TB::ProbeState::FAIL)
|
683 |
+
{
|
684 |
+
thisThread->tbHits.fetch_add(1, std::memory_order_relaxed);
|
685 |
+
|
686 |
+
int drawScore = TB::UseRule50 ? 1 : 0;
|
687 |
+
|
688 |
+
// use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score
|
689 |
+
value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1
|
690 |
+
: wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1
|
691 |
+
: VALUE_DRAW + 2 * wdl * drawScore;
|
692 |
+
|
693 |
+
Bound b = wdl < -drawScore ? BOUND_UPPER
|
694 |
+
: wdl > drawScore ? BOUND_LOWER : BOUND_EXACT;
|
695 |
+
|
696 |
+
if ( b == BOUND_EXACT
|
697 |
+
|| (b == BOUND_LOWER ? value >= beta : value <= alpha))
|
698 |
+
{
|
699 |
+
tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b,
|
700 |
+
std::min(MAX_PLY - 1, depth + 6),
|
701 |
+
MOVE_NONE, VALUE_NONE);
|
702 |
+
|
703 |
+
return value;
|
704 |
+
}
|
705 |
+
|
706 |
+
if (PvNode)
|
707 |
+
{
|
708 |
+
if (b == BOUND_LOWER)
|
709 |
+
bestValue = value, alpha = std::max(alpha, bestValue);
|
710 |
+
else
|
711 |
+
maxValue = value;
|
712 |
+
}
|
713 |
+
}
|
714 |
+
}
|
715 |
+
}
|
716 |
+
|
717 |
+
CapturePieceToHistory& captureHistory = thisThread->captureHistory;
|
718 |
+
|
719 |
+
// Step 6. Static evaluation of the position
|
720 |
+
if (ss->inCheck)
|
721 |
+
{
|
722 |
+
// Skip early pruning when in check
|
723 |
+
ss->staticEval = eval = VALUE_NONE;
|
724 |
+
improving = false;
|
725 |
+
improvement = 0;
|
726 |
+
complexity = 0;
|
727 |
+
goto moves_loop;
|
728 |
+
}
|
729 |
+
else if (ss->ttHit)
|
730 |
+
{
|
731 |
+
// Never assume anything about values stored in TT
|
732 |
+
ss->staticEval = eval = tte->eval();
|
733 |
+
if (eval == VALUE_NONE)
|
734 |
+
ss->staticEval = eval = evaluate(pos, &complexity);
|
735 |
+
else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost
|
736 |
+
complexity = abs(ss->staticEval - pos.psq_eg_stm());
|
737 |
+
|
738 |
+
// ttValue can be used as a better position evaluation (~4 Elo)
|
739 |
+
if ( ttValue != VALUE_NONE
|
740 |
+
&& (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)))
|
741 |
+
eval = ttValue;
|
742 |
+
}
|
743 |
+
else
|
744 |
+
{
|
745 |
+
ss->staticEval = eval = evaluate(pos, &complexity);
|
746 |
+
|
747 |
+
// Save static evaluation into transposition table
|
748 |
+
if (!excludedMove)
|
749 |
+
tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
|
750 |
+
}
|
751 |
+
|
752 |
+
thisThread->complexityAverage.update(complexity);
|
753 |
+
|
754 |
+
// Use static evaluation difference to improve quiet move ordering (~3 Elo)
|
755 |
+
if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture)
|
756 |
+
{
|
757 |
+
int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1914, 1914);
|
758 |
+
thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus;
|
759 |
+
}
|
760 |
+
|
761 |
+
// Set up the improvement variable, which is the difference between the current
|
762 |
+
// static evaluation and the previous static evaluation at our turn (if we were
|
763 |
+
// in check at our previous move we look at the move prior to it). The improvement
|
764 |
+
// margin and the improving flag are used in various pruning heuristics.
|
765 |
+
improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval
|
766 |
+
: (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval
|
767 |
+
: 168;
|
768 |
+
improving = improvement > 0;
|
769 |
+
|
770 |
+
// Step 7. Razoring.
|
771 |
+
// If eval is really low check with qsearch if it can exceed alpha, if it can't,
|
772 |
+
// return a fail low.
|
773 |
+
if (eval < alpha - 369 - 254 * depth * depth)
|
774 |
+
{
|
775 |
+
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
|
776 |
+
if (value < alpha)
|
777 |
+
return value;
|
778 |
+
}
|
779 |
+
|
780 |
+
// Step 8. Futility pruning: child node (~25 Elo).
|
781 |
+
// The depth condition is important for mate finding.
|
782 |
+
if ( !ss->ttPv
|
783 |
+
&& depth < 8
|
784 |
+
&& eval - futility_margin(depth, improving) - (ss-1)->statScore / 303 >= beta
|
785 |
+
&& eval >= beta
|
786 |
+
&& eval < 28031) // larger than VALUE_KNOWN_WIN, but smaller than TB wins
|
787 |
+
return eval;
|
788 |
+
|
789 |
+
// Step 9. Null move search with verification search (~22 Elo)
|
790 |
+
if ( !PvNode
|
791 |
+
&& (ss-1)->currentMove != MOVE_NULL
|
792 |
+
&& (ss-1)->statScore < 17139
|
793 |
+
&& eval >= beta
|
794 |
+
&& eval >= ss->staticEval
|
795 |
+
&& ss->staticEval >= beta - 20 * depth - improvement / 13 + 233 + complexity / 25
|
796 |
+
&& !excludedMove
|
797 |
+
&& pos.non_pawn_material(us)
|
798 |
+
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
|
799 |
+
{
|
800 |
+
assert(eval - beta >= 0);
|
801 |
+
|
802 |
+
// Null move dynamic reduction based on depth, eval and complexity of position
|
803 |
+
Depth R = std::min(int(eval - beta) / 168, 7) + depth / 3 + 4 - (complexity > 861);
|
804 |
+
|
805 |
+
ss->currentMove = MOVE_NULL;
|
806 |
+
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
|
807 |
+
|
808 |
+
pos.do_null_move(st);
|
809 |
+
|
810 |
+
Value nullValue = -search<NonPV>(pos, ss+1, -beta, -beta+1, depth-R, !cutNode);
|
811 |
+
|
812 |
+
pos.undo_null_move();
|
813 |
+
|
814 |
+
if (nullValue >= beta)
|
815 |
+
{
|
816 |
+
// Do not return unproven mate or TB scores
|
817 |
+
if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY)
|
818 |
+
nullValue = beta;
|
819 |
+
|
820 |
+
if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 14))
|
821 |
+
return nullValue;
|
822 |
+
|
823 |
+
assert(!thisThread->nmpMinPly); // Recursive verification is not allowed
|
824 |
+
|
825 |
+
// Do verification search at high depths, with null move pruning disabled
|
826 |
+
// for us, until ply exceeds nmpMinPly.
|
827 |
+
thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4;
|
828 |
+
thisThread->nmpColor = us;
|
829 |
+
|
830 |
+
Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false);
|
831 |
+
|
832 |
+
thisThread->nmpMinPly = 0;
|
833 |
+
|
834 |
+
if (v >= beta)
|
835 |
+
return nullValue;
|
836 |
+
}
|
837 |
+
}
|
838 |
+
|
839 |
+
probCutBeta = beta + 191 - 54 * improving;
|
840 |
+
|
841 |
+
// Step 10. ProbCut (~4 Elo)
|
842 |
+
// If we have a good enough capture and a reduced search returns a value
|
843 |
+
// much above beta, we can (almost) safely prune the previous move.
|
844 |
+
if ( !PvNode
|
845 |
+
&& depth > 4
|
846 |
+
&& abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
|
847 |
+
// if value from transposition table is lower than probCutBeta, don't attempt probCut
|
848 |
+
// there and in further interactions with transposition table cutoff depth is set to depth - 3
|
849 |
+
// because probCut search has depth set to depth - 4 but we also do a move before it
|
850 |
+
// so effective depth is equal to depth - 3
|
851 |
+
&& !( ss->ttHit
|
852 |
+
&& tte->depth() >= depth - 3
|
853 |
+
&& ttValue != VALUE_NONE
|
854 |
+
&& ttValue < probCutBeta))
|
855 |
+
{
|
856 |
+
assert(probCutBeta < VALUE_INFINITE);
|
857 |
+
|
858 |
+
MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
|
859 |
+
|
860 |
+
while ((move = mp.next_move()) != MOVE_NONE)
|
861 |
+
if (move != excludedMove && pos.legal(move))
|
862 |
+
{
|
863 |
+
assert(pos.capture(move) || promotion_type(move) == QUEEN);
|
864 |
+
|
865 |
+
ss->currentMove = move;
|
866 |
+
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
|
867 |
+
[true]
|
868 |
+
[pos.moved_piece(move)]
|
869 |
+
[to_sq(move)];
|
870 |
+
|
871 |
+
pos.do_move(move, st);
|
872 |
+
|
873 |
+
// Perform a preliminary qsearch to verify that the move holds
|
874 |
+
value = -qsearch<NonPV>(pos, ss+1, -probCutBeta, -probCutBeta+1);
|
875 |
+
|
876 |
+
// If the qsearch held, perform the regular search
|
877 |
+
if (value >= probCutBeta)
|
878 |
+
value = -search<NonPV>(pos, ss+1, -probCutBeta, -probCutBeta+1, depth - 4, !cutNode);
|
879 |
+
|
880 |
+
pos.undo_move(move);
|
881 |
+
|
882 |
+
if (value >= probCutBeta)
|
883 |
+
{
|
884 |
+
// Save ProbCut data into transposition table
|
885 |
+
tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval);
|
886 |
+
return value;
|
887 |
+
}
|
888 |
+
}
|
889 |
+
}
|
890 |
+
|
891 |
+
// Step 11. If the position is not in TT, decrease depth by 3.
|
892 |
+
// Use qsearch if depth is equal or below zero (~4 Elo)
|
893 |
+
if ( PvNode
|
894 |
+
&& !ttMove)
|
895 |
+
depth -= 3;
|
896 |
+
|
897 |
+
if (depth <= 0)
|
898 |
+
return qsearch<PV>(pos, ss, alpha, beta);
|
899 |
+
|
900 |
+
if ( cutNode
|
901 |
+
&& depth >= 9
|
902 |
+
&& !ttMove)
|
903 |
+
depth -= 2;
|
904 |
+
|
905 |
+
moves_loop: // When in check, search starts here
|
906 |
+
|
907 |
+
// Step 12. A small Probcut idea, when we are in check (~0 Elo)
|
908 |
+
probCutBeta = beta + 417;
|
909 |
+
if ( ss->inCheck
|
910 |
+
&& !PvNode
|
911 |
+
&& depth >= 2
|
912 |
+
&& ttCapture
|
913 |
+
&& (tte->bound() & BOUND_LOWER)
|
914 |
+
&& tte->depth() >= depth - 3
|
915 |
+
&& ttValue >= probCutBeta
|
916 |
+
&& abs(ttValue) <= VALUE_KNOWN_WIN
|
917 |
+
&& abs(beta) <= VALUE_KNOWN_WIN
|
918 |
+
)
|
919 |
+
return probCutBeta;
|
920 |
+
|
921 |
+
|
922 |
+
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
|
923 |
+
nullptr , (ss-4)->continuationHistory,
|
924 |
+
nullptr , (ss-6)->continuationHistory };
|
925 |
+
|
926 |
+
Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
|
927 |
+
|
928 |
+
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
|
929 |
+
&captureHistory,
|
930 |
+
contHist,
|
931 |
+
countermove,
|
932 |
+
ss->killers);
|
933 |
+
|
934 |
+
value = bestValue;
|
935 |
+
moveCountPruning = singularQuietLMR = false;
|
936 |
+
|
937 |
+
// Indicate PvNodes that will probably fail low if the node was searched
|
938 |
+
// at a depth equal or greater than the current depth, and the result of this search was a fail low.
|
939 |
+
bool likelyFailLow = PvNode
|
940 |
+
&& ttMove
|
941 |
+
&& (tte->bound() & BOUND_UPPER)
|
942 |
+
&& tte->depth() >= depth;
|
943 |
+
|
944 |
+
// Step 13. Loop through all pseudo-legal moves until no moves remain
|
945 |
+
// or a beta cutoff occurs.
|
946 |
+
while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE)
|
947 |
+
{
|
948 |
+
assert(is_ok(move));
|
949 |
+
|
950 |
+
if (move == excludedMove)
|
951 |
+
continue;
|
952 |
+
|
953 |
+
// At root obey the "searchmoves" option and skip moves not listed in Root
|
954 |
+
// Move List. As a consequence any illegal move is also skipped. In MultiPV
|
955 |
+
// mode we also skip PV moves which have been already searched and those
|
956 |
+
// of lower "TB rank" if we are in a TB root position.
|
957 |
+
if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx,
|
958 |
+
thisThread->rootMoves.begin() + thisThread->pvLast, move))
|
959 |
+
continue;
|
960 |
+
|
961 |
+
// Check for legality
|
962 |
+
if (!rootNode && !pos.legal(move))
|
963 |
+
continue;
|
964 |
+
|
965 |
+
ss->moveCount = ++moveCount;
|
966 |
+
|
967 |
+
if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
|
968 |
+
sync_cout << "info depth " << depth
|
969 |
+
<< " currmove " << UCI::move(move, pos.is_chess960())
|
970 |
+
<< " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl;
|
971 |
+
if (PvNode)
|
972 |
+
(ss+1)->pv = nullptr;
|
973 |
+
|
974 |
+
extension = 0;
|
975 |
+
capture = pos.capture(move);
|
976 |
+
movedPiece = pos.moved_piece(move);
|
977 |
+
givesCheck = pos.gives_check(move);
|
978 |
+
|
979 |
+
// Calculate new depth for this move
|
980 |
+
newDepth = depth - 1;
|
981 |
+
|
982 |
+
Value delta = beta - alpha;
|
983 |
+
|
984 |
+
// Step 14. Pruning at shallow depth (~98 Elo). Depth conditions are important for mate finding.
|
985 |
+
if ( !rootNode
|
986 |
+
&& pos.non_pawn_material(us)
|
987 |
+
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
|
988 |
+
{
|
989 |
+
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~7 Elo)
|
990 |
+
moveCountPruning = moveCount >= futility_move_count(improving, depth);
|
991 |
+
|
992 |
+
// Reduced depth of the next LMR search
|
993 |
+
int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount, delta, thisThread->rootDelta), 0);
|
994 |
+
|
995 |
+
if ( capture
|
996 |
+
|| givesCheck)
|
997 |
+
{
|
998 |
+
// Futility pruning for captures (~0 Elo)
|
999 |
+
if ( !givesCheck
|
1000 |
+
&& !PvNode
|
1001 |
+
&& lmrDepth < 7
|
1002 |
+
&& !ss->inCheck
|
1003 |
+
&& ss->staticEval + 180 + 201 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))]
|
1004 |
+
+ captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha)
|
1005 |
+
continue;
|
1006 |
+
|
1007 |
+
// SEE based pruning (~9 Elo)
|
1008 |
+
if (!pos.see_ge(move, Value(-222) * depth))
|
1009 |
+
continue;
|
1010 |
+
}
|
1011 |
+
else
|
1012 |
+
{
|
1013 |
+
int history = (*contHist[0])[movedPiece][to_sq(move)]
|
1014 |
+
+ (*contHist[1])[movedPiece][to_sq(move)]
|
1015 |
+
+ (*contHist[3])[movedPiece][to_sq(move)];
|
1016 |
+
|
1017 |
+
// Continuation history based pruning (~2 Elo)
|
1018 |
+
if ( lmrDepth < 5
|
1019 |
+
&& history < -3875 * (depth - 1))
|
1020 |
+
continue;
|
1021 |
+
|
1022 |
+
history += 2 * thisThread->mainHistory[us][from_to(move)];
|
1023 |
+
|
1024 |
+
// Futility pruning: parent node (~9 Elo)
|
1025 |
+
if ( !ss->inCheck
|
1026 |
+
&& lmrDepth < 13
|
1027 |
+
&& ss->staticEval + 106 + 145 * lmrDepth + history / 52 <= alpha)
|
1028 |
+
continue;
|
1029 |
+
|
1030 |
+
// Prune moves with negative SEE (~3 Elo)
|
1031 |
+
if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth)))
|
1032 |
+
continue;
|
1033 |
+
}
|
1034 |
+
}
|
1035 |
+
|
1036 |
+
// Step 15. Extensions (~66 Elo)
|
1037 |
+
// We take care to not overdo to avoid search getting stuck.
|
1038 |
+
if (ss->ply < thisThread->rootDepth * 2)
|
1039 |
+
{
|
1040 |
+
// Singular extension search (~58 Elo). If all moves but one fail low on a
|
1041 |
+
// search of (alpha-s, beta-s), and just one fails high on (alpha, beta),
|
1042 |
+
// then that move is singular and should be extended. To verify this we do
|
1043 |
+
// a reduced search on all the other moves but the ttMove and if the
|
1044 |
+
// result is lower than ttValue minus a margin, then we will extend the ttMove.
|
1045 |
+
if ( !rootNode
|
1046 |
+
&& depth >= 4 - (thisThread->previousDepth > 24) + 2 * (PvNode && tte->is_pv())
|
1047 |
+
&& move == ttMove
|
1048 |
+
&& !excludedMove // Avoid recursive singular search
|
1049 |
+
/* && ttValue != VALUE_NONE Already implicit in the next condition */
|
1050 |
+
&& abs(ttValue) < VALUE_KNOWN_WIN
|
1051 |
+
&& (tte->bound() & BOUND_LOWER)
|
1052 |
+
&& tte->depth() >= depth - 3)
|
1053 |
+
{
|
1054 |
+
Value singularBeta = ttValue - (3 + (ss->ttPv && !PvNode)) * depth;
|
1055 |
+
Depth singularDepth = (depth - 1) / 2;
|
1056 |
+
|
1057 |
+
ss->excludedMove = move;
|
1058 |
+
value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
|
1059 |
+
ss->excludedMove = MOVE_NONE;
|
1060 |
+
|
1061 |
+
if (value < singularBeta)
|
1062 |
+
{
|
1063 |
+
extension = 1;
|
1064 |
+
singularQuietLMR = !ttCapture;
|
1065 |
+
|
1066 |
+
// Avoid search explosion by limiting the number of double extensions
|
1067 |
+
if ( !PvNode
|
1068 |
+
&& value < singularBeta - 25
|
1069 |
+
&& ss->doubleExtensions <= 9)
|
1070 |
+
extension = 2;
|
1071 |
+
}
|
1072 |
+
|
1073 |
+
// Multi-cut pruning
|
1074 |
+
// Our ttMove is assumed to fail high, and now we failed high also on a reduced
|
1075 |
+
// search without the ttMove. So we assume this expected Cut-node is not singular,
|
1076 |
+
// that multiple moves fail high, and we can prune the whole subtree by returning
|
1077 |
+
// a soft bound.
|
1078 |
+
else if (singularBeta >= beta)
|
1079 |
+
return singularBeta;
|
1080 |
+
|
1081 |
+
// If the eval of ttMove is greater than beta, we reduce it (negative extension)
|
1082 |
+
else if (ttValue >= beta)
|
1083 |
+
extension = -2;
|
1084 |
+
|
1085 |
+
// If the eval of ttMove is less than alpha and value, we reduce it (negative extension)
|
1086 |
+
else if (ttValue <= alpha && ttValue <= value)
|
1087 |
+
extension = -1;
|
1088 |
+
}
|
1089 |
+
|
1090 |
+
// Check extensions (~1 Elo)
|
1091 |
+
else if ( givesCheck
|
1092 |
+
&& depth > 9
|
1093 |
+
&& abs(ss->staticEval) > 82)
|
1094 |
+
extension = 1;
|
1095 |
+
|
1096 |
+
// Quiet ttMove extensions (~0 Elo)
|
1097 |
+
else if ( PvNode
|
1098 |
+
&& move == ttMove
|
1099 |
+
&& move == ss->killers[0]
|
1100 |
+
&& (*contHist[0])[movedPiece][to_sq(move)] >= 5177)
|
1101 |
+
extension = 1;
|
1102 |
+
}
|
1103 |
+
|
1104 |
+
// Add extension to new depth
|
1105 |
+
newDepth += extension;
|
1106 |
+
ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2);
|
1107 |
+
|
1108 |
+
// Speculative prefetch as early as possible
|
1109 |
+
prefetch(TT.first_entry(pos.key_after(move)));
|
1110 |
+
|
1111 |
+
// Update the current move (this must be done after singular extension search)
|
1112 |
+
ss->currentMove = move;
|
1113 |
+
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
|
1114 |
+
[capture]
|
1115 |
+
[movedPiece]
|
1116 |
+
[to_sq(move)];
|
1117 |
+
|
1118 |
+
// Step 16. Make the move
|
1119 |
+
pos.do_move(move, st, givesCheck);
|
1120 |
+
|
1121 |
+
// Step 17. Late moves reduction / extension (LMR, ~98 Elo)
|
1122 |
+
// We use various heuristics for the sons of a node after the first son has
|
1123 |
+
// been searched. In general we would like to reduce them, but there are many
|
1124 |
+
// cases where we extend a son if it has good chances to be "interesting".
|
1125 |
+
if ( depth >= 2
|
1126 |
+
&& moveCount > 1 + (PvNode && ss->ply <= 1)
|
1127 |
+
&& ( !ss->ttPv
|
1128 |
+
|| !capture
|
1129 |
+
|| (cutNode && (ss-1)->moveCount > 1)))
|
1130 |
+
{
|
1131 |
+
Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta);
|
1132 |
+
|
1133 |
+
// Decrease reduction if position is or has been on the PV
|
1134 |
+
// and node is not likely to fail low. (~3 Elo)
|
1135 |
+
if ( ss->ttPv
|
1136 |
+
&& !likelyFailLow)
|
1137 |
+
r -= 2;
|
1138 |
+
|
1139 |
+
// Decrease reduction if opponent's move count is high (~1 Elo)
|
1140 |
+
if ((ss-1)->moveCount > 7)
|
1141 |
+
r--;
|
1142 |
+
|
1143 |
+
// Increase reduction for cut nodes (~3 Elo)
|
1144 |
+
if (cutNode)
|
1145 |
+
r += 2;
|
1146 |
+
|
1147 |
+
// Increase reduction if ttMove is a capture (~3 Elo)
|
1148 |
+
if (ttCapture)
|
1149 |
+
r++;
|
1150 |
+
|
1151 |
+
// Decrease reduction for PvNodes based on depth
|
1152 |
+
if (PvNode)
|
1153 |
+
r -= 1 + 11 / (3 + depth);
|
1154 |
+
|
1155 |
+
// Decrease reduction if ttMove has been singularly extended (~1 Elo)
|
1156 |
+
if (singularQuietLMR)
|
1157 |
+
r--;
|
1158 |
+
|
1159 |
+
// Decrease reduction if we move a threatened piece (~1 Elo)
|
1160 |
+
if ( depth > 9
|
1161 |
+
&& (mp.threatenedPieces & from_sq(move)))
|
1162 |
+
r--;
|
1163 |
+
|
1164 |
+
// Increase reduction if next ply has a lot of fail high
|
1165 |
+
if ((ss+1)->cutoffCnt > 3)
|
1166 |
+
r++;
|
1167 |
+
|
1168 |
+
ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)]
|
1169 |
+
+ (*contHist[0])[movedPiece][to_sq(move)]
|
1170 |
+
+ (*contHist[1])[movedPiece][to_sq(move)]
|
1171 |
+
+ (*contHist[3])[movedPiece][to_sq(move)]
|
1172 |
+
- 4433;
|
1173 |
+
|
1174 |
+
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
|
1175 |
+
r -= ss->statScore / (13628 + 4000 * (depth > 7 && depth < 19));
|
1176 |
+
|
1177 |
+
// In general we want to cap the LMR depth search at newDepth, but when
|
1178 |
+
// reduction is negative, we allow this move a limited search extension
|
1179 |
+
// beyond the first move depth. This may lead to hidden double extensions.
|
1180 |
+
Depth d = std::clamp(newDepth - r, 1, newDepth + 1);
|
1181 |
+
|
1182 |
+
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
|
1183 |
+
|
1184 |
+
// Do full depth search when reduced LMR search fails high
|
1185 |
+
if (value > alpha && d < newDepth)
|
1186 |
+
{
|
1187 |
+
// Adjust full depth search based on LMR results - if result
|
1188 |
+
// was good enough search deeper, if it was bad enough search shallower
|
1189 |
+
const bool doDeeperSearch = value > (alpha + 64 + 11 * (newDepth - d));
|
1190 |
+
const bool doShallowerSearch = value < bestValue + newDepth;
|
1191 |
+
|
1192 |
+
newDepth += doDeeperSearch - doShallowerSearch;
|
1193 |
+
|
1194 |
+
if (newDepth > d)
|
1195 |
+
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
|
1196 |
+
|
1197 |
+
int bonus = value > alpha ? stat_bonus(newDepth)
|
1198 |
+
: -stat_bonus(newDepth);
|
1199 |
+
|
1200 |
+
if (capture)
|
1201 |
+
bonus /= 6;
|
1202 |
+
|
1203 |
+
update_continuation_histories(ss, movedPiece, to_sq(move), bonus);
|
1204 |
+
}
|
1205 |
+
}
|
1206 |
+
|
1207 |
+
// Step 18. Full depth search when LMR is skipped
|
1208 |
+
else if (!PvNode || moveCount > 1)
|
1209 |
+
{
|
1210 |
+
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
|
1211 |
+
}
|
1212 |
+
|
1213 |
+
// For PV nodes only, do a full PV search on the first move or after a fail
|
1214 |
+
// high (in the latter case search only if value < beta), otherwise let the
|
1215 |
+
// parent node fail low with value <= alpha and try another move.
|
1216 |
+
if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta))))
|
1217 |
+
{
|
1218 |
+
(ss+1)->pv = pv;
|
1219 |
+
(ss+1)->pv[0] = MOVE_NONE;
|
1220 |
+
|
1221 |
+
value = -search<PV>(pos, ss+1, -beta, -alpha,
|
1222 |
+
std::min(maxNextDepth, newDepth), false);
|
1223 |
+
}
|
1224 |
+
|
1225 |
+
// Step 19. Undo move
|
1226 |
+
pos.undo_move(move);
|
1227 |
+
|
1228 |
+
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
|
1229 |
+
|
1230 |
+
// Step 20. Check for a new best move
|
1231 |
+
// Finished searching the move. If a stop occurred, the return value of
|
1232 |
+
// the search cannot be trusted, and we return immediately without
|
1233 |
+
// updating best move, PV and TT.
|
1234 |
+
if (Threads.stop.load(std::memory_order_relaxed))
|
1235 |
+
return VALUE_ZERO;
|
1236 |
+
|
1237 |
+
if (rootNode)
|
1238 |
+
{
|
1239 |
+
RootMove& rm = *std::find(thisThread->rootMoves.begin(),
|
1240 |
+
thisThread->rootMoves.end(), move);
|
1241 |
+
|
1242 |
+
rm.averageScore = rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value;
|
1243 |
+
|
1244 |
+
// PV move or new best move?
|
1245 |
+
if (moveCount == 1 || value > alpha)
|
1246 |
+
{
|
1247 |
+
rm.score = value;
|
1248 |
+
rm.selDepth = thisThread->selDepth;
|
1249 |
+
rm.scoreLowerbound = value >= beta;
|
1250 |
+
rm.scoreUpperbound = value <= alpha;
|
1251 |
+
rm.pv.resize(1);
|
1252 |
+
|
1253 |
+
assert((ss+1)->pv);
|
1254 |
+
|
1255 |
+
for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m)
|
1256 |
+
rm.pv.push_back(*m);
|
1257 |
+
|
1258 |
+
// We record how often the best move has been changed in each iteration.
|
1259 |
+
// This information is used for time management. In MultiPV mode,
|
1260 |
+
// we must take care to only do this for the first PV line.
|
1261 |
+
if ( moveCount > 1
|
1262 |
+
&& !thisThread->pvIdx)
|
1263 |
+
++thisThread->bestMoveChanges;
|
1264 |
+
}
|
1265 |
+
else
|
1266 |
+
// All other moves but the PV are set to the lowest value: this
|
1267 |
+
// is not a problem when sorting because the sort is stable and the
|
1268 |
+
// move position in the list is preserved - just the PV is pushed up.
|
1269 |
+
rm.score = -VALUE_INFINITE;
|
1270 |
+
}
|
1271 |
+
|
1272 |
+
if (value > bestValue)
|
1273 |
+
{
|
1274 |
+
bestValue = value;
|
1275 |
+
|
1276 |
+
if (value > alpha)
|
1277 |
+
{
|
1278 |
+
bestMove = move;
|
1279 |
+
|
1280 |
+
if (PvNode && !rootNode) // Update pv even in fail-high case
|
1281 |
+
update_pv(ss->pv, move, (ss+1)->pv);
|
1282 |
+
|
1283 |
+
if (PvNode && value < beta) // Update alpha! Always alpha < beta
|
1284 |
+
{
|
1285 |
+
alpha = value;
|
1286 |
+
|
1287 |
+
// Reduce other moves if we have found at least one score improvement
|
1288 |
+
if ( depth > 1
|
1289 |
+
&& depth < 6
|
1290 |
+
&& beta < VALUE_KNOWN_WIN
|
1291 |
+
&& alpha > -VALUE_KNOWN_WIN)
|
1292 |
+
depth -= 1;
|
1293 |
+
|
1294 |
+
assert(depth > 0);
|
1295 |
+
}
|
1296 |
+
else
|
1297 |
+
{
|
1298 |
+
ss->cutoffCnt++;
|
1299 |
+
assert(value >= beta); // Fail high
|
1300 |
+
break;
|
1301 |
+
}
|
1302 |
+
}
|
1303 |
+
}
|
1304 |
+
|
1305 |
+
|
1306 |
+
// If the move is worse than some previously searched move, remember it to update its stats later
|
1307 |
+
if (move != bestMove)
|
1308 |
+
{
|
1309 |
+
if (capture && captureCount < 32)
|
1310 |
+
capturesSearched[captureCount++] = move;
|
1311 |
+
|
1312 |
+
else if (!capture && quietCount < 64)
|
1313 |
+
quietsSearched[quietCount++] = move;
|
1314 |
+
}
|
1315 |
+
}
|
1316 |
+
|
1317 |
+
// The following condition would detect a stop only after move loop has been
|
1318 |
+
// completed. But in this case bestValue is valid because we have fully
|
1319 |
+
// searched our subtree, and we can anyhow save the result in TT.
|
1320 |
+
/*
|
1321 |
+
if (Threads.stop)
|
1322 |
+
return VALUE_DRAW;
|
1323 |
+
*/
|
1324 |
+
|
1325 |
+
// Step 21. Check for mate and stalemate
|
1326 |
+
// All legal moves have been searched and if there are no legal moves, it
|
1327 |
+
// must be a mate or a stalemate. If we are in a singular extension search then
|
1328 |
+
// return a fail low score.
|
1329 |
+
|
1330 |
+
assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
|
1331 |
+
|
1332 |
+
if (!moveCount)
|
1333 |
+
bestValue = excludedMove ? alpha :
|
1334 |
+
ss->inCheck ? mated_in(ss->ply)
|
1335 |
+
: VALUE_DRAW;
|
1336 |
+
|
1337 |
+
// If there is a move which produces search value greater than alpha we update stats of searched moves
|
1338 |
+
else if (bestMove)
|
1339 |
+
update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq,
|
1340 |
+
quietsSearched, quietCount, capturesSearched, captureCount, depth);
|
1341 |
+
|
1342 |
+
// Bonus for prior countermove that caused the fail low
|
1343 |
+
else if ( (depth >= 5 || PvNode)
|
1344 |
+
&& !priorCapture)
|
1345 |
+
{
|
1346 |
+
//Assign extra bonus if current node is PvNode or cutNode
|
1347 |
+
//or fail low was really bad
|
1348 |
+
bool extraBonus = PvNode
|
1349 |
+
|| cutNode
|
1350 |
+
|| bestValue < alpha - 62 * depth;
|
1351 |
+
|
1352 |
+
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * (1 + extraBonus));
|
1353 |
+
}
|
1354 |
+
|
1355 |
+
if (PvNode)
|
1356 |
+
bestValue = std::min(bestValue, maxValue);
|
1357 |
+
|
1358 |
+
// If no good move is found and the previous position was ttPv, then the previous
|
1359 |
+
// opponent move is probably good and the new position is added to the search tree.
|
1360 |
+
if (bestValue <= alpha)
|
1361 |
+
ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3);
|
1362 |
+
|
1363 |
+
// Write gathered information in transposition table
|
1364 |
+
if (!excludedMove && !(rootNode && thisThread->pvIdx))
|
1365 |
+
tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv,
|
1366 |
+
bestValue >= beta ? BOUND_LOWER :
|
1367 |
+
PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
|
1368 |
+
depth, bestMove, ss->staticEval);
|
1369 |
+
|
1370 |
+
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
|
1371 |
+
|
1372 |
+
return bestValue;
|
1373 |
+
}
|
1374 |
+
|
1375 |
+
|
1376 |
+
// qsearch() is the quiescence search function, which is called by the main search
|
1377 |
+
// function with zero depth, or recursively with further decreasing depth per call.
|
1378 |
+
// (~155 elo)
|
1379 |
+
template <NodeType nodeType>
|
1380 |
+
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
|
1381 |
+
|
1382 |
+
static_assert(nodeType != Root);
|
1383 |
+
constexpr bool PvNode = nodeType == PV;
|
1384 |
+
|
1385 |
+
assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
|
1386 |
+
assert(PvNode || (alpha == beta - 1));
|
1387 |
+
assert(depth <= 0);
|
1388 |
+
|
1389 |
+
Move pv[MAX_PLY+1];
|
1390 |
+
StateInfo st;
|
1391 |
+
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
1392 |
+
|
1393 |
+
TTEntry* tte;
|
1394 |
+
Key posKey;
|
1395 |
+
Move ttMove, move, bestMove;
|
1396 |
+
Depth ttDepth;
|
1397 |
+
Value bestValue, value, ttValue, futilityValue, futilityBase;
|
1398 |
+
bool pvHit, givesCheck, capture;
|
1399 |
+
int moveCount;
|
1400 |
+
|
1401 |
+
if (PvNode)
|
1402 |
+
{
|
1403 |
+
(ss+1)->pv = pv;
|
1404 |
+
ss->pv[0] = MOVE_NONE;
|
1405 |
+
}
|
1406 |
+
|
1407 |
+
Thread* thisThread = pos.this_thread();
|
1408 |
+
bestMove = MOVE_NONE;
|
1409 |
+
ss->inCheck = pos.checkers();
|
1410 |
+
moveCount = 0;
|
1411 |
+
|
1412 |
+
// Check for an immediate draw or maximum ply reached
|
1413 |
+
if ( pos.is_draw(ss->ply)
|
1414 |
+
|| ss->ply >= MAX_PLY)
|
1415 |
+
return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW;
|
1416 |
+
|
1417 |
+
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
1418 |
+
|
1419 |
+
// Decide whether or not to include checks: this fixes also the type of
|
1420 |
+
// TT entry depth that we are going to use. Note that in qsearch we use
|
1421 |
+
// only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS.
|
1422 |
+
ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS
|
1423 |
+
: DEPTH_QS_NO_CHECKS;
|
1424 |
+
// Transposition table lookup
|
1425 |
+
posKey = pos.key();
|
1426 |
+
tte = TT.probe(posKey, ss->ttHit);
|
1427 |
+
ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
|
1428 |
+
ttMove = ss->ttHit ? tte->move() : MOVE_NONE;
|
1429 |
+
pvHit = ss->ttHit && tte->is_pv();
|
1430 |
+
|
1431 |
+
if ( !PvNode
|
1432 |
+
&& ss->ttHit
|
1433 |
+
&& tte->depth() >= ttDepth
|
1434 |
+
&& ttValue != VALUE_NONE // Only in case of TT access race
|
1435 |
+
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
|
1436 |
+
return ttValue;
|
1437 |
+
|
1438 |
+
// Evaluate the position statically
|
1439 |
+
if (ss->inCheck)
|
1440 |
+
{
|
1441 |
+
ss->staticEval = VALUE_NONE;
|
1442 |
+
bestValue = futilityBase = -VALUE_INFINITE;
|
1443 |
+
}
|
1444 |
+
else
|
1445 |
+
{
|
1446 |
+
if (ss->ttHit)
|
1447 |
+
{
|
1448 |
+
// Never assume anything about values stored in TT
|
1449 |
+
if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE)
|
1450 |
+
ss->staticEval = bestValue = evaluate(pos);
|
1451 |
+
|
1452 |
+
// ttValue can be used as a better position evaluation (~7 Elo)
|
1453 |
+
if ( ttValue != VALUE_NONE
|
1454 |
+
&& (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)))
|
1455 |
+
bestValue = ttValue;
|
1456 |
+
}
|
1457 |
+
else
|
1458 |
+
// In case of null move search use previous static eval with a different sign
|
1459 |
+
ss->staticEval = bestValue =
|
1460 |
+
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
|
1461 |
+
: -(ss-1)->staticEval;
|
1462 |
+
|
1463 |
+
// Stand pat. Return immediately if static value is at least beta
|
1464 |
+
if (bestValue >= beta)
|
1465 |
+
{
|
1466 |
+
// Save gathered info in transposition table
|
1467 |
+
if (!ss->ttHit)
|
1468 |
+
tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
|
1469 |
+
DEPTH_NONE, MOVE_NONE, ss->staticEval);
|
1470 |
+
|
1471 |
+
return bestValue;
|
1472 |
+
}
|
1473 |
+
|
1474 |
+
if (PvNode && bestValue > alpha)
|
1475 |
+
alpha = bestValue;
|
1476 |
+
|
1477 |
+
futilityBase = bestValue + 153;
|
1478 |
+
}
|
1479 |
+
|
1480 |
+
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
|
1481 |
+
nullptr , (ss-4)->continuationHistory,
|
1482 |
+
nullptr , (ss-6)->continuationHistory };
|
1483 |
+
|
1484 |
+
// Initialize a MovePicker object for the current position, and prepare
|
1485 |
+
// to search the moves. Because the depth is <= 0 here, only captures,
|
1486 |
+
// queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS)
|
1487 |
+
// will be generated.
|
1488 |
+
Square prevSq = to_sq((ss-1)->currentMove);
|
1489 |
+
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
|
1490 |
+
&thisThread->captureHistory,
|
1491 |
+
contHist,
|
1492 |
+
prevSq);
|
1493 |
+
|
1494 |
+
int quietCheckEvasions = 0;
|
1495 |
+
|
1496 |
+
// Loop through the moves until no moves remain or a beta cutoff occurs
|
1497 |
+
while ((move = mp.next_move()) != MOVE_NONE)
|
1498 |
+
{
|
1499 |
+
assert(is_ok(move));
|
1500 |
+
|
1501 |
+
// Check for legality
|
1502 |
+
if (!pos.legal(move))
|
1503 |
+
continue;
|
1504 |
+
|
1505 |
+
givesCheck = pos.gives_check(move);
|
1506 |
+
capture = pos.capture(move);
|
1507 |
+
|
1508 |
+
moveCount++;
|
1509 |
+
|
1510 |
+
// Futility pruning and moveCount pruning (~5 Elo)
|
1511 |
+
if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY
|
1512 |
+
&& !givesCheck
|
1513 |
+
&& to_sq(move) != prevSq
|
1514 |
+
&& futilityBase > -VALUE_KNOWN_WIN
|
1515 |
+
&& type_of(move) != PROMOTION)
|
1516 |
+
{
|
1517 |
+
|
1518 |
+
if (moveCount > 2)
|
1519 |
+
continue;
|
1520 |
+
|
1521 |
+
futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))];
|
1522 |
+
|
1523 |
+
if (futilityValue <= alpha)
|
1524 |
+
{
|
1525 |
+
bestValue = std::max(bestValue, futilityValue);
|
1526 |
+
continue;
|
1527 |
+
}
|
1528 |
+
|
1529 |
+
if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1))
|
1530 |
+
{
|
1531 |
+
bestValue = std::max(bestValue, futilityBase);
|
1532 |
+
continue;
|
1533 |
+
}
|
1534 |
+
}
|
1535 |
+
|
1536 |
+
// Do not search moves with negative SEE values (~5 Elo)
|
1537 |
+
if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY
|
1538 |
+
&& !pos.see_ge(move))
|
1539 |
+
continue;
|
1540 |
+
|
1541 |
+
// Speculative prefetch as early as possible
|
1542 |
+
prefetch(TT.first_entry(pos.key_after(move)));
|
1543 |
+
|
1544 |
+
ss->currentMove = move;
|
1545 |
+
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
|
1546 |
+
[capture]
|
1547 |
+
[pos.moved_piece(move)]
|
1548 |
+
[to_sq(move)];
|
1549 |
+
|
1550 |
+
// Continuation history based pruning (~2 Elo)
|
1551 |
+
if ( !capture
|
1552 |
+
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY
|
1553 |
+
&& (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0
|
1554 |
+
&& (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0)
|
1555 |
+
continue;
|
1556 |
+
|
1557 |
+
// We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter
|
1558 |
+
// and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead.
|
1559 |
+
if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY
|
1560 |
+
&& quietCheckEvasions > 1)
|
1561 |
+
break;
|
1562 |
+
|
1563 |
+
quietCheckEvasions += !capture && ss->inCheck;
|
1564 |
+
|
1565 |
+
// Make and search the move
|
1566 |
+
pos.do_move(move, st, givesCheck);
|
1567 |
+
value = -qsearch<nodeType>(pos, ss+1, -beta, -alpha, depth - 1);
|
1568 |
+
pos.undo_move(move);
|
1569 |
+
|
1570 |
+
assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
|
1571 |
+
|
1572 |
+
// Check for a new best move
|
1573 |
+
if (value > bestValue)
|
1574 |
+
{
|
1575 |
+
bestValue = value;
|
1576 |
+
|
1577 |
+
if (value > alpha)
|
1578 |
+
{
|
1579 |
+
bestMove = move;
|
1580 |
+
|
1581 |
+
if (PvNode) // Update pv even in fail-high case
|
1582 |
+
update_pv(ss->pv, move, (ss+1)->pv);
|
1583 |
+
|
1584 |
+
if (PvNode && value < beta) // Update alpha here!
|
1585 |
+
alpha = value;
|
1586 |
+
else
|
1587 |
+
break; // Fail high
|
1588 |
+
}
|
1589 |
+
}
|
1590 |
+
}
|
1591 |
+
|
1592 |
+
// All legal moves have been searched. A special case: if we're in check
|
1593 |
+
// and no legal moves were found, it is checkmate.
|
1594 |
+
if (ss->inCheck && bestValue == -VALUE_INFINITE)
|
1595 |
+
{
|
1596 |
+
assert(!MoveList<LEGAL>(pos).size());
|
1597 |
+
|
1598 |
+
return mated_in(ss->ply); // Plies to mate from the root
|
1599 |
+
}
|
1600 |
+
|
1601 |
+
// Save gathered info in transposition table
|
1602 |
+
tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit,
|
1603 |
+
bestValue >= beta ? BOUND_LOWER : BOUND_UPPER,
|
1604 |
+
ttDepth, bestMove, ss->staticEval);
|
1605 |
+
|
1606 |
+
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
|
1607 |
+
|
1608 |
+
return bestValue;
|
1609 |
+
}
|
1610 |
+
|
1611 |
+
|
1612 |
+
// value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to
|
1613 |
+
// "plies to mate from the current position". Standard scores are unchanged.
|
1614 |
+
// The function is called before storing a value in the transposition table.
|
1615 |
+
|
1616 |
+
Value value_to_tt(Value v, int ply) {
|
1617 |
+
|
1618 |
+
assert(v != VALUE_NONE);
|
1619 |
+
|
1620 |
+
return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply
|
1621 |
+
: v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v;
|
1622 |
+
}
|
1623 |
+
|
1624 |
+
|
1625 |
+
// value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score
|
1626 |
+
// from the transposition table (which refers to the plies to mate/be mated from
|
1627 |
+
// current position) to "plies to mate/be mated (TB win/loss) from the root". However,
|
1628 |
+
// for mate scores, to avoid potentially false mate scores related to the 50 moves rule
|
1629 |
+
// and the graph history interaction, we return an optimal TB score instead.
|
1630 |
+
|
1631 |
+
Value value_from_tt(Value v, int ply, int r50c) {
|
1632 |
+
|
1633 |
+
if (v == VALUE_NONE)
|
1634 |
+
return VALUE_NONE;
|
1635 |
+
|
1636 |
+
if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better
|
1637 |
+
{
|
1638 |
+
if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c)
|
1639 |
+
return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score
|
1640 |
+
|
1641 |
+
return v - ply;
|
1642 |
+
}
|
1643 |
+
|
1644 |
+
if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse
|
1645 |
+
{
|
1646 |
+
if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c)
|
1647 |
+
return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score
|
1648 |
+
|
1649 |
+
return v + ply;
|
1650 |
+
}
|
1651 |
+
|
1652 |
+
return v;
|
1653 |
+
}
|
1654 |
+
|
1655 |
+
|
1656 |
+
// update_pv() adds current move and appends child pv[]
|
1657 |
+
|
1658 |
+
void update_pv(Move* pv, Move move, const Move* childPv) {
|
1659 |
+
|
1660 |
+
for (*pv++ = move; childPv && *childPv != MOVE_NONE; )
|
1661 |
+
*pv++ = *childPv++;
|
1662 |
+
*pv = MOVE_NONE;
|
1663 |
+
}
|
1664 |
+
|
1665 |
+
|
1666 |
+
// update_all_stats() updates stats at the end of search() when a bestMove is found
|
1667 |
+
|
1668 |
+
void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq,
|
1669 |
+
Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) {
|
1670 |
+
|
1671 |
+
Color us = pos.side_to_move();
|
1672 |
+
Thread* thisThread = pos.this_thread();
|
1673 |
+
CapturePieceToHistory& captureHistory = thisThread->captureHistory;
|
1674 |
+
Piece moved_piece = pos.moved_piece(bestMove);
|
1675 |
+
PieceType captured = type_of(pos.piece_on(to_sq(bestMove)));
|
1676 |
+
int bonus1 = stat_bonus(depth + 1);
|
1677 |
+
|
1678 |
+
if (!pos.capture(bestMove))
|
1679 |
+
{
|
1680 |
+
int bonus2 = bestValue > beta + 137 ? bonus1 // larger bonus
|
1681 |
+
: stat_bonus(depth); // smaller bonus
|
1682 |
+
|
1683 |
+
// Increase stats for the best move in case it was a quiet move
|
1684 |
+
update_quiet_stats(pos, ss, bestMove, bonus2);
|
1685 |
+
|
1686 |
+
// Decrease stats for all non-best quiet moves
|
1687 |
+
for (int i = 0; i < quietCount; ++i)
|
1688 |
+
{
|
1689 |
+
thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2;
|
1690 |
+
update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2);
|
1691 |
+
}
|
1692 |
+
}
|
1693 |
+
else
|
1694 |
+
// Increase stats for the best move in case it was a capture move
|
1695 |
+
captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
|
1696 |
+
|
1697 |
+
// Extra penalty for a quiet early move that was not a TT move or
|
1698 |
+
// main killer move in previous ply when it gets refuted.
|
1699 |
+
if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0]))
|
1700 |
+
&& !pos.captured_piece())
|
1701 |
+
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1);
|
1702 |
+
|
1703 |
+
// Decrease stats for all non-best capture moves
|
1704 |
+
for (int i = 0; i < captureCount; ++i)
|
1705 |
+
{
|
1706 |
+
moved_piece = pos.moved_piece(capturesSearched[i]);
|
1707 |
+
captured = type_of(pos.piece_on(to_sq(capturesSearched[i])));
|
1708 |
+
captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1;
|
1709 |
+
}
|
1710 |
+
}
|
1711 |
+
|
1712 |
+
|
1713 |
+
// update_continuation_histories() updates histories of the move pairs formed
|
1714 |
+
// by moves at ply -1, -2, -4, and -6 with current move.
|
1715 |
+
|
1716 |
+
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
|
1717 |
+
|
1718 |
+
for (int i : {1, 2, 4, 6})
|
1719 |
+
{
|
1720 |
+
// Only update first 2 continuation histories if we are in check
|
1721 |
+
if (ss->inCheck && i > 2)
|
1722 |
+
break;
|
1723 |
+
if (is_ok((ss-i)->currentMove))
|
1724 |
+
(*(ss-i)->continuationHistory)[pc][to] << bonus;
|
1725 |
+
}
|
1726 |
+
}
|
1727 |
+
|
1728 |
+
|
1729 |
+
// update_quiet_stats() updates move sorting heuristics
|
1730 |
+
|
1731 |
+
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
|
1732 |
+
|
1733 |
+
// Update killers
|
1734 |
+
if (ss->killers[0] != move)
|
1735 |
+
{
|
1736 |
+
ss->killers[1] = ss->killers[0];
|
1737 |
+
ss->killers[0] = move;
|
1738 |
+
}
|
1739 |
+
|
1740 |
+
Color us = pos.side_to_move();
|
1741 |
+
Thread* thisThread = pos.this_thread();
|
1742 |
+
thisThread->mainHistory[us][from_to(move)] << bonus;
|
1743 |
+
update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus);
|
1744 |
+
|
1745 |
+
// Update countermove history
|
1746 |
+
if (is_ok((ss-1)->currentMove))
|
1747 |
+
{
|
1748 |
+
Square prevSq = to_sq((ss-1)->currentMove);
|
1749 |
+
thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move;
|
1750 |
+
}
|
1751 |
+
}
|
1752 |
+
|
1753 |
+
// When playing with strength handicap, choose best move among a set of RootMoves
|
1754 |
+
// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
|
1755 |
+
|
1756 |
+
Move Skill::pick_best(size_t multiPV) {
|
1757 |
+
|
1758 |
+
const RootMoves& rootMoves = Threads.main()->rootMoves;
|
1759 |
+
static PRNG rng(now()); // PRNG sequence should be non-deterministic
|
1760 |
+
|
1761 |
+
// RootMoves are already sorted by score in descending order
|
1762 |
+
Value topScore = rootMoves[0].score;
|
1763 |
+
int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg);
|
1764 |
+
int maxScore = -VALUE_INFINITE;
|
1765 |
+
double weakness = 120 - 2 * level;
|
1766 |
+
|
1767 |
+
// Choose best move. For each move score we add two terms, both dependent on
|
1768 |
+
// weakness. One is deterministic and bigger for weaker levels, and one is
|
1769 |
+
// random. Then we choose the move with the resulting highest score.
|
1770 |
+
for (size_t i = 0; i < multiPV; ++i)
|
1771 |
+
{
|
1772 |
+
// This is our magic formula
|
1773 |
+
int push = int(( weakness * int(topScore - rootMoves[i].score)
|
1774 |
+
+ delta * (rng.rand<unsigned>() % int(weakness))) / 128);
|
1775 |
+
|
1776 |
+
if (rootMoves[i].score + push >= maxScore)
|
1777 |
+
{
|
1778 |
+
maxScore = rootMoves[i].score + push;
|
1779 |
+
best = rootMoves[i].pv[0];
|
1780 |
+
}
|
1781 |
+
}
|
1782 |
+
|
1783 |
+
return best;
|
1784 |
+
}
|
1785 |
+
|
1786 |
+
} // namespace
|
1787 |
+
|
1788 |
+
|
1789 |
+
/// MainThread::check_time() is used to print debug info and, more importantly,
|
1790 |
+
/// to detect when we are out of available time and thus stop the search.
|
1791 |
+
|
1792 |
+
void MainThread::check_time() {
|
1793 |
+
|
1794 |
+
if (--callsCnt > 0)
|
1795 |
+
return;
|
1796 |
+
|
1797 |
+
// When using nodes, ensure checking rate is not lower than 0.1% of nodes
|
1798 |
+
callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024;
|
1799 |
+
|
1800 |
+
static TimePoint lastInfoTime = now();
|
1801 |
+
|
1802 |
+
TimePoint elapsed = Time.elapsed();
|
1803 |
+
TimePoint tick = Limits.startTime + elapsed;
|
1804 |
+
|
1805 |
+
if (tick - lastInfoTime >= 1000)
|
1806 |
+
{
|
1807 |
+
lastInfoTime = tick;
|
1808 |
+
dbg_print();
|
1809 |
+
}
|
1810 |
+
|
1811 |
+
// We should not stop pondering until told so by the GUI
|
1812 |
+
if (ponder)
|
1813 |
+
return;
|
1814 |
+
|
1815 |
+
if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit))
|
1816 |
+
|| (Limits.movetime && elapsed >= Limits.movetime)
|
1817 |
+
|| (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes))
|
1818 |
+
Threads.stop = true;
|
1819 |
+
}
|
1820 |
+
|
1821 |
+
|
1822 |
+
/// UCI::pv() formats PV information according to the UCI protocol. UCI requires
|
1823 |
+
/// that all (if any) unsearched PV lines are sent using a previous search score.
|
1824 |
+
|
1825 |
+
string UCI::pv(const Position& pos, Depth depth) {
|
1826 |
+
|
1827 |
+
std::stringstream ss;
|
1828 |
+
TimePoint elapsed = Time.elapsed() + 1;
|
1829 |
+
const RootMoves& rootMoves = pos.this_thread()->rootMoves;
|
1830 |
+
size_t pvIdx = pos.this_thread()->pvIdx;
|
1831 |
+
size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size());
|
1832 |
+
uint64_t nodesSearched = Threads.nodes_searched();
|
1833 |
+
uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0);
|
1834 |
+
|
1835 |
+
for (size_t i = 0; i < multiPV; ++i)
|
1836 |
+
{
|
1837 |
+
bool updated = rootMoves[i].score != -VALUE_INFINITE;
|
1838 |
+
|
1839 |
+
if (depth == 1 && !updated && i > 0)
|
1840 |
+
continue;
|
1841 |
+
|
1842 |
+
Depth d = updated ? depth : std::max(1, depth - 1);
|
1843 |
+
Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore;
|
1844 |
+
|
1845 |
+
if (v == -VALUE_INFINITE)
|
1846 |
+
v = VALUE_ZERO;
|
1847 |
+
|
1848 |
+
bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY;
|
1849 |
+
v = tb ? rootMoves[i].tbScore : v;
|
1850 |
+
|
1851 |
+
if (ss.rdbuf()->in_avail()) // Not at first line
|
1852 |
+
ss << "\n";
|
1853 |
+
|
1854 |
+
ss << "info"
|
1855 |
+
<< " depth " << d
|
1856 |
+
<< " seldepth " << rootMoves[i].selDepth
|
1857 |
+
<< " multipv " << i + 1
|
1858 |
+
<< " score " << UCI::value(v);
|
1859 |
+
|
1860 |
+
if (Options["UCI_ShowWDL"])
|
1861 |
+
ss << UCI::wdl(v, pos.game_ply());
|
1862 |
+
|
1863 |
+
if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact
|
1864 |
+
ss << (rootMoves[i].scoreLowerbound ? " lowerbound" : (rootMoves[i].scoreUpperbound ? " upperbound" : ""));
|
1865 |
+
|
1866 |
+
ss << " nodes " << nodesSearched
|
1867 |
+
<< " nps " << nodesSearched * 1000 / elapsed
|
1868 |
+
<< " hashfull " << TT.hashfull()
|
1869 |
+
<< " tbhits " << tbHits
|
1870 |
+
<< " time " << elapsed
|
1871 |
+
<< " pv";
|
1872 |
+
|
1873 |
+
for (Move m : rootMoves[i].pv)
|
1874 |
+
ss << " " << UCI::move(m, pos.is_chess960());
|
1875 |
+
}
|
1876 |
+
|
1877 |
+
return ss.str();
|
1878 |
+
}
|
1879 |
+
|
1880 |
+
|
1881 |
+
/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move
|
1882 |
+
/// before exiting the search, for instance, in case we stop the search during a
|
1883 |
+
/// fail high at root. We try hard to have a ponder move to return to the GUI,
|
1884 |
+
/// otherwise in case of 'ponder on' we have nothing to think on.
|
1885 |
+
|
1886 |
+
bool RootMove::extract_ponder_from_tt(Position& pos) {
|
1887 |
+
|
1888 |
+
StateInfo st;
|
1889 |
+
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
1890 |
+
|
1891 |
+
bool ttHit;
|
1892 |
+
|
1893 |
+
assert(pv.size() == 1);
|
1894 |
+
|
1895 |
+
if (pv[0] == MOVE_NONE)
|
1896 |
+
return false;
|
1897 |
+
|
1898 |
+
pos.do_move(pv[0], st);
|
1899 |
+
TTEntry* tte = TT.probe(pos.key(), ttHit);
|
1900 |
+
|
1901 |
+
if (ttHit)
|
1902 |
+
{
|
1903 |
+
Move m = tte->move(); // Local copy to be SMP safe
|
1904 |
+
if (MoveList<LEGAL>(pos).contains(m))
|
1905 |
+
pv.push_back(m);
|
1906 |
+
}
|
1907 |
+
|
1908 |
+
pos.undo_move(pv[0]);
|
1909 |
+
return pv.size() > 1;
|
1910 |
+
}
|
1911 |
+
|
1912 |
+
void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
|
1913 |
+
|
1914 |
+
RootInTB = false;
|
1915 |
+
UseRule50 = bool(Options["Syzygy50MoveRule"]);
|
1916 |
+
ProbeDepth = int(Options["SyzygyProbeDepth"]);
|
1917 |
+
Cardinality = int(Options["SyzygyProbeLimit"]);
|
1918 |
+
bool dtz_available = true;
|
1919 |
+
|
1920 |
+
// Tables with fewer pieces than SyzygyProbeLimit are searched with
|
1921 |
+
// ProbeDepth == DEPTH_ZERO
|
1922 |
+
if (Cardinality > MaxCardinality)
|
1923 |
+
{
|
1924 |
+
Cardinality = MaxCardinality;
|
1925 |
+
ProbeDepth = 0;
|
1926 |
+
}
|
1927 |
+
|
1928 |
+
if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
|
1929 |
+
{
|
1930 |
+
// Rank moves using DTZ tables
|
1931 |
+
RootInTB = root_probe(pos, rootMoves);
|
1932 |
+
|
1933 |
+
if (!RootInTB)
|
1934 |
+
{
|
1935 |
+
// DTZ tables are missing; try to rank moves using WDL tables
|
1936 |
+
dtz_available = false;
|
1937 |
+
RootInTB = root_probe_wdl(pos, rootMoves);
|
1938 |
+
}
|
1939 |
+
}
|
1940 |
+
|
1941 |
+
if (RootInTB)
|
1942 |
+
{
|
1943 |
+
// Sort moves according to TB rank
|
1944 |
+
std::stable_sort(rootMoves.begin(), rootMoves.end(),
|
1945 |
+
[](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } );
|
1946 |
+
|
1947 |
+
// Probe during search only if DTZ is not available and we are winning
|
1948 |
+
if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW)
|
1949 |
+
Cardinality = 0;
|
1950 |
+
}
|
1951 |
+
else
|
1952 |
+
{
|
1953 |
+
// Clean up if root_probe() and root_probe_wdl() have failed
|
1954 |
+
for (auto& m : rootMoves)
|
1955 |
+
m.tbRank = 0;
|
1956 |
+
}
|
1957 |
+
}
|
1958 |
+
|
1959 |
+
} // namespace Stockfish
|
src/search.h
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef SEARCH_H_INCLUDED
|
20 |
+
#define SEARCH_H_INCLUDED
|
21 |
+
|
22 |
+
#include <vector>
|
23 |
+
|
24 |
+
#include "misc.h"
|
25 |
+
#include "movepick.h"
|
26 |
+
#include "types.h"
|
27 |
+
|
28 |
+
namespace Stockfish {
|
29 |
+
|
30 |
+
class Position;
|
31 |
+
|
32 |
+
namespace Search {
|
33 |
+
|
34 |
+
|
35 |
+
/// Stack struct keeps track of the information we need to remember from nodes
|
36 |
+
/// shallower and deeper in the tree during the search. Each search thread has
|
37 |
+
/// its own array of Stack objects, indexed by the current ply.
|
38 |
+
|
39 |
+
struct Stack {
|
40 |
+
Move* pv;
|
41 |
+
PieceToHistory* continuationHistory;
|
42 |
+
int ply;
|
43 |
+
Move currentMove;
|
44 |
+
Move excludedMove;
|
45 |
+
Move killers[2];
|
46 |
+
Value staticEval;
|
47 |
+
int statScore;
|
48 |
+
int moveCount;
|
49 |
+
bool inCheck;
|
50 |
+
bool ttPv;
|
51 |
+
bool ttHit;
|
52 |
+
int doubleExtensions;
|
53 |
+
int cutoffCnt;
|
54 |
+
};
|
55 |
+
|
56 |
+
|
57 |
+
/// RootMove struct is used for moves at the root of the tree. For each root move
|
58 |
+
/// we store a score and a PV (really a refutation in the case of moves which
|
59 |
+
/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
|
60 |
+
|
61 |
+
struct RootMove {
|
62 |
+
|
63 |
+
explicit RootMove(Move m) : pv(1, m) {}
|
64 |
+
bool extract_ponder_from_tt(Position& pos);
|
65 |
+
bool operator==(const Move& m) const { return pv[0] == m; }
|
66 |
+
bool operator<(const RootMove& m) const { // Sort in descending order
|
67 |
+
return m.score != score ? m.score < score
|
68 |
+
: m.previousScore < previousScore;
|
69 |
+
}
|
70 |
+
|
71 |
+
Value score = -VALUE_INFINITE;
|
72 |
+
Value previousScore = -VALUE_INFINITE;
|
73 |
+
Value averageScore = -VALUE_INFINITE;
|
74 |
+
bool scoreLowerbound = false;
|
75 |
+
bool scoreUpperbound = false;
|
76 |
+
int selDepth = 0;
|
77 |
+
int tbRank = 0;
|
78 |
+
Value tbScore;
|
79 |
+
std::vector<Move> pv;
|
80 |
+
};
|
81 |
+
|
82 |
+
typedef std::vector<RootMove> RootMoves;
|
83 |
+
|
84 |
+
|
85 |
+
/// LimitsType struct stores information sent by GUI about available time to
|
86 |
+
/// search the current move, maximum depth/time, or if we are in analysis mode.
|
87 |
+
|
88 |
+
struct LimitsType {
|
89 |
+
|
90 |
+
LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
|
91 |
+
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
|
92 |
+
movestogo = depth = mate = perft = infinite = 0;
|
93 |
+
nodes = 0;
|
94 |
+
}
|
95 |
+
|
96 |
+
bool use_time_management() const {
|
97 |
+
return time[WHITE] || time[BLACK];
|
98 |
+
}
|
99 |
+
|
100 |
+
std::vector<Move> searchmoves;
|
101 |
+
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
|
102 |
+
int movestogo, depth, mate, perft, infinite;
|
103 |
+
int64_t nodes;
|
104 |
+
};
|
105 |
+
|
106 |
+
extern LimitsType Limits;
|
107 |
+
|
108 |
+
void init();
|
109 |
+
void clear();
|
110 |
+
|
111 |
+
} // namespace Search
|
112 |
+
|
113 |
+
} // namespace Stockfish
|
114 |
+
|
115 |
+
#endif // #ifndef SEARCH_H_INCLUDED
|
src/syzygy/tbprobe.cpp
ADDED
@@ -0,0 +1,1628 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <algorithm>
|
20 |
+
#include <atomic>
|
21 |
+
#include <cstdint>
|
22 |
+
#include <cstring> // For std::memset and std::memcpy
|
23 |
+
#include <deque>
|
24 |
+
#include <fstream>
|
25 |
+
#include <iostream>
|
26 |
+
#include <list>
|
27 |
+
#include <sstream>
|
28 |
+
#include <type_traits>
|
29 |
+
#include <mutex>
|
30 |
+
|
31 |
+
#include "../bitboard.h"
|
32 |
+
#include "../movegen.h"
|
33 |
+
#include "../position.h"
|
34 |
+
#include "../search.h"
|
35 |
+
#include "../types.h"
|
36 |
+
#include "../uci.h"
|
37 |
+
|
38 |
+
#include "tbprobe.h"
|
39 |
+
|
40 |
+
#ifndef _WIN32
|
41 |
+
#include <fcntl.h>
|
42 |
+
#include <unistd.h>
|
43 |
+
#include <sys/mman.h>
|
44 |
+
#include <sys/stat.h>
|
45 |
+
#else
|
46 |
+
#define WIN32_LEAN_AND_MEAN
|
47 |
+
#ifndef NOMINMAX
|
48 |
+
# define NOMINMAX // Disable macros min() and max()
|
49 |
+
#endif
|
50 |
+
#include <windows.h>
|
51 |
+
#endif
|
52 |
+
|
53 |
+
using namespace Stockfish::Tablebases;
|
54 |
+
|
55 |
+
int Stockfish::Tablebases::MaxCardinality;
|
56 |
+
|
57 |
+
namespace Stockfish {
|
58 |
+
|
59 |
+
namespace {
|
60 |
+
|
61 |
+
constexpr int TBPIECES = 7; // Max number of supported pieces
|
62 |
+
constexpr int MAX_DTZ = 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit.
|
63 |
+
|
64 |
+
enum { BigEndian, LittleEndian };
|
65 |
+
enum TBType { WDL, DTZ }; // Used as template parameter
|
66 |
+
|
67 |
+
// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables
|
68 |
+
enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 };
|
69 |
+
|
70 |
+
inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
|
71 |
+
inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
|
72 |
+
|
73 |
+
const std::string PieceToChar = " PNBRQK pnbrqk";
|
74 |
+
|
75 |
+
int MapPawns[SQUARE_NB];
|
76 |
+
int MapB1H1H7[SQUARE_NB];
|
77 |
+
int MapA1D1D4[SQUARE_NB];
|
78 |
+
int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB]
|
79 |
+
|
80 |
+
int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements
|
81 |
+
int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB]
|
82 |
+
int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D]
|
83 |
+
|
84 |
+
// Comparison function to sort leading pawns in ascending MapPawns[] order
|
85 |
+
bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; }
|
86 |
+
int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); }
|
87 |
+
|
88 |
+
constexpr Value WDL_to_value[] = {
|
89 |
+
-VALUE_MATE + MAX_PLY + 1,
|
90 |
+
VALUE_DRAW - 2,
|
91 |
+
VALUE_DRAW,
|
92 |
+
VALUE_DRAW + 2,
|
93 |
+
VALUE_MATE - MAX_PLY - 1
|
94 |
+
};
|
95 |
+
|
96 |
+
template<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>
|
97 |
+
inline void swap_endian(T& x)
|
98 |
+
{
|
99 |
+
static_assert(std::is_unsigned<T>::value, "Argument of swap_endian not unsigned");
|
100 |
+
|
101 |
+
uint8_t tmp, *c = (uint8_t*)&x;
|
102 |
+
for (int i = 0; i < Half; ++i)
|
103 |
+
tmp = c[i], c[i] = c[End - i], c[End - i] = tmp;
|
104 |
+
}
|
105 |
+
template<> inline void swap_endian<uint8_t>(uint8_t&) {}
|
106 |
+
|
107 |
+
template<typename T, int LE> T number(void* addr)
|
108 |
+
{
|
109 |
+
T v;
|
110 |
+
|
111 |
+
if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare)
|
112 |
+
std::memcpy(&v, addr, sizeof(T));
|
113 |
+
else
|
114 |
+
v = *((T*)addr);
|
115 |
+
|
116 |
+
if (LE != IsLittleEndian)
|
117 |
+
swap_endian(v);
|
118 |
+
return v;
|
119 |
+
}
|
120 |
+
|
121 |
+
// DTZ tables don't store valid scores for moves that reset the rule50 counter
|
122 |
+
// like captures and pawn moves but we can easily recover the correct dtz of the
|
123 |
+
// previous move if we know the position's WDL score.
|
124 |
+
int dtz_before_zeroing(WDLScore wdl) {
|
125 |
+
return wdl == WDLWin ? 1 :
|
126 |
+
wdl == WDLCursedWin ? 101 :
|
127 |
+
wdl == WDLBlessedLoss ? -101 :
|
128 |
+
wdl == WDLLoss ? -1 : 0;
|
129 |
+
}
|
130 |
+
|
131 |
+
// Return the sign of a number (-1, 0, 1)
|
132 |
+
template <typename T> int sign_of(T val) {
|
133 |
+
return (T(0) < val) - (val < T(0));
|
134 |
+
}
|
135 |
+
|
136 |
+
// Numbers in little endian used by sparseIndex[] to point into blockLength[]
|
137 |
+
struct SparseEntry {
|
138 |
+
char block[4]; // Number of block
|
139 |
+
char offset[2]; // Offset within the block
|
140 |
+
};
|
141 |
+
|
142 |
+
static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");
|
143 |
+
|
144 |
+
typedef uint16_t Sym; // Huffman symbol
|
145 |
+
|
146 |
+
struct LR {
|
147 |
+
enum Side { Left, Right };
|
148 |
+
|
149 |
+
uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12
|
150 |
+
// bits is the right-hand symbol. If symbol has length 1,
|
151 |
+
// then the left-hand symbol is the stored value.
|
152 |
+
template<Side S>
|
153 |
+
Sym get() {
|
154 |
+
return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] :
|
155 |
+
S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1));
|
156 |
+
}
|
157 |
+
};
|
158 |
+
|
159 |
+
static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");
|
160 |
+
|
161 |
+
// Tablebases data layout is structured as following:
|
162 |
+
//
|
163 |
+
// TBFile: memory maps/unmaps the physical .rtbw and .rtbz files
|
164 |
+
// TBTable: one object for each file with corresponding indexing information
|
165 |
+
// TBTables: has ownership of TBTable objects, keeping a list and a hash
|
166 |
+
|
167 |
+
// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are
|
168 |
+
// memory mapped for best performance. Files are mapped at first access: at init
|
169 |
+
// time only existence of the file is checked.
|
170 |
+
class TBFile : public std::ifstream {
|
171 |
+
|
172 |
+
std::string fname;
|
173 |
+
|
174 |
+
public:
|
175 |
+
// Look for and open the file among the Paths directories where the .rtbw
|
176 |
+
// and .rtbz files can be found. Multiple directories are separated by ";"
|
177 |
+
// on Windows and by ":" on Unix-based operating systems.
|
178 |
+
//
|
179 |
+
// Example:
|
180 |
+
// C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6
|
181 |
+
static std::string Paths;
|
182 |
+
|
183 |
+
TBFile(const std::string& f) {
|
184 |
+
|
185 |
+
#ifndef _WIN32
|
186 |
+
constexpr char SepChar = ':';
|
187 |
+
#else
|
188 |
+
constexpr char SepChar = ';';
|
189 |
+
#endif
|
190 |
+
std::stringstream ss(Paths);
|
191 |
+
std::string path;
|
192 |
+
|
193 |
+
while (std::getline(ss, path, SepChar))
|
194 |
+
{
|
195 |
+
fname = path + "/" + f;
|
196 |
+
std::ifstream::open(fname);
|
197 |
+
if (is_open())
|
198 |
+
return;
|
199 |
+
}
|
200 |
+
}
|
201 |
+
|
202 |
+
// Memory map the file and check it. File should be already open and will be
|
203 |
+
// closed after mapping.
|
204 |
+
uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {
|
205 |
+
|
206 |
+
assert(is_open());
|
207 |
+
|
208 |
+
close(); // Need to re-open to get native file descriptor
|
209 |
+
|
210 |
+
#ifndef _WIN32
|
211 |
+
struct stat statbuf;
|
212 |
+
int fd = ::open(fname.c_str(), O_RDONLY);
|
213 |
+
|
214 |
+
if (fd == -1)
|
215 |
+
return *baseAddress = nullptr, nullptr;
|
216 |
+
|
217 |
+
fstat(fd, &statbuf);
|
218 |
+
|
219 |
+
if (statbuf.st_size % 64 != 16)
|
220 |
+
{
|
221 |
+
std::cerr << "Corrupt tablebase file " << fname << std::endl;
|
222 |
+
exit(EXIT_FAILURE);
|
223 |
+
}
|
224 |
+
|
225 |
+
*mapping = statbuf.st_size;
|
226 |
+
*baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
227 |
+
#if defined(MADV_RANDOM)
|
228 |
+
madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);
|
229 |
+
#endif
|
230 |
+
::close(fd);
|
231 |
+
|
232 |
+
if (*baseAddress == MAP_FAILED)
|
233 |
+
{
|
234 |
+
std::cerr << "Could not mmap() " << fname << std::endl;
|
235 |
+
exit(EXIT_FAILURE);
|
236 |
+
}
|
237 |
+
#else
|
238 |
+
// Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.
|
239 |
+
HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
240 |
+
OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
|
241 |
+
|
242 |
+
if (fd == INVALID_HANDLE_VALUE)
|
243 |
+
return *baseAddress = nullptr, nullptr;
|
244 |
+
|
245 |
+
DWORD size_high;
|
246 |
+
DWORD size_low = GetFileSize(fd, &size_high);
|
247 |
+
|
248 |
+
if (size_low % 64 != 16)
|
249 |
+
{
|
250 |
+
std::cerr << "Corrupt tablebase file " << fname << std::endl;
|
251 |
+
exit(EXIT_FAILURE);
|
252 |
+
}
|
253 |
+
|
254 |
+
HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr);
|
255 |
+
CloseHandle(fd);
|
256 |
+
|
257 |
+
if (!mmap)
|
258 |
+
{
|
259 |
+
std::cerr << "CreateFileMapping() failed" << std::endl;
|
260 |
+
exit(EXIT_FAILURE);
|
261 |
+
}
|
262 |
+
|
263 |
+
*mapping = (uint64_t)mmap;
|
264 |
+
*baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);
|
265 |
+
|
266 |
+
if (!*baseAddress)
|
267 |
+
{
|
268 |
+
std::cerr << "MapViewOfFile() failed, name = " << fname
|
269 |
+
<< ", error = " << GetLastError() << std::endl;
|
270 |
+
exit(EXIT_FAILURE);
|
271 |
+
}
|
272 |
+
#endif
|
273 |
+
uint8_t* data = (uint8_t*)*baseAddress;
|
274 |
+
|
275 |
+
constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 },
|
276 |
+
{ 0x71, 0xE8, 0x23, 0x5D } };
|
277 |
+
|
278 |
+
if (memcmp(data, Magics[type == WDL], 4))
|
279 |
+
{
|
280 |
+
std::cerr << "Corrupted table in file " << fname << std::endl;
|
281 |
+
unmap(*baseAddress, *mapping);
|
282 |
+
return *baseAddress = nullptr, nullptr;
|
283 |
+
}
|
284 |
+
|
285 |
+
return data + 4; // Skip Magics's header
|
286 |
+
}
|
287 |
+
|
288 |
+
static void unmap(void* baseAddress, uint64_t mapping) {
|
289 |
+
|
290 |
+
#ifndef _WIN32
|
291 |
+
munmap(baseAddress, mapping);
|
292 |
+
#else
|
293 |
+
UnmapViewOfFile(baseAddress);
|
294 |
+
CloseHandle((HANDLE)mapping);
|
295 |
+
#endif
|
296 |
+
}
|
297 |
+
};
|
298 |
+
|
299 |
+
std::string TBFile::Paths;
|
300 |
+
|
301 |
+
// struct PairsData contains low level indexing information to access TB data.
|
302 |
+
// There are 8, 4 or 2 PairsData records for each TBTable, according to type of
|
303 |
+
// table and if positions have pawns or not. It is populated at first access.
|
304 |
+
struct PairsData {
|
305 |
+
uint8_t flags; // Table flags, see enum TBFlag
|
306 |
+
uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols
|
307 |
+
uint8_t minSymLen; // Minimum length in bits of the Huffman symbols
|
308 |
+
uint32_t blocksNum; // Number of blocks in the TB file
|
309 |
+
size_t sizeofBlock; // Block size in bytes
|
310 |
+
size_t span; // About every span values there is a SparseIndex[] entry
|
311 |
+
Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value
|
312 |
+
LR* btree; // btree[sym] stores the left and right symbols that expand sym
|
313 |
+
uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536
|
314 |
+
uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum
|
315 |
+
SparseEntry* sparseIndex; // Partial indices into blockLength[]
|
316 |
+
size_t sparseIndexSize; // Size of SparseIndex[] table
|
317 |
+
uint8_t* data; // Start of Huffman compressed data
|
318 |
+
std::vector<uint64_t> base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
|
319 |
+
std::vector<uint8_t> symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256
|
320 |
+
Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups
|
321 |
+
uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces
|
322 |
+
int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1)
|
323 |
+
uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)
|
324 |
+
};
|
325 |
+
|
326 |
+
// struct TBTable contains indexing information to access the corresponding TBFile.
|
327 |
+
// There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable
|
328 |
+
// is populated at init time but the nested PairsData records are populated at
|
329 |
+
// first access, when the corresponding file is memory mapped.
|
330 |
+
template<TBType Type>
|
331 |
+
struct TBTable {
|
332 |
+
typedef typename std::conditional<Type == WDL, WDLScore, int>::type Ret;
|
333 |
+
|
334 |
+
static constexpr int Sides = Type == WDL ? 2 : 1;
|
335 |
+
|
336 |
+
std::atomic_bool ready;
|
337 |
+
void* baseAddress;
|
338 |
+
uint8_t* map;
|
339 |
+
uint64_t mapping;
|
340 |
+
Key key;
|
341 |
+
Key key2;
|
342 |
+
int pieceCount;
|
343 |
+
bool hasPawns;
|
344 |
+
bool hasUniquePieces;
|
345 |
+
uint8_t pawnCount[2]; // [Lead color / other color]
|
346 |
+
PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0]
|
347 |
+
|
348 |
+
PairsData* get(int stm, int f) {
|
349 |
+
return &items[stm % Sides][hasPawns ? f : 0];
|
350 |
+
}
|
351 |
+
|
352 |
+
TBTable() : ready(false), baseAddress(nullptr) {}
|
353 |
+
explicit TBTable(const std::string& code);
|
354 |
+
explicit TBTable(const TBTable<WDL>& wdl);
|
355 |
+
|
356 |
+
~TBTable() {
|
357 |
+
if (baseAddress)
|
358 |
+
TBFile::unmap(baseAddress, mapping);
|
359 |
+
}
|
360 |
+
};
|
361 |
+
|
362 |
+
template<>
|
363 |
+
TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
|
364 |
+
|
365 |
+
StateInfo st;
|
366 |
+
Position pos;
|
367 |
+
|
368 |
+
key = pos.set(code, WHITE, &st).material_key();
|
369 |
+
pieceCount = pos.count<ALL_PIECES>();
|
370 |
+
hasPawns = pos.pieces(PAWN);
|
371 |
+
|
372 |
+
hasUniquePieces = false;
|
373 |
+
for (Color c : { WHITE, BLACK })
|
374 |
+
for (PieceType pt = PAWN; pt < KING; ++pt)
|
375 |
+
if (popcount(pos.pieces(c, pt)) == 1)
|
376 |
+
hasUniquePieces = true;
|
377 |
+
|
378 |
+
// Set the leading color. In case both sides have pawns the leading color
|
379 |
+
// is the side with less pawns because this leads to better compression.
|
380 |
+
bool c = !pos.count<PAWN>(BLACK)
|
381 |
+
|| ( pos.count<PAWN>(WHITE)
|
382 |
+
&& pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));
|
383 |
+
|
384 |
+
pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);
|
385 |
+
pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);
|
386 |
+
|
387 |
+
key2 = pos.set(code, BLACK, &st).material_key();
|
388 |
+
}
|
389 |
+
|
390 |
+
template<>
|
391 |
+
TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) : TBTable() {
|
392 |
+
|
393 |
+
// Use the corresponding WDL table to avoid recalculating all from scratch
|
394 |
+
key = wdl.key;
|
395 |
+
key2 = wdl.key2;
|
396 |
+
pieceCount = wdl.pieceCount;
|
397 |
+
hasPawns = wdl.hasPawns;
|
398 |
+
hasUniquePieces = wdl.hasUniquePieces;
|
399 |
+
pawnCount[0] = wdl.pawnCount[0];
|
400 |
+
pawnCount[1] = wdl.pawnCount[1];
|
401 |
+
}
|
402 |
+
|
403 |
+
// class TBTables creates and keeps ownership of the TBTable objects, one for
|
404 |
+
// each TB file found. It supports a fast, hash based, table lookup. Populated
|
405 |
+
// at init time, accessed at probe time.
|
406 |
+
class TBTables {
|
407 |
+
|
408 |
+
struct Entry
|
409 |
+
{
|
410 |
+
Key key;
|
411 |
+
TBTable<WDL>* wdl;
|
412 |
+
TBTable<DTZ>* dtz;
|
413 |
+
|
414 |
+
template <TBType Type>
|
415 |
+
TBTable<Type>* get() const {
|
416 |
+
return (TBTable<Type>*)(Type == WDL ? (void*)wdl : (void*)dtz);
|
417 |
+
}
|
418 |
+
};
|
419 |
+
|
420 |
+
static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb
|
421 |
+
static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket
|
422 |
+
|
423 |
+
Entry hashTable[Size + Overflow];
|
424 |
+
|
425 |
+
std::deque<TBTable<WDL>> wdlTable;
|
426 |
+
std::deque<TBTable<DTZ>> dtzTable;
|
427 |
+
|
428 |
+
void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
|
429 |
+
uint32_t homeBucket = (uint32_t)key & (Size - 1);
|
430 |
+
Entry entry{ key, wdl, dtz };
|
431 |
+
|
432 |
+
// Ensure last element is empty to avoid overflow when looking up
|
433 |
+
for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) {
|
434 |
+
Key otherKey = hashTable[bucket].key;
|
435 |
+
if (otherKey == key || !hashTable[bucket].get<WDL>()) {
|
436 |
+
hashTable[bucket] = entry;
|
437 |
+
return;
|
438 |
+
}
|
439 |
+
|
440 |
+
// Robin Hood hashing: If we've probed for longer than this element,
|
441 |
+
// insert here and search for a new spot for the other element instead.
|
442 |
+
uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1);
|
443 |
+
if (otherHomeBucket > homeBucket) {
|
444 |
+
std::swap(entry, hashTable[bucket]);
|
445 |
+
key = otherKey;
|
446 |
+
homeBucket = otherHomeBucket;
|
447 |
+
}
|
448 |
+
}
|
449 |
+
std::cerr << "TB hash table size too low!" << std::endl;
|
450 |
+
exit(EXIT_FAILURE);
|
451 |
+
}
|
452 |
+
|
453 |
+
public:
|
454 |
+
template<TBType Type>
|
455 |
+
TBTable<Type>* get(Key key) {
|
456 |
+
for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) {
|
457 |
+
if (entry->key == key || !entry->get<Type>())
|
458 |
+
return entry->get<Type>();
|
459 |
+
}
|
460 |
+
}
|
461 |
+
|
462 |
+
void clear() {
|
463 |
+
memset(hashTable, 0, sizeof(hashTable));
|
464 |
+
wdlTable.clear();
|
465 |
+
dtzTable.clear();
|
466 |
+
}
|
467 |
+
size_t size() const { return wdlTable.size(); }
|
468 |
+
void add(const std::vector<PieceType>& pieces);
|
469 |
+
};
|
470 |
+
|
471 |
+
TBTables TBTables;
|
472 |
+
|
473 |
+
// If the corresponding file exists two new objects TBTable<WDL> and TBTable<DTZ>
|
474 |
+
// are created and added to the lists and hash table. Called at init time.
|
475 |
+
void TBTables::add(const std::vector<PieceType>& pieces) {
|
476 |
+
|
477 |
+
std::string code;
|
478 |
+
|
479 |
+
for (PieceType pt : pieces)
|
480 |
+
code += PieceToChar[pt];
|
481 |
+
|
482 |
+
TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK
|
483 |
+
|
484 |
+
if (!file.is_open()) // Only WDL file is checked
|
485 |
+
return;
|
486 |
+
|
487 |
+
file.close();
|
488 |
+
|
489 |
+
MaxCardinality = std::max((int)pieces.size(), MaxCardinality);
|
490 |
+
|
491 |
+
wdlTable.emplace_back(code);
|
492 |
+
dtzTable.emplace_back(wdlTable.back());
|
493 |
+
|
494 |
+
// Insert into the hash keys for both colors: KRvK with KR white and black
|
495 |
+
insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back());
|
496 |
+
insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back());
|
497 |
+
}
|
498 |
+
|
499 |
+
// TB tables are compressed with canonical Huffman code. The compressed data is divided into
|
500 |
+
// blocks of size d->sizeofBlock, and each block stores a variable number of symbols.
|
501 |
+
// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols
|
502 |
+
// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536
|
503 |
+
// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after
|
504 |
+
// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most
|
505 |
+
// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly
|
506 |
+
// of draws or mostly of wins, but such tables are actually quite common. In principle, the
|
507 |
+
// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for
|
508 |
+
// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so
|
509 |
+
// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long.
|
510 |
+
// The generator picks the size that leads to the smallest table. The "book" of symbols and
|
511 |
+
// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file
|
512 |
+
// will have one table for wtm and one for btm, a TB file with pawns will have tables per
|
513 |
+
// file a,b,c,d also in this case one set for wtm and one for btm.
|
514 |
+
int decompress_pairs(PairsData* d, uint64_t idx) {
|
515 |
+
|
516 |
+
// Special case where all table positions store the same value
|
517 |
+
if (d->flags & TBFlag::SingleValue)
|
518 |
+
return d->minSymLen;
|
519 |
+
|
520 |
+
// First we need to locate the right block that stores the value at index "idx".
|
521 |
+
// Because each block n stores blockLength[n] + 1 values, the index i of the block
|
522 |
+
// that contains the value at position idx is:
|
523 |
+
//
|
524 |
+
// for (i = -1, sum = 0; sum <= idx; i++)
|
525 |
+
// sum += blockLength[i + 1] + 1;
|
526 |
+
//
|
527 |
+
// This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that
|
528 |
+
// point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry
|
529 |
+
// that stores the blockLength[] index and the offset within that block of the value
|
530 |
+
// with index I(k), where:
|
531 |
+
//
|
532 |
+
// I(k) = k * d->span + d->span / 2 (1)
|
533 |
+
|
534 |
+
// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)
|
535 |
+
uint32_t k = uint32_t(idx / d->span);
|
536 |
+
|
537 |
+
// Then we read the corresponding SparseIndex[] entry
|
538 |
+
uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
|
539 |
+
int offset = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);
|
540 |
+
|
541 |
+
// Now compute the difference idx - I(k). From definition of k we know that
|
542 |
+
//
|
543 |
+
// idx = k * d->span + idx % d->span (2)
|
544 |
+
//
|
545 |
+
// So from (1) and (2) we can compute idx - I(K):
|
546 |
+
int diff = idx % d->span - d->span / 2;
|
547 |
+
|
548 |
+
// Sum the above to offset to find the offset corresponding to our idx
|
549 |
+
offset += diff;
|
550 |
+
|
551 |
+
// Move to previous/next block, until we reach the correct block that contains idx,
|
552 |
+
// that is when 0 <= offset <= d->blockLength[block]
|
553 |
+
while (offset < 0)
|
554 |
+
offset += d->blockLength[--block] + 1;
|
555 |
+
|
556 |
+
while (offset > d->blockLength[block])
|
557 |
+
offset -= d->blockLength[block++] + 1;
|
558 |
+
|
559 |
+
// Finally, we find the start address of our block of canonical Huffman symbols
|
560 |
+
uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock));
|
561 |
+
|
562 |
+
// Read the first 64 bits in our block, this is a (truncated) sequence of
|
563 |
+
// unknown number of symbols of unknown length but we know the first one
|
564 |
+
// is at the beginning of this 64 bits sequence.
|
565 |
+
uint64_t buf64 = number<uint64_t, BigEndian>(ptr); ptr += 2;
|
566 |
+
int buf64Size = 64;
|
567 |
+
Sym sym;
|
568 |
+
|
569 |
+
while (true)
|
570 |
+
{
|
571 |
+
int len = 0; // This is the symbol length - d->min_sym_len
|
572 |
+
|
573 |
+
// Now get the symbol length. For any symbol s64 of length l right-padded
|
574 |
+
// to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we
|
575 |
+
// can find the symbol length iterating through base64[].
|
576 |
+
while (buf64 < d->base64[len])
|
577 |
+
++len;
|
578 |
+
|
579 |
+
// All the symbols of a given length are consecutive integers (numerical
|
580 |
+
// sequence property), so we can compute the offset of our symbol of
|
581 |
+
// length len, stored at the beginning of buf64.
|
582 |
+
sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen));
|
583 |
+
|
584 |
+
// Now add the value of the lowest symbol of length len to get our symbol
|
585 |
+
sym += number<Sym, LittleEndian>(&d->lowestSym[len]);
|
586 |
+
|
587 |
+
// If our offset is within the number of values represented by symbol sym
|
588 |
+
// we are done...
|
589 |
+
if (offset < d->symlen[sym] + 1)
|
590 |
+
break;
|
591 |
+
|
592 |
+
// ...otherwise update the offset and continue to iterate
|
593 |
+
offset -= d->symlen[sym] + 1;
|
594 |
+
len += d->minSymLen; // Get the real length
|
595 |
+
buf64 <<= len; // Consume the just processed symbol
|
596 |
+
buf64Size -= len;
|
597 |
+
|
598 |
+
if (buf64Size <= 32) { // Refill the buffer
|
599 |
+
buf64Size += 32;
|
600 |
+
buf64 |= (uint64_t)number<uint32_t, BigEndian>(ptr++) << (64 - buf64Size);
|
601 |
+
}
|
602 |
+
}
|
603 |
+
|
604 |
+
// Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols.
|
605 |
+
// We binary-search for our value recursively expanding into the left and
|
606 |
+
// right child symbols until we reach a leaf node where symlen[sym] + 1 == 1
|
607 |
+
// that will store the value we need.
|
608 |
+
while (d->symlen[sym])
|
609 |
+
{
|
610 |
+
Sym left = d->btree[sym].get<LR::Left>();
|
611 |
+
|
612 |
+
// If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and
|
613 |
+
// expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then
|
614 |
+
// we know that, for instance the ten-th value (offset = 10) will be on
|
615 |
+
// the left side because in Recursive Pairing child symbols are adjacent.
|
616 |
+
if (offset < d->symlen[left] + 1)
|
617 |
+
sym = left;
|
618 |
+
else {
|
619 |
+
offset -= d->symlen[left] + 1;
|
620 |
+
sym = d->btree[sym].get<LR::Right>();
|
621 |
+
}
|
622 |
+
}
|
623 |
+
|
624 |
+
return d->btree[sym].get<LR::Left>();
|
625 |
+
}
|
626 |
+
|
627 |
+
bool check_dtz_stm(TBTable<WDL>*, int, File) { return true; }
|
628 |
+
|
629 |
+
bool check_dtz_stm(TBTable<DTZ>* entry, int stm, File f) {
|
630 |
+
|
631 |
+
auto flags = entry->get(stm, f)->flags;
|
632 |
+
return (flags & TBFlag::STM) == stm
|
633 |
+
|| ((entry->key == entry->key2) && !entry->hasPawns);
|
634 |
+
}
|
635 |
+
|
636 |
+
// DTZ scores are sorted by frequency of occurrence and then assigned the
|
637 |
+
// values 0, 1, 2, ... in order of decreasing frequency. This is done for each
|
638 |
+
// of the four WDLScore values. The mapping information necessary to reconstruct
|
639 |
+
// the original values is stored in the TB file and read during map[] init.
|
640 |
+
WDLScore map_score(TBTable<WDL>*, File, int value, WDLScore) { return WDLScore(value - 2); }
|
641 |
+
|
642 |
+
int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
|
643 |
+
|
644 |
+
constexpr int WDLMap[] = { 1, 3, 0, 2, 0 };
|
645 |
+
|
646 |
+
auto flags = entry->get(0, f)->flags;
|
647 |
+
|
648 |
+
uint8_t* map = entry->map;
|
649 |
+
uint16_t* idx = entry->get(0, f)->map_idx;
|
650 |
+
if (flags & TBFlag::Mapped) {
|
651 |
+
if (flags & TBFlag::Wide)
|
652 |
+
value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value];
|
653 |
+
else
|
654 |
+
value = map[idx[WDLMap[wdl + 2]] + value];
|
655 |
+
}
|
656 |
+
|
657 |
+
// DTZ tables store distance to zero in number of moves or plies. We
|
658 |
+
// want to return plies, so we have convert to plies when needed.
|
659 |
+
if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies))
|
660 |
+
|| (wdl == WDLLoss && !(flags & TBFlag::LossPlies))
|
661 |
+
|| wdl == WDLCursedWin
|
662 |
+
|| wdl == WDLBlessedLoss)
|
663 |
+
value *= 2;
|
664 |
+
|
665 |
+
return value + 1;
|
666 |
+
}
|
667 |
+
|
668 |
+
// Compute a unique index out of a position and use it to probe the TB file. To
|
669 |
+
// encode k pieces of same type and color, first sort the pieces by square in
|
670 |
+
// ascending order s1 <= s2 <= ... <= sk then compute the unique index as:
|
671 |
+
//
|
672 |
+
// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk]
|
673 |
+
//
|
674 |
+
template<typename T, typename Ret = typename T::Ret>
|
675 |
+
Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
|
676 |
+
|
677 |
+
Square squares[TBPIECES];
|
678 |
+
Piece pieces[TBPIECES];
|
679 |
+
uint64_t idx;
|
680 |
+
int next = 0, size = 0, leadPawnsCnt = 0;
|
681 |
+
PairsData* d;
|
682 |
+
Bitboard b, leadPawns = 0;
|
683 |
+
File tbFile = FILE_A;
|
684 |
+
|
685 |
+
// A given TB entry like KRK has associated two material keys: KRvk and Kvkr.
|
686 |
+
// If both sides have the same pieces keys are equal. In this case TB tables
|
687 |
+
// only store the 'white to move' case, so if the position to lookup has black
|
688 |
+
// to move, we need to switch the color and flip the squares before to lookup.
|
689 |
+
bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move());
|
690 |
+
|
691 |
+
// TB files are calculated for white as stronger side. For instance we have
|
692 |
+
// KRvK, not KvKR. A position where stronger side is white will have its
|
693 |
+
// material key == entry->key, otherwise we have to switch the color and
|
694 |
+
// flip the squares before to lookup.
|
695 |
+
bool blackStronger = (pos.material_key() != entry->key);
|
696 |
+
|
697 |
+
int flipColor = (symmetricBlackToMove || blackStronger) * 8;
|
698 |
+
int flipSquares = (symmetricBlackToMove || blackStronger) * 56;
|
699 |
+
int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move();
|
700 |
+
|
701 |
+
// For pawns, TB files store 4 separate tables according if leading pawn is on
|
702 |
+
// file a, b, c or d after reordering. The leading pawn is the one with maximum
|
703 |
+
// MapPawns[] value, that is the one most toward the edges and with lowest rank.
|
704 |
+
if (entry->hasPawns) {
|
705 |
+
|
706 |
+
// In all the 4 tables, pawns are at the beginning of the piece sequence and
|
707 |
+
// their color is the reference one. So we just pick the first one.
|
708 |
+
Piece pc = Piece(entry->get(0, 0)->pieces[0] ^ flipColor);
|
709 |
+
|
710 |
+
assert(type_of(pc) == PAWN);
|
711 |
+
|
712 |
+
leadPawns = b = pos.pieces(color_of(pc), PAWN);
|
713 |
+
do
|
714 |
+
squares[size++] = pop_lsb(b) ^ flipSquares;
|
715 |
+
while (b);
|
716 |
+
|
717 |
+
leadPawnsCnt = size;
|
718 |
+
|
719 |
+
std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));
|
720 |
+
|
721 |
+
tbFile = File(edge_distance(file_of(squares[0])));
|
722 |
+
}
|
723 |
+
|
724 |
+
// DTZ tables are one-sided, i.e. they store positions only for white to
|
725 |
+
// move or only for black to move, so check for side to move to be stm,
|
726 |
+
// early exit otherwise.
|
727 |
+
if (!check_dtz_stm(entry, stm, tbFile))
|
728 |
+
return *result = CHANGE_STM, Ret();
|
729 |
+
|
730 |
+
// Now we are ready to get all the position pieces (but the lead pawns) and
|
731 |
+
// directly map them to the correct color and square.
|
732 |
+
b = pos.pieces() ^ leadPawns;
|
733 |
+
do {
|
734 |
+
Square s = pop_lsb(b);
|
735 |
+
squares[size] = s ^ flipSquares;
|
736 |
+
pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);
|
737 |
+
} while (b);
|
738 |
+
|
739 |
+
assert(size >= 2);
|
740 |
+
|
741 |
+
d = entry->get(stm, tbFile);
|
742 |
+
|
743 |
+
// Then we reorder the pieces to have the same sequence as the one stored
|
744 |
+
// in pieces[i]: the sequence that ensures the best compression.
|
745 |
+
for (int i = leadPawnsCnt; i < size - 1; ++i)
|
746 |
+
for (int j = i + 1; j < size; ++j)
|
747 |
+
if (d->pieces[i] == pieces[j])
|
748 |
+
{
|
749 |
+
std::swap(pieces[i], pieces[j]);
|
750 |
+
std::swap(squares[i], squares[j]);
|
751 |
+
break;
|
752 |
+
}
|
753 |
+
|
754 |
+
// Now we map again the squares so that the square of the lead piece is in
|
755 |
+
// the triangle A1-D1-D4.
|
756 |
+
if (file_of(squares[0]) > FILE_D)
|
757 |
+
for (int i = 0; i < size; ++i)
|
758 |
+
squares[i] = flip_file(squares[i]);
|
759 |
+
|
760 |
+
// Encode leading pawns starting with the one with minimum MapPawns[] and
|
761 |
+
// proceeding in ascending order.
|
762 |
+
if (entry->hasPawns) {
|
763 |
+
idx = LeadPawnIdx[leadPawnsCnt][squares[0]];
|
764 |
+
|
765 |
+
std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp);
|
766 |
+
|
767 |
+
for (int i = 1; i < leadPawnsCnt; ++i)
|
768 |
+
idx += Binomial[i][MapPawns[squares[i]]];
|
769 |
+
|
770 |
+
goto encode_remaining; // With pawns we have finished special treatments
|
771 |
+
}
|
772 |
+
|
773 |
+
// In positions without pawns, we further flip the squares to ensure leading
|
774 |
+
// piece is below RANK_5.
|
775 |
+
if (rank_of(squares[0]) > RANK_4)
|
776 |
+
for (int i = 0; i < size; ++i)
|
777 |
+
squares[i] = flip_rank(squares[i]);
|
778 |
+
|
779 |
+
// Look for the first piece of the leading group not on the A1-D4 diagonal
|
780 |
+
// and ensure it is mapped below the diagonal.
|
781 |
+
for (int i = 0; i < d->groupLen[0]; ++i) {
|
782 |
+
if (!off_A1H8(squares[i]))
|
783 |
+
continue;
|
784 |
+
|
785 |
+
if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1
|
786 |
+
for (int j = i; j < size; ++j)
|
787 |
+
squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);
|
788 |
+
break;
|
789 |
+
}
|
790 |
+
|
791 |
+
// Encode the leading group.
|
792 |
+
//
|
793 |
+
// Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR
|
794 |
+
// and bK (each 0...63). The simplest way to map this position to an index
|
795 |
+
// is like this:
|
796 |
+
//
|
797 |
+
// index = wK * 64 * 64 + wR * 64 + bK;
|
798 |
+
//
|
799 |
+
// But this way the TB is going to have 64*64*64 = 262144 positions, with
|
800 |
+
// lots of positions being equivalent (because they are mirrors of each
|
801 |
+
// other) and lots of positions being invalid (two pieces on one square,
|
802 |
+
// adjacent kings, etc.).
|
803 |
+
// Usually the first step is to take the wK and bK together. There are just
|
804 |
+
// 462 ways legal and not-mirrored ways to place the wK and bK on the board.
|
805 |
+
// Once we have placed the wK and bK, there are 62 squares left for the wR
|
806 |
+
// Mapping its square from 0..63 to available squares 0..61 can be done like:
|
807 |
+
//
|
808 |
+
// wR -= (wR > wK) + (wR > bK);
|
809 |
+
//
|
810 |
+
// In words: if wR "comes later" than wK, we deduct 1, and the same if wR
|
811 |
+
// "comes later" than bK. In case of two same pieces like KRRvK we want to
|
812 |
+
// place the two Rs "together". If we have 62 squares left, we can place two
|
813 |
+
// Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be
|
814 |
+
// swapped and still get the same position.)
|
815 |
+
//
|
816 |
+
// In case we have at least 3 unique pieces (included kings) we encode them
|
817 |
+
// together.
|
818 |
+
if (entry->hasUniquePieces) {
|
819 |
+
|
820 |
+
int adjust1 = squares[1] > squares[0];
|
821 |
+
int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]);
|
822 |
+
|
823 |
+
// First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3
|
824 |
+
// triangle to 0...5. There are 63 squares for second piece and and 62
|
825 |
+
// (mapped to 0...61) for the third.
|
826 |
+
if (off_A1H8(squares[0]))
|
827 |
+
idx = ( MapA1D1D4[squares[0]] * 63
|
828 |
+
+ (squares[1] - adjust1)) * 62
|
829 |
+
+ squares[2] - adjust2;
|
830 |
+
|
831 |
+
// First piece is on a1-h8 diagonal, second below: map this occurrence to
|
832 |
+
// 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal
|
833 |
+
// to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27.
|
834 |
+
else if (off_A1H8(squares[1]))
|
835 |
+
idx = ( 6 * 63 + rank_of(squares[0]) * 28
|
836 |
+
+ MapB1H1H7[squares[1]]) * 62
|
837 |
+
+ squares[2] - adjust2;
|
838 |
+
|
839 |
+
// First two pieces are on a1-h8 diagonal, third below
|
840 |
+
else if (off_A1H8(squares[2]))
|
841 |
+
idx = 6 * 63 * 62 + 4 * 28 * 62
|
842 |
+
+ rank_of(squares[0]) * 7 * 28
|
843 |
+
+ (rank_of(squares[1]) - adjust1) * 28
|
844 |
+
+ MapB1H1H7[squares[2]];
|
845 |
+
|
846 |
+
// All 3 pieces on the diagonal a1-h8
|
847 |
+
else
|
848 |
+
idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28
|
849 |
+
+ rank_of(squares[0]) * 7 * 6
|
850 |
+
+ (rank_of(squares[1]) - adjust1) * 6
|
851 |
+
+ (rank_of(squares[2]) - adjust2);
|
852 |
+
} else
|
853 |
+
// We don't have at least 3 unique pieces, like in KRRvKBB, just map
|
854 |
+
// the kings.
|
855 |
+
idx = MapKK[MapA1D1D4[squares[0]]][squares[1]];
|
856 |
+
|
857 |
+
encode_remaining:
|
858 |
+
idx *= d->groupIdx[0];
|
859 |
+
Square* groupSq = squares + d->groupLen[0];
|
860 |
+
|
861 |
+
// Encode remaining pawns then pieces according to square, in ascending order
|
862 |
+
bool remainingPawns = entry->hasPawns && entry->pawnCount[1];
|
863 |
+
|
864 |
+
while (d->groupLen[++next])
|
865 |
+
{
|
866 |
+
std::stable_sort(groupSq, groupSq + d->groupLen[next]);
|
867 |
+
uint64_t n = 0;
|
868 |
+
|
869 |
+
// Map down a square if "comes later" than a square in the previous
|
870 |
+
// groups (similar to what done earlier for leading group pieces).
|
871 |
+
for (int i = 0; i < d->groupLen[next]; ++i)
|
872 |
+
{
|
873 |
+
auto f = [&](Square s) { return groupSq[i] > s; };
|
874 |
+
auto adjust = std::count_if(squares, groupSq, f);
|
875 |
+
n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns];
|
876 |
+
}
|
877 |
+
|
878 |
+
remainingPawns = false;
|
879 |
+
idx += n * d->groupIdx[next];
|
880 |
+
groupSq += d->groupLen[next];
|
881 |
+
}
|
882 |
+
|
883 |
+
// Now that we have the index, decompress the pair and get the score
|
884 |
+
return map_score(entry, tbFile, decompress_pairs(d, idx), wdl);
|
885 |
+
}
|
886 |
+
|
887 |
+
// Group together pieces that will be encoded together. The general rule is that
|
888 |
+
// a group contains pieces of same type and color. The exception is the leading
|
889 |
+
// group that, in case of positions without pawns, can be formed by 3 different
|
890 |
+
// pieces (default) or by the king pair when there is not a unique piece apart
|
891 |
+
// from the kings. When there are pawns, pawns are always first in pieces[].
|
892 |
+
//
|
893 |
+
// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K
|
894 |
+
//
|
895 |
+
// The actual grouping depends on the TB generator and can be inferred from the
|
896 |
+
// sequence of pieces in piece[] array.
|
897 |
+
template<typename T>
|
898 |
+
void set_groups(T& e, PairsData* d, int order[], File f) {
|
899 |
+
|
900 |
+
int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2;
|
901 |
+
d->groupLen[n] = 1;
|
902 |
+
|
903 |
+
// Number of pieces per group is stored in groupLen[], for instance in KRKN
|
904 |
+
// the encoder will default on '111', so groupLen[] will be (3, 1).
|
905 |
+
for (int i = 1; i < e.pieceCount; ++i)
|
906 |
+
if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1])
|
907 |
+
d->groupLen[n]++;
|
908 |
+
else
|
909 |
+
d->groupLen[++n] = 1;
|
910 |
+
|
911 |
+
d->groupLen[++n] = 0; // Zero-terminated
|
912 |
+
|
913 |
+
// The sequence in pieces[] defines the groups, but not the order in which
|
914 |
+
// they are encoded. If the pieces in a group g can be combined on the board
|
915 |
+
// in N(g) different ways, then the position encoding will be of the form:
|
916 |
+
//
|
917 |
+
// g1 * N(g2) * N(g3) + g2 * N(g3) + g3
|
918 |
+
//
|
919 |
+
// This ensures unique encoding for the whole position. The order of the
|
920 |
+
// groups is a per-table parameter and could not follow the canonical leading
|
921 |
+
// pawns/pieces -> remaining pawns -> remaining pieces. In particular the
|
922 |
+
// first group is at order[0] position and the remaining pawns, when present,
|
923 |
+
// are at order[1] position.
|
924 |
+
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
|
925 |
+
int next = pp ? 2 : 1;
|
926 |
+
int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);
|
927 |
+
uint64_t idx = 1;
|
928 |
+
|
929 |
+
for (int k = 0; next < n || k == order[0] || k == order[1]; ++k)
|
930 |
+
if (k == order[0]) // Leading pawns or pieces
|
931 |
+
{
|
932 |
+
d->groupIdx[0] = idx;
|
933 |
+
idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f]
|
934 |
+
: e.hasUniquePieces ? 31332 : 462;
|
935 |
+
}
|
936 |
+
else if (k == order[1]) // Remaining pawns
|
937 |
+
{
|
938 |
+
d->groupIdx[1] = idx;
|
939 |
+
idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]];
|
940 |
+
}
|
941 |
+
else // Remaining pieces
|
942 |
+
{
|
943 |
+
d->groupIdx[next] = idx;
|
944 |
+
idx *= Binomial[d->groupLen[next]][freeSquares];
|
945 |
+
freeSquares -= d->groupLen[next++];
|
946 |
+
}
|
947 |
+
|
948 |
+
d->groupIdx[n] = idx;
|
949 |
+
}
|
950 |
+
|
951 |
+
// In Recursive Pairing each symbol represents a pair of children symbols. So
|
952 |
+
// read d->btree[] symbols data and expand each one in his left and right child
|
953 |
+
// symbol until reaching the leafs that represent the symbol value.
|
954 |
+
uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
|
955 |
+
|
956 |
+
visited[s] = true; // We can set it now because tree is acyclic
|
957 |
+
Sym sr = d->btree[s].get<LR::Right>();
|
958 |
+
|
959 |
+
if (sr == 0xFFF)
|
960 |
+
return 0;
|
961 |
+
|
962 |
+
Sym sl = d->btree[s].get<LR::Left>();
|
963 |
+
|
964 |
+
if (!visited[sl])
|
965 |
+
d->symlen[sl] = set_symlen(d, sl, visited);
|
966 |
+
|
967 |
+
if (!visited[sr])
|
968 |
+
d->symlen[sr] = set_symlen(d, sr, visited);
|
969 |
+
|
970 |
+
return d->symlen[sl] + d->symlen[sr] + 1;
|
971 |
+
}
|
972 |
+
|
973 |
+
uint8_t* set_sizes(PairsData* d, uint8_t* data) {
|
974 |
+
|
975 |
+
d->flags = *data++;
|
976 |
+
|
977 |
+
if (d->flags & TBFlag::SingleValue) {
|
978 |
+
d->blocksNum = d->blockLengthSize = 0;
|
979 |
+
d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init
|
980 |
+
d->minSymLen = *data++; // Here we store the single value
|
981 |
+
return data;
|
982 |
+
}
|
983 |
+
|
984 |
+
// groupLen[] is a zero-terminated list of group lengths, the last groupIdx[]
|
985 |
+
// element stores the biggest index that is the tb size.
|
986 |
+
uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];
|
987 |
+
|
988 |
+
d->sizeofBlock = 1ULL << *data++;
|
989 |
+
d->span = 1ULL << *data++;
|
990 |
+
d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up
|
991 |
+
auto padding = number<uint8_t, LittleEndian>(data++);
|
992 |
+
d->blocksNum = number<uint32_t, LittleEndian>(data); data += sizeof(uint32_t);
|
993 |
+
d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
|
994 |
+
// does not point out of range.
|
995 |
+
d->maxSymLen = *data++;
|
996 |
+
d->minSymLen = *data++;
|
997 |
+
d->lowestSym = (Sym*)data;
|
998 |
+
d->base64.resize(d->maxSymLen - d->minSymLen + 1);
|
999 |
+
|
1000 |
+
// The canonical code is ordered such that longer symbols (in terms of
|
1001 |
+
// the number of bits of their Huffman code) have lower numeric value,
|
1002 |
+
// so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian).
|
1003 |
+
// Starting from this we compute a base64[] table indexed by symbol length
|
1004 |
+
// and containing 64 bit values so that d->base64[i] >= d->base64[i+1].
|
1005 |
+
// See https://en.wikipedia.org/wiki/Huffman_coding
|
1006 |
+
for (int i = d->base64.size() - 2; i >= 0; --i) {
|
1007 |
+
d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])
|
1008 |
+
- number<Sym, LittleEndian>(&d->lowestSym[i + 1])) / 2;
|
1009 |
+
|
1010 |
+
assert(d->base64[i] * 2 >= d->base64[i+1]);
|
1011 |
+
}
|
1012 |
+
|
1013 |
+
// Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more
|
1014 |
+
// than d->base64[i+1] and given the above assert condition, we ensure that
|
1015 |
+
// d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i
|
1016 |
+
// and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i].
|
1017 |
+
for (size_t i = 0; i < d->base64.size(); ++i)
|
1018 |
+
d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits
|
1019 |
+
|
1020 |
+
data += d->base64.size() * sizeof(Sym);
|
1021 |
+
d->symlen.resize(number<uint16_t, LittleEndian>(data)); data += sizeof(uint16_t);
|
1022 |
+
d->btree = (LR*)data;
|
1023 |
+
|
1024 |
+
// The compression scheme used is "Recursive Pairing", that replaces the most
|
1025 |
+
// frequent adjacent pair of symbols in the source message by a new symbol,
|
1026 |
+
// reevaluating the frequencies of all of the symbol pairs with respect to
|
1027 |
+
// the extended alphabet, and then repeating the process.
|
1028 |
+
// See http://www.larsson.dogma.net/dcc99.pdf
|
1029 |
+
std::vector<bool> visited(d->symlen.size());
|
1030 |
+
|
1031 |
+
for (Sym sym = 0; sym < d->symlen.size(); ++sym)
|
1032 |
+
if (!visited[sym])
|
1033 |
+
d->symlen[sym] = set_symlen(d, sym, visited);
|
1034 |
+
|
1035 |
+
return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1);
|
1036 |
+
}
|
1037 |
+
|
1038 |
+
uint8_t* set_dtz_map(TBTable<WDL>&, uint8_t* data, File) { return data; }
|
1039 |
+
|
1040 |
+
uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
|
1041 |
+
|
1042 |
+
e.map = data;
|
1043 |
+
|
1044 |
+
for (File f = FILE_A; f <= maxFile; ++f) {
|
1045 |
+
auto flags = e.get(0, f)->flags;
|
1046 |
+
if (flags & TBFlag::Mapped) {
|
1047 |
+
if (flags & TBFlag::Wide) {
|
1048 |
+
data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table
|
1049 |
+
for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x
|
1050 |
+
e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1);
|
1051 |
+
data += 2 * number<uint16_t, LittleEndian>(data) + 2;
|
1052 |
+
}
|
1053 |
+
}
|
1054 |
+
else {
|
1055 |
+
for (int i = 0; i < 4; ++i) {
|
1056 |
+
e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1);
|
1057 |
+
data += *data + 1;
|
1058 |
+
}
|
1059 |
+
}
|
1060 |
+
}
|
1061 |
+
}
|
1062 |
+
|
1063 |
+
return data += (uintptr_t)data & 1; // Word alignment
|
1064 |
+
}
|
1065 |
+
|
1066 |
+
// Populate entry's PairsData records with data from the just memory mapped file.
|
1067 |
+
// Called at first access.
|
1068 |
+
template<typename T>
|
1069 |
+
void set(T& e, uint8_t* data) {
|
1070 |
+
|
1071 |
+
PairsData* d;
|
1072 |
+
|
1073 |
+
enum { Split = 1, HasPawns = 2 };
|
1074 |
+
|
1075 |
+
assert(e.hasPawns == bool(*data & HasPawns));
|
1076 |
+
assert((e.key != e.key2) == bool(*data & Split));
|
1077 |
+
|
1078 |
+
data++; // First byte stores flags
|
1079 |
+
|
1080 |
+
const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1;
|
1081 |
+
const File maxFile = e.hasPawns ? FILE_D : FILE_A;
|
1082 |
+
|
1083 |
+
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
|
1084 |
+
|
1085 |
+
assert(!pp || e.pawnCount[0]);
|
1086 |
+
|
1087 |
+
for (File f = FILE_A; f <= maxFile; ++f) {
|
1088 |
+
|
1089 |
+
for (int i = 0; i < sides; i++)
|
1090 |
+
*e.get(i, f) = PairsData();
|
1091 |
+
|
1092 |
+
int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF },
|
1093 |
+
{ *data >> 4, pp ? *(data + 1) >> 4 : 0xF } };
|
1094 |
+
data += 1 + pp;
|
1095 |
+
|
1096 |
+
for (int k = 0; k < e.pieceCount; ++k, ++data)
|
1097 |
+
for (int i = 0; i < sides; i++)
|
1098 |
+
e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF);
|
1099 |
+
|
1100 |
+
for (int i = 0; i < sides; ++i)
|
1101 |
+
set_groups(e, e.get(i, f), order[i], f);
|
1102 |
+
}
|
1103 |
+
|
1104 |
+
data += (uintptr_t)data & 1; // Word alignment
|
1105 |
+
|
1106 |
+
for (File f = FILE_A; f <= maxFile; ++f)
|
1107 |
+
for (int i = 0; i < sides; i++)
|
1108 |
+
data = set_sizes(e.get(i, f), data);
|
1109 |
+
|
1110 |
+
data = set_dtz_map(e, data, maxFile);
|
1111 |
+
|
1112 |
+
for (File f = FILE_A; f <= maxFile; ++f)
|
1113 |
+
for (int i = 0; i < sides; i++) {
|
1114 |
+
(d = e.get(i, f))->sparseIndex = (SparseEntry*)data;
|
1115 |
+
data += d->sparseIndexSize * sizeof(SparseEntry);
|
1116 |
+
}
|
1117 |
+
|
1118 |
+
for (File f = FILE_A; f <= maxFile; ++f)
|
1119 |
+
for (int i = 0; i < sides; i++) {
|
1120 |
+
(d = e.get(i, f))->blockLength = (uint16_t*)data;
|
1121 |
+
data += d->blockLengthSize * sizeof(uint16_t);
|
1122 |
+
}
|
1123 |
+
|
1124 |
+
for (File f = FILE_A; f <= maxFile; ++f)
|
1125 |
+
for (int i = 0; i < sides; i++) {
|
1126 |
+
data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment
|
1127 |
+
(d = e.get(i, f))->data = data;
|
1128 |
+
data += d->blocksNum * d->sizeofBlock;
|
1129 |
+
}
|
1130 |
+
}
|
1131 |
+
|
1132 |
+
// If the TB file corresponding to the given position is already memory mapped
|
1133 |
+
// then return its base address, otherwise try to memory map and init it. Called
|
1134 |
+
// at every probe, memory map and init only at first access. Function is thread
|
1135 |
+
// safe and can be called concurrently.
|
1136 |
+
template<TBType Type>
|
1137 |
+
void* mapped(TBTable<Type>& e, const Position& pos) {
|
1138 |
+
|
1139 |
+
static std::mutex mutex;
|
1140 |
+
|
1141 |
+
// Use 'acquire' to avoid a thread reading 'ready' == true while
|
1142 |
+
// another is still working. (compiler reordering may cause this).
|
1143 |
+
if (e.ready.load(std::memory_order_acquire))
|
1144 |
+
return e.baseAddress; // Could be nullptr if file does not exist
|
1145 |
+
|
1146 |
+
std::scoped_lock<std::mutex> lk(mutex);
|
1147 |
+
|
1148 |
+
if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock
|
1149 |
+
return e.baseAddress;
|
1150 |
+
|
1151 |
+
// Pieces strings in decreasing order for each color, like ("KPP","KR")
|
1152 |
+
std::string fname, w, b;
|
1153 |
+
for (PieceType pt = KING; pt >= PAWN; --pt) {
|
1154 |
+
w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]);
|
1155 |
+
b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]);
|
1156 |
+
}
|
1157 |
+
|
1158 |
+
fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w)
|
1159 |
+
+ (Type == WDL ? ".rtbw" : ".rtbz");
|
1160 |
+
|
1161 |
+
uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);
|
1162 |
+
|
1163 |
+
if (data)
|
1164 |
+
set(e, data);
|
1165 |
+
|
1166 |
+
e.ready.store(true, std::memory_order_release);
|
1167 |
+
return e.baseAddress;
|
1168 |
+
}
|
1169 |
+
|
1170 |
+
template<TBType Type, typename Ret = typename TBTable<Type>::Ret>
|
1171 |
+
Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) {
|
1172 |
+
|
1173 |
+
if (pos.count<ALL_PIECES>() == 2) // KvK
|
1174 |
+
return Ret(WDLDraw);
|
1175 |
+
|
1176 |
+
TBTable<Type>* entry = TBTables.get<Type>(pos.material_key());
|
1177 |
+
|
1178 |
+
if (!entry || !mapped(*entry, pos))
|
1179 |
+
return *result = FAIL, Ret();
|
1180 |
+
|
1181 |
+
return do_probe_table(pos, entry, wdl, result);
|
1182 |
+
}
|
1183 |
+
|
1184 |
+
// For a position where the side to move has a winning capture it is not necessary
|
1185 |
+
// to store a winning value so the generator treats such positions as "don't cares"
|
1186 |
+
// and tries to assign to it a value that improves the compression ratio. Similarly,
|
1187 |
+
// if the side to move has a drawing capture, then the position is at least drawn.
|
1188 |
+
// If the position is won, then the TB needs to store a win value. But if the
|
1189 |
+
// position is drawn, the TB may store a loss value if that is better for compression.
|
1190 |
+
// All of this means that during probing, the engine must look at captures and probe
|
1191 |
+
// their results and must probe the position itself. The "best" result of these
|
1192 |
+
// probes is the correct result for the position.
|
1193 |
+
// DTZ tables do not store values when a following move is a zeroing winning move
|
1194 |
+
// (winning capture or winning pawn move). Also DTZ store wrong values for positions
|
1195 |
+
// where the best move is an ep-move (even if losing). So in all these cases set
|
1196 |
+
// the state to ZEROING_BEST_MOVE.
|
1197 |
+
template<bool CheckZeroingMoves>
|
1198 |
+
WDLScore search(Position& pos, ProbeState* result) {
|
1199 |
+
|
1200 |
+
WDLScore value, bestValue = WDLLoss;
|
1201 |
+
StateInfo st;
|
1202 |
+
|
1203 |
+
auto moveList = MoveList<LEGAL>(pos);
|
1204 |
+
size_t totalCount = moveList.size(), moveCount = 0;
|
1205 |
+
|
1206 |
+
for (const Move move : moveList)
|
1207 |
+
{
|
1208 |
+
if ( !pos.capture(move)
|
1209 |
+
&& (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
|
1210 |
+
continue;
|
1211 |
+
|
1212 |
+
moveCount++;
|
1213 |
+
|
1214 |
+
pos.do_move(move, st);
|
1215 |
+
value = -search<false>(pos, result);
|
1216 |
+
pos.undo_move(move);
|
1217 |
+
|
1218 |
+
if (*result == FAIL)
|
1219 |
+
return WDLDraw;
|
1220 |
+
|
1221 |
+
if (value > bestValue)
|
1222 |
+
{
|
1223 |
+
bestValue = value;
|
1224 |
+
|
1225 |
+
if (value >= WDLWin)
|
1226 |
+
{
|
1227 |
+
*result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move
|
1228 |
+
return value;
|
1229 |
+
}
|
1230 |
+
}
|
1231 |
+
}
|
1232 |
+
|
1233 |
+
// In case we have already searched all the legal moves we don't have to probe
|
1234 |
+
// the TB because the stored score could be wrong. For instance TB tables
|
1235 |
+
// do not contain information on position with ep rights, so in this case
|
1236 |
+
// the result of probe_wdl_table is wrong. Also in case of only capture
|
1237 |
+
// moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to
|
1238 |
+
// return with ZEROING_BEST_MOVE set.
|
1239 |
+
bool noMoreMoves = (moveCount && moveCount == totalCount);
|
1240 |
+
|
1241 |
+
if (noMoreMoves)
|
1242 |
+
value = bestValue;
|
1243 |
+
else
|
1244 |
+
{
|
1245 |
+
value = probe_table<WDL>(pos, result);
|
1246 |
+
|
1247 |
+
if (*result == FAIL)
|
1248 |
+
return WDLDraw;
|
1249 |
+
}
|
1250 |
+
|
1251 |
+
// DTZ stores a "don't care" value if bestValue is a win
|
1252 |
+
if (bestValue >= value)
|
1253 |
+
return *result = ( bestValue > WDLDraw
|
1254 |
+
|| noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue;
|
1255 |
+
|
1256 |
+
return *result = OK, value;
|
1257 |
+
}
|
1258 |
+
|
1259 |
+
} // namespace
|
1260 |
+
|
1261 |
+
|
1262 |
+
/// Tablebases::init() is called at startup and after every change to
|
1263 |
+
/// "SyzygyPath" UCI option to (re)create the various tables. It is not thread
|
1264 |
+
/// safe, nor it needs to be.
|
1265 |
+
void Tablebases::init(const std::string& paths) {
|
1266 |
+
|
1267 |
+
TBTables.clear();
|
1268 |
+
MaxCardinality = 0;
|
1269 |
+
TBFile::Paths = paths;
|
1270 |
+
|
1271 |
+
if (paths.empty() || paths == "<empty>")
|
1272 |
+
return;
|
1273 |
+
|
1274 |
+
// MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27
|
1275 |
+
int code = 0;
|
1276 |
+
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
1277 |
+
if (off_A1H8(s) < 0)
|
1278 |
+
MapB1H1H7[s] = code++;
|
1279 |
+
|
1280 |
+
// MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9
|
1281 |
+
std::vector<Square> diagonal;
|
1282 |
+
code = 0;
|
1283 |
+
for (Square s = SQ_A1; s <= SQ_D4; ++s)
|
1284 |
+
if (off_A1H8(s) < 0 && file_of(s) <= FILE_D)
|
1285 |
+
MapA1D1D4[s] = code++;
|
1286 |
+
|
1287 |
+
else if (!off_A1H8(s) && file_of(s) <= FILE_D)
|
1288 |
+
diagonal.push_back(s);
|
1289 |
+
|
1290 |
+
// Diagonal squares are encoded as last ones
|
1291 |
+
for (auto s : diagonal)
|
1292 |
+
MapA1D1D4[s] = code++;
|
1293 |
+
|
1294 |
+
// MapKK[] encodes all the 462 possible legal positions of two kings where
|
1295 |
+
// the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4
|
1296 |
+
// diagonal, the other one shall not to be above the a1-h8 diagonal.
|
1297 |
+
std::vector<std::pair<int, Square>> bothOnDiagonal;
|
1298 |
+
code = 0;
|
1299 |
+
for (int idx = 0; idx < 10; idx++)
|
1300 |
+
for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1)
|
1301 |
+
if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0
|
1302 |
+
{
|
1303 |
+
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
1304 |
+
if ((PseudoAttacks[KING][s1] | s1) & s2)
|
1305 |
+
continue; // Illegal position
|
1306 |
+
|
1307 |
+
else if (!off_A1H8(s1) && off_A1H8(s2) > 0)
|
1308 |
+
continue; // First on diagonal, second above
|
1309 |
+
|
1310 |
+
else if (!off_A1H8(s1) && !off_A1H8(s2))
|
1311 |
+
bothOnDiagonal.emplace_back(idx, s2);
|
1312 |
+
|
1313 |
+
else
|
1314 |
+
MapKK[idx][s2] = code++;
|
1315 |
+
}
|
1316 |
+
|
1317 |
+
// Legal positions with both kings on diagonal are encoded as last ones
|
1318 |
+
for (auto p : bothOnDiagonal)
|
1319 |
+
MapKK[p.first][p.second] = code++;
|
1320 |
+
|
1321 |
+
// Binomial[] stores the Binomial Coefficients using Pascal rule. There
|
1322 |
+
// are Binomial[k][n] ways to choose k elements from a set of n elements.
|
1323 |
+
Binomial[0][0] = 1;
|
1324 |
+
|
1325 |
+
for (int n = 1; n < 64; n++) // Squares
|
1326 |
+
for (int k = 0; k < 6 && k <= n; ++k) // Pieces
|
1327 |
+
Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0)
|
1328 |
+
+ (k < n ? Binomial[k ][n - 1] : 0);
|
1329 |
+
|
1330 |
+
// MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible
|
1331 |
+
// available squares when the leading one is in 's'. Moreover the pawn with
|
1332 |
+
// highest MapPawns[] is the leading pawn, the one nearest the edge and,
|
1333 |
+
// among pawns with same file, the one with lowest rank.
|
1334 |
+
int availableSquares = 47; // Available squares when lead pawn is in a2
|
1335 |
+
|
1336 |
+
// Init the tables for the encoding of leading pawns group: with 7-men TB we
|
1337 |
+
// can have up to 5 leading pawns (KPPPPPK).
|
1338 |
+
for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)
|
1339 |
+
for (File f = FILE_A; f <= FILE_D; ++f)
|
1340 |
+
{
|
1341 |
+
// Restart the index at every file because TB table is split
|
1342 |
+
// by file, so we can reuse the same index for different files.
|
1343 |
+
int idx = 0;
|
1344 |
+
|
1345 |
+
// Sum all possible combinations for a given file, starting with
|
1346 |
+
// the leading pawn on rank 2 and increasing the rank.
|
1347 |
+
for (Rank r = RANK_2; r <= RANK_7; ++r)
|
1348 |
+
{
|
1349 |
+
Square sq = make_square(f, r);
|
1350 |
+
|
1351 |
+
// Compute MapPawns[] at first pass.
|
1352 |
+
// If sq is the leading pawn square, any other pawn cannot be
|
1353 |
+
// below or more toward the edge of sq. There are 47 available
|
1354 |
+
// squares when sq = a2 and reduced by 2 for any rank increase
|
1355 |
+
// due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45
|
1356 |
+
if (leadPawnsCnt == 1)
|
1357 |
+
{
|
1358 |
+
MapPawns[sq] = availableSquares--;
|
1359 |
+
MapPawns[flip_file(sq)] = availableSquares--;
|
1360 |
+
}
|
1361 |
+
LeadPawnIdx[leadPawnsCnt][sq] = idx;
|
1362 |
+
idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];
|
1363 |
+
}
|
1364 |
+
// After a file is traversed, store the cumulated per-file index
|
1365 |
+
LeadPawnsSize[leadPawnsCnt][f] = idx;
|
1366 |
+
}
|
1367 |
+
|
1368 |
+
// Add entries in TB tables if the corresponding ".rtbw" file exists
|
1369 |
+
for (PieceType p1 = PAWN; p1 < KING; ++p1) {
|
1370 |
+
TBTables.add({KING, p1, KING});
|
1371 |
+
|
1372 |
+
for (PieceType p2 = PAWN; p2 <= p1; ++p2) {
|
1373 |
+
TBTables.add({KING, p1, p2, KING});
|
1374 |
+
TBTables.add({KING, p1, KING, p2});
|
1375 |
+
|
1376 |
+
for (PieceType p3 = PAWN; p3 < KING; ++p3)
|
1377 |
+
TBTables.add({KING, p1, p2, KING, p3});
|
1378 |
+
|
1379 |
+
for (PieceType p3 = PAWN; p3 <= p2; ++p3) {
|
1380 |
+
TBTables.add({KING, p1, p2, p3, KING});
|
1381 |
+
|
1382 |
+
for (PieceType p4 = PAWN; p4 <= p3; ++p4) {
|
1383 |
+
TBTables.add({KING, p1, p2, p3, p4, KING});
|
1384 |
+
|
1385 |
+
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
|
1386 |
+
TBTables.add({KING, p1, p2, p3, p4, p5, KING});
|
1387 |
+
|
1388 |
+
for (PieceType p5 = PAWN; p5 < KING; ++p5)
|
1389 |
+
TBTables.add({KING, p1, p2, p3, p4, KING, p5});
|
1390 |
+
}
|
1391 |
+
|
1392 |
+
for (PieceType p4 = PAWN; p4 < KING; ++p4) {
|
1393 |
+
TBTables.add({KING, p1, p2, p3, KING, p4});
|
1394 |
+
|
1395 |
+
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
|
1396 |
+
TBTables.add({KING, p1, p2, p3, KING, p4, p5});
|
1397 |
+
}
|
1398 |
+
}
|
1399 |
+
|
1400 |
+
for (PieceType p3 = PAWN; p3 <= p1; ++p3)
|
1401 |
+
for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4)
|
1402 |
+
TBTables.add({KING, p1, p2, KING, p3, p4});
|
1403 |
+
}
|
1404 |
+
}
|
1405 |
+
|
1406 |
+
sync_cout << "info string Found " << TBTables.size() << " tablebases" << sync_endl;
|
1407 |
+
}
|
1408 |
+
|
1409 |
+
// Probe the WDL table for a particular position.
|
1410 |
+
// If *result != FAIL, the probe was successful.
|
1411 |
+
// The return value is from the point of view of the side to move:
|
1412 |
+
// -2 : loss
|
1413 |
+
// -1 : loss, but draw under 50-move rule
|
1414 |
+
// 0 : draw
|
1415 |
+
// 1 : win, but draw under 50-move rule
|
1416 |
+
// 2 : win
|
1417 |
+
WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) {
|
1418 |
+
|
1419 |
+
*result = OK;
|
1420 |
+
return search<false>(pos, result);
|
1421 |
+
}
|
1422 |
+
|
1423 |
+
// Probe the DTZ table for a particular position.
|
1424 |
+
// If *result != FAIL, the probe was successful.
|
1425 |
+
// The return value is from the point of view of the side to move:
|
1426 |
+
// n < -100 : loss, but draw under 50-move rule
|
1427 |
+
// -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0)
|
1428 |
+
// -1 : loss, the side to move is mated
|
1429 |
+
// 0 : draw
|
1430 |
+
// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0)
|
1431 |
+
// 100 < n : win, but draw under 50-move rule
|
1432 |
+
//
|
1433 |
+
// The return value n can be off by 1: a return value -n can mean a loss
|
1434 |
+
// in n+1 ply and a return value +n can mean a win in n+1 ply. This
|
1435 |
+
// cannot happen for tables with positions exactly on the "edge" of
|
1436 |
+
// the 50-move rule.
|
1437 |
+
//
|
1438 |
+
// This implies that if dtz > 0 is returned, the position is certainly
|
1439 |
+
// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine
|
1440 |
+
// picks moves that preserve dtz + 50-move-counter <= 99.
|
1441 |
+
//
|
1442 |
+
// If n = 100 immediately after a capture or pawn move, then the position
|
1443 |
+
// is also certainly a win, and during the whole phase until the next
|
1444 |
+
// capture or pawn move, the inequality to be preserved is
|
1445 |
+
// dtz + 50-move-counter <= 100.
|
1446 |
+
//
|
1447 |
+
// In short, if a move is available resulting in dtz + 50-move-counter <= 99,
|
1448 |
+
// then do not accept moves leading to dtz + 50-move-counter == 100.
|
1449 |
+
int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
|
1450 |
+
|
1451 |
+
*result = OK;
|
1452 |
+
WDLScore wdl = search<true>(pos, result);
|
1453 |
+
|
1454 |
+
if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws
|
1455 |
+
return 0;
|
1456 |
+
|
1457 |
+
// DTZ stores a 'don't care' value in this case, or even a plain wrong
|
1458 |
+
// one as in case the best move is a losing ep, so it cannot be probed.
|
1459 |
+
if (*result == ZEROING_BEST_MOVE)
|
1460 |
+
return dtz_before_zeroing(wdl);
|
1461 |
+
|
1462 |
+
int dtz = probe_table<DTZ>(pos, result, wdl);
|
1463 |
+
|
1464 |
+
if (*result == FAIL)
|
1465 |
+
return 0;
|
1466 |
+
|
1467 |
+
if (*result != CHANGE_STM)
|
1468 |
+
return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl);
|
1469 |
+
|
1470 |
+
// DTZ stores results for the other side, so we need to do a 1-ply search and
|
1471 |
+
// find the winning move that minimizes DTZ.
|
1472 |
+
StateInfo st;
|
1473 |
+
int minDTZ = 0xFFFF;
|
1474 |
+
|
1475 |
+
for (const Move move : MoveList<LEGAL>(pos))
|
1476 |
+
{
|
1477 |
+
bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;
|
1478 |
+
|
1479 |
+
pos.do_move(move, st);
|
1480 |
+
|
1481 |
+
// For zeroing moves we want the dtz of the move _before_ doing it,
|
1482 |
+
// otherwise we will get the dtz of the next move sequence. Search the
|
1483 |
+
// position after the move to get the score sign (because even in a
|
1484 |
+
// winning position we could make a losing capture or going for a draw).
|
1485 |
+
dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result))
|
1486 |
+
: -probe_dtz(pos, result);
|
1487 |
+
|
1488 |
+
// If the move mates, force minDTZ to 1
|
1489 |
+
if (dtz == 1 && pos.checkers() && MoveList<LEGAL>(pos).size() == 0)
|
1490 |
+
minDTZ = 1;
|
1491 |
+
|
1492 |
+
// Convert result from 1-ply search. Zeroing moves are already accounted
|
1493 |
+
// by dtz_before_zeroing() that returns the DTZ of the previous move.
|
1494 |
+
if (!zeroing)
|
1495 |
+
dtz += sign_of(dtz);
|
1496 |
+
|
1497 |
+
// Skip the draws and if we are winning only pick positive dtz
|
1498 |
+
if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl))
|
1499 |
+
minDTZ = dtz;
|
1500 |
+
|
1501 |
+
pos.undo_move(move);
|
1502 |
+
|
1503 |
+
if (*result == FAIL)
|
1504 |
+
return 0;
|
1505 |
+
}
|
1506 |
+
|
1507 |
+
// When there are no legal moves, the position is mate: we return -1
|
1508 |
+
return minDTZ == 0xFFFF ? -1 : minDTZ;
|
1509 |
+
}
|
1510 |
+
|
1511 |
+
|
1512 |
+
// Use the DTZ tables to rank root moves.
|
1513 |
+
//
|
1514 |
+
// A return value false indicates that not all probes were successful.
|
1515 |
+
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
1516 |
+
|
1517 |
+
ProbeState result;
|
1518 |
+
StateInfo st;
|
1519 |
+
|
1520 |
+
// Obtain 50-move counter for the root position
|
1521 |
+
int cnt50 = pos.rule50_count();
|
1522 |
+
|
1523 |
+
// Check whether a position was repeated since the last zeroing move.
|
1524 |
+
bool rep = pos.has_repeated();
|
1525 |
+
|
1526 |
+
int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1;
|
1527 |
+
|
1528 |
+
// Probe and rank each move
|
1529 |
+
for (auto& m : rootMoves)
|
1530 |
+
{
|
1531 |
+
pos.do_move(m.pv[0], st);
|
1532 |
+
|
1533 |
+
// Calculate dtz for the current move counting from the root position
|
1534 |
+
if (pos.rule50_count() == 0)
|
1535 |
+
{
|
1536 |
+
// In case of a zeroing move, dtz is one of -101/-1/0/1/101
|
1537 |
+
WDLScore wdl = -probe_wdl(pos, &result);
|
1538 |
+
dtz = dtz_before_zeroing(wdl);
|
1539 |
+
}
|
1540 |
+
else if (pos.is_draw(1))
|
1541 |
+
{
|
1542 |
+
// In case a root move leads to a draw by repetition or
|
1543 |
+
// 50-move rule, we set dtz to zero. Note: since we are
|
1544 |
+
// only 1 ply from the root, this must be a true 3-fold
|
1545 |
+
// repetition inside the game history.
|
1546 |
+
dtz = 0;
|
1547 |
+
}
|
1548 |
+
else
|
1549 |
+
{
|
1550 |
+
// Otherwise, take dtz for the new position and correct by 1 ply
|
1551 |
+
dtz = -probe_dtz(pos, &result);
|
1552 |
+
dtz = dtz > 0 ? dtz + 1
|
1553 |
+
: dtz < 0 ? dtz - 1 : dtz;
|
1554 |
+
}
|
1555 |
+
|
1556 |
+
// Make sure that a mating move is assigned a dtz value of 1
|
1557 |
+
if ( pos.checkers()
|
1558 |
+
&& dtz == 2
|
1559 |
+
&& MoveList<LEGAL>(pos).size() == 0)
|
1560 |
+
dtz = 1;
|
1561 |
+
|
1562 |
+
pos.undo_move(m.pv[0]);
|
1563 |
+
|
1564 |
+
if (result == FAIL)
|
1565 |
+
return false;
|
1566 |
+
|
1567 |
+
// Better moves are ranked higher. Certain wins are ranked equally.
|
1568 |
+
// Losing moves are ranked equally unless a 50-move draw is in sight.
|
1569 |
+
int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50))
|
1570 |
+
: dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50))
|
1571 |
+
: 0;
|
1572 |
+
m.tbRank = r;
|
1573 |
+
|
1574 |
+
// Determine the score to be displayed for this move. Assign at least
|
1575 |
+
// 1 cp to cursed wins and let it grow to 49 cp as the positions gets
|
1576 |
+
// closer to a real win.
|
1577 |
+
m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1
|
1578 |
+
: r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValueEg)) / 200)
|
1579 |
+
: r == 0 ? VALUE_DRAW
|
1580 |
+
: r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValueEg)) / 200)
|
1581 |
+
: -VALUE_MATE + MAX_PLY + 1;
|
1582 |
+
}
|
1583 |
+
|
1584 |
+
return true;
|
1585 |
+
}
|
1586 |
+
|
1587 |
+
|
1588 |
+
// Use the WDL tables to rank root moves.
|
1589 |
+
// This is a fallback for the case that some or all DTZ tables are missing.
|
1590 |
+
//
|
1591 |
+
// A return value false indicates that not all probes were successful.
|
1592 |
+
bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
|
1593 |
+
|
1594 |
+
static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ };
|
1595 |
+
|
1596 |
+
ProbeState result;
|
1597 |
+
StateInfo st;
|
1598 |
+
WDLScore wdl;
|
1599 |
+
|
1600 |
+
bool rule50 = Options["Syzygy50MoveRule"];
|
1601 |
+
|
1602 |
+
// Probe and rank each move
|
1603 |
+
for (auto& m : rootMoves)
|
1604 |
+
{
|
1605 |
+
pos.do_move(m.pv[0], st);
|
1606 |
+
|
1607 |
+
if (pos.is_draw(1))
|
1608 |
+
wdl = WDLDraw;
|
1609 |
+
else
|
1610 |
+
wdl = -probe_wdl(pos, &result);
|
1611 |
+
|
1612 |
+
pos.undo_move(m.pv[0]);
|
1613 |
+
|
1614 |
+
if (result == FAIL)
|
1615 |
+
return false;
|
1616 |
+
|
1617 |
+
m.tbRank = WDL_to_rank[wdl + 2];
|
1618 |
+
|
1619 |
+
if (!rule50)
|
1620 |
+
wdl = wdl > WDLDraw ? WDLWin
|
1621 |
+
: wdl < WDLDraw ? WDLLoss : WDLDraw;
|
1622 |
+
m.tbScore = WDL_to_value[wdl + 2];
|
1623 |
+
}
|
1624 |
+
|
1625 |
+
return true;
|
1626 |
+
}
|
1627 |
+
|
1628 |
+
} // namespace Stockfish
|
src/syzygy/tbprobe.h
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef TBPROBE_H
|
20 |
+
#define TBPROBE_H
|
21 |
+
|
22 |
+
#include <ostream>
|
23 |
+
|
24 |
+
#include "../search.h"
|
25 |
+
|
26 |
+
namespace Stockfish::Tablebases {
|
27 |
+
|
28 |
+
enum WDLScore {
|
29 |
+
WDLLoss = -2, // Loss
|
30 |
+
WDLBlessedLoss = -1, // Loss, but draw under 50-move rule
|
31 |
+
WDLDraw = 0, // Draw
|
32 |
+
WDLCursedWin = 1, // Win, but draw under 50-move rule
|
33 |
+
WDLWin = 2, // Win
|
34 |
+
};
|
35 |
+
|
36 |
+
// Possible states after a probing operation
|
37 |
+
enum ProbeState {
|
38 |
+
FAIL = 0, // Probe failed (missing file table)
|
39 |
+
OK = 1, // Probe successful
|
40 |
+
CHANGE_STM = -1, // DTZ should check the other side
|
41 |
+
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
|
42 |
+
};
|
43 |
+
|
44 |
+
extern int MaxCardinality;
|
45 |
+
|
46 |
+
void init(const std::string& paths);
|
47 |
+
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
48 |
+
int probe_dtz(Position& pos, ProbeState* result);
|
49 |
+
bool root_probe(Position& pos, Search::RootMoves& rootMoves);
|
50 |
+
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves);
|
51 |
+
void rank_root_moves(Position& pos, Search::RootMoves& rootMoves);
|
52 |
+
|
53 |
+
inline std::ostream& operator<<(std::ostream& os, const WDLScore v) {
|
54 |
+
|
55 |
+
os << (v == WDLLoss ? "Loss" :
|
56 |
+
v == WDLBlessedLoss ? "Blessed loss" :
|
57 |
+
v == WDLDraw ? "Draw" :
|
58 |
+
v == WDLCursedWin ? "Cursed win" :
|
59 |
+
v == WDLWin ? "Win" : "None");
|
60 |
+
|
61 |
+
return os;
|
62 |
+
}
|
63 |
+
|
64 |
+
inline std::ostream& operator<<(std::ostream& os, const ProbeState v) {
|
65 |
+
|
66 |
+
os << (v == FAIL ? "Failed" :
|
67 |
+
v == OK ? "Success" :
|
68 |
+
v == CHANGE_STM ? "Probed opponent side" :
|
69 |
+
v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None");
|
70 |
+
|
71 |
+
return os;
|
72 |
+
}
|
73 |
+
|
74 |
+
} // namespace Stockfish::Tablebases
|
75 |
+
|
76 |
+
#endif
|
src/thread.cpp
ADDED
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <cassert>
|
20 |
+
|
21 |
+
#include <algorithm> // For std::count
|
22 |
+
#include "movegen.h"
|
23 |
+
#include "search.h"
|
24 |
+
#include "thread.h"
|
25 |
+
#include "uci.h"
|
26 |
+
#include "syzygy/tbprobe.h"
|
27 |
+
#include "tt.h"
|
28 |
+
|
29 |
+
namespace Stockfish {
|
30 |
+
|
31 |
+
ThreadPool Threads; // Global object
|
32 |
+
|
33 |
+
|
34 |
+
/// Thread constructor launches the thread and waits until it goes to sleep
|
35 |
+
/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
36 |
+
|
37 |
+
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
|
38 |
+
|
39 |
+
wait_for_search_finished();
|
40 |
+
}
|
41 |
+
|
42 |
+
|
43 |
+
/// Thread destructor wakes up the thread in idle_loop() and waits
|
44 |
+
/// for its termination. Thread should be already waiting.
|
45 |
+
|
46 |
+
Thread::~Thread() {
|
47 |
+
|
48 |
+
assert(!searching);
|
49 |
+
|
50 |
+
exit = true;
|
51 |
+
start_searching();
|
52 |
+
stdThread.join();
|
53 |
+
}
|
54 |
+
|
55 |
+
|
56 |
+
/// Thread::clear() reset histories, usually before a new game
|
57 |
+
|
58 |
+
void Thread::clear() {
|
59 |
+
|
60 |
+
counterMoves.fill(MOVE_NONE);
|
61 |
+
mainHistory.fill(0);
|
62 |
+
captureHistory.fill(0);
|
63 |
+
previousDepth = 0;
|
64 |
+
|
65 |
+
for (bool inCheck : { false, true })
|
66 |
+
for (StatsType c : { NoCaptures, Captures })
|
67 |
+
for (auto& to : continuationHistory[inCheck][c])
|
68 |
+
for (auto& h : to)
|
69 |
+
h->fill(-71);
|
70 |
+
}
|
71 |
+
|
72 |
+
|
73 |
+
/// Thread::start_searching() wakes up the thread that will start the search
|
74 |
+
|
75 |
+
void Thread::start_searching() {
|
76 |
+
|
77 |
+
std::lock_guard<std::mutex> lk(mutex);
|
78 |
+
searching = true;
|
79 |
+
cv.notify_one(); // Wake up the thread in idle_loop()
|
80 |
+
}
|
81 |
+
|
82 |
+
|
83 |
+
/// Thread::wait_for_search_finished() blocks on the condition variable
|
84 |
+
/// until the thread has finished searching.
|
85 |
+
|
86 |
+
void Thread::wait_for_search_finished() {
|
87 |
+
|
88 |
+
std::unique_lock<std::mutex> lk(mutex);
|
89 |
+
cv.wait(lk, [&]{ return !searching; });
|
90 |
+
}
|
91 |
+
|
92 |
+
|
93 |
+
/// Thread::idle_loop() is where the thread is parked, blocked on the
|
94 |
+
/// condition variable, when it has no work to do.
|
95 |
+
|
96 |
+
void Thread::idle_loop() {
|
97 |
+
|
98 |
+
// If OS already scheduled us on a different group than 0 then don't overwrite
|
99 |
+
// the choice, eventually we are one of many one-threaded processes running on
|
100 |
+
// some Windows NUMA hardware, for instance in fishtest. To make it simple,
|
101 |
+
// just check if running threads are below a threshold, in this case all this
|
102 |
+
// NUMA machinery is not needed.
|
103 |
+
if (Options["Threads"] > 8)
|
104 |
+
WinProcGroup::bindThisThread(idx);
|
105 |
+
|
106 |
+
while (true)
|
107 |
+
{
|
108 |
+
std::unique_lock<std::mutex> lk(mutex);
|
109 |
+
searching = false;
|
110 |
+
cv.notify_one(); // Wake up anyone waiting for search finished
|
111 |
+
cv.wait(lk, [&]{ return searching; });
|
112 |
+
|
113 |
+
if (exit)
|
114 |
+
return;
|
115 |
+
|
116 |
+
lk.unlock();
|
117 |
+
|
118 |
+
search();
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
/// ThreadPool::set() creates/destroys threads to match the requested number.
|
123 |
+
/// Created and launched threads will immediately go to sleep in idle_loop.
|
124 |
+
/// Upon resizing, threads are recreated to allow for binding if necessary.
|
125 |
+
|
126 |
+
void ThreadPool::set(size_t requested) {
|
127 |
+
|
128 |
+
if (size() > 0) // destroy any existing thread(s)
|
129 |
+
{
|
130 |
+
main()->wait_for_search_finished();
|
131 |
+
|
132 |
+
while (size() > 0)
|
133 |
+
delete back(), pop_back();
|
134 |
+
}
|
135 |
+
|
136 |
+
if (requested > 0) // create new thread(s)
|
137 |
+
{
|
138 |
+
push_back(new MainThread(0));
|
139 |
+
|
140 |
+
while (size() < requested)
|
141 |
+
push_back(new Thread(size()));
|
142 |
+
clear();
|
143 |
+
|
144 |
+
// Reallocate the hash with the new threadpool size
|
145 |
+
TT.resize(size_t(Options["Hash"]));
|
146 |
+
|
147 |
+
// Init thread number dependent search params.
|
148 |
+
Search::init();
|
149 |
+
}
|
150 |
+
}
|
151 |
+
|
152 |
+
|
153 |
+
/// ThreadPool::clear() sets threadPool data to initial values
|
154 |
+
|
155 |
+
void ThreadPool::clear() {
|
156 |
+
|
157 |
+
for (Thread* th : *this)
|
158 |
+
th->clear();
|
159 |
+
|
160 |
+
main()->callsCnt = 0;
|
161 |
+
main()->bestPreviousScore = VALUE_INFINITE;
|
162 |
+
main()->bestPreviousAverageScore = VALUE_INFINITE;
|
163 |
+
main()->previousTimeReduction = 1.0;
|
164 |
+
}
|
165 |
+
|
166 |
+
|
167 |
+
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
|
168 |
+
/// returns immediately. Main thread will wake up other threads and start the search.
|
169 |
+
|
170 |
+
void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
171 |
+
const Search::LimitsType& limits, bool ponderMode) {
|
172 |
+
|
173 |
+
main()->wait_for_search_finished();
|
174 |
+
|
175 |
+
main()->stopOnPonderhit = stop = false;
|
176 |
+
increaseDepth = true;
|
177 |
+
main()->ponder = ponderMode;
|
178 |
+
Search::Limits = limits;
|
179 |
+
Search::RootMoves rootMoves;
|
180 |
+
|
181 |
+
for (const auto& m : MoveList<LEGAL>(pos))
|
182 |
+
if ( limits.searchmoves.empty()
|
183 |
+
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
184 |
+
rootMoves.emplace_back(m);
|
185 |
+
|
186 |
+
if (!rootMoves.empty())
|
187 |
+
Tablebases::rank_root_moves(pos, rootMoves);
|
188 |
+
|
189 |
+
// After ownership transfer 'states' becomes empty, so if we stop the search
|
190 |
+
// and call 'go' again without setting a new position states.get() == NULL.
|
191 |
+
assert(states.get() || setupStates.get());
|
192 |
+
|
193 |
+
if (states.get())
|
194 |
+
setupStates = std::move(states); // Ownership transfer, states is now empty
|
195 |
+
|
196 |
+
// We use Position::set() to set root position across threads. But there are
|
197 |
+
// some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot
|
198 |
+
// be deduced from a fen string, so set() clears them and they are set from
|
199 |
+
// setupStates->back() later. The rootState is per thread, earlier states are shared
|
200 |
+
// since they are read-only.
|
201 |
+
for (Thread* th : *this)
|
202 |
+
{
|
203 |
+
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
|
204 |
+
th->rootDepth = th->completedDepth = 0;
|
205 |
+
th->rootMoves = rootMoves;
|
206 |
+
th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
|
207 |
+
th->rootState = setupStates->back();
|
208 |
+
}
|
209 |
+
|
210 |
+
main()->start_searching();
|
211 |
+
}
|
212 |
+
|
213 |
+
Thread* ThreadPool::get_best_thread() const {
|
214 |
+
|
215 |
+
Thread* bestThread = front();
|
216 |
+
std::map<Move, int64_t> votes;
|
217 |
+
Value minScore = VALUE_NONE;
|
218 |
+
|
219 |
+
// Find minimum score of all threads
|
220 |
+
for (Thread* th: *this)
|
221 |
+
minScore = std::min(minScore, th->rootMoves[0].score);
|
222 |
+
|
223 |
+
// Vote according to score and depth, and select the best thread
|
224 |
+
auto thread_value = [minScore](Thread* th) {
|
225 |
+
return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
226 |
+
};
|
227 |
+
|
228 |
+
for (Thread* th : *this)
|
229 |
+
votes[th->rootMoves[0].pv[0]] += thread_value(th);
|
230 |
+
|
231 |
+
for (Thread* th : *this)
|
232 |
+
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
233 |
+
{
|
234 |
+
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
235 |
+
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
236 |
+
bestThread = th;
|
237 |
+
}
|
238 |
+
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
239 |
+
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
240 |
+
&& ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]
|
241 |
+
|| ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]]
|
242 |
+
&& thread_value(th) > thread_value(bestThread)))))
|
243 |
+
bestThread = th;
|
244 |
+
|
245 |
+
return bestThread;
|
246 |
+
}
|
247 |
+
|
248 |
+
|
249 |
+
/// Start non-main threads
|
250 |
+
|
251 |
+
void ThreadPool::start_searching() {
|
252 |
+
|
253 |
+
for (Thread* th : *this)
|
254 |
+
if (th != front())
|
255 |
+
th->start_searching();
|
256 |
+
}
|
257 |
+
|
258 |
+
|
259 |
+
/// Wait for non-main threads
|
260 |
+
|
261 |
+
void ThreadPool::wait_for_search_finished() const {
|
262 |
+
|
263 |
+
for (Thread* th : *this)
|
264 |
+
if (th != front())
|
265 |
+
th->wait_for_search_finished();
|
266 |
+
}
|
267 |
+
|
268 |
+
} // namespace Stockfish
|
src/thread.h
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef THREAD_H_INCLUDED
|
20 |
+
#define THREAD_H_INCLUDED
|
21 |
+
|
22 |
+
#include <atomic>
|
23 |
+
#include <condition_variable>
|
24 |
+
#include <mutex>
|
25 |
+
#include <thread>
|
26 |
+
#include <vector>
|
27 |
+
|
28 |
+
#include "material.h"
|
29 |
+
#include "movepick.h"
|
30 |
+
#include "pawns.h"
|
31 |
+
#include "position.h"
|
32 |
+
#include "search.h"
|
33 |
+
#include "thread_win32_osx.h"
|
34 |
+
|
35 |
+
namespace Stockfish {
|
36 |
+
|
37 |
+
/// Thread class keeps together all the thread-related stuff. We use
|
38 |
+
/// per-thread pawn and material hash tables so that once we get a
|
39 |
+
/// pointer to an entry its life time is unlimited and we don't have
|
40 |
+
/// to care about someone changing the entry under our feet.
|
41 |
+
|
42 |
+
class Thread {
|
43 |
+
|
44 |
+
std::mutex mutex;
|
45 |
+
std::condition_variable cv;
|
46 |
+
size_t idx;
|
47 |
+
bool exit = false, searching = true; // Set before starting std::thread
|
48 |
+
NativeThread stdThread;
|
49 |
+
|
50 |
+
public:
|
51 |
+
explicit Thread(size_t);
|
52 |
+
virtual ~Thread();
|
53 |
+
virtual void search();
|
54 |
+
void clear();
|
55 |
+
void idle_loop();
|
56 |
+
void start_searching();
|
57 |
+
void wait_for_search_finished();
|
58 |
+
size_t id() const { return idx; }
|
59 |
+
|
60 |
+
Pawns::Table pawnsTable;
|
61 |
+
Material::Table materialTable;
|
62 |
+
size_t pvIdx, pvLast;
|
63 |
+
RunningAverage complexityAverage;
|
64 |
+
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
65 |
+
int selDepth, nmpMinPly;
|
66 |
+
Color nmpColor;
|
67 |
+
Value bestValue, optimism[COLOR_NB];
|
68 |
+
|
69 |
+
Position rootPos;
|
70 |
+
StateInfo rootState;
|
71 |
+
Search::RootMoves rootMoves;
|
72 |
+
Depth rootDepth, completedDepth, previousDepth;
|
73 |
+
Value rootDelta;
|
74 |
+
CounterMoveHistory counterMoves;
|
75 |
+
ButterflyHistory mainHistory;
|
76 |
+
CapturePieceToHistory captureHistory;
|
77 |
+
ContinuationHistory continuationHistory[2][2];
|
78 |
+
};
|
79 |
+
|
80 |
+
|
81 |
+
/// MainThread is a derived class specific for main thread
|
82 |
+
|
83 |
+
struct MainThread : public Thread {
|
84 |
+
|
85 |
+
using Thread::Thread;
|
86 |
+
|
87 |
+
void search() override;
|
88 |
+
void check_time();
|
89 |
+
|
90 |
+
double previousTimeReduction;
|
91 |
+
Value bestPreviousScore;
|
92 |
+
Value bestPreviousAverageScore;
|
93 |
+
Value iterValue[4];
|
94 |
+
int callsCnt;
|
95 |
+
bool stopOnPonderhit;
|
96 |
+
std::atomic_bool ponder;
|
97 |
+
};
|
98 |
+
|
99 |
+
|
100 |
+
/// ThreadPool struct handles all the threads-related stuff like init, starting,
|
101 |
+
/// parking and, most importantly, launching a thread. All the access to threads
|
102 |
+
/// is done through this class.
|
103 |
+
|
104 |
+
struct ThreadPool : public std::vector<Thread*> {
|
105 |
+
|
106 |
+
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
|
107 |
+
void clear();
|
108 |
+
void set(size_t);
|
109 |
+
|
110 |
+
MainThread* main() const { return static_cast<MainThread*>(front()); }
|
111 |
+
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
112 |
+
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
|
113 |
+
Thread* get_best_thread() const;
|
114 |
+
void start_searching();
|
115 |
+
void wait_for_search_finished() const;
|
116 |
+
|
117 |
+
std::atomic_bool stop, increaseDepth;
|
118 |
+
|
119 |
+
private:
|
120 |
+
StateListPtr setupStates;
|
121 |
+
|
122 |
+
uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
|
123 |
+
|
124 |
+
uint64_t sum = 0;
|
125 |
+
for (Thread* th : *this)
|
126 |
+
sum += (th->*member).load(std::memory_order_relaxed);
|
127 |
+
return sum;
|
128 |
+
}
|
129 |
+
};
|
130 |
+
|
131 |
+
extern ThreadPool Threads;
|
132 |
+
|
133 |
+
} // namespace Stockfish
|
134 |
+
|
135 |
+
#endif // #ifndef THREAD_H_INCLUDED
|
src/thread_win32_osx.h
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef THREAD_WIN32_OSX_H_INCLUDED
|
20 |
+
#define THREAD_WIN32_OSX_H_INCLUDED
|
21 |
+
|
22 |
+
#include <thread>
|
23 |
+
|
24 |
+
/// On OSX threads other than the main thread are created with a reduced stack
|
25 |
+
/// size of 512KB by default, this is too low for deep searches, which require
|
26 |
+
/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.
|
27 |
+
/// The implementation calls pthread_create() with the stack size parameter
|
28 |
+
/// equal to the linux 8MB default, on platforms that support it.
|
29 |
+
|
30 |
+
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
|
31 |
+
|
32 |
+
#include <pthread.h>
|
33 |
+
|
34 |
+
namespace Stockfish {
|
35 |
+
|
36 |
+
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
37 |
+
|
38 |
+
template <class T, class P = std::pair<T*, void(T::*)()>>
|
39 |
+
void* start_routine(void* ptr)
|
40 |
+
{
|
41 |
+
P* p = reinterpret_cast<P*>(ptr);
|
42 |
+
(p->first->*(p->second))(); // Call member function pointer
|
43 |
+
delete p;
|
44 |
+
return NULL;
|
45 |
+
}
|
46 |
+
|
47 |
+
class NativeThread {
|
48 |
+
|
49 |
+
pthread_t thread;
|
50 |
+
|
51 |
+
public:
|
52 |
+
template<class T, class P = std::pair<T*, void(T::*)()>>
|
53 |
+
explicit NativeThread(void(T::*fun)(), T* obj) {
|
54 |
+
pthread_attr_t attr_storage, *attr = &attr_storage;
|
55 |
+
pthread_attr_init(attr);
|
56 |
+
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
57 |
+
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
|
58 |
+
}
|
59 |
+
void join() { pthread_join(thread, NULL); }
|
60 |
+
};
|
61 |
+
|
62 |
+
} // namespace Stockfish
|
63 |
+
|
64 |
+
#else // Default case: use STL classes
|
65 |
+
|
66 |
+
namespace Stockfish {
|
67 |
+
|
68 |
+
typedef std::thread NativeThread;
|
69 |
+
|
70 |
+
} // namespace Stockfish
|
71 |
+
|
72 |
+
#endif
|
73 |
+
|
74 |
+
#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
|
src/timeman.cpp
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <algorithm>
|
20 |
+
#include <cfloat>
|
21 |
+
#include <cmath>
|
22 |
+
|
23 |
+
#include "search.h"
|
24 |
+
#include "timeman.h"
|
25 |
+
#include "uci.h"
|
26 |
+
|
27 |
+
namespace Stockfish {
|
28 |
+
|
29 |
+
TimeManagement Time; // Our global time management object
|
30 |
+
|
31 |
+
|
32 |
+
/// TimeManagement::init() is called at the beginning of the search and calculates
|
33 |
+
/// the bounds of time allowed for the current game ply. We currently support:
|
34 |
+
// 1) x basetime (+ z increment)
|
35 |
+
// 2) x moves in y seconds (+ z increment)
|
36 |
+
|
37 |
+
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
38 |
+
|
39 |
+
TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
|
40 |
+
TimePoint slowMover = TimePoint(Options["Slow Mover"]);
|
41 |
+
TimePoint npmsec = TimePoint(Options["nodestime"]);
|
42 |
+
|
43 |
+
// optScale is a percentage of available time to use for the current move.
|
44 |
+
// maxScale is a multiplier applied to optimumTime.
|
45 |
+
double optScale, maxScale;
|
46 |
+
|
47 |
+
// If we have to play in 'nodes as time' mode, then convert from time
|
48 |
+
// to nodes, and use resulting values in time management formulas.
|
49 |
+
// WARNING: to avoid time losses, the given npmsec (nodes per millisecond)
|
50 |
+
// must be much lower than the real engine speed.
|
51 |
+
if (npmsec)
|
52 |
+
{
|
53 |
+
if (!availableNodes) // Only once at game start
|
54 |
+
availableNodes = npmsec * limits.time[us]; // Time is in msec
|
55 |
+
|
56 |
+
// Convert from milliseconds to nodes
|
57 |
+
limits.time[us] = TimePoint(availableNodes);
|
58 |
+
limits.inc[us] *= npmsec;
|
59 |
+
limits.npmsec = npmsec;
|
60 |
+
}
|
61 |
+
|
62 |
+
startTime = limits.startTime;
|
63 |
+
|
64 |
+
// Maximum move horizon of 50 moves
|
65 |
+
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
|
66 |
+
|
67 |
+
// Make sure timeLeft is > 0 since we may use it as a divisor
|
68 |
+
TimePoint timeLeft = std::max(TimePoint(1),
|
69 |
+
limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg));
|
70 |
+
|
71 |
+
// Use extra time with larger increments
|
72 |
+
double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12);
|
73 |
+
|
74 |
+
// A user may scale time usage by setting UCI option "Slow Mover"
|
75 |
+
// Default is 100 and changing this value will probably lose elo.
|
76 |
+
timeLeft = slowMover * timeLeft / 100;
|
77 |
+
|
78 |
+
// x basetime (+ z increment)
|
79 |
+
// If there is a healthy increment, timeLeft can exceed actual available
|
80 |
+
// game time for the current move, so also cap to 20% of available game time.
|
81 |
+
if (limits.movestogo == 0)
|
82 |
+
{
|
83 |
+
optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039,
|
84 |
+
0.2 * limits.time[us] / double(timeLeft))
|
85 |
+
* optExtra;
|
86 |
+
maxScale = std::min(7.0, 4.0 + ply / 12.0);
|
87 |
+
}
|
88 |
+
|
89 |
+
// x moves in y seconds (+ z increment)
|
90 |
+
else
|
91 |
+
{
|
92 |
+
optScale = std::min((0.88 + ply / 116.4) / mtg,
|
93 |
+
0.88 * limits.time[us] / double(timeLeft));
|
94 |
+
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
|
95 |
+
}
|
96 |
+
|
97 |
+
// Never use more than 80% of the available time for this move
|
98 |
+
optimumTime = TimePoint(optScale * timeLeft);
|
99 |
+
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime));
|
100 |
+
|
101 |
+
if (Options["Ponder"])
|
102 |
+
optimumTime += optimumTime / 4;
|
103 |
+
}
|
104 |
+
|
105 |
+
} // namespace Stockfish
|
src/timeman.h
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#ifndef TIMEMAN_H_INCLUDED
|
20 |
+
#define TIMEMAN_H_INCLUDED
|
21 |
+
|
22 |
+
#include "misc.h"
|
23 |
+
#include "search.h"
|
24 |
+
#include "thread.h"
|
25 |
+
|
26 |
+
namespace Stockfish {
|
27 |
+
|
28 |
+
/// The TimeManagement class computes the optimal time to think depending on
|
29 |
+
/// the maximum available time, the game move number and other parameters.
|
30 |
+
|
31 |
+
class TimeManagement {
|
32 |
+
public:
|
33 |
+
void init(Search::LimitsType& limits, Color us, int ply);
|
34 |
+
TimePoint optimum() const { return optimumTime; }
|
35 |
+
TimePoint maximum() const { return maximumTime; }
|
36 |
+
TimePoint elapsed() const { return Search::Limits.npmsec ?
|
37 |
+
TimePoint(Threads.nodes_searched()) : now() - startTime; }
|
38 |
+
|
39 |
+
int64_t availableNodes; // When in 'nodes as time' mode
|
40 |
+
|
41 |
+
private:
|
42 |
+
TimePoint startTime;
|
43 |
+
TimePoint optimumTime;
|
44 |
+
TimePoint maximumTime;
|
45 |
+
};
|
46 |
+
|
47 |
+
extern TimeManagement Time;
|
48 |
+
|
49 |
+
} // namespace Stockfish
|
50 |
+
|
51 |
+
#endif // #ifndef TIMEMAN_H_INCLUDED
|
src/tt.cpp
ADDED
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
3 |
+
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
|
4 |
+
|
5 |
+
Stockfish is free software: you can redistribute it and/or modify
|
6 |
+
it under the terms of the GNU General Public License as published by
|
7 |
+
the Free Software Foundation, either version 3 of the License, or
|
8 |
+
(at your option) any later version.
|
9 |
+
|
10 |
+
Stockfish is distributed in the hope that it will be useful,
|
11 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13 |
+
GNU General Public License for more details.
|
14 |
+
|
15 |
+
You should have received a copy of the GNU General Public License
|
16 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17 |
+
*/
|
18 |
+
|
19 |
+
#include <cstring> // For std::memset
|
20 |
+
#include <iostream>
|
21 |
+
#include <thread>
|
22 |
+
|
23 |
+
#include "bitboard.h"
|
24 |
+
#include "misc.h"
|
25 |
+
#include "thread.h"
|
26 |
+
#include "tt.h"
|
27 |
+
#include "uci.h"
|
28 |
+
|
29 |
+
namespace Stockfish {
|
30 |
+
|
31 |
+
TranspositionTable TT; // Our global transposition table
|
32 |
+
|
33 |
+
/// TTEntry::save() populates the TTEntry with a new node's data, possibly
|
34 |
+
/// overwriting an old position. Update is not atomic and can be racy.
|
35 |
+
|
36 |
+
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
|
37 |
+
|
38 |
+
// Preserve any existing move for the same position
|
39 |
+
if (m || (uint16_t)k != key16)
|
40 |
+
move16 = (uint16_t)m;
|
41 |
+
|
42 |
+
// Overwrite less valuable entries (cheapest checks first)
|
43 |
+
if ( b == BOUND_EXACT
|
44 |
+
|| (uint16_t)k != key16
|
45 |
+
|| d - DEPTH_OFFSET + 2 * pv > depth8 - 4)
|
46 |
+
{
|
47 |
+
assert(d > DEPTH_OFFSET);
|
48 |
+
assert(d < 256 + DEPTH_OFFSET);
|
49 |
+
|
50 |
+
key16 = (uint16_t)k;
|
51 |
+
depth8 = (uint8_t)(d - DEPTH_OFFSET);
|
52 |
+
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
|
53 |
+
value16 = (int16_t)v;
|
54 |
+
eval16 = (int16_t)ev;
|
55 |
+
}
|
56 |
+
}
|
57 |
+
|
58 |
+
|
59 |
+
/// TranspositionTable::resize() sets the size of the transposition table,
|
60 |
+
/// measured in megabytes. Transposition table consists of a power of 2 number
|
61 |
+
/// of clusters and each cluster consists of ClusterSize number of TTEntry.
|
62 |
+
|
63 |
+
void TranspositionTable::resize(size_t mbSize) {
|
64 |
+
|
65 |
+
Threads.main()->wait_for_search_finished();
|
66 |
+
|
67 |
+
aligned_large_pages_free(table);
|
68 |
+
|
69 |
+
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
70 |
+
|
71 |
+
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
|
72 |
+
if (!table)
|
73 |
+
{
|
74 |
+
std::cerr << "Failed to allocate " << mbSize
|
75 |
+
<< "MB for transposition table." << std::endl;
|
76 |
+
exit(EXIT_FAILURE);
|
77 |
+
}
|
78 |
+
|
79 |
+
clear();
|
80 |
+
}
|
81 |
+
|
82 |
+
|
83 |
+
/// TranspositionTable::clear() initializes the entire transposition table to zero,
|
84 |
+
// in a multi-threaded way.
|
85 |
+
|
86 |
+
void TranspositionTable::clear() {
|
87 |
+
|
88 |
+
std::vector<std::thread> threads;
|
89 |
+
|
90 |
+
for (size_t idx = 0; idx < Options["Threads"]; ++idx)
|
91 |
+
{
|
92 |
+
threads.emplace_back([this, idx]() {
|
93 |
+
|
94 |
+
// Thread binding gives faster search on systems with a first-touch policy
|
95 |
+
if (Options["Threads"] > 8)
|
96 |
+
WinProcGroup::bindThisThread(idx);
|
97 |
+
|
98 |
+
// Each thread will zero its part of the hash table
|
99 |
+
const size_t stride = size_t(clusterCount / Options["Threads"]),
|
100 |
+
start = size_t(stride * idx),
|
101 |
+
len = idx != Options["Threads"] - 1 ?
|
102 |
+
stride : clusterCount - start;
|
103 |
+
|
104 |
+
std::memset(&table[start], 0, len * sizeof(Cluster));
|
105 |
+
});
|
106 |
+
}
|
107 |
+
|
108 |
+
for (std::thread& th : threads)
|
109 |
+
th.join();
|
110 |
+
}
|
111 |
+
|
112 |
+
|
113 |
+
/// TranspositionTable::probe() looks up the current position in the transposition
|
114 |
+
/// table. It returns true and a pointer to the TTEntry if the position is found.
|
115 |
+
/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
|
116 |
+
/// to be replaced later. The replace value of an entry is calculated as its depth
|
117 |
+
/// minus 8 times its relative age. TTEntry t1 is considered more valuable than
|
118 |
+
/// TTEntry t2 if its replace value is greater than that of t2.
|
119 |
+
|
120 |
+
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
121 |
+
|
122 |
+
TTEntry* const tte = first_entry(key);
|
123 |
+
const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
|
124 |
+
|
125 |
+
for (int i = 0; i < ClusterSize; ++i)
|
126 |
+
if (tte[i].key16 == key16 || !tte[i].depth8)
|
127 |
+
{
|
128 |
+
tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh
|
129 |
+
|
130 |
+
return found = (bool)tte[i].depth8, &tte[i];
|
131 |
+
}
|
132 |
+
|
133 |
+
// Find an entry to be replaced according to the replacement strategy
|
134 |
+
TTEntry* replace = tte;
|
135 |
+
for (int i = 1; i < ClusterSize; ++i)
|
136 |
+
// Due to our packed storage format for generation and its cyclic
|
137 |
+
// nature we add GENERATION_CYCLE (256 is the modulus, plus what
|
138 |
+
// is needed to keep the unrelated lowest n bits from affecting
|
139 |
+
// the result) to calculate the entry age correctly even after
|
140 |
+
// generation8 overflows into the next cycle.
|
141 |
+
if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK)
|
142 |
+
> tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK))
|
143 |
+
replace = &tte[i];
|
144 |
+
|
145 |
+
return found = false, replace;
|
146 |
+
}
|
147 |
+
|
148 |
+
|
149 |
+
/// TranspositionTable::hashfull() returns an approximation of the hashtable
|
150 |
+
/// occupation during a search. The hash is x permill full, as per UCI protocol.
|
151 |
+
|
152 |
+
int TranspositionTable::hashfull() const {
|
153 |
+
|
154 |
+
int cnt = 0;
|
155 |
+
for (int i = 0; i < 1000; ++i)
|
156 |
+
for (int j = 0; j < ClusterSize; ++j)
|
157 |
+
cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
|
158 |
+
|
159 |
+
return cnt / ClusterSize;
|
160 |
+
}
|
161 |
+
|
162 |
+
} // namespace Stockfish
|