diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..0e94119a968be28522ca4687435e17f380d27133 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +stockfish/stockfish-ubuntu-x86-64-avx2 filter=lfs diff=lfs merge=lfs -text diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..bcbf1e7bae9eaa20c7260d1a965897d8993de964 --- /dev/null +++ b/app.py @@ -0,0 +1,24 @@ +import gradio as gr + +import chess +import chess.engine + +engine = chess.engine.SimpleEngine.popen_uci(r"./stockfish/stockfish-ubuntu-x86-64-avx2") + +board = chess.Board() +while not board.is_game_over(): + result = engine.play(board, chess.engine.Limit(time=0.1)) + board.push(result.move) + +engine.quit() + +def greet(name, intensity): + return "Hello, " + name + "!" * int(intensity) + +demo = gr.Interface( + fn=greet, + inputs=["text", "slider"], + outputs=["text"], +) + +demo.launch() diff --git a/puzzler/LICENSE b/puzzler/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..bc4d6ca81f34be79944220c41052b511112c57c0 --- /dev/null +++ b/puzzler/LICENSE @@ -0,0 +1,7 @@ +Copyright 2020 lichess.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/puzzler/generator/README.md b/puzzler/generator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ec4ef1c7c00e24a2603a681e6c89ebb42b748461 --- /dev/null +++ b/puzzler/generator/README.md @@ -0,0 +1,13 @@ +Generate puzzles from a PGN file! It will look at all games, and analyse them if needed. + +``` +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +python3 generator.py --help +python3 generator.py -t 6 -v -f my_file.pgn # If stockfish installed globally, otherwise use `--engine PATH_TO_YOUR_UCI_ENGINE` +``` + +BOT games are also looked at if any. The ouput file will be a csv with the same name as your input PGN file, and the following headers `white,black,game_id,fen,ply,moves,cp,generator_version`. + +Important! If something does not work, make sure you version matches [this one](https://github.com/kraktus/lichess-puzzler/blob/WC/generator/generator.py#L21). if you don't see `WC` in the version, you have probably not chosen the right branch. diff --git a/puzzler/generator/generator.py b/puzzler/generator/generator.py new file mode 100644 index 0000000000000000000000000000000000000000..7a1092586d6a28167ae3d38bc1248083eceada45 --- /dev/null +++ b/puzzler/generator/generator.py @@ -0,0 +1,340 @@ +import logging +import argparse +import chess +import chess.pgn +import chess.engine +import copy +import sys +import util +import zstandard +from model import Puzzle, NextMovePair +from io import StringIO +from chess import Move, Color +from chess.engine import SimpleEngine, Mate, Cp, Score, PovScore +from chess.pgn import Game, ChildNode + +from pathlib import Path +from typing import List, Optional, Union, Set +from util import count_mates, get_next_move_pair, material_count, material_diff, is_up_in_material, maximum_castling_rights, win_chances +from server import Server + +version = "48WC9" # Was made for the World Championship first + +logger = logging.getLogger(__name__) +logging.basicConfig(format='%(asctime)s %(levelname)-4s %(message)s', datefmt='%m/%d %H:%M') + +eval_limit = chess.engine.Limit(depth = 15, time = 30, nodes = 10_000_000) # when the move isn't analysed +pair_limit = chess.engine.Limit(depth = 50, time = 30, nodes = 30_000_000) +mate_defense_limit = chess.engine.Limit(depth = 15, time = 10, nodes = 10_000_000) + +mate_soon = Mate(15) + +class Generator: + def __init__(self, engine: SimpleEngine, server: Server): + self.engine = engine + self.server = server + self.not_analysed_warning = False + + def is_valid_mate_in_one(self, pair: NextMovePair) -> bool: + if pair.best.score != Mate(1): + return False + non_mate_win_threshold = 0.6 + if not pair.second or win_chances(pair.second.score) <= non_mate_win_threshold: + return True + if pair.second.score == Mate(1): + # if there's more than one mate in one, gotta look if the best non-mating move is bad enough + logger.debug('Looking for best non-mating move...') + mates = count_mates(copy.deepcopy(pair.node.board())) + info = self.engine.analyse(pair.node.board(), multipv = mates + 1, limit = pair_limit) + scores = [pv["score"].pov(pair.winner) for pv in info] + # the first non-matein1 move is the last element + if scores[-1] < Mate(1) and win_chances(scores[-1]) > non_mate_win_threshold: + return False + return True + return False + + # is pair.best the only continuation? + def is_valid_attack(self, pair: NextMovePair) -> bool: + return ( + pair.second is None or + self.is_valid_mate_in_one(pair) or + win_chances(pair.best.score) > win_chances(pair.second.score) + 0.7 + ) + + def get_next_pair(self, node: ChildNode, winner: Color) -> Optional[NextMovePair]: + pair = get_next_move_pair(self.engine, node, winner, pair_limit) + if node.board().turn == winner and not self.is_valid_attack(pair): + logger.debug("No valid attack {}".format(pair)) + return None + return pair + + def get_next_move(self, node: ChildNode, limit: chess.engine.Limit) -> Optional[Move]: + result = self.engine.play(node.board(), limit = limit) + return result.move if result else None + + def cook_mate(self, node: ChildNode, winner: Color) -> Optional[List[Move]]: + + board = node.board() + + if board.is_game_over(): + return [] + + if board.turn == winner: + pair = self.get_next_pair(node, winner) + if not pair: + return None + if pair.best.score < mate_soon: + logger.debug("Best move is not a mate, we're probably not searching deep enough") + return None + move = pair.best.move + else: + next = self.get_next_move(node, mate_defense_limit) + if not next: + return None + move = next + + follow_up = self.cook_mate(node.add_main_variation(move), winner) + + if follow_up is None: + return None + + return [move] + follow_up + + + def cook_advantage(self, node: ChildNode, winner: Color) -> Optional[List[NextMovePair]]: + + board = node.board() + + if board.is_repetition(2): + logger.debug("Found repetition, canceling") + return None + + pair = self.get_next_pair(node, winner) + if not pair: + return [] + if pair.best.score < Cp(200): + logger.debug("Not winning enough, aborting") + return None + + follow_up = self.cook_advantage(node.add_main_variation(pair.best.move), winner) + + if follow_up is None: + return None + + return [pair] + follow_up + + + def analyze_game(self, game: Game, tier: int) -> Optional[Puzzle]: + + logger.debug(f'Analyzing tier {tier} {game.headers.get("Site")}...') + + prev_score: Score = Cp(20) + seen_epds: Set[str] = set() + board = game.board() + skip_until_irreversible = False + + for node in game.mainline(): + if skip_until_irreversible: + if board.is_irreversible(node.move): + skip_until_irreversible = False + seen_epds.clear() + else: + board.push(node.move) + continue + + current_eval = node.eval() + + if not current_eval: + if not self.not_analysed_warning: + logger.warning("Game not already analysed by stockfish, will make one but consider using already analysed games from Lichess") + self.not_analysed_warning = True + logger.debug("Move without eval on ply {}, computing...".format(node.ply())) + current_eval = self.engine.analyse(node.board(), eval_limit)["score"] + + board.push(node.move) + epd = board.epd() + if epd in seen_epds: + skip_until_irreversible = True + continue + seen_epds.add(epd) + + if board.castling_rights != maximum_castling_rights(board): + continue + + result = self.analyze_position(node, prev_score, current_eval, tier) + + if isinstance(result, Puzzle): + return result + + prev_score = -result + + logger.debug("Found nothing from {}".format(game.headers.get("Site"))) + + return None + + + def analyze_position(self, node: ChildNode, prev_score: Score, current_eval: PovScore, tier: int) -> Union[Puzzle, Score]: + + board = node.board() + winner = board.turn + score = current_eval.pov(winner) + + if board.legal_moves.count() < 2: + return score + + game_url = node.game().headers.get("Site") + + logger.debug("{} {} to {}".format(node.ply(), node.move.uci() if node.move else None, score)) + + if prev_score > Cp(300) and score < mate_soon: + logger.debug("{} Too much of a winning position to start with {} -> {}".format(node.ply(), prev_score, score)) + return score + if is_up_in_material(board, winner): + logger.debug("{} already up in material {} {} {}".format(node.ply(), winner, material_count(board, winner), material_count(board, not winner))) + return score + elif score >= Mate(1) and tier < 3: + logger.debug("{} mate in one".format(node.ply())) + return score + elif score > mate_soon: + logger.debug("Mate {}#{} Probing...".format(game_url, node.ply())) + if self.server.is_seen_pos(node): + logger.debug("Skip duplicate position") + return score + mate_solution = self.cook_mate(copy.deepcopy(node), winner) + if mate_solution is None or (tier == 1 and len(mate_solution) == 3): + return score + return Puzzle(node, mate_solution, 999999999) + elif score >= Cp(200) and win_chances(score) > win_chances(prev_score) + 0.6: + if score < Cp(400) and material_diff(board, winner) > -1: + logger.debug("Not clearly winning and not from being down in material, aborting") + return score + logger.debug("Advantage {}#{} {} -> {}. Probing...".format(game_url, node.ply(), prev_score, score)) + if self.server.is_seen_pos(node): + logger.debug("Skip duplicate position") + return score + puzzle_node = copy.deepcopy(node) + solution : Optional[List[NextMovePair]] = self.cook_advantage(puzzle_node, winner) + self.server.set_seen(node.game()) + if not solution: + return score + while len(solution) % 2 == 0 or not solution[-1].second: + if not solution[-1].second: + logger.debug("Remove final only-move") + solution = solution[:-1] + if not solution or len(solution) == 1 : + logger.debug("Discard one-mover") + return score + if tier < 3 and len(solution) == 3: + logger.debug("Discard two-mover") + return score + cp = solution[len(solution) - 1].best.score.score() + return Puzzle(node, [p.best.move for p in solution], 999999998 if cp is None else cp) + else: + return score + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + prog='generator.py', + description='takes a pgn file and produces chess puzzles') + parser.add_argument("--file", "-f", help="input PGN file", required=True, metavar="FILE.pgn") + parser.add_argument("--engine", "-e", help="analysis engine", default="stockfish") + parser.add_argument("--threads", "-t", help="count of cpu threads for engine searches", default="4") + parser.add_argument("--url", "-u", help="URL where to post puzzles", default="") + parser.add_argument("--token", help="Server secret token", default="changeme") + parser.add_argument("--skip", help="How many games to skip from the source", default="0") + parser.add_argument("--verbose", "-v", help="increase verbosity", action="count") + parser.add_argument("--players", nargs='+', help="A list of players. If set, only generate games in which one of them played") + + return parser.parse_args() + + +def make_engine(executable: str, threads: int) -> SimpleEngine: + engine = SimpleEngine.popen_uci(executable) + engine.configure({'Threads': threads}) + return engine + + +def open_file(file: str): + if file.endswith(".zst"): + return zstandard.open(file, "rt") + return open(file) + +def main() -> None: + sys.setrecursionlimit(10000) # else node.deepcopy() sometimes fails? + args = parse_args() + if args.verbose >= 2: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + engine = make_engine(args.engine, args.threads) + server = Server(logger, args.url, args.token, version) + generator = Generator(engine, server) + file = Path(args.file) + games = 0 + site = "?" + has_master = False + tier = 10 + skip = int(args.skip) + write_h = skip > 0 # Erase the .csv file if not skipping any game (i.e starting over), else add + logger.info("Skipping first {} games".format(skip)) + + print(f'v{version}') + players = args.players + + try: + i = None + with open_file(args.file) as pgn: + filtered_games_offset: List[int] = [] + while "Look for all headers of the file": + offset = pgn.tell() + headers = chess.pgn.read_headers(pgn) + if skip > 0: + skip -= 1 + continue + if headers is None: + break + games = games + 1 + if games % 1000 == 0: + logger.info(f"{games} headers parsed") + variant = headers.get("Variant", "Standard") + black = headers.get("Black", "?") + white = headers.get("White", "?") + white_title = headers.get("WhiteTitle", "?") + black_title = headers.get("BlackTitle", "?") + if variant != "Standard" and variant != "Chess960": + continue + if players is not None and black not in players and white not in players: + continue + filtered_games_offset.append(offset) + logger.info(f"All headers parsed, {len(filtered_games_offset)}/{games} games that match the criterias.") + + for i, game_offset in enumerate(filtered_games_offset): + pgn.seek(game_offset) + game = chess.pgn.read_game(pgn) + black = game.headers.get("Black", "?") + white = game.headers.get("White", "?") + if game.errors: + logger.error(f"Illegal move detected in {white} vs {black}, game {i}") + assert(game) + game_id = game.headers.get("Site", "?")[20:] + # logger.info(f'https://lichess.org/{game_id} tier {tier}') + try: + puzzle = generator.analyze_game(game, tier) + if puzzle is not None: + logger.info(f'v{version} {args.file} {util.avg_knps()} knps, tier {tier}, game {i}') + print(f"Game: {game_id}") + server.post(game, puzzle, "_".join(players) if players is not None else file.stem, write_h) + write_h = False + except Exception as e: + logger.error("Exception on {}: {}".format(game_id, e)) + except KeyboardInterrupt: + print(f'v{version} {args.file} Game {i}') + sys.exit(1) + + engine.close() + print(f'v{version} {args.file} Game {i}') + +if __name__ == "__main__": + print('#'*80) + main() diff --git a/puzzler/generator/model.py b/puzzler/generator/model.py new file mode 100644 index 0000000000000000000000000000000000000000..52ced88f870e1a2e519760e5f486f93c1c607ef3 --- /dev/null +++ b/puzzler/generator/model.py @@ -0,0 +1,29 @@ +from chess.pgn import GameNode, ChildNode +from chess import Move, Color +from chess.engine import Score +from dataclasses import dataclass +from typing import Tuple, List, Optional + +@dataclass +class Puzzle: + node: ChildNode + moves: List[Move] + cp: int + +@dataclass +class Line: + nb: Tuple[int, int] + letter: str + password: str + +@dataclass +class EngineMove: + move: Move + score: Score + +@dataclass +class NextMovePair: + node: GameNode + winner: Color + best: EngineMove + second: Optional[EngineMove] diff --git a/puzzler/generator/requirements.txt b/puzzler/generator/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..b3a12854cedfcec36187897512fce24264d28cfb --- /dev/null +++ b/puzzler/generator/requirements.txt @@ -0,0 +1,3 @@ +chess==1.3.0 +requests==2.24.0 +zstandard==0.19.0 diff --git a/puzzler/generator/server.py b/puzzler/generator/server.py new file mode 100644 index 0000000000000000000000000000000000000000..7cf80d30ffcea643551608e30f20db87939fae16 --- /dev/null +++ b/puzzler/generator/server.py @@ -0,0 +1,87 @@ +import logging +import csv +from chess.pgn import Game, GameNode, ChildNode +from model import Puzzle +import requests +import urllib.parse +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry + +retry_strategy = Retry( + total=999999999, + backoff_factor=0.1, + status_forcelist=[429, 500, 502, 503, 504], + method_whitelist=["GET", "POST"] +) +adapter = HTTPAdapter(max_retries=retry_strategy) +http = requests.Session() +http.mount("https://", adapter) +http.mount("http://", adapter) + +TIMEOUT = 5 + +class Server: + + def __init__(self, logger: logging.Logger, url: str, token: str, version: str) -> None: + self.logger = logger + self.url = url + self.token = token + self.version = version + + def is_seen(self, id: str) -> bool: + if not self.url: + return False + try: + status = http.get(self._seen_url(id), timeout = TIMEOUT).status_code + return status == 200 + except Exception as e: + self.logger.error(e) + return False + + def set_seen(self, game: Game) -> None: + try: + if self.url: + http.post(self._seen_url(game.headers.get("Site", "?")[20:]), timeout = TIMEOUT) + except Exception as e: + self.logger.error(e) + + def is_seen_pos(self, node: ChildNode) -> bool: + if not self.url: + return False + id = urllib.parse.quote(f"{node.parent.board().fen()}:{node.uci()}") + try: + status = http.get(self._seen_url(id), timeout = TIMEOUT).status_code + return status == 200 + except Exception as e: + self.logger.error(e) + return False + + def _seen_url(self, id: str) -> str: + return "{}/seen?token={}&id={}".format(self.url, self.token, id) + + def post(self, game: Game, puzzle: Puzzle, name: str, write_h: bool = False) -> None: + parent = puzzle.node.parent + assert parent + json = { + 'white': game.headers.get("White", "?"), + 'black': game.headers.get("Black", "?"), + 'game_id': game.headers.get("Site", "?")[20:], + 'fen': parent.board().fen(), + 'ply': parent.ply(), + 'moves': [puzzle.node.uci()] + list(map(lambda m : m.uci(), puzzle.moves)), + 'cp': puzzle.cp, + 'generator_version': self.version, + } + if not self.url: + print(json) + with open(f"{name}.csv", "w" if write_h else "a") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=json.keys()) + if write_h: + writer.writeheader() + writer.writerow(json) + return None + try: + r = http.post("{}/puzzle?token={}".format(self.url, self.token), json=json) + self.logger.info(r.text if r.ok else "FAILURE {}".format(r.text)) + except Exception as e: + self.logger.error("Couldn't post puzzle: {}".format(e)) diff --git a/puzzler/generator/test.py b/puzzler/generator/test.py new file mode 100644 index 0000000000000000000000000000000000000000..6bb172a39741e0ad029007a5934c8f81b5b9f879 --- /dev/null +++ b/puzzler/generator/test.py @@ -0,0 +1,192 @@ +import unittest +import logging +import chess +from model import Puzzle +from generator import logger +from server import Server +from chess.engine import SimpleEngine, Mate, Cp, Score, PovScore +from chess import Move, Color, Board, WHITE, BLACK +from chess.pgn import Game, GameNode +from typing import List, Optional, Tuple, Literal, Union + +from generator import Generator, Server, make_engine + +class TestGenerator(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.engine = make_engine("stockfish", 6) # don't use more than 6 threads! it fails at finding mates + cls.server = Server(logger, "", "", 0) + cls.gen = Generator(cls.engine, cls.server) + logger.setLevel(logging.DEBUG) + + def test_puzzle_1(self) -> None: + # https://lichess.org/analysis/standard/3q1k2/p7/1p2Q2p/5P1K/1P4P1/P7/8/8_w_-_-_5_57#112 + self.get_puzzle("3q1k2/p7/1p2Q2p/5P1K/1P4P1/P7/8/8 w - - 5 57", + Cp(-1000), "h5g6", Mate(2), "d8g5 g6h7 g5g7") + + def test_puzzle_3(self) -> None: + # https://lichess.org/wQptHOX6/white#61 + self.get_puzzle("1r4k1/5p1p/pr1p2p1/q2Bb3/2P5/P1R3PP/KB1R1Q2/8 b - - 1 31", + Cp(-4), "e5c3", Mate(3), "f2f7 g8h8 f7f6 c3f6 b2f6") + + def test_puzzle_4(self) -> None: + # https://lichess.org/eVww5aBo#122 + self.get_puzzle("8/8/3Rpk2/2PpNp2/KP1P4/4r3/P1n5/8 w - - 3 62", + Cp(0), "d6d7", Cp(580), "e3a3 a4b5 c2d4 b5b6 f6e5") + + # can't be done because there are 2 possible defensive moves + def test_puzzle_5(self) -> None: + # https://lichess.org/2YRgIXwk/black#32 + self.get_puzzle("r1b3k1/pp3p1p/2pp2p1/8/2P2q2/2N1r2P/PP2BPP1/R2Q1K1R w - - 0 17", + Cp(-520), "d1d2", Cp(410), "e3h3 h1h3 f4d2") + + def test_puzzle_6(self) -> None: + self.get_puzzle("4nr1k/2r1qNpp/p3pn2/1b2N2Q/1p6/7P/BP1R1PP1/4R1K1 b - - 0 1", + Cp(130), "f8f7", Cp(550), "e5g6 h8g8 g6e7") + + # https://lichess.org/YCjcYuK6#81 + # def test_puzzle_7(self) -> None: + # self.get_puzzle("7r/1k6/pPp1qp2/2Q3p1/P4p2/5P2/5KP1/1RR4r b - - 5 41", + # Cp(-1500), "e6a2", Cp(530), "c1c2 a2e6 b1h1 h8h1 c2e2 e6d7 e2e7 d7e7 c5e7") + + # r1bq3r/pppp1kpp/2n5/2b1P1N1/3p2n1/2P5/P4PPP/RNBQ1RK1 b - - 1 10 + # def test_puzzle_8(self) -> None: + # self.get_puzzle("r1bq3r/pppp1kpp/2n5/2b1P1N1/3p2n1/2P5/P4PPP/RNBQ1RK1 b - - 1 10", + # Cp(0), "f7g8", Mate(4), "d1b3 d7d5 e5d6 c8e6 b3e6 g8f8 e6f7") + + def test_puzzle_9(self) -> None: + self.get_puzzle("7k/p3r1bP/1p1rp2q/8/2PBB3/4P3/P3KQ2/6R1 b - - 0 38", + Cp(-110), "e6e5", Mate(2), "f2f8 g7f8 g1g8") + + # https://lichess.org/ejvEklSH/black#50 + def test_puzzle_10(self) -> None: + self.get_puzzle("5rk1/pp3p2/1q1R3p/6p1/5pBb/2P4P/PPQ2PP1/3Rr1K1 w - - 6 26", + Cp(-450), "g1h2", Mate(2), "h4g3 f2g3 b6g1") + + # https://lichess.org/3GvkmJcw#43 + def test_puzzle_15(self): + self.get_puzzle("7k/pp1q2pp/1n1p2r1/4p3/P3P3/1Q3N1P/1P3PP1/5RK1 w - - 3 22", + Cp(-80), "a4a5", Cp(856), "d7h3 g2g3 g6h6 f3h4 h6h4 g3h4 h3b3") + + def test_puzzle_16(self): + self.get_puzzle("kr6/p5pp/Q4np1/3p4/6P1/2P1qP2/PK4P1/3R3R w - - 1 26", + Cp(-30), "b2a1", Mate(1), "e3c3") + + def test_puzzle_17(self): + self.get_puzzle("8/8/6k1/5R2/5KP1/5P2/5r2/8 w - - 17 66", + Cp(-410), "g4g5", Cp(0), "f2f3 f4f3 g6f5") + + # one mover + # def test_puzzle_17(self): + # self.get_puzzle("6k1/Q4pp1/8/6p1/3pr3/4q2P/P1P3P1/3R3K w - - 0 31", + # Cp(0), "d1d3", Cp(2000), "e3c1") + + def test_not_puzzle_1(self) -> None: + # https://lichess.org/LywqL7uc#32 + self.not_puzzle("r2q1rk1/1pp2pp1/p4n1p/b1pP4/4PB2/P3RQ2/1P3PPP/RN4K1 w - - 1 17", + Cp(-230), "b1c3", Cp(160)) + + def test_not_puzzle_2(self) -> None: + # https://lichess.org/TIH1K2BQ#51 + self.not_puzzle("5b1r/kpQ2ppp/4p3/4P3/1P4q1/8/P3N3/1nK2B2 b - - 0 26", + Cp(-1520), "b1a3", Cp(0)) + + # def test_not_puzzle_3(self) -> None: + # https://lichess.org/StRzB2gY#59 + # self.not_puzzle("7k/p6p/4p1p1/8/1q1p3P/2r1P1P1/P4Q2/5RK1 b - - 1 30", + # Cp(0), "d4e3", Cp(580)) + + def test_not_puzzle_4(self) -> None: + self.not_puzzle("r2qk2r/p1p1bppp/1p1ppn2/8/2PP1B2/3Q1N2/PP3PPP/3RR1K1 b kq - 6 12", + Cp(-110), "h7h6", Cp(150)) + + # https://lichess.org/ynAkXFBr/black#92 + def test_not_puzzle_5(self) -> None: + self.not_puzzle("4Rb2/N4k1p/8/5pp1/1n2p2P/4P1K1/3P4/8 w - - 1 47", + Cp(-40), "e8c8", Cp(610)) + + def test_not_puzzle_6(self) -> None: + self.not_puzzle("5r1k/1Q3p2/5q1p/8/2P4p/1P4P1/P4P2/R4RK1 w - - 0 29", + Cp(-1020), "g3h4", Cp(0)) + + # https://lichess.org/N99i0nfU#11 + def test_not_puzzle_7(self): + self.not_puzzle("rnb1k1nr/ppp2p1p/3p1qp1/2b1p3/2B1P3/2NP1Q2/PPP2PPP/R1B1K1NR b KQkq - 1 6", + Cp(-50), "c8g4", Cp(420)) + + def test_not_puzzle_8(self): + self.not_puzzle("r1bq1rk1/pp1nbppp/4p3/3pP3/8/1P1B4/PBP2PPP/RN1Q1RK1 w - - 1 11", + Cp(-40), "d3h7", Cp(380)) + + def test_not_puzzle_9(self): + self.not_puzzle("5k2/5ppp/2r1p3/1p6/1b1R4/p1n1P1P1/B4PKP/1N6 w - - 2 34", + Cp(0), "b1c3", Cp(520)) + + def test_not_puzzle_10(self): + self.not_puzzle("2Qr3k/p2P2p1/2p1n3/4n1p1/8/4q1P1/PP2P2P/R4R1K w - - 0 33", + Cp(100), "c8d8", Cp(500)) + + def test_not_puzzle_11(self) -> None: + self.not_puzzle("2kr3r/ppp2pp1/1b6/1P2p3/4P3/P2B2P1/2P2PP1/R4RK1 w - - 0 18", + Cp(20), "f1d1", Mate(4)) + + def test_not_puzzle_12(self): + self.not_puzzle("5r1k/1Q3p2/5q1p/8/2P4p/1P4P1/P4P2/R4RK1 w - - 0 29", + Cp(-1010), "g3h4", Cp(0)) + + # https://lichess.org/oKiQW6Wn/black#86 + def test_not_puzzle_13(self): + self.not_puzzle("8/5p1k/4p1p1/4Q3/3Pp1Kp/4P2P/5qP1/8 w - - 2 44", + Cp(-6360), "e5e4", Mate(1)) + + def test_not_puzzle_14(self) -> None: + # https://lichess.org/nq1x9tln/black#76 + self.not_puzzle("3R4/1Q2nk2/4p2p/4n3/BP3ppP/P7/5PP1/2r3K1 w - - 2 39", + Cp(-1000), "g1h2", Mate(4)) + + def test_not_puzzle_15(self) -> None: + # https://lichess.org/nq1x9tln/black#76 + self.not_puzzle("3r4/8/2p2n2/7k/P1P4p/1P6/2K5/6R1 w - - 0 43", + Cp(-1000), "b3b4", Mate(4)) + + def test_not_puzzle_16(self) -> None: + self.not_puzzle("8/Pkp3pp/8/4p3/1P2b3/4K3/1P3r1P/R7 b - - 1 30", + Cp(0), "f2f3", Cp(5000)) + + def test_not_puzzle_17(self) -> None: + with open("test_pgn_3fold_uDMCM.pgn") as pgn: + game = chess.pgn.read_game(pgn) + puzzle = self.gen.analyze_game(game, tier=10) + self.assertEqual(puzzle, None) + + def get_puzzle(self, fen: str, prev_score: Score, move: str, current_score: Score, moves: str) -> None: + board = Board(fen) + game = Game.from_board(board) + node = game.add_main_variation(Move.from_uci(move)) + current_eval = PovScore(current_score, not board.turn) + result = self.gen.analyze_position(node, prev_score, current_eval, tier=10) + self.assert_is_puzzle_with_moves(result, [Move.from_uci(x) for x in moves.split()]) + + + def not_puzzle(self, fen: str, prev_score: Score, move: str, current_score: Score) -> None: + board = Board(fen) + game = Game.from_board(board) + node = game.add_main_variation(Move.from_uci(move)) + current_eval = PovScore(current_score, not board.turn) + result = self.gen.analyze_position( node, prev_score, current_eval, tier=10) + self.assertIsInstance(result, Score) + + + def assert_is_puzzle_with_moves(self, puzzle: Union[Puzzle, Score], moves: List[Move]) -> None: + self.assertIsInstance(puzzle, Puzzle) + if isinstance(puzzle, Puzzle): + self.assertEqual(puzzle.moves, moves) + + @classmethod + def tearDownClass(cls): + cls.engine.close() + + +if __name__ == '__main__': + unittest.main() diff --git a/puzzler/generator/test_pgn_3fold_uDMCM.pgn b/puzzler/generator/test_pgn_3fold_uDMCM.pgn new file mode 100644 index 0000000000000000000000000000000000000000..50d738ea7a388a4bc611b2083845522f61333f32 --- /dev/null +++ b/puzzler/generator/test_pgn_3fold_uDMCM.pgn @@ -0,0 +1,24 @@ +[Event "Rated Blitz game"] +[Site "https://lichess.org/ZlCTzfMG"] +[Date "2021.06.28"] +[White "genassien"] +[Black "Freiheitskaempfer"] +[Result "1-0"] +[UTCDate "2021.06.28"] +[UTCTime "18:21:43"] +[WhiteElo "2370"] +[BlackElo "2441"] +[WhiteRatingDiff "+7"] +[BlackRatingDiff "-7"] +[WhiteTitle "FM"] +[BlackTitle "FM"] +[Variant "Standard"] +[TimeControl "300+0"] +[ECO "C24"] +[Opening "Bishop's Opening: Berlin Defense"] +[Termination "Time forfeit"] +[Annotator "lichess.org"] + +1. e4 { [%eval 0.24] [%clk 0:05:00] } 1... e5 { [%eval 0.2] [%clk 0:05:00] } 2. Bc4 { [%eval 0.0] [%clk 0:04:58] } 2... Nf6 { [%eval 0.0] [%clk 0:04:59] } { C24 Bishop's Opening: Berlin Defense } 3. d3 { [%eval 0.0] [%clk 0:04:53] } 3... c6 { [%eval 0.0] [%clk 0:04:58] } 4. Nf3 { [%eval 0.0] [%clk 0:04:52] } 4... d5 { [%eval 0.02] [%clk 0:04:58] } 5. Bb3 { [%eval 0.0] [%clk 0:04:51] } 5... Bb4+ { [%eval 0.05] [%clk 0:04:57] } 6. c3 { [%eval 0.05] [%clk 0:04:45] } 6... Bd6 { [%eval 0.09] [%clk 0:04:56] } 7. Nbd2 { [%eval -0.35] [%clk 0:04:33] } 7... Nbd7 { [%eval -0.24] [%clk 0:04:54] } 8. O-O { [%eval -0.14] [%clk 0:04:26] } 8... O-O { [%eval -0.04] [%clk 0:04:50] } 9. Re1 { [%eval -0.33] [%clk 0:04:25] } 9... dxe4 { [%eval -0.19] [%clk 0:04:42] } 10. dxe4 { [%eval -0.01] [%clk 0:04:16] } 10... Qe7 { [%eval 0.0] [%clk 0:04:35] } 11. Nc4 { [%eval -0.08] [%clk 0:04:14] } 11... Bc7 { [%eval 0.0] [%clk 0:04:34] } 12. Ne3? { (0.00 → -1.25) Mistake. a4 was best. } { [%eval -1.25] [%clk 0:04:13] } (12. a4 Rd8 13. Qe2 Nb6 14. Na5 Nbd7) 12... Nc5 { [%eval -1.25] [%clk 0:04:28] } 13. Bc2 { [%eval -1.12] [%clk 0:04:08] } 13... Ncxe4 { [%eval -0.98] [%clk 0:04:23] } 14. Nc4?! { (-0.98 → -1.74) Inaccuracy. Nf1 was best. } { [%eval -1.74] [%clk 0:03:49] } (14. Nf1 Nc5 15. b4 Ncd7 16. Ng3 Re8 17. a4 Nb6 18. h3 Nbd5 19. Bd2 h6 20. b5 Qc5) 14... Qc5?! { (-1.74 → -0.99) Inaccuracy. Bf5 was best. } { [%eval -0.99] [%clk 0:03:42] } (14... Bf5 15. Ng5) 15. Bxe4?! { (-0.99 → -1.80) Inaccuracy. Qe2 was best. } { [%eval -1.8] [%clk 0:03:13] } (15. Qe2 Bf5) 15... Qxc4 { [%eval -1.69] [%clk 0:03:40] } 16. Bc2?! { (-1.69 → -2.37) Inaccuracy. Bd3 was best. } { [%eval -2.37] [%clk 0:03:06] } (16. Bd3) 16... Bg4?! { (-2.37 → -1.36) Inaccuracy. Rd8 was best. } { [%eval -1.36] [%clk 0:03:15] } (16... Rd8 17. Nd2 Qh4 18. Qe2 Nd5 19. Nf3 Qh5 20. a4 a5 21. h3 f6 22. Bd2 Qf7 23. c4) 17. Bg5 { [%eval -1.16] [%clk 0:03:01] } 17... Rad8 { [%eval -0.83] [%clk 0:03:08] } 18. Qb1 { [%eval -1.1] [%clk 0:02:54] } 18... Bxf3 { [%eval -1.38] [%clk 0:02:53] } 19. Bxf6?? { (-1.38 → Mate in 6) Checkmate is now unavoidable. gxf3 was best. } { [%eval #-6] [%clk 0:02:53] } (19. gxf3) 19... gxf6?? { (Mate in 6 → -3.06) Lost forced checkmate sequence. Qg4 was best. } { [%eval -3.06] [%clk 0:02:50] } (19... Qg4 20. Bxh7+ Kh8 21. Qg6 fxg6 22. g3 Qh3 23. Bxg7+ Kxg7 24. a3 Qg2#) 20. Bxh7+? { (-3.06 → -5.75) Mistake. gxf3 was best. } { [%eval -5.75] [%clk 0:02:50] } (20. gxf3 Kh8 21. Re4 Rg8+ 22. Kh1 Qd5 23. Bb3 Qd2 24. Rh4 Rg7 25. Rg4 Rxg4 26. fxg4 Qxf2) 20... Kg7 { [%eval -6.06] [%clk 0:02:47] } 21. gxf3 { [%eval -6.09] [%clk 0:02:42] } 21... Qh4 { [%eval -5.99] [%clk 0:02:34] } 22. Be4?! { (-5.99 → -10.07) Inaccuracy. Qf5 was best. } { [%eval -10.07] [%clk 0:02:38] } (22. Qf5 Qxh7 23. Qxh7+ Kxh7 24. Rad1 Kg6 25. Kf1 Rh8 26. Rxd8 Rxd8 27. h3 f5 28. Ke2 Rh8) 22... Rh8 { [%eval -7.71] [%clk 0:02:29] } 23. Kf1 { [%eval -8.01] [%clk 0:01:43] } 23... Rd2 { [%eval -6.46] [%clk 0:02:25] } 24. Re2 { [%eval -6.29] [%clk 0:01:42] } 24... Rhd8 { [%eval -6.04] [%clk 0:02:13] } 25. Rxd2 { [%eval -6.4] [%clk 0:01:32] } 25... Rxd2 { [%eval -6.33] [%clk 0:02:11] } 26. Qe1 { [%eval -6.46] [%clk 0:01:31] } 26... Rxb2 { [%eval -6.73] [%clk 0:02:07] } 27. Rb1 { [%eval -7.46] [%clk 0:01:27] } 27... Qh3+?? { (-7.46 → -2.67) Blunder. Rxa2 was best. } { [%eval -2.67] [%clk 0:01:56] } (27... Rxa2) 28. Kg1 { [%eval -2.84] [%clk 0:01:25] } 28... Rxb1?? { (-2.84 → -0.88) Blunder. Rxa2 was best. } { [%eval -0.88] [%clk 0:01:42] } (28... Rxa2 29. Qf1 Qxf1+ 30. Kxf1 f5 31. Bxf5 Bb6 32. Kg2 Kf6 33. Bc8 Rxf2+ 34. Kg3 Rc2 35. Bxb7) 29. Qxb1 { [%eval -0.77] [%clk 0:01:24] } 29... Bb6 { [%eval -0.75] [%clk 0:01:37] } 30. Qf1 { [%eval -0.91] [%clk 0:01:10] } 30... Qh6?! { (-0.91 → -0.30) Inaccuracy. Qe6 was best. } { [%eval -0.3] [%clk 0:01:31] } (30... Qe6 31. Qg2+ Kf8 32. Qg4 Ke7 33. c4 Kd8 34. Qf5 Kc7 35. h4 Bc5 36. f4 Qxc4 37. fxe5) 31. Qg2+ { [%eval -0.3] [%clk 0:00:56] } 31... Kf8 { [%eval -0.3] [%clk 0:01:29] } 32. Qg4 { [%eval -0.31] [%clk 0:00:53] } 32... Qc1+ { [%eval -0.12] [%clk 0:00:57] } 33. Kg2 { [%eval -0.17] [%clk 0:00:52] } 33... Qd2 { [%eval -0.01] [%clk 0:00:39] } 34. Qc8+ { [%eval 0.0] [%clk 0:00:48] } 34... Ke7 { [%eval 0.0] [%clk 0:00:38] } 35. Qxb7+ { [%eval 0.0] [%clk 0:00:47] } 35... Kf8 { [%eval 0.0] [%clk 0:00:35] } 36. Qc8+ { [%eval 0.0] [%clk 0:00:45] } 36... Ke7 { [%eval 0.0] [%clk 0:00:33] } 37. Bxc6 { [%eval 0.0] [%clk 0:00:37] } 37... Qxf2+ { [%eval 0.0] [%clk 0:00:32] } 38. Kh3 { [%eval 0.0] [%clk 0:00:35] } 38... Qf1+ { [%eval 0.0] [%clk 0:00:31] } 39. Kg4?? { (0.00 → Mate in 3) Checkmate is now unavoidable. Kh4 was best. } { [%eval #-3] [%clk 0:00:31] } (39. Kh4 Qc4+ 40. Kh5 Qxa2 41. Qd7+ Kf8 42. Qc8+) 39... Qg2+ { [%eval #-2] [%clk 0:00:30] } 40. Kf5 { [%eval #-1] [%clk 0:00:26] } 40... Qg5+ { [%eval #-2] [%clk 0:00:28] } 41. Ke4 { [%eval #-2] [%clk 0:00:25] } 41... Qe3+ { [%eval #-1] [%clk 0:00:27] } 42. Kd5 { [%eval #-1] [%clk 0:00:23] } 42... Qc5+ { [%eval #-2] [%clk 0:00:25] } 43. Ke4 { [%eval #-2] [%clk 0:00:23] } 43... Qe3+ { [%eval #-1] [%clk 0:00:22] } 44. Kd5 { [%eval #-1] [%clk 0:00:22] } 44... Qc5+ { [%eval #-2] [%clk 0:00:21] } 45. Ke4 { [%eval #-2] [%clk 0:00:21] } 45... f5+?? { (Mate in 2 → 0.00) Lost forced checkmate sequence. Qc4+ was best. } { [%eval 0.0] [%clk 0:00:17] } (45... Qc4+ 46. Kf5 Qf4#) 46. Kxf5 { [%eval 0.0] [%clk 0:00:20] } 46... e4+ { [%eval 0.0] [%clk 0:00:16] } 47. Kxe4 { [%eval 0.0] [%clk 0:00:17] } 47... Qe3+ { [%eval 0.0] [%clk 0:00:16] } 48. Kf5 { [%eval 0.0] [%clk 0:00:15] } 48... Qd3+ { [%eval 0.02] [%clk 0:00:09] } 49. Be4 { [%eval 0.0] [%clk 0:00:13] } 49... Qd6 { [%eval 0.03] [%clk 0:00:05] } 50. Qb7+ { [%eval 0.11] [%clk 0:00:09] } 50... Bc7 { [%eval 0.17] [%clk 0:00:04] } 51. Qb4 { [%eval 0.21] [%clk 0:00:07] } 51... Qxb4 { [%eval 0.16] [%clk 0:00:02] } 52. cxb4 { [%eval 0.21] [%clk 0:00:07] } 52... Bxh2 { [%eval 0.13] [%clk 0:00:01] } 53. Kg4 { [%eval 0.21] [%clk 0:00:07] } 53... Bd6 { [%eval 0.09] [%clk 0:00:00] } 54. f4 { [%eval -0.04] [%clk 0:00:07] } 54... Bxb4 { [%eval -0.04] [%clk 0:00:00] } 55. Kf5 { [%eval -0.03] [%clk 0:00:07] } 55... Bd6 { [%eval 0.0] [%clk 0:00:00] } 56. Kg4 { [%eval 0.0] [%clk 0:00:06] } 56... f6 { [%eval 0.0] [%clk 0:00:00] } 57. f5 { [%eval 0.0] [%clk 0:00:04] } 57... Bc5 { [%eval 0.0] [%clk 0:00:00] } 58. a3 { [%eval -0.12] [%clk 0:00:04] } 58... Bd6 { [%eval 0.0] [%clk 0:00:00] } 59. a4 { [%eval 0.0] [%clk 0:00:04] } { White wins on time. } 1-0 + + diff --git a/puzzler/generator/util.py b/puzzler/generator/util.py new file mode 100644 index 0000000000000000000000000000000000000000..ba030e021d313edc57c68b46cc6ab792736bcb2c --- /dev/null +++ b/puzzler/generator/util.py @@ -0,0 +1,94 @@ +from dataclasses import dataclass +import math +import chess +import chess.engine +from model import EngineMove, NextMovePair +from chess import Color, Board +from chess.pgn import GameNode +from chess.engine import SimpleEngine, Score +from typing import Optional + +nps = [] + +def material_count(board: Board, side: Color) -> int: + values = { chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9 } + return sum(len(board.pieces(piece_type, side)) * value for piece_type, value in values.items()) + +def material_diff(board: Board, side: Color) -> int: + return material_count(board, side) - material_count(board, not side) + +def is_up_in_material(board: Board, side: Color) -> bool: + return material_diff(board, side) > 0 + +def maximum_castling_rights(board: chess.Board) -> chess.Bitboard: + return ( + (board.pieces_mask(chess.ROOK, chess.WHITE) & (chess.BB_A1 | chess.BB_H1) if board.king(chess.WHITE) == chess.E1 else chess.BB_EMPTY) | + (board.pieces_mask(chess.ROOK, chess.BLACK) & (chess.BB_A8 | chess.BB_H8) if board.king(chess.BLACK) == chess.E8 else chess.BB_EMPTY) + ) + + +def get_next_move_pair(engine: SimpleEngine, node: GameNode, winner: Color, limit: chess.engine.Limit) -> NextMovePair: + info = engine.analyse(node.board(), multipv = 2, limit = limit) + global nps + nps.append(info[0]["nps"] / 1000) + nps = nps[-10000:] + # print(info) + best = EngineMove(info[0]["pv"][0], info[0]["score"].pov(winner)) + second = EngineMove(info[1]["pv"][0], info[1]["score"].pov(winner)) if len(info) > 1 else None + return NextMovePair(node, winner, best, second) + +def avg_knps(): + global nps + return round(sum(nps) / len(nps)) if nps else 0 + +def win_chances(score: Score) -> float: + """ + winning chances from -1 to 1 https://graphsketch.com/?eqn1_color=1&eqn1_eqn=100+*+%282+%2F+%281+%2B+exp%28-0.004+*+x%29%29+-+1%29&eqn2_color=2&eqn2_eqn=&eqn3_color=3&eqn3_eqn=&eqn4_color=4&eqn4_eqn=&eqn5_color=5&eqn5_eqn=&eqn6_color=6&eqn6_eqn=&x_min=-1000&x_max=1000&y_min=-100&y_max=100&x_tick=100&y_tick=10&x_label_freq=2&y_label_freq=2&do_grid=0&do_grid=1&bold_labeled_lines=0&bold_labeled_lines=1&line_width=4&image_w=850&image_h=525 + """ + mate = score.mate() + if mate is not None: + return 1 if mate > 0 else -1 + + cp = score.score() + MULTIPLIER = -0.00368208 # https://github.com/lichess-org/lila/pull/11148 + return 2 / (1 + math.exp(MULTIPLIER * cp)) - 1 if cp is not None else 0 + +def time_control_tier(line: str) -> Optional[int]: + if not line.startswith("[TimeControl "): + return None + try: + seconds, increment = line[1:][:-2].split()[1].replace("\"", "").split("+") + total = int(seconds) + int(increment) * 40 + if total >= 480: + return 3 + if total >= 180: + return 2 + if total > 60: + return 1 + return 0 + except: + return 0 + +def count_mates(board:chess.Board) -> int: + mates = 0 + for move in board.legal_moves: + board.push(move) + if board.is_checkmate(): + mates += 1 + board.pop() + return mates + +def rating_tier(line: str) -> Optional[int]: + if not line.startswith("[WhiteElo ") and not line.startswith("[BlackElo "): + return None + try: + rating = int(line[11:15]) + if rating > 1750: + return 3 + if rating > 1600: + return 2 + if rating > 1500: + return 1 + return 0 + except: + return 0 diff --git a/puzzler/tagger/README.md b/puzzler/tagger/README.md new file mode 100644 index 0000000000000000000000000000000000000000..baaffda4328fd712391282fbbfb26910824cf418 --- /dev/null +++ b/puzzler/tagger/README.md @@ -0,0 +1,5 @@ + +``` +const toMake = id => {p=db.puzzle2.findOne({_id:id}); return `make("${id}", "${p.fen}", "${p.moves.join(' ')}")`} +const toTest = id => `self.assertTrue(cook.test(${toMake(id)}))` +``` diff --git a/puzzler/tagger/cook.py b/puzzler/tagger/cook.py new file mode 100644 index 0000000000000000000000000000000000000000..b5278f64db7b86fb9648681a6a36328cbaecfda8 --- /dev/null +++ b/puzzler/tagger/cook.py @@ -0,0 +1,793 @@ +import logging + +from typing import List, Optional, Union +import chess +from chess import square_rank, square_file, Board, SquareSet, Piece, PieceType, square_distance +from chess import KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN +from chess import WHITE, BLACK +from chess.pgn import ChildNode +from model import Puzzle, TagKind +import util +from util import material_diff + +logger = logging.getLogger(__name__) +logging.basicConfig(format='%(asctime)s %(levelname)-4s %(message)s', datefmt='%m/%d %H:%M') +logger.setLevel(logging.INFO) + +def log(puzzle: Puzzle) -> None: + logger.info("https://lichess.org/training/{}".format(puzzle.id)) + +def cook(puzzle: Puzzle) -> List[TagKind]: + tags : List[TagKind] = [] + + mate_tag = mate_in(puzzle) + if mate_tag: + tags.append(mate_tag) + tags.append("mate") + if smothered_mate(puzzle): + tags.append("smotheredMate") + elif back_rank_mate(puzzle): + tags.append("backRankMate") + elif anastasia_mate(puzzle): + tags.append("anastasiaMate") + elif hook_mate(puzzle): + tags.append("hookMate") + elif arabian_mate(puzzle): + tags.append("arabianMate") + else: + found = boden_or_double_bishop_mate(puzzle) + if found: + tags.append(found) + elif dovetail_mate(puzzle): + tags.append("dovetailMate") + elif puzzle.cp > 600: + tags.append("crushing") + elif puzzle.cp > 200: + tags.append("advantage") + else: + tags.append("equality") + + if attraction(puzzle): + tags.append("attraction") + + if deflection(puzzle): + tags.append("deflection") + elif overloading(puzzle): + tags.append("overloading") + + if advanced_pawn(puzzle): + tags.append("advancedPawn") + + if double_check(puzzle): + tags.append("doubleCheck") + + if quiet_move(puzzle): + tags.append("quietMove") + + if defensive_move(puzzle) or check_escape(puzzle): + tags.append("defensiveMove") + + if sacrifice(puzzle): + tags.append("sacrifice") + + if x_ray(puzzle): + tags.append("xRayAttack") + + if fork(puzzle): + tags.append("fork") + + if hanging_piece(puzzle): + tags.append("hangingPiece") + + if trapped_piece(puzzle): + tags.append("trappedPiece") + + if discovered_attack(puzzle): + tags.append("discoveredAttack") + + if exposed_king(puzzle): + tags.append("exposedKing") + + if skewer(puzzle): + tags.append("skewer") + + if self_interference(puzzle) or interference(puzzle): + tags.append("interference") + + if intermezzo(puzzle): + tags.append("intermezzo") + + if pin_prevents_attack(puzzle) or pin_prevents_escape(puzzle): + tags.append("pin") + + if attacking_f2_f7(puzzle): + tags.append("attackingF2F7") + + if clearance(puzzle): + tags.append("clearance") + + if en_passant(puzzle): + tags.append("enPassant") + + if castling(puzzle): + tags.append("castling") + + if promotion(puzzle): + tags.append("promotion") + + if under_promotion(puzzle): + tags.append("underPromotion") + + if capturing_defender(puzzle): + tags.append("capturingDefender") + + if piece_endgame(puzzle, PAWN): + tags.append("pawnEndgame") + elif piece_endgame(puzzle, QUEEN): + tags.append("queenEndgame") + elif piece_endgame(puzzle, ROOK): + tags.append("rookEndgame") + elif piece_endgame(puzzle, BISHOP): + tags.append("bishopEndgame") + elif piece_endgame(puzzle, KNIGHT): + tags.append("knightEndgame") + elif queen_rook_endgame(puzzle): + tags.append("queenRookEndgame") + + if "backRankMate" not in tags and "fork" not in tags: + if kingside_attack(puzzle): + tags.append("kingsideAttack") + elif queenside_attack(puzzle): + tags.append("queensideAttack") + + if len(puzzle.mainline) == 2: + tags.append("oneMove") + elif len(puzzle.mainline) == 4: + tags.append("short") + elif len(puzzle.mainline) >= 8: + tags.append("veryLong") + else: + tags.append("long") + + return tags + +def advanced_pawn(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + if util.is_very_advanced_pawn_move(node): + return True + return False + +def double_check(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + if len(node.board().checkers()) > 1: + return True + return False + +def sacrifice(puzzle: Puzzle) -> bool: + # down in material compared to initial position, after moving + diffs = [material_diff(n.board(), puzzle.pov) for n in puzzle.mainline] + initial = diffs[0] + for d in diffs[1::2][1:]: + if d - initial <= -2: + return not any(n.move.promotion for n in puzzle.mainline[::2][1:]) + return False + +def x_ray(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2][1:]: + if not util.is_capture(node): + continue + prev_op_node = node.parent + assert isinstance(prev_op_node, ChildNode) + if prev_op_node.move.to_square != node.move.to_square or util.moved_piece_type(prev_op_node) == KING: + continue + prev_pl_node = prev_op_node.parent + assert isinstance(prev_pl_node, ChildNode) + if prev_pl_node.move.to_square != prev_op_node.move.to_square: + continue + if prev_op_node.move.from_square in SquareSet.between(node.move.from_square, node.move.to_square): + return True + + return False + +def fork(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2][:-1]: + if util.moved_piece_type(node) is not KING: + board = node.board() + if util.is_in_bad_spot(board, node.move.to_square): + continue + nb = 0 + for (piece, square) in util.attacked_opponent_squares(board, node.move.to_square, puzzle.pov): + if piece.piece_type == PAWN: + continue + if ( + util.king_values[piece.piece_type] > util.king_values[util.moved_piece_type(node)] or ( + util.is_hanging(board, piece, square) and + square not in board.attackers(not puzzle.pov, node.move.to_square) + ) + ): + nb += 1 + if nb > 1: + return True + return False + +def hanging_piece(puzzle: Puzzle) -> bool: + to = puzzle.mainline[1].move.to_square + captured = puzzle.mainline[0].board().piece_at(to) + if puzzle.mainline[0].board().is_check() and (not captured or captured.piece_type == PAWN): + return False + if captured and captured.piece_type != PAWN: + if util.is_hanging(puzzle.mainline[0].board(), captured, to): + op_move = puzzle.mainline[0].move + op_capture = puzzle.game.board().piece_at(op_move.to_square) + if op_capture and util.values[op_capture.piece_type] >= util.values[captured.piece_type] and op_move.to_square == to: + return False + if len(puzzle.mainline) < 4: + return True + if material_diff(puzzle.mainline[3].board(), puzzle.pov) >= material_diff(puzzle.mainline[1].board(), puzzle.pov): + return True + return False + +def trapped_piece(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2][1:]: + square = node.move.to_square + captured = node.parent.board().piece_at(square) + if captured and captured.piece_type != PAWN: + prev = node.parent + assert isinstance(prev, ChildNode) + if prev.move.to_square == square: + square = prev.move.from_square + if util.is_trapped(prev.parent.board(), square): + return True + return False + +def overloading(puzzle: Puzzle) -> bool: + return False + +def discovered_attack(puzzle: Puzzle) -> bool: + if discovered_check(puzzle): + return True + for node in puzzle.mainline[1::2][1:]: + if util.is_capture(node): + between = SquareSet.between(node.move.from_square, node.move.to_square) + assert isinstance(node.parent, ChildNode) + if node.parent.move.to_square == node.move.to_square: + return False + prev = node.parent.parent + assert isinstance(prev, ChildNode) + if (prev.move.from_square in between and + node.move.to_square != prev.move.to_square and + node.move.from_square != prev.move.to_square and + not util.is_castling(prev) + ): + return True + return False + +def discovered_check(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + board = node.board() + checkers = board.checkers() + if checkers and not node.move.to_square in checkers: + return True + return False + +def quiet_move(puzzle: Puzzle) -> bool: + for node in puzzle.mainline: + if ( + # on player move, not the last move of the puzzle + node.turn() != puzzle.pov and not node.is_end() and + # no check given or escaped + not node.board().is_check() and not node.parent.board().is_check() and + # no capture made or threatened + not util.is_capture(node) and not util.attacked_opponent_pieces(node.board(), node.move.to_square, puzzle.pov) and + # no advanced pawn push + not util.is_advanced_pawn_move(node) and + util.moved_piece_type(node) != KING + ): + return True + return False + +def defensive_move(puzzle: Puzzle) -> bool: + # like quiet_move, but on last move + # at least 3 legal moves + if puzzle.mainline[-2].board().legal_moves.count() < 3: + return False + node = puzzle.mainline[-1] + # no check given, no piece taken + if node.board().is_check() or util.is_capture(node): + return False + # no piece attacked + if util.attacked_opponent_pieces(node.board(), node.move.to_square, puzzle.pov): + return False + # no advanced pawn push + return not util.is_advanced_pawn_move(node) + +def check_escape(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + if node.board().is_check() or util.is_capture(node): + return False + if node.parent.board().legal_moves.count() < 3: + return False + if node.parent.board().is_check(): + return True + return False + +def attraction(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1:]: + if node.turn() == puzzle.pov: + continue + # 1. player moves to a square + first_move_to = node.move.to_square + opponent_reply = util.next_node(node) + # 2. opponent captures on that square + if opponent_reply and opponent_reply.move.to_square == first_move_to: + attracted_piece = util.moved_piece_type(opponent_reply) + if attracted_piece in [KING, QUEEN, ROOK]: + attracted_to_square = opponent_reply.move.to_square + next_node = util.next_node(opponent_reply) + if next_node: + attackers = next_node.board().attackers(puzzle.pov, attracted_to_square) + # 3. player attacks that square + if next_node.move.to_square in attackers: + # 4. player checks on that square + if attracted_piece == KING: + return True + n3 = util.next_next_node(next_node) + # 4. or player later captures on that square + if n3 and n3.move.to_square == attracted_to_square: + return True + return False + +def deflection(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2][1:]: + captured_piece = node.parent.board().piece_at(node.move.to_square) + if captured_piece or node.move.promotion: + capturing_piece = util.moved_piece_type(node) + if captured_piece and util.king_values[captured_piece.piece_type] > util.king_values[capturing_piece]: + continue + square = node.move.to_square + prev_op_move = node.parent.move + assert(prev_op_move) + grandpa = node.parent.parent + assert isinstance(grandpa, ChildNode) + prev_player_move = grandpa.move + prev_player_capture = grandpa.parent.board().piece_at(prev_player_move.to_square) + if ( + (not prev_player_capture or util.values[prev_player_capture.piece_type] < util.moved_piece_type(grandpa)) and + square != prev_op_move.to_square and square != prev_player_move.to_square and + (prev_op_move.to_square == prev_player_move.to_square or grandpa.board().is_check()) and + ( + square in grandpa.board().attacks(prev_op_move.from_square) or + node.move.promotion and + square_file(node.move.to_square) == square_file(prev_op_move.from_square) and + node.move.from_square in grandpa.board().attacks(prev_op_move.from_square) + ) and + (not square in node.parent.board().attacks(prev_op_move.to_square)) + ): + return True + return False + +def exposed_king(puzzle: Puzzle) -> bool: + if puzzle.pov: + pov = puzzle.pov + board = puzzle.mainline[0].board() + else: + pov = not puzzle.pov + board = puzzle.mainline[0].board().mirror() + king = board.king(not pov) + assert king is not None + if chess.square_rank(king) < 5: + return False + squares = SquareSet.from_square(king - 8) + if chess.square_file(king) > 0: + squares.add(king - 1) + squares.add(king - 9) + if chess.square_file(king) < 7: + squares.add(king + 1) + squares.add(king - 7) + for square in squares: + if board.piece_at(square) == Piece(PAWN, not pov): + return False + for node in puzzle.mainline[1::2][1:-1]: + if node.board().is_check(): + return True + return False + +def skewer(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2][1:]: + prev = node.parent + assert isinstance(prev, ChildNode) + capture = prev.board().piece_at(node.move.to_square) + if capture and util.moved_piece_type(node) in util.ray_piece_types and not node.board().is_checkmate(): + between = SquareSet.between(node.move.from_square, node.move.to_square) + op_move = prev.move + assert op_move + if (op_move.to_square == node.move.to_square or not op_move.from_square in between): + continue + if ( + util.king_values[util.moved_piece_type(prev)] > util.king_values[capture.piece_type] and + util.is_in_bad_spot(prev.board(), node.move.to_square) + ): + return True + return False + +def self_interference(puzzle: Puzzle) -> bool: + # intereference by opponent piece + for node in puzzle.mainline[1::2][1:]: + prev_board = node.parent.board() + square = node.move.to_square + capture = prev_board.piece_at(square) + if capture and util.is_hanging(prev_board, capture, square): + grandpa = node.parent.parent + assert grandpa + init_board = grandpa.board() + defenders = init_board.attackers(capture.color, square) + defender = defenders.pop() if defenders else None + defender_piece = init_board.piece_at(defender) if defender else None + if defender and defender_piece and defender_piece.piece_type in util.ray_piece_types: + if node.parent.move and node.parent.move.to_square in SquareSet.between(square, defender): + return True + return False + +def interference(puzzle: Puzzle) -> bool: + # intereference by player piece + for node in puzzle.mainline[1::2][1:]: + prev_board = node.parent.board() + square = node.move.to_square + capture = prev_board.piece_at(square) + assert node.parent.move + if capture and square != node.parent.move.to_square and util.is_hanging(prev_board, capture, square): + assert node.parent + assert node.parent.parent + assert node.parent.parent.parent + init_board = node.parent.parent.parent.board() + defenders = init_board.attackers(capture.color, square) + defender = defenders.pop() if defenders else None + defender_piece = init_board.piece_at(defender) if defender else None + if defender and defender_piece and defender_piece.piece_type in util.ray_piece_types: + interfering = node.parent.parent + if interfering.move and interfering.move.to_square in SquareSet.between(square, defender): + return True + return False + +def intermezzo(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2][1:]: + if util.is_capture(node): + capture_move = node.move + capture_square = node.move.to_square + op_node = node.parent + assert isinstance(op_node, ChildNode) + prev_pov_node = node.parent.parent + assert isinstance(prev_pov_node, ChildNode) + if not op_node.move.from_square in prev_pov_node.board().attackers(not puzzle.pov, capture_square): + if prev_pov_node.move.to_square != capture_square: + prev_op_node = prev_pov_node.parent + assert isinstance(prev_op_node, ChildNode) + return ( + prev_op_node.move.to_square == capture_square and + util.is_capture(prev_op_node) and + capture_move in prev_op_node.board().legal_moves + ) + return False + +# the pinned piece can't attack a player piece +def pin_prevents_attack(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + board = node.board() + for square, piece in board.piece_map().items(): + if piece.color == puzzle.pov: + continue + pin_dir = board.pin(piece.color, square) + if pin_dir == chess.BB_ALL: + continue + for attack in board.attacks(square): + attacked = board.piece_at(attack) + if attacked and attacked.color == puzzle.pov and not attack in pin_dir and ( + util.values[attacked.piece_type] > util.values[piece.piece_type] or + util.is_hanging(board, attacked, attack) + ): + return True + return False + +# the pinned piece can't escape the attack +def pin_prevents_escape(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + board = node.board() + for pinned_square, pinned_piece in board.piece_map().items(): + if pinned_piece.color == puzzle.pov: + continue + pin_dir = board.pin(pinned_piece.color, pinned_square) + if pin_dir == chess.BB_ALL: + continue + for attacker_square in board.attackers(puzzle.pov, pinned_square): + if attacker_square in pin_dir: + attacker = board.piece_at(attacker_square) + assert(attacker) + if util.values[pinned_piece.piece_type] > util.values[attacker.piece_type]: + return True + if (util.is_hanging(board, pinned_piece, pinned_square) and + pinned_square not in board.attackers(not puzzle.pov, attacker_square) and + [m for m in board.pseudo_legal_moves if m.from_square == pinned_square and m.to_square not in pin_dir] + ): + return True + return False + +def attacking_f2_f7(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + square = node.move.to_square + if node.parent.board().piece_at(node.move.to_square) and square in [chess.F2, chess.F7]: + king = node.board().piece_at(chess.E8 if square == chess.F7 else chess.E1) + return king is not None and king.piece_type == KING and king.color != puzzle.pov + return False + +def kingside_attack(puzzle: Puzzle) -> bool: + return side_attack(puzzle, 7, [6, 7], 20) + +def queenside_attack(puzzle: Puzzle) -> bool: + return side_attack(puzzle, 0, [0, 1, 2], 18) + +def side_attack(puzzle: Puzzle, corner_file: int, king_files: List[int], nb_pieces: int) -> bool: + back_rank = 7 if puzzle.pov else 0 + init_board = puzzle.mainline[0].board() + king_square = init_board.king(not puzzle.pov) + if ( + not king_square or + square_rank(king_square) != back_rank or + square_file(king_square) not in king_files or + len(init_board.piece_map()) < nb_pieces or # no endgames + not any(node.board().is_check() for node in puzzle.mainline[1::2]) + ): + return False + score = 0 + corner = chess.square(corner_file, back_rank) + for node in puzzle.mainline[1::2]: + corner_dist = square_distance(corner, node.move.to_square) + if node.board().is_check(): + score += 1 + if util.is_capture(node) and corner_dist <= 3: + score += 1 + elif corner_dist >= 5: + score -= 1 + return score >= 2 + +def clearance(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2][1:]: + board = node.board() + if not node.parent.board().piece_at(node.move.to_square): + piece = board.piece_at(node.move.to_square) + if piece and piece.piece_type in util.ray_piece_types: + prev = node.parent.parent + assert prev + prev_move = prev.move + assert prev_move + assert isinstance(node.parent, ChildNode) + if (not prev_move.promotion and + prev_move.to_square != node.move.from_square and + prev_move.to_square != node.move.to_square and + not node.parent.board().is_check() and + (not board.is_check() or util.moved_piece_type(node.parent) != KING)): + if (prev_move.from_square == node.move.to_square or + prev_move.from_square in SquareSet.between(node.move.from_square, node.move.to_square)): + if prev.parent and not prev.parent.board().piece_at(prev_move.to_square) or util.is_in_bad_spot(prev.board(), prev_move.to_square): + return True + return False + +def en_passant(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + if (util.moved_piece_type(node) == PAWN and + square_file(node.move.from_square) != square_file(node.move.to_square) and + not node.parent.board().piece_at(node.move.to_square) + ): + return True + return False + +def castling(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + if util.is_castling(node): + return True + return False + +def promotion(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + if node.move.promotion: + return True + return False + +def under_promotion(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + if node.board().is_checkmate(): + return True if node.move.promotion == KNIGHT else False + elif node.move.promotion and node.move.promotion != QUEEN: + return True + return False + +def capturing_defender(puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2][1:]: + board = node.board() + capture = node.parent.board().piece_at(node.move.to_square) + assert isinstance(node.parent, ChildNode) + if board.is_checkmate() or ( + capture and + util.moved_piece_type(node) != KING and + util.values[capture.piece_type] <= util.values[util.moved_piece_type(node)] and + util.is_hanging(node.parent.board(), capture, node.move.to_square) and + node.parent.move.to_square != node.move.to_square + ): + prev = node.parent.parent + assert isinstance(prev, ChildNode) + if not prev.board().is_check() and prev.move.to_square != node.move.from_square: + assert prev.parent + init_board = prev.parent.board() + defender_square = prev.move.to_square + defender = init_board.piece_at(defender_square) + if (defender and + defender_square in init_board.attackers(defender.color, node.move.to_square) and + not init_board.is_check()): + return True + return False + +def back_rank_mate(puzzle: Puzzle) -> bool: + node = puzzle.game.end() + board = node.board() + king = board.king(not puzzle.pov) + assert king is not None + assert isinstance(node, ChildNode) + back_rank = 7 if puzzle.pov else 0 + if board.is_checkmate() and square_rank(king) == back_rank: + squares = SquareSet.from_square(king + (-8 if puzzle.pov else 8)) + if puzzle.pov: + if chess.square_file(king) < 7: + squares.add(king - 7) + if chess.square_file(king) > 0: + squares.add(king - 9) + else: + if chess.square_file(king) < 7: + squares.add(king + 9) + if chess.square_file(king) > 0: + squares.add(king + 7) + for square in squares: + piece = board.piece_at(square) + if piece is None or piece.color == puzzle.pov or board.attackers(puzzle.pov, square): + return False + return any(square_rank(checker) == back_rank for checker in board.checkers()) + return False + +def anastasia_mate(puzzle: Puzzle) -> bool: + node = puzzle.game.end() + board = node.board() + king = board.king(not puzzle.pov) + assert king is not None + assert isinstance(node, ChildNode) + if square_file(king) in [0, 7] and square_rank(king) not in [0, 7]: + if square_file(node.move.to_square) == square_file(king) and util.moved_piece_type(node) in [QUEEN, ROOK]: + if square_file(king) != 0: + board.apply_transform(chess.flip_horizontal) + king = board.king(not puzzle.pov) + assert king is not None + blocker = board.piece_at(king + 1) + if blocker is not None and blocker.color != puzzle.pov: + knight = board.piece_at(king + 3) + if knight is not None and knight.color == puzzle.pov and knight.piece_type == KNIGHT: + return True + return False + +def hook_mate(puzzle: Puzzle) -> bool: + node = puzzle.game.end() + board = node.board() + king = board.king(not puzzle.pov) + assert king is not None + assert isinstance(node, ChildNode) + if util.moved_piece_type(node) == ROOK and square_distance(node.move.to_square, king) == 1: + for rook_defender_square in board.attackers(puzzle.pov, node.move.to_square): + defender = board.piece_at(rook_defender_square) + if defender and defender.piece_type == KNIGHT and square_distance(rook_defender_square, king) == 1: + for knight_defender_square in board.attackers(puzzle.pov, rook_defender_square): + pawn = board.piece_at(knight_defender_square) + if pawn and pawn.piece_type == PAWN: + return True + return False + +def arabian_mate(puzzle: Puzzle) -> bool: + node = puzzle.game.end() + board = node.board() + king = board.king(not puzzle.pov) + assert king is not None + assert isinstance(node, ChildNode) + if square_file(king) in [0, 7] and square_rank(king) in [0, 7] and util.moved_piece_type(node) == ROOK and square_distance(node.move.to_square, king) == 1: + for knight_square in board.attackers(puzzle.pov, node.move.to_square): + knight = board.piece_at(knight_square) + if knight and knight.piece_type == KNIGHT and ( + abs(square_rank(knight_square) - square_rank(king)) == 2 and + abs(square_file(knight_square) - square_file(king)) == 2 + ): + return True + return False + +def boden_or_double_bishop_mate(puzzle: Puzzle) -> Optional[TagKind]: + node = puzzle.game.end() + board = node.board() + king = board.king(not puzzle.pov) + assert king is not None + assert isinstance(node, ChildNode) + bishop_squares = list(board.pieces(BISHOP, puzzle.pov)) + if len(bishop_squares) < 2: + return None + for square in [s for s in SquareSet(chess.BB_ALL) if square_distance(s, king) < 2]: + if not all([p.piece_type == BISHOP for p in util.attacker_pieces(board, puzzle.pov, square)]): + return None + if (square_file(bishop_squares[0]) < square_file(king)) == (square_file(bishop_squares[1]) > square_file(king)): + return "bodenMate" + else: + return "doubleBishopMate" + +def dovetail_mate(puzzle: Puzzle) -> bool: + node = puzzle.game.end() + board = node.board() + king = board.king(not puzzle.pov) + assert king is not None + assert isinstance(node, ChildNode) + if square_file(king) in [0, 7] or square_rank(king) in [0, 7]: + return False + queen_square = node.move.to_square + if (util.moved_piece_type(node) != QUEEN or + square_file(queen_square) == square_file(king) or + square_rank(queen_square) == square_rank(king) or + square_distance(queen_square, king) > 1): + return False + for square in [s for s in SquareSet(chess.BB_ALL) if square_distance(s, king) == 1]: + if square == queen_square: + continue + attackers = list(board.attackers(puzzle.pov, square)) + if attackers == [queen_square]: + if board.piece_at(square): + return False + elif attackers: + return False + return True + +def piece_endgame(puzzle: Puzzle, piece_type: PieceType) -> bool: + for board in [puzzle.mainline[i].board() for i in [0, 1]]: + if not board.pieces(piece_type, WHITE) and not board.pieces(piece_type, BLACK): + return False + for piece in board.piece_map().values(): + if not piece.piece_type in [KING, PAWN, piece_type]: + return False + return True + +def queen_rook_endgame(puzzle: Puzzle) -> bool: + def test(board: Board) -> bool: + pieces = board.piece_map().values() + return ( + len([p for p in pieces if p.piece_type == QUEEN]) == 1 and + any(p.piece_type == ROOK for p in pieces) and + all(p.piece_type in [QUEEN, ROOK, PAWN, KING] for p in pieces) + ) + return all(test(puzzle.mainline[i].board()) for i in [0, 1]) + +def smothered_mate(puzzle: Puzzle) -> bool: + board = puzzle.game.end().board() + king_square = board.king(not puzzle.pov) + assert king_square is not None + for checker_square in board.checkers(): + piece = board.piece_at(checker_square) + assert piece + if piece.piece_type == KNIGHT: + for escape_square in [s for s in chess.SQUARES if square_distance(s, king_square) == 1]: + blocker = board.piece_at(escape_square) + if not blocker or blocker.color == puzzle.pov: + return False + return True + return False + +def mate_in(puzzle: Puzzle) -> Optional[TagKind]: + if not puzzle.game.end().board().is_checkmate(): + return None + moves_to_mate = len(puzzle.mainline) // 2 + if moves_to_mate == 1: + return "mateIn1" + elif moves_to_mate == 2: + return "mateIn2" + elif moves_to_mate == 3: + return "mateIn3" + elif moves_to_mate == 4: + return "mateIn4" + return "mateIn5" diff --git a/puzzler/tagger/model.py b/puzzler/tagger/model.py new file mode 100644 index 0000000000000000000000000000000000000000..ea4c4add8208851e8d177dd3f5ed77410effca62 --- /dev/null +++ b/puzzler/tagger/model.py @@ -0,0 +1,76 @@ +from dataclasses import dataclass, field +from chess.pgn import Game, ChildNode +from chess import Color +from typing import List, Literal, Optional + +TagKind = Literal[ + "advancedPawn", + "advantage", + "anastasiaMate", + "arabianMate", + "attackingF2F7", + "attraction", + "backRankMate", + "bishopEndgame", + "bodenMate", + "capturingDefender", + "castling", + "clearance", + "coercion", + "crushing", + "defensiveMove", + "discoveredAttack", + "deflection", + "doubleBishopMate", + "doubleCheck", + "dovetailMate", + "equality", + "enPassant", + "exposedKing", + "fork", + "hangingPiece", + "hookMate", + "interference", + "intermezzo", + "kingsideAttack", + "knightEndgame", + "long", + "mate", + "mateIn5", + "mateIn4", + "mateIn3", + "mateIn2", + "mateIn1", + "oneMove", + "overloading", + "pawnEndgame", + "pin", + "promotion", + "queenEndgame", + "queensideAttack", + "quietMove", + "rookEndgame", + "queenRookEndgame", + "sacrifice", + "short", + "simplification", + "skewer", + "smotheredMate", + "trappedPiece", + "underPromotion", + "veryLong", + "xRayAttack", + "zugzwang" +] + +@dataclass +class Puzzle: + id: str + game: Game + pov : Color = field(init=False) + mainline: List[ChildNode] = field(init=False) + cp: int + + def __post_init__(self): + self.pov = not self.game.turn() + self.mainline = list(self.game.mainline()) diff --git a/puzzler/tagger/requirements.txt b/puzzler/tagger/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2360d528acb2eed8fbd7c911791bae2fccab6fe6 --- /dev/null +++ b/puzzler/tagger/requirements.txt @@ -0,0 +1,2 @@ +chess==1.3.0 +pymongo==3.11.0 diff --git a/puzzler/tagger/tagger.py b/puzzler/tagger/tagger.py new file mode 100644 index 0000000000000000000000000000000000000000..a4c5069721ef03db938925b20166ce410b1707d4 --- /dev/null +++ b/puzzler/tagger/tagger.py @@ -0,0 +1,147 @@ +import pymongo +import logging +import argparse +from multiprocessing import Process, Queue, Pool, Manager +from datetime import datetime +from chess import Move, Board +from chess.pgn import Game, GameNode +from chess.engine import SimpleEngine, Mate, Cp +from typing import List, Tuple, Dict, Any +from model import Puzzle, TagKind +import cook +import chess.engine +from zugzwang import zugzwang + +logger = logging.getLogger(__name__) +logging.basicConfig(format='%(asctime)s %(levelname)-4s %(message)s', datefmt='%m/%d %H:%M') +logger.setLevel(logging.INFO) + +def read(doc) -> Puzzle: + board = Board(doc["fen"]) + node: GameNode = Game.from_board(board) + for uci in (doc["line"].split(' ') if "line" in doc else doc["moves"]): + move = Move.from_uci(uci) + node = node.add_main_variation(move) + return Puzzle(doc["_id"], node.game(), int(doc["cp"])) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(prog='tagger.py', description='automatically tags lichess puzzles') + parser.add_argument("--zug", "-z", help="only zugzwang", action="store_true") + parser.add_argument("--bad_mate", help="find bad mates", action="store_true") + parser.add_argument("--dry", "-d", help="dry run", action="store_true") + parser.add_argument("--all", "-a", help="don't skip existing", action="store_true") + parser.add_argument("--threads", "-t", help="count of cpu threads for engine searches", default="4") + parser.add_argument("--engine", "-e", help="analysis engine", default="stockfish") + args = parser.parse_args() + + if args.zug: + threads = int(args.threads) + def cruncher(thread_id: int): + db = pymongo.MongoClient()['puzzler'] + round_coll = db['puzzle2_round'] + play_coll = db['puzzle2_puzzle'] + engine = SimpleEngine.popen_uci(args.engine) + engine.configure({'Threads': 2}) + for doc in round_coll.aggregate([ + {"$match":{"_id":{"$regex":"^lichess:"},"t":{"$nin":['+zugzwang','-zugzwang']}}}, + {'$lookup':{'from':'puzzle2_puzzle','as':'puzzle','localField':'p','foreignField':'_id'}}, + {'$unwind':'$puzzle'},{'$replaceRoot':{'newRoot':'$puzzle'}} + ]): + try: + if ord(doc["_id"][4]) % threads != thread_id: + continue + puzzle = read(doc) + round_id = f'lichess:{puzzle.id}' + zug = zugzwang(engine, puzzle) + if zug: + cook.log(puzzle) + round_coll.update_one( + { "_id": round_id }, + {"$addToSet": {"t": "+zugzwang" if zug else "-zugzwang"}} + ) + play_coll.update_one({"_id":puzzle.id},{"$set":{"dirty":True}}) + except Exception as e: + print(doc) + logger.error(e) + engine.close() + exit(1) + engine.close() + with Pool(processes=threads) as pool: + for i in range(int(args.threads)): + Process(target=cruncher, args=(i,)).start() + exit(0) + + if args.bad_mate: + threads = int(args.threads) + def cruncher(thread_id: int): + db = pymongo.MongoClient()['puzzler'] + bad_coll = db['puzzle2_bad_maybe'] + play_coll = db['puzzle2_puzzle'] + engine = SimpleEngine.popen_uci('./stockfish') + engine.configure({'Threads': 4}) + for doc in bad_coll.find({"bad": {"$exists":False}}): + try: + if ord(doc["_id"][4]) % threads != thread_id: + continue + doc = play_coll.find_one({'_id': doc['_id']}) + if not doc: + continue + puzzle = read(doc) + board = puzzle.mainline[len(puzzle.mainline) - 2].board() + info = engine.analyse(board, multipv = 5, limit = chess.engine.Limit(nodes = 30_000_000)) + bad = False + for score in [pv["score"].pov(puzzle.pov) for pv in info]: + if score < Mate(1) and score > Cp(250): + bad = True + # logger.info(puzzle.id) + bad_coll.update_one({"_id":puzzle.id},{"$set":{"bad":bad}}) + except Exception as e: + logger.error(e) + with Pool(processes=threads) as pool: + for i in range(int(args.threads)): + Process(target=cruncher, args=(i,)).start() + exit(0) + + threads = int(args.threads) + + def cruncher(thread_id: int): + db = pymongo.MongoClient()['puzzler'] + play_coll = db['puzzle2_puzzle'] + round_coll = db['puzzle2_round'] + total = 0 + computed = 0 + updated = 0 + for doc in play_coll.find({'themes':[]}): + total += 1 + if not thread_id and total % 1000 == 0: + logger.info(f'{total} / {computed} / {updated}') + if ord(doc["_id"][4]) % threads != thread_id: + continue + computed += 1 + id = doc["_id"] + if not args.all and round_coll.count_documents({"_id": f"lichess:{id}", "t.1": {"$exists":True}}): + continue + tags = cook.cook(read(doc)) + round_id = f"lichess:{id}" + if not args.dry: + existing = round_coll.find_one({"_id": round_id},{"t":True}) + zugs = [t for t in existing["t"] if t in ['+zugzwang', '-zugzwang']] if existing else [] + new_tags = [f"+{t}" for t in tags] + zugs + if not existing or set(new_tags) != set(existing["t"]): + updated += 1 + round_coll.update_one({ + "_id": round_id + }, { + "$set": { + "p": id, + "d": datetime.now(), + "e": 100, + "t": new_tags + } + }, upsert = True); + play_coll.update_many({"_id":id},{"$set":{"dirty":True}}) + print(f'{thread_id}/{args.threads} done') + + with Pool(processes=threads) as pool: + for i in range(int(args.threads)): + Process(target=cruncher, args=(i,)).start() diff --git a/puzzler/tagger/test.py b/puzzler/tagger/test.py new file mode 100644 index 0000000000000000000000000000000000000000..e7bd76bd1e6cbe921f65869908f343c03a4e03b3 --- /dev/null +++ b/puzzler/tagger/test.py @@ -0,0 +1,235 @@ +import unittest +import logging +import chess +import util +import cook +from model import Puzzle +from tagger import logger, read +from chess import parse_square, ROOK + +def make(id: str, fen: str, line: str) -> Puzzle: + return read({ "_id": id, "fen": fen, "line": line, "cp": 999999998 }) + +class TestTagger(unittest.TestCase): + + logger.setLevel(logging.DEBUG) + + def test_attraction(self): + self.assertFalse(cook.attraction(make("yUM8F", + "r1bq1rk1/ppp1bppp/2n2n2/4p1B1/4N1P1/3P1N1P/PPP2P2/R2QKB1R w KQ - 1 9", + "d1d2 f6e4 d3e4 c6d4 e1c1 d4f3 d2d8 e7g5 d8g5 f3g5" + ))) + + self.assertFalse(cook.attraction(make("wFGMa", + "4r1k1/1R3ppp/1N3n2/1bP5/1P6/3p3P/6P1/3R2K1 w - - 0 28", + "b6d5 f6d5 b7b5 d5c3 d1d3 c3b5" + ))) + + self.assertTrue(cook.attraction(make("uf4XN", + "r4rk1/pp3pp1/7p/b2Pn3/4N3/6RQ/P4PPP/q1B1R1K1 b - - 8 26", + "a5e1 g3g7 g8g7 h3h6 g7g8 e4f6" + ))) + + self.assertTrue(cook.attraction(make("wRDRr", "2kr1b1r/1p1b2pp/p1P1p2n/2P3N1/P4q2/5N2/4BKPP/R2Q3R b - - 2 18", "d7c6 d1d8 c8d8 g5e6 d8c8 e6f4"))) + + def test_sacrifice(self): + self.assertTrue(cook.sacrifice(make("1NHUV", "r1b2rk1/pppp1ppp/2n5/3Q2B1/2B5/2P2N2/P1q3PP/4RK1R b - - 1 14", "d7d6 d5f7 f8f7 e1e8"))) + self.assertFalse(cook.sacrifice(make("1HDGN", "3qr1k1/R4pbp/2p3p1/1p1p4/1P3Q2/2P1P3/3B2P1/7K b - - 0 33", "d8b8 f4f7 g8h8 f7g7"))) + self.assertTrue(cook.sacrifice(make("1PljR", "1R1r2k1/5ppp/p7/3q1P2/2pr1B2/3n2PP/4Q3/5RK1 b - - 4 30", "d3f4 e2e8 d8e8 b8e8"))) + self.assertTrue(cook.sacrifice(make("7frsv", "4r1k1/pb3ppp/1p1b1n2/2pP4/4P1q1/2N5/PBQ2PPP/R4RK1 w - - 0 19", "c2e2 d6h2 g1h2 g4h4 h2g1 f6g4 e2g4 h4g4"))) + self.assertFalse(cook.sacrifice(make("2FSmI", "r2q1rk1/pp3p2/4pn1R/8/3Q4/5N2/PPP2PPb/R5K1 w - - 0 19", "g1h2 d8d4 f3d4 f6g4 h2g3 g4h6"))) + self.assertTrue(cook.sacrifice(make("6UjJO", "r1bqnrk1/pp1n2p1/3bp1N1/3p1p2/2pP1P2/2P1PN1R/PP4PP/R1BQ2K1 b - - 1 15", "f8f6 h3h8 g8f7 f3g5 f7g6 d1h5"))) + self.assertTrue(cook.sacrifice(make("uHVch", "4r3/1b4p1/p7/1p1Pp1kr/4Qp2/1B1R1RP1/PP3P1P/2q3K1 w - - 1 31", "g1g2 h5h2 g2h2 e8h8 e4h7 h8h7 h2g2 c1h1"))) + self.assertFalse(cook.sacrifice(make("51K8X", "r3r1k1/pp1n1pp1/2p3p1/3p4/3PnqPN/2P4P/PPQN1P2/4RRK1 w - - 2 18", "h4g2 f4d2 c2d2 e4d2 e1e8 a8e8"))) + # temporary exchange sac + self.assertTrue(cook.sacrifice(make("2pqYA", "6k1/p6p/2r2bp1/1pp4r/5P2/3R2P1/P5BP/3R3K b - - 1 29", "c5c4 d3d8 f6d8 d1d8 g8f7 g2c6"))) + self.assertFalse(cook.sacrifice(make("bIcc9", "8/8/2R5/7P/2Pk4/p1r5/6P1/6K1 w - - 0 41", "h5h6 a3a2 c6d6 d4c5 d6d1 c3b3 h6h7 b3b1 h7h8q b1d1 g1h2 a2a1q"))) + + def test_defensive(self): + self.assertFalse(cook.defensive_move(make("6MVFt", "8/2P5/3K4/8/4pk2/2r3p1/R7/8 b - - 0 50", "f4f3 a2a3 c3a3 c7c8q"))) + self.assertFalse(cook.defensive_move(make("5Winv", "6k1/2Q2pp1/p5rp/3P4/2pn3r/5P1q/P1N2RPP/4R1K1 w - - 0 32", "c2d4 h4d4 c7b8 g8h7"))) + + def test_check_escape(self): + self.assertTrue(cook.check_escape(make("i6rNU", "1R6/1P4p1/8/6k1/4K3/1r4pP/8/8 w - - 0 39", "h3h4 g5g4"))) + + def test_capturing_defender(self): + self.assertTrue(cook.capturing_defender(make("P6RR5", "3rk3/1RRn4/3r1p2/3pp3/8/2P1B3/5KP1/8 b - - 3 33", "d8b8 c7d7 d6d7 b7b8"))) + self.assertTrue(cook.capturing_defender(make("gfj87", "2rqk2r/pp2ppbp/1n1p2p1/3P4/2P5/2N1B3/PP2QPPP/R4RK1 b k - 0 14", "c8c4 e3b6 d8b6 e2c4"))) + self.assertFalse(cook.capturing_defender(make("i73wX", "2r3k1/1p4bp/pq2p1p1/3pr3/4nPP1/2N4P/PPPB3K/1R1Q1R2 b - - 2 22", "e4c3 b2c3 b6b1 d1b1"))) + + def test_fork(self): + self.assertTrue(cook.fork(make("0PQep", "6q1/p6p/6p1/4k3/1P2N3/2B2P2/4K1P1/8 b - - 3 43", "e5d5 e4f6 d5c4 f6g8"))) + self.assertFalse(cook.fork(make("0O5RW", "rnb1k2r/p1B2ppp/4p3/1Bb5/8/4P3/PP1K1PPP/nN4NR b kq - 0 12", "b8d7 b5c6 c8a6 c6a8 c5b4 b1c3"))) + self.assertTrue(cook.fork(make("1NxIN", "r3k2r/p2q1ppp/4pn2/1Qp5/8/4P3/PP1N1PPP/R3K2R w KQkq - 2 16", "b5c5 d7d2 e1d2 f6e4 d2e2 e4c5"))) + self.assertFalse(cook.fork(make("6ppA2", "8/p7/1p6/2p5/P6P/2P2Nk1/1r4P1/4R1K1 w - - 1 39", "f3d2 b2d2 h4h5 d2g2"))) + self.assertFalse(cook.fork(make("bypCs", "rnbq1b1r/p1k1pQp1/2p4p/1p1nP1p1/2pP4/2N3B1/PP3P1P/R3KBNR w KQ - 5 14", "c3d5 d8d5 f7d5 c6d5"))) + self.assertFalse(cook.fork(make("qgSLr", "2r3k1/6p1/p2q1rRp/3pp3/3P1p1R/3Q3P/PP3PP1/6K1 w - - 0 31", "g6f6 d6f6 h4h5 e5e4 d3b3 g7g5 b3d5 f6f7 d5e4 c8c1 g1h2 f7h5"))) + self.assertFalse(cook.fork(make("2eqdQ", "r4rk1/pp2qppp/5p2/1b1p4/1b1Q4/2N1B3/PPP2PPP/2KR3R b - - 7 13", "b4c5 d4c5 e7c5 e3c5"))) + self.assertFalse(cook.fork(make("QNrtc", "r2qr1k1/5p1p/pn3bp1/1p6/3P2bN/1P1B2PP/PB3PQ1/R3R1K1 b - - 0 19", "f6d4 e1e8 d8e8 b2d4"))) + self.assertFalse(cook.fork(make("J72FN", "6k1/7p/3R2p1/8/5p2/P4P2/1P1N2PP/3r1nK1 w - - 0 33", "d2e4 f1d2 g1f2 d2e4"))) + # can't detect ray-defended bishop + # self.assertTrue(cook.fork(make("q0ot7", "3q1rk1/1p1bbppp/8/1PrQP3/8/5N2/1B3PPP/R4RK1 w - - 1 26", "d5b7 c5b5 b7a6 b5b2"))) + + def test_trapped(self): + self.assertTrue(cook.trapped_piece(make("nPqjh", "r4rk1/pp1nppbp/3p1n2/q4p2/8/N1P1PP2/PP1BB1PP/2RQ1RK1 b - - 0 13", "b7b6 e2b5 a7a6 c3c4 a5a3 b2a3"))) + self.assertFalse(cook.trapped_piece(make("pjqyb", "r1b1k3/1pp4R/3p4/p2P4/2P5/8/PP2pKPP/8 b - - 1 34", "c8f5 h7h8 e8e7 h8a8 e2e1q f2e1"))) + self.assertTrue(cook.trapped_piece(make("pqkqG", "rnb1k2r/ppppqppp/8/2b4n/4P1N1/2N5/PPPP1PPP/R1BQKB1R w KQkq - 3 6", "f2f3 e7h4 g2g3 h5g3 h2g3 h4h1"))) + self.assertFalse(cook.trapped_piece(make("23J63", "2r2rk1/3bbpp1/p2p1n1p/1p1Pp3/4P3/5QNP/PPq2PPN/R1B1R1K1 w - - 6 19", "e1e2 c2d1 h2f1 c8c1 a1c1 d1c1"))) + self.assertFalse(cook.trapped_piece(make("2NQ68", "3qr1k1/p5pp/1p3n2/3p2P1/2rQ4/5B1P/PBb2P2/2R2RK1 w - - 1 21", "f3d5 d8d5 d4d5 f6d5"))) + # wontfix + # self.assertFalse(cook.trapped_piece(make("RBAaK", "rnbqkbnr/pp3ppp/2p1p3/1N2N3/1B1P4/5Q2/P1P2PPP/1R2KB1R b Kkq - 3 12", "f7f6 f3h5 g7g6 e5g6 h7g6 h5h8 f8b4 b1b4"))) + self.assertTrue(cook.trapped_piece(make("yKXxP", "2kr4/Qp3ppp/1p1q1n2/4r3/8/8/PPP1B1PP/2K1R2R w - - 0 19", "e2d3 e5a5 a7a5 b6a5"))) + self.assertTrue(cook.trapped_piece(make("ybteL", "1r3rk1/4qppp/p1P1p3/Qp2P3/2n5/1R3BP1/P4P1P/1R4K1 w - - 3 30", "a5a6 b8b6 a6b6 c4b6"))) + self.assertTrue(cook.trapped_piece(make("sXDZi", "r5k1/5Npp/8/3r4/4b3/2R2RP1/P5PP/6K1 w - - 1 28", "f3e3 a8a2 f7h6 g7h6"))) + self.assertTrue(cook.trapped_piece(make("0fuIS", "6k1/pp2rpp1/2p4p/8/1Pr5/PB2PpP1/5PbP/1R2K1R1 b - - 3 28", "c4c3 e1d2 e7e3 f2e3 c3b3 b1b3"))) + self.assertTrue(cook.trapped_piece(make("sBEHV", "r2q1r1k/pbp2pp1/3b1n1p/2p1Q3/8/2NB3P/PPP2PP1/R1B1R1K1 w - - 1 16", "e5f5 g7g6 f5f4 d6f4"))) + # wontfix + # self.assertFalse(cook.trapped_piece(make("RArZa", "r1b2rk1/3qnpb1/p2pp1p1/1pp3B1/4P3/1PNPR2Q/1PP2PPP/R5K1 b - - 2 16", "e6e5 h3d7 c8d7 g5e7"))) + + def test_discovered_attack(self): + self.assertFalse(cook.discovered_attack(make("0e7Q3", "5rk1/2pqnrpp/p3p1b1/N3P3/1PRPPp2/P4Q2/3B1RPP/6K1 w - - 3 30", "d2f4 f7f4 f3f4 f8f4"))) + self.assertFalse(cook.discovered_attack(make("0ZSP0", "5rk1/3R4/p1p3pp/1p2b3/2P1n2q/4Q2P/PP3PP1/4R1K1 w - - 4 27", "e3e4 h4f2 g1h1 f2f1 e1f1 f8f1"))) + self.assertTrue(cook.discovered_attack(make("01Y7w", "r2q1rk1/pppb1pbp/2n1pnp1/1BPpB3/3P4/4PN2/PP3PPP/RN1QK2R w KQ - 3 9", "e1g1 c6e5 d4e5 d7b5"))) + self.assertTrue(cook.discovered_attack(make("07jQK", "r4rk1/p1p1qppp/3b4/4n3/Q7/2NP4/PP3PPP/R1B2RK1 w - - 0 16", "f1e1 e5f3 g2f3 e7e1"))) + self.assertTrue(cook.discovered_attack(make("0VlKP", "5r2/6k1/8/p1p1p1p1/Pp1p2P1/1P1PnN1P/2P1KR2/8 w - - 3 38", "f3e5 f8e8 e5c6 e3g4 e2f1 g4f2"))) + self.assertFalse(cook.discovered_attack(make("m3h3k", "2r3k1/1r2pp1p/bqNp2p1/3P4/1p2P3/4bN2/1P4PP/2RQR2K w - - 0 24", "c6e7 b7e7 c1c8 a6c8"))) + self.assertFalse(cook.discovered_attack(make("PsryZ", "4r2k/6pp/1R6/1pq5/8/P4QPP/1P3P1K/8 w - - 3 41", "f3c6 c5f2 c6g2 f2b6"))) + + def test_deflection(self): + self.assertTrue(cook.deflection(make("25Qpt", "r1bqkbnr/pp3p1p/6p1/2pBp3/4P3/2P1B3/PP3PPP/RN1QK2R b KQkq - 0 9", "g8f6 d5f7 e8f7 d1d8"))) + self.assertFalse(cook.deflection(make("0EgUL", "rnb1k2r/pppp2p1/4p2p/5p2/1q1Pn2P/2NQPN2/PPP2PP1/R3KB1R w KQkq - 1 9", "a2a3 b4b2 a1b1 b2c3 d3c3 e4c3"))) + self.assertFalse(cook.deflection(make("08vBP", "8/1R4p1/p5rp/4bN2/5kP1/2P4K/PP6/8 b - - 0 40", "g6g4 b7b4 f4f5 b4g4"))) + self.assertFalse(cook.deflection(make("0ZSP0", "5rk1/3R4/p1p3pp/1p2b3/2P1n2q/4Q2P/PP3PP1/4R1K1 w - - 4 27", "e3e4 h4f2 g1h1 f2f1 e1f1 f8f1"))) + self.assertFalse(cook.deflection(make("3J2Nl", "r2q2k1/pp4bp/3pnppn/3N4/4Pp1B/7P/PPPQ2P1/R4RK1 w - - 0 19", "d5f4 e6f4 f1f4 g6g5 d2d5 g8h8 f4f2 g5h4"))) + self.assertFalse(cook.deflection(make("3051j", "r2k2r1/1b2nQb1/1p2p2p/p3Pp2/2P4q/P6P/NP2R1PN/2R4K b - - 0 26", "h4d4 a2c3 g8f8 f7g7 f8g8 g7h6"))) + self.assertFalse(cook.deflection(make("0VlKP", "5r2/6k1/8/p1p1p1p1/Pp1p2P1/1P1PnN1P/2P1KR2/8 w - - 3 38", "f3e5 f8e8 e5c6 e3g4 e2f1 g4f2"))) + self.assertTrue(cook.deflection(make("7ycL5", "r1bqkb1r/4pp1p/p1pp1np1/4P3/P1B5/2N5/1PP2PPP/R1BQK2R b KQkq - 0 9", "d6e5 c4f7 e8f7 d1d8"))) + self.assertTrue(cook.deflection(make("oGLtH", "8/8/PR4K1/8/5k1P/r7/4p3/8 w - - 0 52", "b6e6 a3a6 e6a6 e2e1q"))) + self.assertTrue(cook.deflection(make("bZQyl", "8/R4pk1/6p1/P6p/3n3P/5PK1/r4NP1/8 w - - 3 43", "a5a6 d4f5 g3h2 a2f2"))) + + def test_skewer(self): + self.assertTrue(cook.skewer(make("29HGS", "3r4/6p1/5r1p/7k/3N1P2/3K2P1/3R4/3R4 w - - 1 50", "d2e2 d8d4 d3d4 f6d6 d4e5 d6d1"))) + self.assertFalse(cook.skewer(make("aUuGJ", "5R2/p2rkpKp/1p2p1p1/4P1P1/8/8/P7/8 b - - 9 47", "a7a5 f8f7 e7e8 f7d7 e8d7 g7h7 b6b5 h7g6"))) + self.assertTrue(cook.skewer(make("tipFF", "1rr5/3k4/3bpp2/q5p1/PpRP3p/1P3N1P/1K1Q1PP1/7R w - - 2 25", "h1c1 d6f4 d2d3 f4c1"))) + self.assertTrue(cook.skewer(make("DcZWN", "rn3rk1/p2p3p/1pb1p1pn/4Q3/P1B5/qNP2PP1/3N3P/4RRK1 b - - 2 21", "c6a4 e1a1 a3e7 a1a4"))) + self.assertTrue(cook.skewer(make("GeNY1", "3k4/R7/8/1BK3p1/P1P2bPp/6r1/8/8 w - - 3 67", "a4a5 f4e3 c5c6 e3a7"))) + self.assertTrue(cook.skewer(make("frYL7", "7r/3q4/5k1p/8/4pp2/2Q5/P1P3PP/6K1 b - - 1 35", "f6f5 c3h3 f5f6 h3d7"))) + + def test_interference(self): + self.assertFalse(cook.interference(make("2t6Xz", "6k1/1b1q1pbp/4pnp1/2Pp4/rp1P1P2/3BPRNP/4Q1P1/4B1K1 b - - 1 26", "f6e4 d3b5 b7c6 b5a4"))) + self.assertTrue(cook.interference(make("QssMO", "r5k1/ppp2r2/3p3p/3Pp3/1P2N1bb/R5N1/1P3P1K/6R1 b - - 5 25", "g4f3 g3f5 g8h7 a3f3"))) + + # def test_overloading(self): + # self.assertTrue(cook.overloading(make("MB3gB", "r4rk1/ppq2ppp/2nb1n2/1Bpp2N1/6b1/P1N1P1P1/1PQP1P1P/R1B1R1K1 b - - 6 12", "h7h6 c3d5 h6g5 d5c7"))) + + # def test_clearance(self): + # self.assertTrue(cook.clearance(make("iq12Z", "1R6/1P2r1pk/7p/6pr/3Pp3/1KP1R3/8/8 b - - 0 55", "g5g4 b8h8 h7h8 b7b8q"))) + + def test_x_ray(self): + self.assertTrue(cook.x_ray(make("fo0LG", "5R2/8/p1p4p/1p1p2k1/6r1/1P2P1r1/P1PKR3/8 b - - 3 33", "g3g2 f8g8 g5f6 e2g2 g4g2 g8g2"))) + + def test_quiet_move(self): + self.assertFalse(cook.quiet_move(make("SxOf2", "7r/3k4/1P3p2/1K1Pp1p1/2N1P1P1/8/8/8 b - - 2 49", "h8h4 b6b7 h4h1 b7b8n"))) + + def test_pin_prevents_attack(self): + # pins the queen from attacking the g2 pawn + self.assertTrue(cook.pin_prevents_attack(make("P2D4h", "2k5/p7/bpq1p3/8/2PP2P1/1K2P1p1/4Q1P1/8 b - - 4 36", "a6c4 e2c4 c6c4 b3c4"))) + self.assertTrue(cook.pin_prevents_attack(make("aJPsJ", "r2q1r1k/pp3pp1/2p2n1p/3PB2b/3P4/1B5P/P1PQ1PP1/R3R1K1 b - - 0 18", "f6d5 d2h6 h8g8 h6g7"))) + self.assertFalse(cook.pin_prevents_attack(make("9CkIh", "r4r2/pp3pkp/2p5/3pPp1q/3p1P2/3Q1R2/PPP3PP/R5K1 b - - 3 18", "c6c5 f3h3 h5g6 h3g3 g7h8 g3g6"))) + self.assertFalse(cook.pin_prevents_attack(make("0CR44", "r2q4/4b1kp/6p1/2ppPr2/3P4/2P2N2/P4RQP/R5K1 w - - 0 27", "f3d2 f5g5 d2f3 g5g2"))) + self.assertFalse(cook.pin_prevents_attack(make("NCP9T", "1kr5/p3R3/7p/5Pp1/6P1/6K1/PP1R1P1P/6r1 w - - 1 32", "g3h3 h6h5 g4h5 c8h8 e7e8 h8e8"))) + # relative pin + # self.assertTrue(cook.pin_prevents_attack(make("spQRx", "8/8/5K2/5p2/6k1/3R2Pr/8/8 w - - 16 56", "f6e5 f5f4"))) + + def test_pin_prevents_escape(self): + self.assertFalse(cook.pin_prevents_escape(make("P2D4h", "2k5/p7/bpq1p3/8/2PP2P1/1K2P1p1/4Q1P1/8 b - - 4 36", "a6c4 e2c4 c6c4 b3c4"))) + self.assertFalse(cook.pin_prevents_escape(make("aJPsJ", "r2q1r1k/pp3pp1/2p2n1p/3PB2b/3P4/1B5P/P1PQ1PP1/R3R1K1 b - - 0 18", "f6d5 d2h6 h8g8 h6g7"))) + self.assertTrue(cook.pin_prevents_escape(make("9CkIh", "r4r2/pp3pkp/2p5/3pPp1q/3p1P2/3Q1R2/PPP3PP/R5K1 b - - 3 18", "c6c5 f3h3 h5g6 h3g3 g7h8 g3g6"))) + self.assertTrue(cook.pin_prevents_escape(make("0CR44", "r2q4/4b1kp/6p1/2ppPr2/3P4/2P2N2/P4RQP/R5K1 w - - 0 27", "f3d2 f5g5 d2f3 g5g2"))) + self.assertFalse(cook.pin_prevents_escape(make("NCP9T", "1kr5/p3R3/7p/5Pp1/6P1/6K1/PP1R1P1P/6r1 w - - 1 32", "g3h3 h6h5 g4h5 c8h8 e7e8 h8e8"))) + self.assertFalse(cook.pin_prevents_escape(make("6Jh1x", "2r5/1KP5/8/4k3/7p/7P/4p3/2R5 b - - 1 49", "c8e8 c1e1 e5f4 e1e2 e8e2 c7c8q"))) + + def test_hanging_piece(self): + self.assertTrue(cook.hanging_piece(make("069il", "r2qr1k1/1p3ppp/p1p2nb1/8/4P3/1P5P/PBQN1PP1/R3R1K1 w - - 1 17", "c2c4 d8d2 b2f6 g7f6"))) + self.assertTrue(cook.hanging_piece(make("cWlcD", "8/p4p2/2p2Pk1/1p1p2pp/1P4P1/2P4P/2r2R2/5K2 b - - 1 40", "h5g4 f2c2"))) + self.assertTrue(cook.hanging_piece(make("YJNLD", "2B2k2/pp5p/2p5/2n1p3/1PPbPp1q/P6P/4Q1P1/3N3K b - - 0 28", "c5e4 e2e4"))) + self.assertTrue(cook.hanging_piece(make("yXCrM", "2r3k1/5ppp/bq2p3/p2pPnP1/5PR1/NP3NbP/P2Q4/2BK4 b - - 0 27", "b6g1 f3g1"))) + + def test_advanced_pawn(self): + self.assertFalse(cook.advanced_pawn(make("C3gv2", "4r3/R1p2k2/3p1pp1/2r2p1p/1pN2Pn1/1P2PKP1/2P3P1/4R3 b - - 3 39", "d6d5 c4d6 f7e7 d6e8"))) + self.assertFalse(cook.advanced_pawn(make("JgJgO", "1R6/6kp/4Pp1q/3P4/R1P5/P5pP/6P1/7K w - - 1 34", "e6e7 h6c1"))) + self.assertTrue(cook.advanced_pawn(make("PKGhN", "2R5/2P2kpp/8/1p4b1/4n3/P6P/2p2PPK/2B5 b - - 0 41", "g5c1 c8f8 f7f8 c7c8q"))) + self.assertFalse(cook.advanced_pawn(make("qqs1r", "6r1/pppq3k/2np2np/8/3P2pB/N1PR1p2/PP2QPBN/6K1 w - - 0 33", "g2f3 g4f3 e2f1 g6h4"))) + + def test_rook_endgame(self): + self.assertFalse(cook.piece_endgame(make("qgryh", "8/p5KP/k7/6R1/6P1/1p6/8/7r w - - 0 44", "h7h8q h1h8 g7h8 b3b2 g5h5 b2b1q"), ROOK)) + self.assertFalse(cook.piece_endgame(make("p5BrZ", "8/4R1P1/8/3r4/6K1/8/4p3/3k4 b - - 0 62", "e2e1q e7e1 d1e1 g7g8q"), ROOK)) + self.assertTrue(cook.piece_endgame(make("j0qyE", "8/5p2/5k2/p4p2/8/1PPp1R2/r7/3K2R1 w - - 1 36", "f3d3 a2a1 d1d2 a1g1"), ROOK)) + self.assertFalse(cook.piece_endgame(make("Zjk3J", "8/pppk4/3p4/3P1p1p/PP3Rr1/4PpPK/5P2/8 w - - 5 36", "f4g4 h5g4 h3h4 c7c5 d5c6 b7c6 h4g5 d7e6"), ROOK)) + + def test_intermezzo(self): + self.assertTrue(cook.intermezzo(make("11pYZ", "8/5rpk/7p/8/3Q4/B4NKP/R2n2P1/5q2 b - - 3 42", "d2f3 d4e4 g7g6 g2f3"))) + self.assertTrue(cook.intermezzo(make("1E2zU", "6k1/4rpp1/3r3p/p2N4/PbB5/1Pq2Q1P/R2p1PP1/3R2K1 b - - 8 31", "c3f3 d5e7 g8f8 g2f3"))) + self.assertFalse(cook.intermezzo(make("1KWbk", "3r2k1/p3bqpp/2b1p3/2p2p2/8/2PNB1QP/PP3PP1/R5K1 w - - 2 26", "d3c5 f5f4 e3f4 e7c5"))) + self.assertFalse(cook.intermezzo(make("21ViC", "4b2r/r6k/6p1/3BQ1Rn/3P1P1P/p1qN4/2P5/2K5 b - - 0 33", "c3c7 g5h5 g6h5 d5e4 e8g6 e5h5 h7g8 h5g6"))) + + def test_back_rank_mate(self): + self.assertTrue(cook.back_rank_mate(make("tMEri", "5r1k/4q1p1/p2pP2p/1p6/1P2Q3/PB6/1BP3PP/6K1 w - - 1 27", "e4g6 e7a7 b2d4 a7d4 g1h1 f8f1"))) + self.assertFalse(cook.back_rank_mate(make("08VjT", "3r2k1/1bQ3p1/p2p3p/3qp1b1/1p6/1P1B4/P1P3PP/1K3R2 b - - 4 25", "d5c6 c7f7 g8h8 f7f8 d8f8 f1f8"))) + self.assertTrue(cook.back_rank_mate(make("LYKY0", "r5k1/pQ3ppp/8/8/B1pp4/4q3/PP5P/5R1K b - - 0 26", "a8d8 b7f7 g8h8 f7f8 d8f8 f1f8"))) + self.assertFalse(cook.back_rank_mate(make("ABCL2", "3r2k1/1b4pp/1p2pr2/p5N1/8/PP2n1P1/1BR2bBP/4R2K w - - 1 27", "b2f6 b7g2"))) + + def test_side_attack(self): + self.assertFalse(cook.kingside_attack(make("UTR0G", "rn1qk2r/5ppp/2p1p3/pp3bN1/1b1P2n1/1QN1PP2/P3B2P/R1B2RK1 w kq - 0 13", "f3g4 d8g5 e3e4 g5d8 e4f5 d8d4"))) + self.assertFalse(cook.kingside_attack(make("KnAMG", "6k1/1p4p1/p1p4p/3p1rq1/3Pp1N1/2P5/PP2K1Q1/5R2 w - - 0 39", "g4h6 g7h6 g2g5 f5g5"))) + self.assertTrue(cook.kingside_attack(make("mFqus", "3r2k1/1p3ppp/pq6/8/P3B3/5PNb/1PP1Qb1P/R1B3K1 w - - 0 25", "e2f2 d8d1 g3f1 d1f1"))) + self.assertTrue(cook.kingside_attack(make("XbfXS", "r4rk1/bpp3p1/p2p2qp/3bp3/1P6/P1PP3P/1B1Q1PPN/R4RK1 w - - 1 21", "g2g3 g6g3"))) + self.assertTrue(cook.kingside_attack(make("djudB", "r1b1kb2/pp1n1p2/4p3/3pP2r/3n4/3B1N1q/PP3P1P/R1BQ1RK1 w q - 0 17", "f3d4 h3h2"))) + self.assertTrue(cook.kingside_attack(make("NZvxf", "rn1q1rk1/pp1bbpp1/2p4p/2PpN3/3PnN1P/3B1P2/PPQ3P1/R1B2RK1 b - - 0 15", "e4g3 d3h7 g8h8 e5f7 f8f7 f4g6 h8h7 g6f8 h7g8 c2h7 g8f8 h7h8"))) + # self.assertFalse("kingsideAttack" in cook.cook(make("6h1wj", "r3r1k1/pppb2pp/1b6/3PRp2/1q3B2/1N3Q2/PP3PPP/R5K1 w - - 1 18", "a1e1 b4e1 e5e1 e8e1"))) + self.assertFalse(cook.kingside_attack(make("fLaC0", "2r2rk1/1q3pp1/p3p2p/1p1N1b2/8/P3PQ2/1P3PPP/R2R2K1 b - - 0 25", "f5c2 d5f6 g7f6 f3b7"))) + self.assertFalse("kingsideAttack" in cook.cook(make("NIxjp", "r1b1k2r/pppp2pp/5n2/b3q3/8/2PBP3/PP4PP/RNBQ1RK1 w kq - 3 11", "b1d2 e5e3 g1h1 e3d3"))) + + self.assertTrue(cook.queenside_attack(make("gO5Jg", "2k2b2/1p3b1p/2p2p2/1p1qp3/6PN/1P2Q2P/P1P2P2/2KB4 w - - 1 28", "h4f5 f8a3 c1b1 d5d1 e3c1 d1c1"))) + self.assertTrue(cook.queenside_attack(make("Oiyfh", "k2r1b2/ppR1p1p1/7r/4B2p/8/1P3B2/P2PK1PP/8 b - - 2 25", "d8b8 f3b7 b8b7 c7c8 b7b8 c8b8"))) + # self.assertTrue(cook.queenside_attack(make("nAZo8", "2kr1b2/Q1p2pp1/8/1bPpN1p1/8/2P5/Pr6/R3K3 b - - 0 23", "g5g4 a7a8"))) + self.assertFalse(cook.queenside_attack(make("TiK0f", "1kq5/pp3pQ1/2p5/4p3/4Pn1p/5P1P/1PP2P1K/5B2 b - - 0 38", "c8c7 g7h8 c7c8 h8e5"))) + self.assertFalse(cook.queenside_attack(make("Zk4Jr", "3rr1k1/p5pp/2p5/8/1P1bbp2/P1PP1B2/6PP/RK3R2 w - - 0 28", "c3d4 e4d3 b1b2 d3f1"))) + self.assertFalse(cook.queenside_attack(make("umd5a", "r4r1k/5p1p/5qp1/p3b1RP/1p3P2/8/PP1BQ3/2K4R w - - 0 32", "g5e5 f6c6 c1d1 c6h1"))) + + def test_underpromotion(self): + #unnecessary underpromotion to rook with mate + self.assertFalse(cook.under_promotion(make("1nFrQ", "8/1Pp3p1/8/2p5/2P5/5kbp/3p4/7K w - - 0 52", "b7b8q d2d1r"))) + #underpromotion to knight with mate + self.assertTrue(cook.under_promotion(make("2WyFZ", "3R3r/p1P1kp1b/4pnpp/7P/6P1/2p5/P4P2/3R2K1 b - - 0 31", "c3c2 c7c8n"))) + #Necessary underpromotions to knight, rook & bishop respectively:- + self.assertTrue(cook.under_promotion(make("0Xyxz", "6k1/p7/4pr2/2P3r1/4Bp1q/1Q3PpP/P4bP1/3R1R1K w - - 1 33", "d1d7 h4h3 g2h3 g3g2 h1h2 g2f1n h2h1 g5g1"))) + self.assertTrue(cook.under_promotion(make("AB2ON", "R7/P7/8/8/6k1/7p/r7/5K2 b - - 0 51", "g4g3 a8g8 g3h2 a7a8r"))) + self.assertTrue(cook.under_promotion(make("DzdfL", "6k1/P5P1/1n4K1/8/8/8/8/8 b - - 2 68", "b6c8 a7a8b c8e7 g6f6"))) + #underpromotion to bishop with mate + self.assertFalse(cook.under_promotion(make("iLgxo", "3B4/1pp5/p1b1B3/P3Q1N1/1P5k/2P5/R3R1p1/1q4K1 w - - 2 39", "g1h2 g2g1b"))) + #promotion to queen with mate(also possible with rook) + self.assertFalse(cook.under_promotion(make("00ueM", "2kr4/2pR4/2P1K1P1/8/8/p3n3/8/8 b - - 1 49", "a3a2 d7d8 c8d8 g6g7 a2a1q g7g8q"))) + + +class TestUtil(unittest.TestCase): + + def test_trapped(self): + self.assertFalse(util.is_trapped( + chess.Board("q3k3/7p/8/4N2q/3PP3/4B3/8/4K2R b - - 0 1"), parse_square("h5") + )) + self.assertTrue(util.is_trapped( + chess.Board("q3k3/7p/8/4N2q/3PP3/4B3/7R/4K2R b - - 0 1"), parse_square("h5") + )) + self.assertFalse(util.is_trapped( + chess.Board("q3k3/7p/8/4N2b/3PP3/4B3/7R/4K2R b - - 0 1"), parse_square("h5") + )) + self.assertFalse(util.is_trapped( + chess.Board("4k3/7p/8/4N2q/3PP2p/4B3/8/4K3 b - - 0 1"), parse_square("h5") + )) + self.assertTrue(util.is_trapped( + chess.Board("8/3P4/8/4N2b/7p/6N1/8/4K3 b - - 0 1"), parse_square("h5") + )) + +if __name__ == '__main__': + unittest.main() diff --git a/puzzler/tagger/util.py b/puzzler/tagger/util.py new file mode 100644 index 0000000000000000000000000000000000000000..d6d659239ff825197abfd8843f8988e4755e1ce2 --- /dev/null +++ b/puzzler/tagger/util.py @@ -0,0 +1,134 @@ +from typing import List, Optional, Tuple +import chess +from chess import square_rank, Color, Board, Square, Piece, square_distance +from chess import KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN +from chess.pgn import ChildNode +from typing import Type, TypeVar + +A = TypeVar('A') +def pp(a: A, msg = None) -> A: + print(f'{msg + ": " if msg else ""}{a}') + return a + +def moved_piece_type(node: ChildNode) -> chess.PieceType: + pt = node.board().piece_type_at(node.move.to_square) + assert(pt) + return pt + +def is_advanced_pawn_move(node: ChildNode) -> bool: + if node.move.promotion: + return True + if moved_piece_type(node) != chess.PAWN: + return False + to_rank = square_rank(node.move.to_square) + return to_rank < 3 if node.turn() else to_rank > 4 + +def is_very_advanced_pawn_move(node: ChildNode) -> bool: + if not is_advanced_pawn_move(node): + return False + to_rank = square_rank(node.move.to_square) + return to_rank < 2 if node.turn() else to_rank > 5 + +def is_king_move(node: ChildNode) -> bool: + return moved_piece_type(node) == chess.KING + +def is_castling(node: ChildNode) -> bool: + return is_king_move(node) and square_distance(node.move.from_square, node.move.to_square) > 1 + +def is_capture(node: ChildNode) -> bool: + return node.parent.board().is_capture(node.move) + +def next_node(node: ChildNode) -> Optional[ChildNode]: + return node.variations[0] if node.variations else None + +def next_next_node(node: ChildNode) -> Optional[ChildNode]: + nn = next_node(node) + return next_node(nn) if nn else None + +values = { PAWN: 1, KNIGHT: 3, BISHOP: 3, ROOK: 5, QUEEN: 9 } +king_values = { PAWN: 1, KNIGHT: 3, BISHOP: 3, ROOK: 5, QUEEN: 9, KING: 99 } +ray_piece_types = [QUEEN, ROOK, BISHOP] + +def piece_value(piece_type: chess.PieceType) -> int: + return values[piece_type] + +def material_count(board: Board, side: Color) -> int: + return sum(len(board.pieces(piece_type, side)) * value for piece_type, value in values.items()) + +def material_diff(board: Board, side: Color) -> int: + return material_count(board, side) - material_count(board, not side) + +def attacked_opponent_pieces(board: Board, from_square: Square, pov: Color) -> List[Piece]: + return [piece for (piece, _) in attacked_opponent_squares(board, from_square, pov)] + +def attacked_opponent_squares(board: Board, from_square: Square, pov: Color) -> List[Tuple[Piece, Square]]: + pieces = [] + for attacked_square in board.attacks(from_square): + attacked_piece = board.piece_at(attacked_square) + if attacked_piece and attacked_piece.color != pov: + pieces.append((attacked_piece, attacked_square)) + return pieces + +def is_defended(board: Board, piece: Piece, square: Square) -> bool: + if board.attackers(piece.color, square): + return True + # ray defense https://lichess.org/editor/6k1/3q1pbp/2b1p1p1/1BPp4/rp1PnP2/4PRNP/4Q1P1/4B1K1_w_-_-_0_1 + for attacker in board.attackers(not piece.color, square): + attacker_piece = board.piece_at(attacker) + assert(attacker_piece) + if attacker_piece.piece_type in ray_piece_types: + bc = board.copy(stack = False) + bc.remove_piece_at(attacker) + if bc.attackers(piece.color, square): + return True + + return False + +def is_hanging(board: Board, piece: Piece, square: Square) -> bool: + return not is_defended(board, piece, square) + +def can_be_taken_by_lower_piece(board: Board, piece: Piece, square: Square) -> bool: + for attacker_square in board.attackers(not piece.color, square): + attacker = board.piece_at(attacker_square) + assert(attacker) + if attacker.piece_type != chess.KING and values[attacker.piece_type] < values[piece.piece_type]: + return True + return False + +def is_in_bad_spot(board: Board, square: Square) -> bool: + # hanging or takeable by lower piece + piece = board.piece_at(square) + assert(piece) + return (bool(board.attackers(not piece.color, square)) and + (is_hanging(board, piece, square) or can_be_taken_by_lower_piece(board, piece, square))) + +def is_trapped(board: Board, square: Square) -> bool: + if board.is_check() or board.is_pinned(board.turn, square): + return False + piece = board.piece_at(square) + assert(piece) + if piece.piece_type in [PAWN, KING]: + return False + if not is_in_bad_spot(board, square): + return False + for escape in board.legal_moves: + if escape.from_square == square: + capturing = board.piece_at(escape.to_square) + if capturing and values[capturing.piece_type] >= values[piece.piece_type]: + return False + board.push(escape) + if not is_in_bad_spot(board, escape.to_square): + return False + board.pop() + return True + +def attacker_pieces(board: Board, color: Color, square: Square) -> List[Piece]: + return [p for p in [board.piece_at(s) for s in board.attackers(color, square)] if p] + +# def takers(board: Board, square: Square) -> List[Tuple[Piece, Square]]: +# # pieces that can legally take on a square +# t = [] +# for attack in board.legal_moves: +# if attack.to_square == square: +# t.append((board.piece_at(attack.from_square), attack.from_square)) +# return t diff --git a/puzzler/tagger/zugzwang.py b/puzzler/tagger/zugzwang.py new file mode 100644 index 0000000000000000000000000000000000000000..caf31cddc7aeb145e8396e037839baa2c9d29d46 --- /dev/null +++ b/puzzler/tagger/zugzwang.py @@ -0,0 +1,44 @@ +import chess +import chess.engine +import math +from chess import Board, Move, Color +from chess.engine import SimpleEngine, Score +from model import Puzzle + +engine_limit = chess.engine.Limit(depth = 30, time = 10, nodes = 12_000_000) + +def zugzwang(engine: SimpleEngine, puzzle: Puzzle) -> bool: + for node in puzzle.mainline[1::2]: + board = node.board() + if board.is_check(): + continue + if len(list(board.legal_moves)) > 15: + continue + + score = score_of(engine, board, not puzzle.pov) + + rev_board = node.board() + rev_board.push(Move.null()) + rev_score = score_of(engine, rev_board, not puzzle.pov) + + if win_chances(score) < win_chances(rev_score) - 0.3: + return True + + return False + +def score_of(engine: SimpleEngine, board: Board, pov: Color): + info = engine.analyse(board, limit = engine_limit) + if "nps" in info: + print(f'knps: {int(info["nps"] / 1000)} kn: {int(info["nodes"] / 1000)} depth: {info["depth"]} time: {info["time"]}') + return info["score"].pov(pov) + +def win_chances(score: Score) -> float: + """ + winning chances from -1 to 1 https://graphsketch.com/?eqn1_color=1&eqn1_eqn=100+*+%282+%2F+%281+%2B+exp%28-0.004+*+x%29%29+-+1%29&eqn2_color=2&eqn2_eqn=&eqn3_color=3&eqn3_eqn=&eqn4_color=4&eqn4_eqn=&eqn5_color=5&eqn5_eqn=&eqn6_color=6&eqn6_eqn=&x_min=-1000&x_max=1000&y_min=-100&y_max=100&x_tick=100&y_tick=10&x_label_freq=2&y_label_freq=2&do_grid=0&do_grid=1&bold_labeled_lines=0&bold_labeled_lines=1&line_width=4&image_w=850&image_h=525 + """ + mate = score.mate() + if mate is not None: + return 1 if mate > 0 else -1 + + cp = score.score() + return 2 / (1 + math.exp(-0.004 * cp)) - 1 if cp is not None else 0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..f43d0308008eead6b5f18fa166138d90ad8a70c3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +chess \ No newline at end of file diff --git a/stockfish/AUTHORS b/stockfish/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..a345bb69f1bd51b18bec7cdb721ba857830f11af --- /dev/null +++ b/stockfish/AUTHORS @@ -0,0 +1,255 @@ +# Founders of the Stockfish project and Fishtest infrastructure +Tord Romstad (romstad) +Marco Costalba (mcostalba) +Joona Kiiski (zamar) +Gary Linscott (glinscott) + +# Authors and inventors of NNUE, training, and NNUE port +Yu Nasu (ynasu87) +Motohiro Isozaki (yaneurao) +Hisayori Noda (nodchip) + +# All other authors of Stockfish code (in alphabetical order) +Aditya (absimaldata) +Adrian Petrescu (apetresc) +Ahmed Kerimov (wcdbmv) +Ajith Chandy Jose (ajithcj) +Alain Savard (Rocky640) +Alayan Feh (Alayan-stk-2) +Alexander Kure +Alexander Pagel (Lolligerhans) +Alfredo Menezes (lonfom169) +Ali AlZhrani (Cooffe) +Andreas Jan van der Meulen (Andyson007) +Andreas Matthies (Matthies) +Andrei Vetrov (proukornew) +Andrew Grant (AndyGrant) +Andrey Neporada (nepal) +Andy Duplain +Antoine Champion (antoinechampion) +Aram Tumanian (atumanian) +Arjun Temurnikar +Artem Solopiy (EntityFX) +Auguste Pop +Balazs Szilagyi +Balint Pfliegel +Ben Chaney (Chaneybenjamini) +Ben Koshy (BKSpurgeon) +Bill Henry (VoyagerOne) +Bojun Guo (noobpwnftw, Nooby) +borg323 +Boštjan Mejak (PedanticHacker) +braich +Brian Sheppard (SapphireBrand, briansheppard-toast) +Bruno de Melo Costa (BM123499) +Bruno Pellanda (pellanda) +Bryan Cross (crossbr) +candirufish +Carlos Esparza Sánchez (ces42) +Chess13234 +Chris Bao (sscg13) +Chris Cain (ceebo) +Ciekce +clefrks +Clemens L. (rn5f107s2) +Cody Ho (aesrentai) +Dale Weiler (graphitemaster) +Daniel Axtens (daxtens) +Daniel Dugovic (ddugovic) +Daniel Monroe (Ergodice) +Dan Schmidt (dfannius) +Dariusz Orzechowski (dorzechowski) +David (dav1312) +David Zar +Daylen Yang (daylen) +Deshawn Mohan-Smith (GoldenRare) +Dieter Dobbelaere (ddobbelaere) +DiscanX +Dominik Schlösser (domschl) +double-beep +Douglas Matos Gomes (dsmsgms) +Dubslow +Eduardo Cáceres (eduherminio) +Eelco de Groot (KingDefender) +Ehsan Rashid (erashid) +Elvin Liu (solarlight2) +erbsenzaehler +Ernesto Gatti +evqsx +Fabian Beuke (madnight) +Fabian Fichter (ianfab) +Fanael Linithien (Fanael) +fanon +Fauzi Akram Dabat (fauzi2) +Felix Wittmann +gamander +Gabriele Lombardo (gabe) +Gahtan Nahdi +Gary Heckman (gheckman) +George Sobala (gsobala) +gguliash +Giacomo Lorenzetti (G-Lorenz) +Gian-Carlo Pascutto (gcp) +Goh CJ (cj5716) +Gontran Lemaire (gonlem) +Goodkov Vasiliy Aleksandrovich (goodkov) +Gregor Cramer +GuardianRM +Guy Vreuls (gvreuls) +Günther Demetz (pb00067, pb00068) +Henri Wiechers +Hiraoka Takuya (HiraokaTakuya) +homoSapiensSapiens +Hongzhi Cheng +Ivan Ivec (IIvec) +Jacques B. (Timshel) +Jake Senne (w1wwwwww) +Jan Ondruš (hxim) +Jared Kish (Kurtbusch, kurt22i) +Jarrod Torriero (DU-jdto) +Jasper Shovelton (Beanie496) +Jean-Francois Romang (jromang) +Jean Gauthier (OuaisBla) +Jekaa +Jerry Donald Watson (jerrydonaldwatson) +jjoshua2 +Jonathan Buladas Dumale (SFisGOD) +Jonathan Calovski (Mysseno) +Jonathan McDermid (jonathanmcdermid) +Joost VandeVondele (vondele) +Joseph Ellis (jhellis3) +Joseph R. Prostko +Jörg Oster (joergoster) +Julian Willemer (NightlyKing) +jundery +Justin Blanchard (UncombedCoconut) +Kelly Wilson +Ken Takusagawa +Kenneth Lee (kennethlee33) +Kian E (KJE-98) +kinderchocolate +Kiran Panditrao (Krgp) +Kojirion +Krisztián Peőcz +Krystian Kuzniarek (kuzkry) +Leonardo Ljubičić (ICCF World Champion) +Leonid Pechenik (lp--) +Li Ying (yl25946) +Liam Keegan (lkeegan) +Linmiao Xu (linrock) +Linus Arver (listx) +loco-loco +Lub van den Berg (ElbertoOne) +Luca Brivio (lucabrivio) +Lucas Braesch (lucasart) +Lyudmil Antonov (lantonov) +Maciej Żenczykowski (zenczykowski) +Malcolm Campbell (xoto10) +Mark Tenzer (31m059) +marotear +Mathias Parnaudeau (mparnaudeau) +Matt Ginsberg (mattginsberg) +Matthew Lai (matthewlai) +Matthew Sullivan (Matt14916) +Max A. (Disservin) +Maxim Masiutin (maximmasiutin) +Maxim Molchanov (Maxim) +Michael An (man) +Michael Byrne (MichaelB7) +Michael Chaly (Vizvezdenec) +Michael Stembera (mstembera) +Michael Whiteley (protonspring) +Michel Van den Bergh (vdbergh) +Miguel Lahoz (miguel-l) +Mikael Bäckman (mbootsector) +Mike Babigian (Farseer) +Mira +Miroslav Fontán (Hexik) +Moez Jellouli (MJZ1977) +Mohammed Li (tthsqe12) +Muzhen J (XInTheDark) +Nathan Rugg (nmrugg) +Nguyen Pham (nguyenpham) +Nicklas Persson (NicklasPersson) +Nick Pelling (nickpelling) +Niklas Fiekas (niklasf) +Nikolay Kostov (NikolayIT) +Norman Schmidt (FireFather) +notruck +Nour Berakdar (Nonlinear) +Ofek Shochat (OfekShochat, ghostway) +Ondrej Mosnáček (WOnder93) +Ondřej Mišina (AndrovT) +Oskar Werkelin Ahlin +Ömer Faruk Tutkun (OmerFarukTutkun) +Pablo Vazquez +Panthee +Pascal Romaret +Pasquale Pigazzini (ppigazzini) +Patrick Jansen (mibere) +Peter Schneider (pschneider1968) +Peter Zsifkovits (CoffeeOne) +PikaCat +Praveen Kumar Tummala (praveentml) +Prokop Randáček (ProkopRandacek) +Rahul Dsilva (silversolver1) +Ralph Stößer (Ralph Stoesser) +Raminder Singh +renouve +Reuven Peleg (R-Peleg) +Richard Lloyd (Richard-Lloyd) +Robert Nürnberg (robertnurnberg) +Rodrigo Exterckötter Tjäder +Rodrigo Roim (roim) +Ronald de Man (syzygy1, syzygy) +Ron Britvich (Britvich) +rqs +Rui Coelho (ruicoelhopedro) +Ryan Schmitt +Ryan Takker +Sami Kiminki (skiminki) +Sebastian Buchwald (UniQP) +Sergei Antonov (saproj) +Sergei Ivanov (svivanov72) +Sergio Vieri (sergiovieri) +sf-x +Shahin M. Shahin (peregrine) +Shane Booth (shane31) +Shawn Varghese (xXH4CKST3RXx) +Shawn Xu (xu-shawn) +Siad Daboul (Topologist) +Stefan Geschwentner (locutus2) +Stefano Cardanobile (Stefano80) +Stefano Di Martino (StefanoD) +Steinar Gunderson (sesse) +Stéphane Nicolet (snicolet) +Stephen Touset (stouset) +Syine Mineta (MinetaS) +Taras Vuk (TarasVuk) +Thanar2 +thaspel +theo77186 +TierynnB +Ting-Hsuan Huang (fffelix-huang) +Tobias Steinmann +Tomasz Sobczyk (Sopel97) +Tom Truscott +Tom Vijlbrief (tomtor) +Torsten Franz (torfranz, tfranzer) +Torsten Hellwig (Torom) +Tracey Emery (basepr1me) +tttak +Unai Corzo (unaiic) +Uri Blass (uriblass) +Vince Negri (cuddlestmonkey) +Viren +Wencey Wang +windfishballad +xefoci7612 +Xiang Wang (KatyushaScarlet) +zz4032 + +# Additionally, we acknowledge the authors and maintainers of fishtest, +# an amazing and essential framework for Stockfish development! +# +# https://github.com/official-stockfish/fishtest/blob/master/AUTHORS diff --git a/stockfish/CITATION.cff b/stockfish/CITATION.cff new file mode 100644 index 0000000000000000000000000000000000000000..bc0889a8b69e11adbf1e86fa754c2a1c76c02f46 --- /dev/null +++ b/stockfish/CITATION.cff @@ -0,0 +1,23 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: Stockfish +message: >- + Please cite this software using the metadata from this + file. +type: software +authors: + - name: The Stockfish developers (see AUTHORS file) +repository-code: 'https://github.com/official-stockfish/Stockfish' +url: 'https://stockfishchess.org/' +repository-artifact: 'https://stockfishchess.org/download/' +abstract: Stockfish is a free and strong UCI chess engine. +keywords: + - chess + - artificial intelligence (AI) + - tree search + - alpha-beta search + - neural networks (NN) + - efficiently updatable neural networks (NNUE) +license: GPL-3.0 diff --git a/stockfish/CONTRIBUTING.md b/stockfish/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..0b6fbce0d0741717dda87ea4bf7f072afa707795 --- /dev/null +++ b/stockfish/CONTRIBUTING.md @@ -0,0 +1,96 @@ +# Contributing to Stockfish + +Welcome to the Stockfish project! We are excited that you are interested in +contributing. This document outlines the guidelines and steps to follow when +making contributions to Stockfish. + +## Table of Contents + +- [Building Stockfish](#building-stockfish) +- [Making Contributions](#making-contributions) + - [Reporting Issues](#reporting-issues) + - [Submitting Pull Requests](#submitting-pull-requests) +- [Code Style](#code-style) +- [Community and Communication](#community-and-communication) +- [License](#license) + +## Building Stockfish + +In case you do not have a C++ compiler installed, you can follow the +instructions from our wiki. + +- [Ubuntu][ubuntu-compiling-link] +- [Windows][windows-compiling-link] +- [macOS][macos-compiling-link] + +## Making Contributions + +### Reporting Issues + +If you find a bug, please open an issue on the +[issue tracker][issue-tracker-link]. Be sure to include relevant information +like your operating system, build environment, and a detailed description of the +problem. + +_Please note that Stockfish's development is not focused on adding new features. +Thus any issue regarding missing features will potentially be closed without +further discussion._ + +### Submitting Pull Requests + +- Functional changes need to be tested on fishtest. See + [Creating my First Test][creating-my-first-test] for more details. + The accompanying pull request should include a link to the test results and + the new bench. + +- Non-functional changes (e.g. refactoring, code style, documentation) do not + need to be tested on fishtest, unless they might impact performance. + +- Provide a clear and concise description of the changes in the pull request + description. + +_First time contributors should add their name to [AUTHORS](./AUTHORS)._ + +_Stockfish's development is not focused on adding new features. Thus any pull +request introducing new features will potentially be closed without further +discussion._ + +## Code Style + +Changes to Stockfish C++ code should respect our coding style defined by +[.clang-format](.clang-format). You can format your changes by running +`make format`. This requires clang-format version 18 to be installed on your system. + +## Navigate + +For experienced Git users who frequently use git blame, it is recommended to +configure the blame.ignoreRevsFile setting. +This setting is useful for excluding noisy formatting commits. + +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` + +## Community and Communication + +- Join the [Stockfish discord][discord-link] to discuss ideas, issues, and + development. +- Participate in the [Stockfish GitHub discussions][discussions-link] for + broader conversations. + +## License + +By contributing to Stockfish, you agree that your contributions will be licensed +under the GNU General Public License v3.0. See [Copying.txt][copying-link] for +more details. + +Thank you for contributing to Stockfish and helping us make it even better! + +[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt +[discord-link]: https://discord.gg/GWDRS3kU6R +[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new +[creating-my-first-test]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test#create-your-test +[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues +[ubuntu-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-1 +[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler +[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-2 diff --git a/stockfish/Copying.txt b/stockfish/Copying.txt new file mode 100644 index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7 --- /dev/null +++ b/stockfish/Copying.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/stockfish/README.md b/stockfish/README.md new file mode 100644 index 0000000000000000000000000000000000000000..621f1d13076fe3af45f52ce13002290f7a7973e0 --- /dev/null +++ b/stockfish/README.md @@ -0,0 +1,161 @@ +
+ + [![Stockfish][stockfish128-logo]][website-link] + +

Stockfish

+ + A free and strong UCI chess engine. +
+ [Explore Stockfish docs »][wiki-link] +
+
+ [Report bug][issue-link] + · + [Open a discussion][discussions-link] + · + [Discord][discord-link] + · + [Blog][website-blog-link] + + [![Build][build-badge]][build-link] + [![License][license-badge]][license-link] +
+ [![Release][release-badge]][release-link] + [![Commits][commits-badge]][commits-link] +
+ [![Website][website-badge]][website-link] + [![Fishtest][fishtest-badge]][fishtest-link] + [![Discord][discord-badge]][discord-link] + +
+ +## Overview + +[Stockfish][website-link] is a **free and strong UCI chess engine** derived from +Glaurung 2.1 that analyzes chess positions and computes the optimal moves. + +Stockfish **does not include a graphical user interface** (GUI) that is required +to display a chessboard and to make it easy to input moves. These GUIs are +developed independently from Stockfish and are available online. **Read the +documentation for your GUI** of choice for information about how to use +Stockfish with it. + +See also the Stockfish [documentation][wiki-usage-link] for further usage help. + +## Files + +This distribution of Stockfish consists of the following files: + + * [README.md][readme-link], the file you are currently reading. + + * [Copying.txt][license-link], a text file containing the GNU General Public + License version 3. + + * [AUTHORS][authors-link], a text file with the list of authors for the project. + + * [src][src-link], a subdirectory containing the full source code, including a + Makefile that can be used to compile Stockfish on Unix-like systems. + + * a file with the .nnue extension, storing the neural network for the NNUE + evaluation. Binary distributions will have this file embedded. + +## Contributing + +__See [Contributing Guide](CONTRIBUTING.md).__ + +### Donating hardware + +Improving Stockfish requires a massive amount of testing. You can donate your +hardware resources by installing the [Fishtest Worker][worker-link] and viewing +the current tests on [Fishtest][fishtest-link]. + +### Improving the code + +In the [chessprogramming wiki][programming-link], many techniques used in +Stockfish are explained with a lot of background information. +The [section on Stockfish][programmingsf-link] describes many features +and techniques used by Stockfish. However, it is generic rather than +focused on Stockfish's precise implementation. + +The engine testing is done on [Fishtest][fishtest-link]. +If you want to help improve Stockfish, please read this [guideline][guideline-link] +first, where the basics of Stockfish development are explained. + +Discussions about Stockfish take place these days mainly in the Stockfish +[Discord server][discord-link]. This is also the best place to ask questions +about the codebase and how to improve it. + +## Compiling Stockfish + +Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions, +big-endian machines such as Power PC, and other platforms. + +On Unix-like systems, it should be easy to compile Stockfish directly from the +source code with the included Makefile in the folder `src`. In general, it is +recommended to run `make help` to see a list of make targets with corresponding +descriptions. An example suitable for most Intel and AMD chips: + +``` +cd src +make -j profile-build +``` + +Detailed compilation instructions for all platforms can be found in our +[documentation][wiki-compile-link]. Our wiki also has information about +the [UCI commands][wiki-uci-link] supported by Stockfish. + +## Terms of use + +Stockfish is free and distributed under the +[**GNU General Public License version 3**][license-link] (GPL v3). Essentially, +this means you are free to do almost exactly what you want with the program, +including distributing it among your friends, making it available for download +from your website, selling it (either by itself or as part of some bigger +software package), or using it as the starting point for a software project of +your own. + +The only real limitation is that whenever you distribute Stockfish in some way, +you MUST always include the license and the full source code (or a pointer to +where the source code can be found) to generate the exact binary you are +distributing. If you make any changes to the source code, these changes must +also be made available under GPL v3. + +## Acknowledgements + +Stockfish uses neural networks trained on [data provided by the Leela Chess Zero +project][lc0-data-link], which is made available under the [Open Database License][odbl-link] (ODbL). + + +[authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS +[build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml +[commits-link]: https://github.com/official-stockfish/Stockfish/commits/master +[discord-link]: https://discord.gg/GWDRS3kU6R +[issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml +[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new +[fishtest-link]: https://tests.stockfishchess.org/tests +[guideline-link]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test +[license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt +[programming-link]: https://www.chessprogramming.org/Main_Page +[programmingsf-link]: https://www.chessprogramming.org/Stockfish +[readme-link]: https://github.com/official-stockfish/Stockfish/blob/master/README.md +[release-link]: https://github.com/official-stockfish/Stockfish/releases/latest +[src-link]: https://github.com/official-stockfish/Stockfish/tree/master/src +[stockfish128-logo]: https://stockfishchess.org/images/logo/icon_128x128.png +[uci-link]: https://backscattering.de/chess/uci/ +[website-link]: https://stockfishchess.org +[website-blog-link]: https://stockfishchess.org/blog/ +[wiki-link]: https://github.com/official-stockfish/Stockfish/wiki +[wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source +[wiki-uci-link]: https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands +[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage +[worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker +[lc0-data-link]: https://storage.lczero.org/files/training_data +[odbl-link]: https://opendatacommons.org/licenses/odbl/odbl-10.txt + +[build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github +[commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge +[discord-badge]: https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord +[fishtest-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests%2Ffinished +[license-badge]: https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success +[release-badge]: https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release +[website-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org diff --git a/stockfish/Top CPU Contributors.txt b/stockfish/Top CPU Contributors.txt new file mode 100644 index 0000000000000000000000000000000000000000..4e598ecfc5b3480bdaddc813f30bb66edc325e34 --- /dev/null +++ b/stockfish/Top CPU Contributors.txt @@ -0,0 +1,322 @@ +Contributors to Fishtest with >10,000 CPU hours, as of 2025-03-22. +Thank you! + +Username CPU Hours Games played +------------------------------------------------------------------ +noobpwnftw 41712226 3294628533 +vdv 28993864 954145232 +technologov 24984442 1115931964 +linrock 11463033 741692823 +mlang 3026000 200065824 +okrout 2726068 248285678 +olafm 2420096 161297116 +pemo 1838361 62294199 +TueRens 1804847 80170868 +dew 1689162 100033738 +sebastronomy 1655637 67294942 +grandphish2 1474752 92156319 +JojoM 1130625 73666098 +rpngn 973590 59996557 +oz 921203 60370346 +tvijlbrief 796125 51897690 +gvreuls 792215 55184194 +mibere 703840 46867607 +leszek 599745 44681421 +cw 519602 34988289 +fastgm 503862 30260818 +CSU_Dynasty 474794 31654170 +maximmasiutin 441753 28129452 +robal 437950 28869118 +ctoks 435150 28542141 +crunchy 427414 27371625 +bcross 415724 29061187 +mgrabiak 380202 27586936 +velislav 342588 22140902 +ncfish1 329039 20624527 +Fisherman 327231 21829379 +Sylvain27 317021 11494912 +marrco 310446 19587107 +Dantist 296386 18031762 +Fifis 289595 14969251 +tolkki963 286043 23596996 +Calis007 272677 17281620 +cody 258835 13301710 +nordlandia 249322 16420192 +javran 212141 16507618 +glinscott 208125 13277240 +drabel 204167 13930674 +mhoram 202894 12601997 +bking_US 198894 11876016 +Wencey 198537 9606420 +Thanar 179852 12365359 +sschnee 170521 10891112 +armo9494 168141 11177514 +DesolatedDodo 160605 10392474 +spams 157128 10319326 +maposora 155839 13963260 +sqrt2 147963 9724586 +vdbergh 140514 9242985 +jcAEie 140086 10603658 +CoffeeOne 137100 5024116 +malala 136182 8002293 +Goatminola 134893 11640524 +xoto 133759 9159372 +markkulix 132104 11000548 +naclosagc 131472 4660806 +Dubslow 129685 8527664 +davar 129023 8376525 +DMBK 122960 8980062 +dsmith 122059 7570238 +Wolfgang 120919 8619168 +CypressChess 120902 8683904 +amicic 119661 7938029 +cuistot 116864 7828864 +sterni1971 113754 6054022 +Data 113305 8220352 +BrunoBanani 112960 7436849 +megaman7de 109139 7360928 +skiminki 107583 7218170 +zeryl 104523 6618969 +MaZePallas 102823 6633619 +sunu 100167 7040199 +thirdlife 99178 2246544 +ElbertoOne 99028 7023771 +TataneSan 97257 4239502 +romangol 95662 7784954 +bigpen0r 94825 6529241 +brabos 92118 6186135 +Maxim 90818 3283364 +psk 89957 5984901 +szupaw 89775 7800606 +jromang 87260 5988073 +racerschmacer 85805 6122790 +Vizvezdenec 83761 5344740 +0x3C33 82614 5271253 +Spprtr 82103 5663635 +BRAVONE 81239 5054681 +MarcusTullius 78930 5189659 +Mineta 78731 4947996 +Torom 77978 2651656 +nssy 76497 5259388 +woutboat 76379 6031688 +teddybaer 75125 5407666 +Pking_cda 73776 5293873 +Viren6 73664 1356502 +yurikvelo 73611 5046822 +Bobo1239 70579 4794999 +solarlight 70517 5028306 +dv8silencer 70287 3883992 +manap 66273 4121774 +tinker 64333 4268790 +qurashee 61208 3429862 +DanielMiao1 60181 1317252 +AGI 58316 4336328 +jojo2357 57435 4944212 +robnjr 57262 4053117 +Freja 56938 3733019 +MaxKlaxxMiner 56879 3423958 +ttruscott 56010 3680085 +rkl 55132 4164467 +jmdana 54988 4041917 +notchris 53936 4184018 +renouve 53811 3501516 +CounterFlow 52536 3203740 +finfish 51360 3370515 +eva42 51272 3599691 +eastorwest 51117 3454811 +rap 49985 3219146 +pb00067 49733 3298934 +GPUex 48686 3684998 +OuaisBla 48626 3445134 +ronaldjerum 47654 3240695 +biffhero 46564 3111352 +oryx 46141 3583236 +jibarbosa 45890 4541218 +DeepnessFulled 45734 3944282 +abdicj 45577 2631772 +VoyagerOne 45476 3452465 +mecevdimitar 44240 2584396 +speedycpu 43842 3003273 +jbwiebe 43305 2805433 +gopeto 43046 2821514 +YvesKn 42628 2177630 +Antihistamine 41788 2761312 +mhunt 41735 2691355 +somethingintheshadows 41502 3330418 +homyur 39893 2850481 +gri 39871 2515779 +vidar808 39774 1656372 +Garf 37741 2999686 +SC 37299 2731694 +Gaster319 37229 3289674 +csnodgrass 36207 2688994 +ZacHFX 35528 2486328 +icewulf 34782 2415146 +strelock 34716 2074055 +EthanOConnor 33370 2090311 +slakovv 32915 2021889 +shawnxu 32144 2814668 +Gelma 31771 1551204 +srowen 31181 1732120 +kdave 31157 2198362 +manapbk 30987 1810399 +votoanthuan 30691 2460856 +Prcuvu 30377 2170122 +anst 30301 2190091 +jkiiski 30136 1904470 +spcc 29925 1901692 +hyperbolic.tom 29840 2017394 +chuckstablers 29659 2093438 +Pyafue 29650 1902349 +WoodMan777 29300 2579864 +belzedar94 28846 1811530 +chriswk 26902 1868317 +xwziegtm 26897 2124586 +Jopo12321 26818 1816482 +achambord 26582 1767323 +Patrick_G 26276 1801617 +yorkman 26193 1992080 +Ulysses 25517 1711634 +SFTUser 25182 1675689 +nabildanial 25068 1531665 +Sharaf_DG 24765 1786697 +rodneyc 24376 1416402 +jsys14 24297 1721230 +AndreasKrug 24235 1934711 +agg177 23890 1395014 +Ente 23752 1678188 +JanErik 23408 1703875 +Isidor 23388 1680691 +Norabor 23371 1603244 +Nullvalue 23155 2022752 +fishtester 23115 1581502 +wizardassassin 23073 1789536 +Skiff84 22984 1053680 +cisco2015 22920 1763301 +ols 22914 1322047 +Hjax 22561 1566151 +Zirie 22542 1472937 +team-oh 22272 1636708 +mkstockfishtester 22253 2029566 +Roady 22220 1465606 +MazeOfGalious 21978 1629593 +sg4032 21950 1643373 +tsim67 21939 1343944 +ianh2105 21725 1632562 +Serpensin 21704 1809188 +xor12 21628 1680365 +dex 21612 1467203 +nesoneg 21494 1463031 +IslandLambda 21468 1239756 +user213718 21454 1404128 +sphinx 21211 1384728 +qoo_charly_cai 21136 1514927 +jjoshua2 21001 1423089 +Zake9298 20938 1565848 +horst.prack 20878 1465656 +0xB00B1ES 20590 1208666 +Dinde 20459 1292774 +t3hf1sht3ster 20456 670646 +j3corre 20405 941444 +0x539 20332 1039516 +Adrian.Schmidt123 20316 1281436 +malfoy 20313 1350694 +purpletree 20019 1461026 +wei 19973 1745989 +teenychess 19819 1762006 +rstoesser 19569 1293588 +eudhan 19274 1283717 +nalanzeyu 19211 396674 +vulcan 18871 1729392 +Karpovbot 18766 1053178 +jundery 18445 1115855 +Farseer 18281 1074642 +sebv15 18267 1262588 +whelanh 17887 347974 +ville 17883 1384026 +chris 17698 1487385 +purplefishies 17595 1092533 +dju 17414 981289 +iisiraider 17275 1049015 +Karby 17177 1030688 +DragonLord 17014 1162790 +pirt 16991 1274215 +redstone59 16842 1461780 +Alb11747 16787 1213990 +Naven94 16414 951718 +scuzzi 16155 995347 +IgorLeMasson 16064 1147232 +ako027ako 15671 1173203 +xuhdev 15516 1528278 +infinigon 15285 965966 +Nikolay.IT 15154 1068349 +Andrew Grant 15114 895539 +OssumOpossum 14857 1007129 +LunaticBFF57 14525 1190310 +enedene 14476 905279 +YELNAMRON 14475 1141330 +RickGroszkiewicz 14272 1385984 +joendter 14269 982014 +bpfliegel 14233 882523 +mpx86 14019 759568 +jpulman 13982 870599 +getraideBFF 13871 1172846 +crocogoat 13817 1119086 +Nesa92 13806 1116101 +joster 13710 946160 +mbeier 13650 1044928 +Pablohn26 13552 1088532 +wxt9861 13550 1312306 +Dark_wizzie 13422 1007152 +Rudolphous 13244 883140 +Jackfish 13177 894206 +MooTheCow 13091 892304 +Machariel 13010 863104 +mabichito 12903 749391 +thijsk 12886 722107 +AdrianSA 12860 804972 +Flopzee 12698 894821 +szczur90 12684 977536 +Kyrega 12661 456438 +mschmidt 12644 863193 +korposzczur 12606 838168 +fatmurphy 12547 853210 +Oakwen 12532 855759 +SapphireBrand 12416 969604 +deflectooor 12386 579392 +modolief 12386 896470 +ckaz 12273 754644 +Hongildong 12201 648712 +pgontarz 12151 848794 +dbernier 12103 860824 +FormazChar 12051 913497 +shreven 12044 884734 +rensonthemove 11999 971993 +stocky 11954 699440 +3cho 11842 1036786 +ImperiumAeternum 11482 979142 +infinity 11470 727027 +aga 11412 695127 +Def9Infinity 11408 700682 +torbjo 11395 729145 +Thomas A. Anderson 11372 732094 +savage84 11358 670860 +d64 11263 789184 +ali-al-zhrani 11245 779246 +vaskoul 11144 953906 +snicolet 11106 869170 +dapper 11032 771402 +Ethnikoi 10993 945906 +Snuuka 10938 435504 +Karmatron 10871 678306 +gerbil 10871 1005842 +OliverClarke 10696 942654 +basepi 10637 744851 +michaelrpg 10624 748179 +Cubox 10621 826448 +dragon123118 10421 936506 +OIVAS7572 10420 995586 +GBx3TV 10388 339952 +Garruk 10365 706465 +dzjp 10343 732529 +borinot 10026 902130 diff --git a/stockfish/scripts/get_native_properties.sh b/stockfish/scripts/get_native_properties.sh new file mode 100644 index 0000000000000000000000000000000000000000..132bd6f484fa724a0f64a8e0c4256ce7157b1932 --- /dev/null +++ b/stockfish/scripts/get_native_properties.sh @@ -0,0 +1,153 @@ +#!/bin/sh + +# +# Returns properties of the native system. +# best architecture as supported by the CPU +# filename of the best binary uploaded as an artifact during CI +# + +# Check if all the given flags are present in the CPU flags list +check_flags() { + for flag; do + printf '%s\n' "$flags" | grep -q -w "$flag" || return 1 + done +} + +# Set the CPU flags list +# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 +get_flags() { + flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo) +} + +# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid +check_znver_1_2() { + vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo) + cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo) + [ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true +} + +# Set the file CPU loongarch64 architecture +set_arch_loongarch64() { + if check_flags 'lasx'; then + true_arch='loongarch64-lasx' + elif check_flags 'lsx'; then + true_arch='lonngarch64-lsx' + else + true_arch='loongarch64' + fi +} + +# Set the file CPU x86_64 architecture +set_arch_x86_64() { + if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then + true_arch='x86-64-vnni256' + elif check_flags 'avx512f' 'avx512bw'; then + true_arch='x86-64-avx512' + elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then + true_arch='x86-64-bmi2' + elif check_flags 'avx2'; then + true_arch='x86-64-avx2' + elif check_flags 'sse41' && check_flags 'popcnt'; then + true_arch='x86-64-sse41-popcnt' + else + true_arch='x86-64' + fi +} + +set_arch_ppc_64() { + if $(grep -q -w "altivec" /proc/cpuinfo); then + power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo) + if [ "0$power" -gt 7 ]; then + # VSX started with POWER8 + true_arch='ppc-64-vsx' + else + true_arch='ppc-64-altivec' + fi + else + true_arch='ppc-64' + fi +} + +# Check the system type +uname_s=$(uname -s) +uname_m=$(uname -m) +case $uname_s in + 'Darwin') # Mac OSX system + case $uname_m in + 'arm64') + true_arch='apple-silicon' + file_arch='m1-apple-silicon' + ;; + 'x86_64') + flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') + set_arch_x86_64 + if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then + file_arch='x86-64-bmi2' + fi + ;; + esac + file_os='macos' + file_ext='tar' + ;; + 'Linux') # Linux system + get_flags + case $uname_m in + 'x86_64') + file_os='ubuntu' + check_znver_1_2 + set_arch_x86_64 + ;; + 'i686') + file_os='ubuntu' + true_arch='x86-32' + ;; + 'ppc64'*) + file_os='ubuntu' + set_arch_ppc_64 + ;; + 'aarch64') + file_os='android' + true_arch='armv8' + if check_flags 'asimddp'; then + true_arch="$true_arch-dotprod" + fi + ;; + 'armv7'*) + file_os='android' + true_arch='armv7' + if check_flags 'neon'; then + true_arch="$true_arch-neon" + fi + ;; + 'loongarch64'*) + file_os='linux' + set_arch_loongarch64 + ;; + *) # Unsupported machine type, exit with error + printf 'Unsupported machine type: %s\n' "$uname_m" + exit 1 + ;; + esac + file_ext='tar' + ;; + 'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows system with POSIX compatibility layer + get_flags + check_znver_1_2 + set_arch_x86_64 + file_os='windows' + file_ext='zip' + ;; + *) + # Unknown system type, exit with error + printf 'Unsupported system type: %s\n' "$uname_s" + exit 1 + ;; +esac + +if [ -z "$file_arch" ]; then + file_arch=$true_arch +fi + +file_name="stockfish-$file_os-$file_arch.$file_ext" + +printf '%s %s\n' "$true_arch" "$file_name" diff --git a/stockfish/scripts/net.sh b/stockfish/scripts/net.sh new file mode 100644 index 0000000000000000000000000000000000000000..1aa1fbfb188ea0851491444da1c76874386aa243 --- /dev/null +++ b/stockfish/scripts/net.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -qO-") || \ + (command -v curl > /dev/null 2>&1 && echo "curl -skL")) + + +sha256sum=$( (command -v shasum > /dev/null 2>&1 && echo "shasum -a 256") || \ + (command -v sha256sum > /dev/null 2>&1 && echo "sha256sum")) + +if [ -z "$sha256sum" ]; then + >&2 echo "sha256sum not found, NNUE files will be assumed valid." +fi + +get_nnue_filename() { + grep "$1" evaluate.h | grep "#define" | sed "s/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/" +} + +validate_network() { + # If no sha256sum command is available, assume the file is always valid. + if [ -n "$sha256sum" ] && [ -f "$1" ]; then + if [ "$1" != "nn-$($sha256sum "$1" | cut -c 1-12).nnue" ]; then + rm -f "$1" + return 1 + fi + fi +} + +fetch_network() { + _filename="$(get_nnue_filename "$1")" + + if [ -z "$_filename" ]; then + >&2 echo "NNUE file name not found for: $1" + return 1 + fi + + if [ -f "$_filename" ]; then + if validate_network "$_filename"; then + echo "Existing $_filename validated, skipping download" + return + else + echo "Removing invalid NNUE file: $_filename" + fi + fi + + if [ -z "$wget_or_curl" ]; then + >&2 printf "%s\n" "Neither wget or curl is installed." \ + "Install one of these tools to download NNUE files automatically." + exit 1 + fi + + for url in \ + "https://tests.stockfishchess.org/api/nn/$_filename" \ + "https://github.com/official-stockfish/networks/raw/master/$_filename"; do + echo "Downloading from $url ..." + if $wget_or_curl "$url" > "$_filename"; then + if validate_network "$_filename"; then + echo "Successfully validated $_filename" + else + echo "Downloaded $_filename is invalid" + continue + fi + else + echo "Failed to download from $url" + fi + if [ -f "$_filename" ]; then + return + fi + done + + # Download was not successful in the loop, return false. + >&2 echo "Failed to download $_filename" + return 1 +} + +fetch_network EvalFileDefaultNameBig && \ +fetch_network EvalFileDefaultNameSmall diff --git a/stockfish/src/Makefile b/stockfish/src/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..76b94785ecb59a3e58a57325091e1d6cff66723d --- /dev/null +++ b/stockfish/src/Makefile @@ -0,0 +1,1123 @@ +# Stockfish, a UCI chess playing engine derived from Glaurung 2.1 +# Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) +# +# Stockfish is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Stockfish is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +### ========================================================================== +### Section 1. General Configuration +### ========================================================================== + +### Establish the operating system name +KERNEL := $(shell uname -s) +ifeq ($(KERNEL),Linux) + OS := $(shell uname -o) +endif + +### Target Windows OS +ifeq ($(OS),Windows_NT) + ifneq ($(COMP),ndk) + target_windows = yes + endif +else ifeq ($(COMP),mingw) + target_windows = yes + ifeq ($(WINE_PATH),) + WINE_PATH := $(shell which wine) + endif +endif + +### Executable name +ifeq ($(target_windows),yes) + EXE = stockfish.exe +else + EXE = stockfish +endif + +### Installation dir definitions +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin + +### Built-in benchmark for pgo-builds +PGOBENCH = $(WINE_PATH) ./$(EXE) bench + +### Source and object files +SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ + misc.cpp movegen.cpp movepick.cpp position.cpp \ + search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ + nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \ + engine.cpp score.cpp memory.cpp + +HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ + nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ + nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ + nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ + search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h + +OBJS = $(notdir $(SRCS:.cpp=.o)) + +VPATH = syzygy:nnue:nnue/features + +### ========================================================================== +### Section 2. High-level Configuration +### ========================================================================== +# +# flag --- Comp switch --- Description +# ---------------------------------------------------------------------------- +# +# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode +# sanitize = none/ ... (-fsanitize ) +# --- ( undefined ) --- enable undefined behavior checks +# --- ( thread ) --- enable threading error checks +# --- ( address ) --- enable memory access checks +# --- ...etc... --- see compiler documentation for supported sanitizers +# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations +# arch = (name) --- (-arch) --- Target architecture +# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system +# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction +# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction +# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction +# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions +# mmx = yes/no --- -mmmx --- Use Intel MMX instructions +# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2 +# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3 +# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1 +# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 +# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX +# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 +# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands +# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 +# altivec = yes/no --- -maltivec --- Use PowerPC Altivec SIMD extension +# vsx = yes/no --- -mvsx --- Use POWER VSX SIMD extension +# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture +# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions +# lsx = yes/no --- -mlsx --- Use Loongson SIMD eXtension +# lasx = yes/no --- -mlasx --- use Loongson Advanced SIMD eXtension +# +# Note that Makefile is space sensitive, so when adding new architectures +# or modifying existing flags, you have to make sure there are no extra spaces +# at the end of the line for flag values. +# +# Example of use for these flags: +# make build ARCH=x86-64-avx512 debug=yes sanitize="address undefined" + + +### 2.1. General and architecture defaults + +ifeq ($(ARCH),) + ARCH = native +endif + +ifeq ($(ARCH), native) + override ARCH := $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1) +endif + +# explicitly check for the list of supported architectures (as listed with make help), +# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true` +ifeq ($(ARCH), $(filter $(ARCH), \ + x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ + x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ + x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \ + armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \ + loongarch64 loongarch64-lsx loongarch64-lasx)) + SUPPORTED_ARCH=true +else + SUPPORTED_ARCH=false +endif + +optimize = yes +debug = no +sanitize = none +bits = 64 +prefetch = no +popcnt = no +pext = no +sse = no +mmx = no +sse2 = no +ssse3 = no +sse41 = no +avx2 = no +avxvnni = no +avx512 = no +vnni256 = no +vnni512 = no +altivec = no +vsx = no +neon = no +dotprod = no +arm_version = 0 +lsx = no +lasx = no +STRIP = strip + +ifneq ($(shell which clang-format-18 2> /dev/null),) + CLANG-FORMAT = clang-format-18 +else + CLANG-FORMAT = clang-format +endif + +### 2.2 Architecture specific + +ifeq ($(findstring x86,$(ARCH)),x86) + +# x86-32/64 + +ifeq ($(findstring x86-32,$(ARCH)),x86-32) + arch = i386 + bits = 32 + sse = no + mmx = yes +else + arch = x86_64 + sse = yes + sse2 = yes +endif + +ifeq ($(findstring -sse,$(ARCH)),-sse) + sse = yes +endif + +ifeq ($(findstring -popcnt,$(ARCH)),-popcnt) + popcnt = yes +endif + +ifeq ($(findstring -mmx,$(ARCH)),-mmx) + mmx = yes +endif + +ifeq ($(findstring -sse2,$(ARCH)),-sse2) + sse = yes + sse2 = yes +endif + +ifeq ($(findstring -ssse3,$(ARCH)),-ssse3) + sse = yes + sse2 = yes + ssse3 = yes +endif + +ifeq ($(findstring -sse41,$(ARCH)),-sse41) + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes +endif + +ifeq ($(findstring -modern,$(ARCH)),-modern) + $(warning *** ARCH=$(ARCH) is deprecated, defaulting to ARCH=x86-64-sse41-popcnt. Execute `make help` for a list of available architectures. ***) + $(shell sleep 5) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes +endif + +ifeq ($(findstring -avx2,$(ARCH)),-avx2) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes +endif + +ifeq ($(findstring -avxvnni,$(ARCH)),-avxvnni) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + avxvnni = yes + pext = yes +endif + +ifeq ($(findstring -bmi2,$(ARCH)),-bmi2) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes +endif + +ifeq ($(findstring -avx512,$(ARCH)),-avx512) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes +endif + +ifeq ($(findstring -vnni256,$(ARCH)),-vnni256) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + vnni256 = yes +endif + +ifeq ($(findstring -vnni512,$(ARCH)),-vnni512) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes + vnni512 = yes +endif + +ifeq ($(sse),yes) + prefetch = yes +endif + +# 64-bit pext is not available on x86-32 +ifeq ($(bits),32) + pext = no +endif + +else + +# all other architectures + +ifeq ($(ARCH),general-32) + arch = any + bits = 32 +endif + +ifeq ($(ARCH),general-64) + arch = any +endif + +ifeq ($(ARCH),armv7) + arch = armv7 + prefetch = yes + bits = 32 + arm_version = 7 +endif + +ifeq ($(ARCH),armv7-neon) + arch = armv7 + prefetch = yes + popcnt = yes + neon = yes + bits = 32 + arm_version = 7 +endif + +ifeq ($(ARCH),armv8) + arch = armv8 + prefetch = yes + popcnt = yes + neon = yes + arm_version = 8 +endif + +ifeq ($(ARCH),armv8-dotprod) + arch = armv8 + prefetch = yes + popcnt = yes + neon = yes + dotprod = yes + arm_version = 8 +endif + +ifeq ($(ARCH),apple-silicon) + arch = arm64 + prefetch = yes + popcnt = yes + neon = yes + dotprod = yes + arm_version = 8 +endif + +ifeq ($(ARCH),ppc-32) + arch = ppc + bits = 32 +endif + +ifeq ($(ARCH),ppc-64) + arch = ppc64 + popcnt = yes + prefetch = yes +endif + +ifeq ($(ARCH),ppc-64-altivec) + arch = ppc64 + popcnt = yes + prefetch = yes + altivec = yes +endif + +ifeq ($(ARCH),ppc-64-vsx) + arch = ppc64 + popcnt = yes + prefetch = yes + vsx = yes +endif + +ifeq ($(findstring e2k,$(ARCH)),e2k) + arch = e2k + mmx = yes + bits = 64 + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + popcnt = yes +endif + +ifeq ($(ARCH),riscv64) + arch = riscv64 +endif + +ifeq ($(findstring loongarch64,$(ARCH)),loongarch64) + arch = loongarch64 + prefetch = yes + +ifeq ($(findstring -lasx,$(ARCH)),-lasx) + lsx = yes + lasx = yes +endif + +ifeq ($(findstring -lsx,$(ARCH)),-lsx) + lsx = yes +endif + +endif +endif + + +### ========================================================================== +### Section 3. Low-level Configuration +### ========================================================================== + +### 3.1 Selecting compiler (default = gcc) +ifeq ($(MAKELEVEL),0) + export ENV_CXXFLAGS := $(CXXFLAGS) + export ENV_DEPENDFLAGS := $(DEPENDFLAGS) + export ENV_LDFLAGS := $(LDFLAGS) +endif + +CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) +DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17 +LDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS) + +ifeq ($(COMP),) + COMP=gcc +endif + +ifeq ($(COMP),gcc) + comp=gcc + CXX=g++ + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations + + ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + ifeq ($(ARCH),riscv64) + CXXFLAGS += -latomic + endif + else ifeq ($(arch),loongarch64) + CXXFLAGS += -latomic + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + + ifeq ($(arch),$(filter $(arch),armv7)) + LDFLAGS += -latomic + endif + + ifneq ($(KERNEL),Darwin) + LDFLAGS += -Wl,--no-as-needed + endif +endif + +ifeq ($(target_windows),yes) + LDFLAGS += -static +endif + +ifeq ($(COMP),mingw) + comp=mingw + + ifeq ($(bits),64) + ifeq ($(shell which x86_64-w64-mingw32-c++-posix 2> /dev/null),) + CXX=x86_64-w64-mingw32-c++ + else + CXX=x86_64-w64-mingw32-c++-posix + endif + else + ifeq ($(shell which i686-w64-mingw32-c++-posix 2> /dev/null),) + CXX=i686-w64-mingw32-c++ + else + CXX=i686-w64-mingw32-c++-posix + endif + endif + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations +endif + +ifeq ($(COMP),icx) + comp=icx + CXX=icpx + CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \ + -Wconditional-uninitialized -Wabi -Wdeprecated +endif + +ifeq ($(COMP),clang) + comp=clang + CXX=clang++ + ifeq ($(target_windows),yes) + CXX=x86_64-w64-mingw32-clang++ + endif + + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \ + -Wconditional-uninitialized + + ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),) + ifeq ($(target_windows),) + ifneq ($(RTLIB),compiler-rt) + LDFLAGS += -latomic + endif + endif + endif + + ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + ifeq ($(ARCH),riscv64) + CXXFLAGS += -latomic + endif + else ifeq ($(arch),loongarch64) + CXXFLAGS += -latomic + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif +endif + +ifeq ($(KERNEL),Darwin) + CXXFLAGS += -mmacosx-version-min=10.15 + LDFLAGS += -mmacosx-version-min=10.15 + ifneq ($(arch),any) + CXXFLAGS += -arch $(arch) + LDFLAGS += -arch $(arch) + endif + XCRUN = xcrun +endif + +# To cross-compile for Android, NDK version r21 or later is recommended. +# In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. +# Currently we don't know how to make PGO builds with the NDK yet. +ifeq ($(COMP),ndk) + CXXFLAGS += -stdlib=libc++ -fPIE + comp=clang + ifeq ($(arch),armv7) + CXX=armv7a-linux-androideabi16-clang++ + CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon + ifneq ($(shell which arm-linux-androideabi-strip 2>/dev/null),) + STRIP=arm-linux-androideabi-strip + else + STRIP=llvm-strip + endif + endif + ifeq ($(arch),armv8) + CXX=aarch64-linux-android21-clang++ + ifneq ($(shell which aarch64-linux-android-strip 2>/dev/null),) + STRIP=aarch64-linux-android-strip + else + STRIP=llvm-strip + endif + endif + ifeq ($(arch),x86_64) + CXX=x86_64-linux-android21-clang++ + ifneq ($(shell which x86_64-linux-android-strip 2>/dev/null),) + STRIP=x86_64-linux-android-strip + else + STRIP=llvm-strip + endif + endif + LDFLAGS += -static-libstdc++ -pie -lm -latomic +endif + +ifeq ($(comp),icx) + profile_make = icx-profile-make + profile_use = icx-profile-use +else ifeq ($(comp),clang) + profile_make = clang-profile-make + profile_use = clang-profile-use +else + profile_make = gcc-profile-make + profile_use = gcc-profile-use + ifeq ($(KERNEL),Darwin) + EXTRAPROFILEFLAGS = -fvisibility=hidden + endif +endif + +### Allow overwriting CXX from command line +ifdef COMPCXX + CXX=$(COMPCXX) +endif + +### Sometimes gcc is really clang +ifeq ($(COMP),gcc) + gccversion := $(shell $(CXX) --version 2>/dev/null) + gccisclang := $(findstring clang,$(gccversion)) + ifneq ($(gccisclang),) + profile_make = clang-profile-make + profile_use = clang-profile-use + endif +endif + +### On mingw use Windows threads, otherwise POSIX +ifneq ($(comp),mingw) + CXXFLAGS += -DUSE_PTHREADS + # On Android Bionic's C library comes with its own pthread implementation bundled in + ifneq ($(OS),Android) + # Haiku has pthreads in its libroot, so only link it in on other platforms + ifneq ($(KERNEL),Haiku) + ifneq ($(COMP),ndk) + LDFLAGS += -lpthread + endif + endif + endif +endif + +### 3.2.1 Debugging +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG +else + CXXFLAGS += -g +endif + +### 3.2.2 Debugging with undefined behavior sanitizers +ifneq ($(sanitize),none) + CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize)) + LDFLAGS += $(addprefix -fsanitize=,$(sanitize)) +endif + +### 3.3 Optimization +ifeq ($(optimize),yes) + + CXXFLAGS += -O3 -funroll-loops + + ifeq ($(comp),gcc) + ifeq ($(OS), Android) + CXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp + endif + endif + + ifeq ($(KERNEL),Darwin) + ifeq ($(comp),$(filter $(comp),clang icx)) + CXXFLAGS += -mdynamic-no-pic + endif + + ifeq ($(comp),gcc) + ifneq ($(arch),arm64) + CXXFLAGS += -mdynamic-no-pic + endif + endif + endif + + ifeq ($(comp),clang) + clangmajorversion := $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) + ifeq ($(shell expr $(clangmajorversion) \< 16),1) + CXXFLAGS += -fexperimental-new-pass-manager + endif + endif +endif + +### 3.4 Bits +ifeq ($(bits),64) + CXXFLAGS += -DIS_64BIT +endif + +### 3.5 prefetch and popcount +ifeq ($(prefetch),yes) + ifeq ($(sse),yes) + CXXFLAGS += -msse + endif +else + CXXFLAGS += -DNO_PREFETCH +endif + +ifeq ($(popcnt),yes) + ifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64)) + CXXFLAGS += -DUSE_POPCNT + else + CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT + endif +endif + +### 3.6 SIMD architectures +ifeq ($(avx2),yes) + CXXFLAGS += -DUSE_AVX2 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx2 -mbmi + endif +endif + +ifeq ($(avxvnni),yes) + CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavxvnni + endif +endif + +ifeq ($(avx512),yes) + CXXFLAGS += -DUSE_AVX512 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512bw + endif +endif + +ifeq ($(vnni256),yes) + CXXFLAGS += -DUSE_VNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256 + endif +endif + +ifeq ($(vnni512),yes) + CXXFLAGS += -DUSE_VNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512 + endif +endif + +ifeq ($(sse41),yes) + CXXFLAGS += -DUSE_SSE41 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -msse4.1 + endif +endif + +ifeq ($(ssse3),yes) + CXXFLAGS += -DUSE_SSSE3 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mssse3 + endif +endif + +ifeq ($(sse2),yes) + CXXFLAGS += -DUSE_SSE2 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -msse2 + endif +endif + +ifeq ($(mmx),yes) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mmmx + endif +endif + +ifeq ($(altivec),yes) + CXXFLAGS += -maltivec + ifeq ($(COMP),gcc) + CXXFLAGS += -mabi=altivec + endif +endif + +ifeq ($(vsx),yes) + CXXFLAGS += -mvsx + ifeq ($(COMP),gcc) + CXXFLAGS += -DNO_WARN_X86_INTRINSICS -DUSE_SSE2 + endif +endif + +ifeq ($(neon),yes) + CXXFLAGS += -DUSE_NEON=$(arm_version) + ifeq ($(KERNEL),Linux) + ifneq ($(COMP),ndk) + ifneq ($(arch),armv8) + CXXFLAGS += -mfpu=neon + endif + endif + endif +endif + +ifeq ($(dotprod),yes) + CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD +endif + +ifeq ($(lasx),yes) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mlasx + endif +endif + +ifeq ($(lsx),yes) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mlsx + endif +endif + +### 3.7 pext +ifeq ($(pext),yes) + CXXFLAGS += -DUSE_PEXT + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mbmi2 + endif +endif + +### 3.8.1 Try to include git commit sha for versioning +GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) +ifneq ($(GIT_SHA), ) + CXXFLAGS += -DGIT_SHA=$(GIT_SHA) +endif + +### 3.8.2 Try to include git commit date for versioning +GIT_DATE := $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) +ifneq ($(GIT_DATE), ) + CXXFLAGS += -DGIT_DATE=$(GIT_DATE) +endif + +### 3.8.3 Try to include architecture +ifneq ($(ARCH), ) + CXXFLAGS += -DARCH=$(ARCH) +endif + +### 3.9 Link Time Optimization +### This is a mix of compile and link time options because the lto link phase +### needs access to the optimization flags. +ifeq ($(optimize),yes) +ifeq ($(debug), no) + ifeq ($(comp),$(filter $(comp),clang icx)) + CXXFLAGS += -flto=full + ifeq ($(comp),icx) + CXXFLAGS += -fwhole-program-vtables + endif + ifeq ($(target_windows),yes) + CXXFLAGS += -fuse-ld=lld + endif + LDFLAGS += $(CXXFLAGS) + +# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be +# GCC on some systems. + else ifeq ($(comp),gcc) + ifeq ($(gccisclang),) + CXXFLAGS += -flto -flto-partition=one + LDFLAGS += $(CXXFLAGS) -flto=jobserver + else + CXXFLAGS += -flto=full + LDFLAGS += $(CXXFLAGS) + endif + +# To use LTO and static linking on Windows, +# the tool chain requires gcc version 10.1 or later. + else ifeq ($(comp),mingw) + CXXFLAGS += -flto -flto-partition=one + LDFLAGS += $(CXXFLAGS) -save-temps + endif +endif +endif + +### 3.10 Android 5 can only run position independent executables. Note that this +### breaks Android 4.0 and earlier. +ifeq ($(OS), Android) + CXXFLAGS += -fPIE + LDFLAGS += -fPIE -pie +endif + +### ========================================================================== +### Section 4. Public Targets +### ========================================================================== + +help: + @echo "" && \ + echo "To compile stockfish, type: " && \ + echo "" && \ + echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]" && \ + echo "" && \ + echo "Supported targets:" && \ + echo "" && \ + echo "help > Display architecture details" && \ + echo "profile-build > standard build with profile-guided optimization" && \ + echo "build > skip profile-guided optimization" && \ + echo "net > Download the default nnue nets" && \ + echo "strip > Strip executable" && \ + echo "install > Install executable" && \ + echo "clean > Clean up" && \ + echo "" && \ + echo "Supported archs:" && \ + echo "" && \ + echo "native > select the best architecture for the host processor (default)" && \ + echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \ + echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \ + echo "x86-64-avx512 > x86 64-bit with avx512 support" && \ + echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" && \ + echo "x86-64-bmi2 > x86 64-bit with bmi2 support" && \ + echo "x86-64-avx2 > x86 64-bit with avx2 support" && \ + echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" && \ + echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt" && \ + echo "x86-64-ssse3 > x86 64-bit with ssse3 support" && \ + echo "x86-64-sse3-popcnt > x86 64-bit with sse3 compile and popcnt support" && \ + echo "x86-64 > x86 64-bit generic (with sse2 support)" && \ + echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support" && \ + echo "x86-32-sse2 > x86 32-bit with sse2 support" && \ + echo "x86-32 > x86 32-bit generic (with mmx compile support)" && \ + echo "ppc-64 > PPC 64-bit" && \ + echo "ppc-64-altivec > PPC 64-bit with altivec support" && \ + echo "ppc-64-vsx > PPC 64-bit with vsx support" && \ + echo "ppc-32 > PPC 32-bit" && \ + echo "armv7 > ARMv7 32-bit" && \ + echo "armv7-neon > ARMv7 32-bit with popcnt and neon" && \ + echo "armv8 > ARMv8 64-bit with popcnt and neon" && \ + echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" && \ + echo "e2k > Elbrus 2000" && \ + echo "apple-silicon > Apple silicon ARM64" && \ + echo "general-64 > unspecified 64-bit" && \ + echo "general-32 > unspecified 32-bit" && \ + echo "riscv64 > RISC-V 64-bit" && \ + echo "loongarch64 > LoongArch 64-bit" && \ + echo "loongarch64-lsx > LoongArch 64-bit with SIMD eXtension" && \ + echo "loongarch64-lasx > LoongArch 64-bit with Advanced SIMD eXtension" && \ + echo "" && \ + echo "Supported compilers:" && \ + echo "" && \ + echo "gcc > GNU compiler (default)" && \ + echo "mingw > GNU compiler with MinGW under Windows" && \ + echo "clang > LLVM Clang compiler" && \ + echo "icx > Intel oneAPI DPC++/C++ Compiler" && \ + echo "ndk > Google NDK to cross-compile for Android" && \ + echo "" && \ + echo "Simple examples. If you don't know what to do, you likely want to run one of: " && \ + echo "" && \ + echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " && \ + echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " && \ + echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " && \ + echo "" && \ + echo "Advanced examples, for experienced users: " && \ + echo "" && \ + echo "make -j profile-build ARCH=x86-64-avxvnni" && \ + echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" && \ + echo "make -j build ARCH=x86-64-ssse3 COMP=clang" && \ + echo "" +ifneq ($(SUPPORTED_ARCH), true) + @echo "Specify a supported architecture with the ARCH option for more details" + @echo "" +endif + + +.PHONY: help analyze build profile-build strip install clean net \ + objclean profileclean config-sanity \ + icx-profile-use icx-profile-make \ + gcc-profile-use gcc-profile-make \ + clang-profile-use clang-profile-make FORCE \ + format analyze + +analyze: net config-sanity objclean + $(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS) + +build: net config-sanity + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all + +profile-build: net config-sanity objclean profileclean + @echo "" + @echo "Step 1/4. Building instrumented executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) + @echo "" + @echo "Step 2/4. Running benchmark for pgo-build ..." + $(PGOBENCH) > PGOBENCH.out 2>&1 + tail -n 4 PGOBENCH.out + @echo "" + @echo "Step 3/4. Building optimized executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) + @echo "" + @echo "Step 4/4. Deleting profile data ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean + +strip: + $(STRIP) $(EXE) + +install: + -mkdir -p -m 755 $(BINDIR) + -cp $(EXE) $(BINDIR) + $(STRIP) $(BINDIR)/$(EXE) + +# clean all +clean: objclean profileclean + @rm -f .depend *~ core + +# clean binaries and objects +objclean: + @rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o + +# clean auxiliary profiling files +profileclean: + @rm -rf profdir + @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s PGOBENCH.out + @rm -f stockfish.profdata *.profraw + @rm -f stockfish.*args* + @rm -f stockfish.*lt* + @rm -f stockfish.res + @rm -f ./-lstdc++.res + +# evaluation network (nnue) +net: + @$(SHELL) ../scripts/net.sh + +format: + $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file + +# default target +default: + help + +### ========================================================================== +### Section 5. Private Targets +### ========================================================================== + +all: $(EXE) .depend + +config-sanity: net + @echo "" + @echo "Config:" && \ + echo "debug: '$(debug)'" && \ + echo "sanitize: '$(sanitize)'" && \ + echo "optimize: '$(optimize)'" && \ + echo "arch: '$(arch)'" && \ + echo "bits: '$(bits)'" && \ + echo "kernel: '$(KERNEL)'" && \ + echo "os: '$(OS)'" && \ + echo "prefetch: '$(prefetch)'" && \ + echo "popcnt: '$(popcnt)'" && \ + echo "pext: '$(pext)'" && \ + echo "sse: '$(sse)'" && \ + echo "mmx: '$(mmx)'" && \ + echo "sse2: '$(sse2)'" && \ + echo "ssse3: '$(ssse3)'" && \ + echo "sse41: '$(sse41)'" && \ + echo "avx2: '$(avx2)'" && \ + echo "avxvnni: '$(avxvnni)'" && \ + echo "avx512: '$(avx512)'" && \ + echo "vnni256: '$(vnni256)'" && \ + echo "vnni512: '$(vnni512)'" && \ + echo "altivec: '$(altivec)'" && \ + echo "vsx: '$(vsx)'" && \ + echo "neon: '$(neon)'" && \ + echo "dotprod: '$(dotprod)'" && \ + echo "arm_version: '$(arm_version)'" && \ + echo "lsx: '$(lsx)'" && \ + echo "lasx: '$(lasx)'" && \ + echo "target_windows: '$(target_windows)'" && \ + echo "" && \ + echo "Flags:" && \ + echo "CXX: $(CXX)" && \ + echo "CXXFLAGS: $(CXXFLAGS)" && \ + echo "LDFLAGS: $(LDFLAGS)" && \ + echo "" && \ + echo "Testing config sanity. If this fails, try 'make help' ..." && \ + echo "" && \ + (test "$(debug)" = "yes" || test "$(debug)" = "no") && \ + (test "$(optimize)" = "yes" || test "$(optimize)" = "no") && \ + (test "$(SUPPORTED_ARCH)" = "true") && \ + (test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ + test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \ + test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || \ + test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64") && \ + (test "$(bits)" = "32" || test "$(bits)" = "64") && \ + (test "$(prefetch)" = "yes" || test "$(prefetch)" = "no") && \ + (test "$(popcnt)" = "yes" || test "$(popcnt)" = "no") && \ + (test "$(pext)" = "yes" || test "$(pext)" = "no") && \ + (test "$(sse)" = "yes" || test "$(sse)" = "no") && \ + (test "$(mmx)" = "yes" || test "$(mmx)" = "no") && \ + (test "$(sse2)" = "yes" || test "$(sse2)" = "no") && \ + (test "$(ssse3)" = "yes" || test "$(ssse3)" = "no") && \ + (test "$(sse41)" = "yes" || test "$(sse41)" = "no") && \ + (test "$(avx2)" = "yes" || test "$(avx2)" = "no") && \ + (test "$(avx512)" = "yes" || test "$(avx512)" = "no") && \ + (test "$(vnni256)" = "yes" || test "$(vnni256)" = "no") && \ + (test "$(vnni512)" = "yes" || test "$(vnni512)" = "no") && \ + (test "$(altivec)" = "yes" || test "$(altivec)" = "no") && \ + (test "$(vsx)" = "yes" || test "$(vsx)" = "no") && \ + (test "$(neon)" = "yes" || test "$(neon)" = "no") && \ + (test "$(lsx)" = "yes" || test "$(lsx)" = "no") && \ + (test "$(lasx)" = "yes" || test "$(lasx)" = "no") && \ + (test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || \ + test "$(comp)" = "clang" || test "$(comp)" = "armv7a-linux-androideabi16-clang" || \ + test "$(comp)" = "aarch64-linux-android21-clang") + +$(EXE): $(OBJS) + +$(CXX) -o $@ $(OBJS) $(LDFLAGS) + +# Force recompilation to ensure version info is up-to-date +misc.o: FORCE +FORCE: + +clang-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-generate ' \ + EXTRALDFLAGS=' -fprofile-generate' \ + all + +clang-profile-use: + $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-use=stockfish.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ + all + +gcc-profile-make: + @mkdir -p profdir + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-generate=profdir' \ + EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \ + EXTRALDFLAGS='-lgcov' \ + all + +gcc-profile-use: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \ + EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \ + EXTRALDFLAGS='-lgcov' \ + all + +icx-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-generate ' \ + EXTRALDFLAGS=' -fprofile-instr-generate' \ + all + +icx-profile-use: + $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ + all + +.depend: $(SRCS) + -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null + +ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity)) +-include .depend +endif diff --git a/stockfish/src/benchmark.cpp b/stockfish/src/benchmark.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a9f70f10d1a715fd1ce4f365c826a03c2ce8575e --- /dev/null +++ b/stockfish/src/benchmark.cpp @@ -0,0 +1,512 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "benchmark.h" +#include "numa.h" + +#include +#include +#include +#include + +namespace { + +// clang-format off +const std::vector Defaults = { + "setoption name UCI_Chess960 value false", + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", + "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11", + "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19", + "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6", + "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4", + "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15", + "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", + "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16", + "4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17", + "2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11", + "r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16", + "3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22", + "r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18", + "4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22", + "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26", + "6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1", + "3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1", + "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3", + "8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1", + "7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1", + "8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1", + "8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1", + "8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1", + "8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1", + "5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1", + "6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1", + "1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1", + "6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1", + "8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1", + "5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90", + "4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21", + "r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16", + "3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40", + "4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1", + + // 5-man positions + "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate + "8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate + "8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", // draw + + // 6-man positions + "8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1", // Re5 - mate + "8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1", // Ka2 - mate + "8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1", // Nd2 - draw + + // 7-man positions + "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw + + // Mate and stalemate positions + "6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1", + "r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1", + "8/8/8/8/8/6k1/6p1/6K1 w - -", + "7k/7P/6K1/8/3B4/8/8/8 b - -", + + // Chess 960 + "setoption name UCI_Chess960 value true", + "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6", + "nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1", + "setoption name UCI_Chess960 value false" +}; +// clang-format on + +// clang-format off +// human-randomly picked 5 games with <60 moves from +// https://tests.stockfishchess.org/tests/view/665c71f9fd45fb0f907c21e0 +// only moves for one side +const std::vector> BenchmarkPositions = { + { + "rnbq1k1r/ppp1bppp/4pn2/8/2B5/2NP1N2/PPP2PPP/R1BQR1K1 b - - 2 8", + "rnbq1k1r/pp2bppp/4pn2/2p5/2B2B2/2NP1N2/PPP2PPP/R2QR1K1 b - - 1 9", + "r1bq1k1r/pp2bppp/2n1pn2/2p5/2B1NB2/3P1N2/PPP2PPP/R2QR1K1 b - - 3 10", + "r1bq1k1r/pp2bppp/2n1p3/2p5/2B1PB2/5N2/PPP2PPP/R2QR1K1 b - - 0 11", + "r1b2k1r/pp2bppp/2n1p3/2p5/2B1PB2/5N2/PPP2PPP/3RR1K1 b - - 0 12", + "r1b1k2r/pp2bppp/2n1p3/2p5/2B1PB2/2P2N2/PP3PPP/3RR1K1 b - - 0 13", + "r1b1k2r/1p2bppp/p1n1p3/2p5/4PB2/2P2N2/PP2BPPP/3RR1K1 b - - 1 14", + "r1b1k2r/4bppp/p1n1p3/1pp5/P3PB2/2P2N2/1P2BPPP/3RR1K1 b - - 0 15", + "r1b1k2r/4bppp/p1n1p3/1P6/2p1PB2/2P2N2/1P2BPPP/3RR1K1 b - - 0 16", + "r1b1k2r/4bppp/2n1p3/1p6/2p1PB2/1PP2N2/4BPPP/3RR1K1 b - - 0 17", + "r3k2r/3bbppp/2n1p3/1p6/2P1PB2/2P2N2/4BPPP/3RR1K1 b - - 0 18", + "r3k2r/3bbppp/2n1p3/8/1pP1P3/2P2N2/3BBPPP/3RR1K1 b - - 1 19", + "1r2k2r/3bbppp/2n1p3/8/1pPNP3/2P5/3BBPPP/3RR1K1 b - - 3 20", + "1r2k2r/3bbppp/2n1p3/8/2PNP3/2B5/4BPPP/3RR1K1 b - - 0 21", + "1r2k2r/3bb1pp/2n1pp2/1N6/2P1P3/2B5/4BPPP/3RR1K1 b - - 1 22", + "1r2k2r/3b2pp/2n1pp2/1N6/1BP1P3/8/4BPPP/3RR1K1 b - - 0 23", + "1r2k2r/3b2pp/4pp2/1N6/1nP1P3/8/3RBPPP/4R1K1 b - - 1 24", + "1r5r/3bk1pp/4pp2/1N6/1nP1PP2/8/3RB1PP/4R1K1 b - - 0 25", + "1r5r/3bk1pp/2n1pp2/1N6/2P1PP2/8/3RBKPP/4R3 b - - 2 26", + "1r5r/3bk1pp/2n2p2/1N2p3/2P1PP2/6P1/3RBK1P/4R3 b - - 0 27", + "1r1r4/3bk1pp/2n2p2/1N2p3/2P1PP2/6P1/3RBK1P/R7 b - - 2 28", + "1r1r4/N3k1pp/2n1bp2/4p3/2P1PP2/6P1/3RBK1P/R7 b - - 4 29", + "1r1r4/3bk1pp/2N2p2/4p3/2P1PP2/6P1/3RBK1P/R7 b - - 0 30", + "1r1R4/4k1pp/2b2p2/4p3/2P1PP2/6P1/4BK1P/R7 b - - 0 31", + "3r4/4k1pp/2b2p2/4P3/2P1P3/6P1/4BK1P/R7 b - - 0 32", + "3r4/R3k1pp/2b5/4p3/2P1P3/6P1/4BK1P/8 b - - 1 33", + "8/3rk1pp/2b5/R3p3/2P1P3/6P1/4BK1P/8 b - - 3 34", + "8/3r2pp/2bk4/R1P1p3/4P3/6P1/4BK1P/8 b - - 0 35", + "8/2kr2pp/2b5/R1P1p3/4P3/4K1P1/4B2P/8 b - - 2 36", + "1k6/3r2pp/2b5/RBP1p3/4P3/4K1P1/7P/8 b - - 4 37", + "8/1k1r2pp/2b5/R1P1p3/4P3/3BK1P1/7P/8 b - - 6 38", + "1k6/3r2pp/2b5/2P1p3/4P3/3BK1P1/7P/R7 b - - 8 39", + "1k6/r5pp/2b5/2P1p3/4P3/3BK1P1/7P/5R2 b - - 10 40", + "1k3R2/6pp/2b5/2P1p3/4P3/r2BK1P1/7P/8 b - - 12 41", + "5R2/2k3pp/2b5/2P1p3/4P3/r2B2P1/3K3P/8 b - - 14 42", + "5R2/2k3pp/2b5/2P1p3/4P3/3BK1P1/r6P/8 b - - 16 43", + "5R2/2k3pp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 18 44", + "5R2/2k3pp/2b5/2P1p3/4P3/3B1KP1/r6P/8 b - - 20 45", + "8/2k2Rpp/2b5/2P1p3/4P3/r2B1KP1/7P/8 b - - 22 46", + "3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 24 47", + "3k4/5Rpp/2b5/2P1p3/4P3/3B1KP1/r6P/8 b - - 26 48", + "3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 28 49", + "3k4/5Rpp/2b5/2P1p3/4P3/3BK1P1/r6P/8 b - - 30 50", + "3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/3K3P/8 b - - 32 51", + "3k4/5Rpp/2b5/2P1p3/4P3/2KB2P1/r6P/8 b - - 34 52", + "3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/2K4P/8 b - - 36 53", + "3k4/5Rpp/2b5/2P1p3/4P3/1K1B2P1/r6P/8 b - - 38 54", + "3k4/6Rp/2b5/2P1p3/4P3/1K1B2P1/7r/8 b - - 0 55", + "3k4/8/2b3Rp/2P1p3/4P3/1K1B2P1/7r/8 b - - 1 56", + "8/2k3R1/2b4p/2P1p3/4P3/1K1B2P1/7r/8 b - - 3 57", + "3k4/8/2b3Rp/2P1p3/4P3/1K1B2P1/7r/8 b - - 5 58", + "8/2k5/2b3Rp/2P1p3/1K2P3/3B2P1/7r/8 b - - 7 59", + "8/2k5/2b3Rp/2P1p3/4P3/2KB2P1/3r4/8 b - - 9 60", + "8/2k5/2b3Rp/2P1p3/1K2P3/3B2P1/6r1/8 b - - 11 61", + "8/2k5/2b3Rp/2P1p3/4P3/2KB2P1/3r4/8 b - - 13 62", + "8/2k5/2b3Rp/2P1p3/2K1P3/3B2P1/6r1/8 b - - 15 63", + "4b3/2k3R1/7p/2P1p3/2K1P3/3B2P1/6r1/8 b - - 17 64", + }, + { + "r1bqkbnr/npp1pppp/p7/3P4/4pB2/2N5/PPP2PPP/R2QKBNR w KQkq - 1 6", + "r1bqkb1r/npp1pppp/p4n2/3P4/4pB2/2N5/PPP1QPPP/R3KBNR w KQkq - 3 7", + "r2qkb1r/npp1pppp/p4n2/3P1b2/4pB2/2N5/PPP1QPPP/2KR1BNR w kq - 5 8", + "r2qkb1r/1pp1pppp/p4n2/1n1P1b2/4pB2/2N4P/PPP1QPP1/2KR1BNR w kq - 1 9", + "r2qkb1r/1pp1pppp/5n2/1p1P1b2/4pB2/7P/PPP1QPP1/2KR1BNR w kq - 0 10", + "r2qkb1r/1ppbpppp/5n2/1Q1P4/4pB2/7P/PPP2PP1/2KR1BNR w kq - 1 11", + "3qkb1r/1Qpbpppp/5n2/3P4/4pB2/7P/rPP2PP1/2KR1BNR w k - 0 12", + "q3kb1r/1Qpbpppp/5n2/3P4/4pB2/7P/rPP2PP1/1K1R1BNR w k - 2 13", + "r3kb1r/2pbpppp/5n2/3P4/4pB2/7P/1PP2PP1/1K1R1BNR w k - 0 14", + "r3kb1r/2Bb1ppp/4pn2/3P4/4p3/7P/1PP2PP1/1K1R1BNR w k - 0 15", + "r3kb1r/2Bb2pp/4pn2/8/4p3/7P/1PP2PP1/1K1R1BNR w k - 0 16", + "r3k2r/2Bb2pp/4pn2/2b5/4p3/7P/1PP1NPP1/1K1R1B1R w k - 2 17", + "r6r/2Bbk1pp/4pn2/2b5/3Np3/7P/1PP2PP1/1K1R1B1R w - - 4 18", + "r6r/b2bk1pp/4pn2/4B3/3Np3/7P/1PP2PP1/1K1R1B1R w - - 6 19", + "r1r5/b2bk1pp/4pn2/4B3/2BNp3/7P/1PP2PP1/1K1R3R w - - 8 20", + "r7/b2bk1pp/4pn2/2r1B3/2BNp3/1P5P/2P2PP1/1K1R3R w - - 1 21", + "rb6/3bk1pp/4pn2/2r1B3/2BNpP2/1P5P/2P3P1/1K1R3R w - - 1 22", + "1r6/3bk1pp/4pn2/2r5/2BNpP2/1P5P/2P3P1/1K1R3R w - - 0 23", + "1r6/3bk1p1/4pn1p/2r5/2BNpP2/1P5P/2P3P1/2KR3R w - - 0 24", + "8/3bk1p1/1r2pn1p/2r5/2BNpP1P/1P6/2P3P1/2KR3R w - - 1 25", + "8/3bk3/1r2pnpp/2r5/2BNpP1P/1P6/2P3P1/2K1R2R w - - 0 26", + "2b5/4k3/1r2pnpp/2r5/2BNpP1P/1P4P1/2P5/2K1R2R w - - 1 27", + "8/1b2k3/1r2pnpp/2r5/2BNpP1P/1P4P1/2P5/2K1R1R1 w - - 3 28", + "8/1b1nk3/1r2p1pp/2r5/2BNpPPP/1P6/2P5/2K1R1R1 w - - 1 29", + "8/1b2k3/1r2p1pp/2r1nP2/2BNp1PP/1P6/2P5/2K1R1R1 w - - 1 30", + "8/1b2k3/1r2p1p1/2r1nPp1/2BNp2P/1P6/2P5/2K1R1R1 w - - 0 31", + "8/1b2k3/1r2p1n1/2r3p1/2BNp2P/1P6/2P5/2K1R1R1 w - - 0 32", + "8/1b2k3/1r2p1n1/6r1/2BNp2P/1P6/2P5/2K1R3 w - - 0 33", + "8/1b2k3/1r2p3/4n1P1/2BNp3/1P6/2P5/2K1R3 w - - 1 34", + "8/1b2k3/1r2p3/4n1P1/2BN4/1P2p3/2P5/2K4R w - - 0 35", + "8/1b2k3/1r2p2R/6P1/2nN4/1P2p3/2P5/2K5 w - - 0 36", + "8/1b2k3/3rp2R/6P1/2PN4/4p3/2P5/2K5 w - - 1 37", + "8/4k3/3rp2R/6P1/2PN4/2P1p3/6b1/2K5 w - - 1 38", + "8/4k3/r3p2R/2P3P1/3N4/2P1p3/6b1/2K5 w - - 1 39", + "8/3k4/r3p2R/2P2NP1/8/2P1p3/6b1/2K5 w - - 3 40", + "8/3k4/4p2R/2P3P1/8/2P1N3/6b1/r1K5 w - - 1 41", + "8/3k4/4p2R/2P3P1/8/2P1N3/3K2b1/6r1 w - - 3 42", + "8/3k4/4p2R/2P3P1/8/2PKNb2/8/6r1 w - - 5 43", + "8/4k3/4p1R1/2P3P1/8/2PKNb2/8/6r1 w - - 7 44", + "8/4k3/4p1R1/2P3P1/3K4/2P1N3/8/6rb w - - 9 45", + "8/3k4/4p1R1/2P1K1P1/8/2P1N3/8/6rb w - - 11 46", + "8/3k4/4p1R1/2P3P1/5K2/2P1N3/8/4r2b w - - 13 47", + "8/3k4/2b1p2R/2P3P1/5K2/2P1N3/8/4r3 w - - 15 48", + "8/3k4/2b1p3/2P3P1/5K2/2P1N2R/8/6r1 w - - 17 49", + "2k5/7R/2b1p3/2P3P1/5K2/2P1N3/8/6r1 w - - 19 50", + "2k5/7R/4p3/2P3P1/b1P2K2/4N3/8/6r1 w - - 1 51", + "2k5/3bR3/4p3/2P3P1/2P2K2/4N3/8/6r1 w - - 3 52", + "3k4/3b2R1/4p3/2P3P1/2P2K2/4N3/8/6r1 w - - 5 53", + "3kb3/6R1/4p1P1/2P5/2P2K2/4N3/8/6r1 w - - 1 54", + "3kb3/6R1/4p1P1/2P5/2P2KN1/8/8/2r5 w - - 3 55", + "3kb3/6R1/4p1P1/2P1N3/2P2K2/8/8/5r2 w - - 5 56", + "3kb3/6R1/4p1P1/2P1N3/2P5/4K3/8/4r3 w - - 7 57", + }, + { + "rnbq1rk1/ppp1npb1/4p1p1/3P3p/3PP3/2N2N2/PP2BPPP/R1BQ1RK1 b - - 0 8", + "rnbq1rk1/ppp1npb1/6p1/3pP2p/3P4/2N2N2/PP2BPPP/R1BQ1RK1 b - - 0 9", + "rn1q1rk1/ppp1npb1/6p1/3pP2p/3P2b1/2N2N2/PP2BPPP/R1BQR1K1 b - - 2 10", + "r2q1rk1/ppp1npb1/2n3p1/3pP2p/3P2bN/2N5/PP2BPPP/R1BQR1K1 b - - 4 11", + "r4rk1/pppqnpb1/2n3p1/3pP2p/3P2bN/2N4P/PP2BPP1/R1BQR1K1 b - - 0 12", + "r4rk1/pppqnpb1/2n3p1/3pP2p/3P3N/7P/PP2NPP1/R1BQR1K1 b - - 0 13", + "r4rk1/pppq1pb1/2n3p1/3pPN1p/3P4/7P/PP2NPP1/R1BQR1K1 b - - 0 14", + "r4rk1/ppp2pb1/2n3p1/3pPq1p/3P1N2/7P/PP3PP1/R1BQR1K1 b - - 1 15", + "r4rk1/pppq1pb1/2n3p1/3pP2p/P2P1N2/7P/1P3PP1/R1BQR1K1 b - - 0 16", + "r2n1rk1/pppq1pb1/6p1/3pP2p/P2P1N2/R6P/1P3PP1/2BQR1K1 b - - 2 17", + "r4rk1/pppq1pb1/4N1p1/3pP2p/P2P4/R6P/1P3PP1/2BQR1K1 b - - 0 18", + "r4rk1/ppp2pb1/4q1p1/3pP1Bp/P2P4/R6P/1P3PP1/3QR1K1 b - - 1 19", + "r3r1k1/ppp2pb1/4q1p1/3pP1Bp/P2P1P2/R6P/1P4P1/3QR1K1 b - - 0 20", + "r3r1k1/ppp3b1/4qpp1/3pP2p/P2P1P1B/R6P/1P4P1/3QR1K1 b - - 1 21", + "r3r1k1/ppp3b1/4q1p1/3pP2p/P4P1B/R6P/1P4P1/3QR1K1 b - - 0 22", + "r4rk1/ppp3b1/4q1p1/3pP1Bp/P4P2/R6P/1P4P1/3QR1K1 b - - 2 23", + "r4rk1/pp4b1/4q1p1/2ppP1Bp/P4P2/3R3P/1P4P1/3QR1K1 b - - 1 24", + "r4rk1/pp4b1/4q1p1/2p1P1Bp/P2p1PP1/3R3P/1P6/3QR1K1 b - - 0 25", + "r4rk1/pp4b1/4q1p1/2p1P1B1/P2p1PP1/3R4/1P6/3QR1K1 b - - 0 26", + "r5k1/pp3rb1/4q1p1/2p1P1B1/P2p1PP1/6R1/1P6/3QR1K1 b - - 2 27", + "5rk1/pp3rb1/4q1p1/2p1P1B1/P2pRPP1/6R1/1P6/3Q2K1 b - - 4 28", + "5rk1/1p3rb1/p3q1p1/P1p1P1B1/3pRPP1/6R1/1P6/3Q2K1 b - - 0 29", + "4r1k1/1p3rb1/p3q1p1/P1p1P1B1/3pRPP1/1P4R1/8/3Q2K1 b - - 0 30", + "4r1k1/5rb1/pP2q1p1/2p1P1B1/3pRPP1/1P4R1/8/3Q2K1 b - - 0 31", + "4r1k1/5rb1/pq4p1/2p1P1B1/3pRPP1/1P4R1/4Q3/6K1 b - - 1 32", + "4r1k1/1r4b1/pq4p1/2p1P1B1/3pRPP1/1P4R1/2Q5/6K1 b - - 3 33", + "4r1k1/1r4b1/1q4p1/p1p1P1B1/3p1PP1/1P4R1/2Q5/4R1K1 b - - 1 34", + "4r1k1/3r2b1/1q4p1/p1p1P1B1/2Qp1PP1/1P4R1/8/4R1K1 b - - 3 35", + "4r1k1/3r2b1/4q1p1/p1p1P1B1/2Qp1PP1/1P4R1/5K2/4R3 b - - 5 36", + "4r1k1/3r2b1/6p1/p1p1P1B1/2Pp1PP1/6R1/5K2/4R3 b - - 0 37", + "4r1k1/3r2b1/6p1/p1p1P1B1/2P2PP1/3p2R1/5K2/3R4 b - - 1 38", + "5rk1/3r2b1/6p1/p1p1P1B1/2P2PP1/3p2R1/8/3RK3 b - - 3 39", + "5rk1/6b1/6p1/p1p1P1B1/2Pr1PP1/3R4/8/3RK3 b - - 0 40", + "5rk1/3R2b1/6p1/p1p1P1B1/2r2PP1/8/8/3RK3 b - - 1 41", + "5rk1/3R2b1/6p1/p1p1P1B1/4rPP1/8/3K4/3R4 b - - 3 42", + "1r4k1/3R2b1/6p1/p1p1P1B1/4rPP1/2K5/8/3R4 b - - 5 43", + "1r4k1/3R2b1/6p1/p1p1P1B1/2K2PP1/4r3/8/3R4 b - - 7 44", + "1r3bk1/8/3R2p1/p1p1P1B1/2K2PP1/4r3/8/3R4 b - - 9 45", + "1r3bk1/8/6R1/2p1P1B1/p1K2PP1/4r3/8/3R4 b - - 0 46", + "1r3b2/5k2/R7/2p1P1B1/p1K2PP1/4r3/8/3R4 b - - 2 47", + "5b2/1r3k2/R7/2p1P1B1/p1K2PP1/4r3/8/7R b - - 4 48", + "5b2/5k2/R7/2pKP1B1/pr3PP1/4r3/8/7R b - - 6 49", + "5b2/5k2/R1K5/2p1P1B1/p2r1PP1/4r3/8/7R b - - 8 50", + "8/R4kb1/2K5/2p1P1B1/p2r1PP1/4r3/8/7R b - - 10 51", + "8/R5b1/2K3k1/2p1PPB1/p2r2P1/4r3/8/7R b - - 0 52", + "8/6R1/2K5/2p1PPk1/p2r2P1/4r3/8/7R b - - 0 53", + "8/6R1/2K5/2p1PP2/p2r1kP1/4r3/8/5R2 b - - 2 54", + "8/6R1/2K2P2/2p1P3/p2r2P1/4r1k1/8/5R2 b - - 0 55", + "8/5PR1/2K5/2p1P3/p2r2P1/4r3/6k1/5R2 b - - 0 56", + }, + { + "rn1qkb1r/p1pbpppp/5n2/8/2pP4/2N5/1PQ1PPPP/R1B1KBNR w KQkq - 0 7", + "r2qkb1r/p1pbpppp/2n2n2/8/2pP4/2N2N2/1PQ1PPPP/R1B1KB1R w KQkq - 2 8", + "r2qkb1r/p1pbpppp/5n2/8/1npPP3/2N2N2/1PQ2PPP/R1B1KB1R w KQkq - 1 9", + "r2qkb1r/p1pb1ppp/4pn2/8/1npPP3/2N2N2/1P3PPP/R1BQKB1R w KQkq - 0 10", + "r2qk2r/p1pbbppp/4pn2/8/1nBPP3/2N2N2/1P3PPP/R1BQK2R w KQkq - 1 11", + "r2q1rk1/p1pbbppp/4pn2/8/1nBPP3/2N2N2/1P3PPP/R1BQ1RK1 w - - 3 12", + "r2q1rk1/2pbbppp/p3pn2/8/1nBPPB2/2N2N2/1P3PPP/R2Q1RK1 w - - 0 13", + "r2q1rk1/2p1bppp/p3pn2/1b6/1nBPPB2/2N2N2/1P3PPP/R2QR1K1 w - - 2 14", + "r2q1rk1/4bppp/p1p1pn2/1b6/1nBPPB2/1PN2N2/5PPP/R2QR1K1 w - - 0 15", + "r4rk1/3qbppp/p1p1pn2/1b6/1nBPPB2/1PN2N2/3Q1PPP/R3R1K1 w - - 2 16", + "r4rk1/1q2bppp/p1p1pn2/1b6/1nBPPB2/1PN2N1P/3Q1PP1/R3R1K1 w - - 1 17", + "r3r1k1/1q2bppp/p1p1pn2/1b6/1nBPPB2/1PN2N1P/4QPP1/R3R1K1 w - - 3 18", + "r3r1k1/1q1nbppp/p1p1p3/1b6/1nBPPB2/1PN2N1P/4QPP1/3RR1K1 w - - 5 19", + "r3rbk1/1q1n1ppp/p1p1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/4R1K1 w - - 7 20", + "r3rbk1/1q3ppp/pnp1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/4R2K w - - 9 21", + "2r1rbk1/1q3ppp/pnp1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/1R5K w - - 11 22", + "2r1rbk1/1q4pp/pnp1pp2/1b6/1nBPPB2/1PN2N1P/4QPP1/1R1R3K w - - 0 23", + "2r1rbk1/5qpp/pnp1pp2/1b6/1nBPP3/1PN1BN1P/4QPP1/1R1R3K w - - 2 24", + "2r1rbk1/5qp1/pnp1pp1p/1b6/1nBPP3/1PN1BN1P/4QPP1/1R1R2K1 w - - 0 25", + "2r1rbk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/n3QPP1/1R1R2K1 w - - 0 26", + "r3rbk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/Q4PP1/1R1R2K1 w - - 1 27", + "rr3bk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/Q4PP1/R2R2K1 w - - 3 28", + "rr2qbk1/6p1/pnp1pp1p/1b6/2BPP3/1P2BN1P/4QPP1/R2R2K1 w - - 5 29", + "rr2qbk1/6p1/1np1pp1p/pb6/2BPP3/1P1QBN1P/5PP1/R2R2K1 w - - 0 30", + "rr2qbk1/6p1/1n2pp1p/pp6/3PP3/1P1QBN1P/5PP1/R2R2K1 w - - 0 31", + "rr2qbk1/6p1/1n2pp1p/1p1P4/p3P3/1P1QBN1P/5PP1/R2R2K1 w - - 0 32", + "rr2qbk1/3n2p1/3Ppp1p/1p6/p3P3/1P1QBN1P/5PP1/R2R2K1 w - - 1 33", + "rr3bk1/3n2p1/3Ppp1p/1p5q/pP2P3/3QBN1P/5PP1/R2R2K1 w - - 1 34", + "rr3bk1/3n2p1/3Ppp1p/1p5q/1P2P3/p2QBN1P/5PP1/2RR2K1 w - - 0 35", + "1r3bk1/3n2p1/r2Ppp1p/1p5q/1P2P3/pQ2BN1P/5PP1/2RR2K1 w - - 2 36", + "1r2qbk1/2Rn2p1/r2Ppp1p/1p6/1P2P3/pQ2BN1P/5PP1/3R2K1 w - - 4 37", + "1r2qbk1/2Rn2p1/r2Ppp1p/1pB5/1P2P3/1Q3N1P/p4PP1/3R2K1 w - - 0 38", + "1r2q1k1/2Rn2p1/r2bpp1p/1pB5/1P2P3/1Q3N1P/p4PP1/R5K1 w - - 0 39", + "1r2q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/1Q3N1P/p4PP1/R5K1 w - - 0 40", + "2r1q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 1 41", + "1r2q1k1/1R1n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 3 42", + "2r1q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 5 43", + "1r2q1k1/1R1n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 7 44", + "1rq3k1/R2n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 9 45", + "2q3k1/Rr1n2p1/3rpp1p/1p6/1P2P3/5N1P/4QPP1/R5K1 w - - 11 46", + "Rrq3k1/3n2p1/3rpp1p/1p6/1P2P3/5N1P/4QPP1/R5K1 w - - 13 47", + }, + { + "rn1qkb1r/1pp2ppp/p4p2/3p1b2/5P2/1P2PN2/P1PP2PP/RN1QKB1R b KQkq - 1 6", + "r2qkb1r/1pp2ppp/p1n2p2/3p1b2/3P1P2/1P2PN2/P1P3PP/RN1QKB1R b KQkq - 0 7", + "r2qkb1r/1pp2ppp/p4p2/3p1b2/1n1P1P2/1P1BPN2/P1P3PP/RN1QK2R b KQkq - 2 8", + "r2qkb1r/1pp2ppp/p4p2/3p1b2/3P1P2/1P1PPN2/P5PP/RN1QK2R b KQkq - 0 9", + "r2qk2r/1pp2ppp/p2b1p2/3p1b2/3P1P2/1PNPPN2/P5PP/R2QK2R b KQkq - 2 10", + "r2qk2r/1p3ppp/p1pb1p2/3p1b2/3P1P2/1PNPPN2/P5PP/R2Q1RK1 b kq - 1 11", + "r2q1rk1/1p3ppp/p1pb1p2/3p1b2/3P1P2/1PNPPN2/P2Q2PP/R4RK1 b - - 3 12", + "r2qr1k1/1p3ppp/p1pb1p2/3p1b2/3P1P2/1P1PPN2/P2QN1PP/R4RK1 b - - 5 13", + "r3r1k1/1p3ppp/pqpb1p2/3p1b2/3P1P2/1P1PPNN1/P2Q2PP/R4RK1 b - - 7 14", + "r3r1k1/1p3ppp/pqp2p2/3p1b2/1b1P1P2/1P1PPNN1/P1Q3PP/R4RK1 b - - 9 15", + "r3r1k1/1p1b1ppp/pqp2p2/3p4/1b1P1P2/1P1PPNN1/P4QPP/R4RK1 b - - 11 16", + "2r1r1k1/1p1b1ppp/pqp2p2/3p4/1b1PPP2/1P1P1NN1/P4QPP/R4RK1 b - - 0 17", + "2r1r1k1/1p1b1ppp/pq3p2/2pp4/1b1PPP2/PP1P1NN1/5QPP/R4RK1 b - - 0 18", + "2r1r1k1/1p1b1ppp/pq3p2/2Pp4/4PP2/PPbP1NN1/5QPP/R4RK1 b - - 0 19", + "2r1r1k1/1p1b1ppp/p4p2/2Pp4/4PP2/PqbP1NN1/5QPP/RR4K1 b - - 1 20", + "2r1r1k1/1p1b1ppp/p4p2/2Pp4/q3PP2/P1bP1NN1/R4QPP/1R4K1 b - - 3 21", + "2r1r1k1/1p3ppp/p4p2/1bPP4/q4P2/P1bP1NN1/R4QPP/1R4K1 b - - 0 22", + "2r1r1k1/1p3ppp/p4p2/2PP4/q4P2/P1bb1NN1/R4QPP/2R3K1 b - - 1 23", + "2r1r1k1/1p3ppp/p2P1p2/2P5/2q2P2/P1bb1NN1/R4QPP/2R3K1 b - - 0 24", + "2rr2k1/1p3ppp/p2P1p2/2P5/2q2P2/P1bb1NN1/R4QPP/2R4K b - - 2 25", + "2rr2k1/1p3ppp/p2P1p2/2Q5/5P2/P1bb1NN1/R5PP/2R4K b - - 0 26", + "3r2k1/1p3ppp/p2P1p2/2r5/5P2/P1bb1N2/R3N1PP/2R4K b - - 1 27", + "3r2k1/1p3ppp/p2P1p2/2r5/5P2/P1b2N2/4R1PP/2R4K b - - 0 28", + "3r2k1/1p3ppp/p2P1p2/2r5/1b3P2/P4N2/4R1PP/3R3K b - - 2 29", + "3r2k1/1p2Rppp/p2P1p2/b1r5/5P2/P4N2/6PP/3R3K b - - 4 30", + "3r2k1/1R3ppp/p1rP1p2/b7/5P2/P4N2/6PP/3R3K b - - 0 31", + "3r2k1/1R3ppp/p2R1p2/b7/5P2/P4N2/6PP/7K b - - 0 32", + "6k1/1R3ppp/p2r1p2/b7/5P2/P4NP1/7P/7K b - - 0 33", + "6k1/1R3p1p/p2r1pp1/b7/5P1P/P4NP1/8/7K b - - 0 34", + "6k1/3R1p1p/pr3pp1/b7/5P1P/P4NP1/8/7K b - - 2 35", + "6k1/5p2/pr3pp1/b2R3p/5P1P/P4NP1/8/7K b - - 1 36", + "6k1/5p2/pr3pp1/7p/5P1P/P1bR1NP1/8/7K b - - 3 37", + "6k1/5p2/p1r2pp1/7p/5P1P/P1bR1NP1/6K1/8 b - - 5 38", + "6k1/5p2/p1r2pp1/b2R3p/5P1P/P4NP1/6K1/8 b - - 7 39", + "6k1/5p2/p4pp1/b2R3p/5P1P/P4NPK/2r5/8 b - - 9 40", + "6k1/2b2p2/p4pp1/7p/5P1P/P2R1NPK/2r5/8 b - - 11 41", + "6k1/2b2p2/5pp1/p6p/3N1P1P/P2R2PK/2r5/8 b - - 1 42", + "6k1/2b2p2/5pp1/p6p/3N1P1P/P1R3PK/r7/8 b - - 3 43", + "6k1/5p2/1b3pp1/p6p/5P1P/P1R3PK/r1N5/8 b - - 5 44", + "8/5pk1/1bR2pp1/p6p/5P1P/P5PK/r1N5/8 b - - 7 45", + "3b4/5pk1/2R2pp1/p4P1p/7P/P5PK/r1N5/8 b - - 0 46", + "8/4bpk1/2R2pp1/p4P1p/6PP/P6K/r1N5/8 b - - 0 47", + "8/5pk1/2R2pP1/p6p/6PP/b6K/r1N5/8 b - - 0 48", + "8/6k1/2R2pp1/p6P/7P/b6K/r1N5/8 b - - 0 49", + "8/6k1/2R2p2/p6p/7P/b5K1/r1N5/8 b - - 1 50", + "8/8/2R2pk1/p6p/7P/b4K2/r1N5/8 b - - 3 51", + "8/8/2R2pk1/p6p/7P/4NK2/rb6/8 b - - 5 52", + "2R5/8/5pk1/7p/p6P/4NK2/rb6/8 b - - 1 53", + "6R1/8/5pk1/7p/p6P/4NK2/1b6/r7 b - - 3 54", + "R7/5k2/5p2/7p/p6P/4NK2/1b6/r7 b - - 5 55", + "R7/5k2/5p2/7p/7P/p3N3/1b2K3/r7 b - - 1 56", + "8/R4k2/5p2/7p/7P/p3N3/1b2K3/7r b - - 3 57", + "8/8/5pk1/7p/R6P/p3N3/1b2K3/7r b - - 5 58", + "8/8/5pk1/7p/R6P/p7/4K3/2bN3r b - - 7 59", + "8/8/5pk1/7p/R6P/p7/4KN1r/2b5 b - - 9 60", + "8/8/5pk1/7p/R6P/p3K3/1b3N1r/8 b - - 11 61", + "8/8/R4pk1/7p/7P/p1b1K3/5N1r/8 b - - 13 62", + "8/8/5pk1/7p/7P/2b1K3/R4N1r/8 b - - 0 63", + "8/8/5pk1/7p/3K3P/8/R4N1r/4b3 b - - 2 64", + } +}; +// clang-format on + +} // namespace + +namespace Stockfish::Benchmark { + +// Builds a list of UCI commands to be run by bench. There +// are five parameters: TT size in MB, number of search threads that +// should be used, the limit value spent for each position, a file name +// where to look for positions in FEN format, and the type of the limit: +// depth, perft, nodes and movetime (in milliseconds). Examples: +// +// bench : search default positions up to depth 13 +// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB) +// bench 64 1 100000 default nodes : search default positions for 100K nodes each +// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec +// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" +std::vector setup_bench(const std::string& currentFen, std::istream& is) { + + std::vector fens, list; + std::string go, token; + + // Assign default values to missing arguments + std::string ttSize = (is >> token) ? token : "16"; + std::string threads = (is >> token) ? token : "1"; + std::string limit = (is >> token) ? token : "13"; + std::string fenFile = (is >> token) ? token : "default"; + std::string limitType = (is >> token) ? token : "depth"; + + go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; + + if (fenFile == "default") + fens = Defaults; + + else if (fenFile == "current") + fens.push_back(currentFen); + + else + { + std::string fen; + std::ifstream file(fenFile); + + if (!file.is_open()) + { + std::cerr << "Unable to open file " << fenFile << std::endl; + exit(EXIT_FAILURE); + } + + while (getline(file, fen)) + if (!fen.empty()) + fens.push_back(fen); + + file.close(); + } + + list.emplace_back("setoption name Threads value " + threads); + list.emplace_back("setoption name Hash value " + ttSize); + list.emplace_back("ucinewgame"); + + for (const std::string& fen : fens) + if (fen.find("setoption") != std::string::npos) + list.emplace_back(fen); + else + { + list.emplace_back("position fen " + fen); + list.emplace_back(go); + } + + return list; +} + +BenchmarkSetup setup_benchmark(std::istream& is) { + // TT_SIZE_PER_THREAD is chosen such that roughly half of the hash is used all positions + // for the current sequence have been searched. + static constexpr int TT_SIZE_PER_THREAD = 128; + + static constexpr int DEFAULT_DURATION_S = 150; + + BenchmarkSetup setup{}; + + // Assign default values to missing arguments + int desiredTimeS; + + if (!(is >> setup.threads)) + setup.threads = get_hardware_concurrency(); + else + setup.originalInvocation += std::to_string(setup.threads); + + if (!(is >> setup.ttSize)) + setup.ttSize = TT_SIZE_PER_THREAD * setup.threads; + else + setup.originalInvocation += " " + std::to_string(setup.ttSize); + + if (!(is >> desiredTimeS)) + desiredTimeS = DEFAULT_DURATION_S; + else + setup.originalInvocation += " " + std::to_string(desiredTimeS); + + setup.filledInvocation += std::to_string(setup.threads) + " " + std::to_string(setup.ttSize) + + " " + std::to_string(desiredTimeS); + + auto getCorrectedTime = [&](int ply) { + // time per move is fit roughly based on LTC games + // seconds = 50/{ply+15} + // ms = 50000/{ply+15} + // with this fit 10th move gets 2000ms + // adjust for desired 10th move time + return 50000.0 / (static_cast(ply) + 15.0); + }; + + float totalTime = 0; + for (const auto& game : BenchmarkPositions) + { + setup.commands.emplace_back("ucinewgame"); + int ply = 1; + for (int i = 0; i < static_cast(game.size()); ++i) + { + const float correctedTime = getCorrectedTime(ply); + totalTime += correctedTime; + ply += 1; + } + } + + float timeScaleFactor = static_cast(desiredTimeS * 1000) / totalTime; + + for (const auto& game : BenchmarkPositions) + { + setup.commands.emplace_back("ucinewgame"); + int ply = 1; + for (const std::string& fen : game) + { + setup.commands.emplace_back("position fen " + fen); + + const int correctedTime = static_cast(getCorrectedTime(ply) * timeScaleFactor); + setup.commands.emplace_back("go movetime " + std::to_string(correctedTime)); + + ply += 1; + } + } + + return setup; +} + +} // namespace Stockfish \ No newline at end of file diff --git a/stockfish/src/benchmark.h b/stockfish/src/benchmark.h new file mode 100644 index 0000000000000000000000000000000000000000..d6bdc275df949adcc3c5d18272c54eb553b46acd --- /dev/null +++ b/stockfish/src/benchmark.h @@ -0,0 +1,42 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef BENCHMARK_H_INCLUDED +#define BENCHMARK_H_INCLUDED + +#include +#include +#include + +namespace Stockfish::Benchmark { + +std::vector setup_bench(const std::string&, std::istream&); + +struct BenchmarkSetup { + int ttSize; + int threads; + std::vector commands; + std::string originalInvocation; + std::string filledInvocation; +}; + +BenchmarkSetup setup_benchmark(std::istream&); + +} // namespace Stockfish + +#endif // #ifndef BENCHMARK_H_INCLUDED diff --git a/stockfish/src/bitboard.cpp b/stockfish/src/bitboard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8798c5701d566f3e85c60ce270fd13a947862b36 --- /dev/null +++ b/stockfish/src/bitboard.cpp @@ -0,0 +1,228 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "bitboard.h" + +#include +#include +#include + +#include "misc.h" + +namespace Stockfish { + +uint8_t PopCnt16[1 << 16]; +uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; + +Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; +Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; +Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; + +alignas(64) Magic Magics[SQUARE_NB][2]; + +namespace { + +Bitboard RookTable[0x19000]; // To store rook attacks +Bitboard BishopTable[0x1480]; // To store bishop attacks + +void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]); + +// Returns the bitboard of target square for the given step +// from the given square. If the step is off the board, returns empty bitboard. +Bitboard safe_destination(Square s, int step) { + Square to = Square(s + step); + return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); +} +} + +// Returns an ASCII representation of a bitboard suitable +// to be printed to standard output. Useful for debugging. +std::string Bitboards::pretty(Bitboard b) { + + std::string s = "+---+---+---+---+---+---+---+---+\n"; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + s += b & make_square(f, r) ? "| X " : "| "; + + s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; + } + s += " a b c d e f g h\n"; + + return s; +} + + +// Initializes various bitboard tables. It is called at +// startup and relies on global objects to be already zero-initialized. +void Bitboards::init() { + + for (unsigned i = 0; i < (1 << 16); ++i) + PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); + + init_magics(ROOK, RookTable, Magics); + init_magics(BISHOP, BishopTable, Magics); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + + for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) + PseudoAttacks[KING][s1] |= safe_destination(s1, step); + + for (int step : {-17, -15, -10, -6, 6, 10, 15, 17}) + PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); + + PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); + PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb(s1, 0); + + for (PieceType pt : {BISHOP, ROOK}) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { + if (PseudoAttacks[pt][s1] & s2) + { + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = + (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + } + BetweenBB[s1][s2] |= s2; + } + } +} + +namespace { + +Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { + + Bitboard attacks = 0; + Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; + Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + + for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) + { + Square s = sq; + while (safe_destination(s, d)) + { + attacks |= (s += d); + if (occupied & s) + { + break; + } + } + } + + return attacks; +} + + +// Computes all rook and bishop attacks at startup. Magic +// bitboards are used to look up attacks of sliding pieces. As a reference see +// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use +// the so called "fancy" approach. +void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { + +#ifndef USE_PEXT + // Optimal PRNG seeds to pick the correct magics in the shortest time + int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020}, + {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}; + + Bitboard occupancy[4096]; + int epoch[4096] = {}, cnt = 0; +#endif + Bitboard reference[4096]; + int size = 0; + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + // Board edges are not considered in the relevant occupancies + Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); + + // Given a square 's', the mask is the bitboard of sliding attacks from + // 's' computed on an empty board. The index must be big enough to contain + // all the attacks for each possible subset of the mask and so is 2 power + // the number of 1s of the mask. Hence we deduce the size of the shift to + // apply to the 64 or 32 bits word to get the index. + Magic& m = magics[s][pt - BISHOP]; + m.mask = sliding_attack(pt, s, 0) & ~edges; +#ifndef USE_PEXT + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); +#endif + // Set the offset for the attacks table of the square. We have individual + // table sizes for each square with "Fancy Magic Bitboards". + m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size; + size = 0; + + // Use Carry-Rippler trick to enumerate all subsets of masks[s] and + // store the corresponding sliding attack bitboard in reference[]. + Bitboard b = 0; + do + { +#ifndef USE_PEXT + occupancy[size] = b; +#endif + reference[size] = sliding_attack(pt, s, b); + + if (HasPext) + m.attacks[pext(b, m.mask)] = reference[size]; + + size++; + b = (b - m.mask) & m.mask; + } while (b); + +#ifndef USE_PEXT + PRNG rng(seeds[Is64Bit][rank_of(s)]); + + // Find a magic for square 's' picking up an (almost) random number + // until we find the one that passes the verification test. + for (int i = 0; i < size;) + { + for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;) + m.magic = rng.sparse_rand(); + + // A good magic must map every possible occupancy to an index that + // looks up the correct sliding attack in the attacks[s] database. + // Note that we build up the database for square 's' as a side + // effect of verifying the magic. Keep track of the attempt count + // and save it in epoch[], little speed-up trick to avoid resetting + // m.attacks[] after every failed attempt. + for (++cnt, i = 0; i < size; ++i) + { + unsigned idx = m.index(occupancy[i]); + + if (epoch[idx] < cnt) + { + epoch[idx] = cnt; + m.attacks[idx] = reference[i]; + } + else if (m.attacks[idx] != reference[i]) + break; + } + } +#endif + } +} +} + +} // namespace Stockfish diff --git a/stockfish/src/bitboard.h b/stockfish/src/bitboard.h new file mode 100644 index 0000000000000000000000000000000000000000..df15113bda3428b32721893f891d7dc748ae4440 --- /dev/null +++ b/stockfish/src/bitboard.h @@ -0,0 +1,374 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef BITBOARD_H_INCLUDED +#define BITBOARD_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" + +namespace Stockfish { + +namespace Bitboards { + +void init(); +std::string pretty(Bitboard b); + +} // namespace Stockfish::Bitboards + +constexpr Bitboard FileABB = 0x0101010101010101ULL; +constexpr Bitboard FileBBB = FileABB << 1; +constexpr Bitboard FileCBB = FileABB << 2; +constexpr Bitboard FileDBB = FileABB << 3; +constexpr Bitboard FileEBB = FileABB << 4; +constexpr Bitboard FileFBB = FileABB << 5; +constexpr Bitboard FileGBB = FileABB << 6; +constexpr Bitboard FileHBB = FileABB << 7; + +constexpr Bitboard Rank1BB = 0xFF; +constexpr Bitboard Rank2BB = Rank1BB << (8 * 1); +constexpr Bitboard Rank3BB = Rank1BB << (8 * 2); +constexpr Bitboard Rank4BB = Rank1BB << (8 * 3); +constexpr Bitboard Rank5BB = Rank1BB << (8 * 4); +constexpr Bitboard Rank6BB = Rank1BB << (8 * 5); +constexpr Bitboard Rank7BB = Rank1BB << (8 * 6); +constexpr Bitboard Rank8BB = Rank1BB << (8 * 7); + +extern uint8_t PopCnt16[1 << 16]; +extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; + +extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; +extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; + + +// Magic holds all magic bitboards relevant data for a single square +struct Magic { + Bitboard mask; + Bitboard* attacks; +#ifndef USE_PEXT + Bitboard magic; + unsigned shift; +#endif + + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { + +#ifdef USE_PEXT + return unsigned(pext(occupied, mask)); +#else + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); + + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; +#endif + } + + Bitboard attacks_bb(Bitboard occupied) const { return attacks[index(occupied)]; } +}; + +extern Magic Magics[SQUARE_NB][2]; + +constexpr Bitboard square_bb(Square s) { + assert(is_ok(s)); + return (1ULL << s); +} + + +// Overloads of bitwise operators between a Bitboard and a Square for testing +// whether a given bit is set in a bitboard, and for setting and clearing bits. + +inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } +inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } +inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } +inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } +inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } + +inline Bitboard operator&(Square s, Bitboard b) { return b & s; } +inline Bitboard operator|(Square s, Bitboard b) { return b | s; } +inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } + +inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } + +constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } + + +// rank_bb() and file_bb() return a bitboard representing all the squares on +// the given file or rank. + +constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); } + +constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); } + +constexpr Bitboard file_bb(File f) { return FileABB << f; } + +constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); } + + +// Moves a bitboard one or two steps as specified by the direction D +template +constexpr Bitboard shift(Bitboard b) { + return D == NORTH ? b << 8 + : D == SOUTH ? b >> 8 + : D == NORTH + NORTH ? b << 16 + : D == SOUTH + SOUTH ? b >> 16 + : D == EAST ? (b & ~FileHBB) << 1 + : D == WEST ? (b & ~FileABB) >> 1 + : D == NORTH_EAST ? (b & ~FileHBB) << 9 + : D == NORTH_WEST ? (b & ~FileABB) << 7 + : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 + : D == SOUTH_WEST ? (b & ~FileABB) >> 9 + : 0; +} + + +// Returns the squares attacked by pawns of the given color +// from the squares in the given bitboard. +template +constexpr Bitboard pawn_attacks_bb(Bitboard b) { + return C == WHITE ? shift(b) | shift(b) + : shift(b) | shift(b); +} + +inline Bitboard pawn_attacks_bb(Color c, Square s) { + + assert(is_ok(s)); + return PawnAttacks[c][s]; +} + +// Returns a bitboard representing an entire line (from board edge +// to board edge) that intersects the two given squares. If the given squares +// are not on a same file/rank/diagonal, the function returns 0. For instance, +// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. +inline Bitboard line_bb(Square s1, Square s2) { + + assert(is_ok(s1) && is_ok(s2)); + return LineBB[s1][s2]; +} + + +// Returns a bitboard representing the squares in the semi-open +// segment between the squares s1 and s2 (excluding s1 but including s2). If the +// given squares are not on a same file/rank/diagonal, it returns s2. For instance, +// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but +// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick +// allows to generate non-king evasion moves faster: the defending piece must either +// interpose itself to cover the check or capture the checking piece. +inline Bitboard between_bb(Square s1, Square s2) { + + assert(is_ok(s1) && is_ok(s2)); + return BetweenBB[s1][s2]; +} + +// Returns true if the squares s1, s2 and s3 are aligned either on a +// straight or on a diagonal line. +inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } + + +// distance() functions return the distance between x and y, defined as the +// number of steps for a king in x to reach y. + +template +inline int distance(Square x, Square y); + +template<> +inline int distance(Square x, Square y) { + return std::abs(file_of(x) - file_of(y)); +} + +template<> +inline int distance(Square x, Square y) { + return std::abs(rank_of(x) - rank_of(y)); +} + +template<> +inline int distance(Square x, Square y) { + return SquareDistance[x][y]; +} + +inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } + +// Returns the pseudo attacks of the given piece type +// assuming an empty board. +template +inline Bitboard attacks_bb(Square s) { + + assert((Pt != PAWN) && (is_ok(s))); + return PseudoAttacks[Pt][s]; +} + + +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. +template +inline Bitboard attacks_bb(Square s, Bitboard occupied) { + + assert((Pt != PAWN) && (is_ok(s))); + + switch (Pt) + { + case BISHOP : + case ROOK : + return Magics[s][Pt - BISHOP].attacks_bb(occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[Pt][s]; + } +} + +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. +inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { + + assert((pt != PAWN) && (is_ok(s))); + + switch (pt) + { + case BISHOP : + return attacks_bb(s, occupied); + case ROOK : + return attacks_bb(s, occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[pt][s]; + } +} + + +// Counts the number of non-zero bits in a bitboard. +inline int popcount(Bitboard b) { + +#ifndef USE_POPCNT + + std::uint16_t indices[4]; + std::memcpy(indices, &b, sizeof(b)); + return PopCnt16[indices[0]] + PopCnt16[indices[1]] + PopCnt16[indices[2]] + + PopCnt16[indices[3]]; + +#elif defined(_MSC_VER) + + return int(_mm_popcnt_u64(b)); + +#else // Assumed gcc or compatible compiler + + return __builtin_popcountll(b); + +#endif +} + +// Returns the least significant bit in a non-zero bitboard. +inline Square lsb(Bitboard b) { + assert(b); + +#if defined(__GNUC__) // GCC, Clang, ICX + + return Square(__builtin_ctzll(b)); + +#elif defined(_MSC_VER) + #ifdef _WIN64 // MSVC, WIN64 + + unsigned long idx; + _BitScanForward64(&idx, b); + return Square(idx); + + #else // MSVC, WIN32 + unsigned long idx; + + if (b & 0xffffffff) + { + _BitScanForward(&idx, int32_t(b)); + return Square(idx); + } + else + { + _BitScanForward(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } + #endif +#else // Compiler is neither GCC nor MSVC compatible + #error "Compiler not supported." +#endif +} + +// Returns the most significant bit in a non-zero bitboard. +inline Square msb(Bitboard b) { + assert(b); + +#if defined(__GNUC__) // GCC, Clang, ICX + + return Square(63 ^ __builtin_clzll(b)); + +#elif defined(_MSC_VER) + #ifdef _WIN64 // MSVC, WIN64 + + unsigned long idx; + _BitScanReverse64(&idx, b); + return Square(idx); + + #else // MSVC, WIN32 + + unsigned long idx; + + if (b >> 32) + { + _BitScanReverse(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } + else + { + _BitScanReverse(&idx, int32_t(b)); + return Square(idx); + } + #endif +#else // Compiler is neither GCC nor MSVC compatible + #error "Compiler not supported." +#endif +} + +// Returns the bitboard of the least significant +// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). +inline Bitboard least_significant_square_bb(Bitboard b) { + assert(b); + return b & -b; +} + +// Finds and clears the least significant bit in a non-zero bitboard. +inline Square pop_lsb(Bitboard& b) { + assert(b); + const Square s = lsb(b); + b &= b - 1; + return s; +} + +} // namespace Stockfish + +#endif // #ifndef BITBOARD_H_INCLUDED diff --git a/stockfish/src/engine.cpp b/stockfish/src/engine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a4c0bb1ebebd77768c43ff9182157a81fe6bcdb8 --- /dev/null +++ b/stockfish/src/engine.cpp @@ -0,0 +1,372 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "engine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "nnue/network.h" +#include "nnue/nnue_common.h" +#include "numa.h" +#include "perft.h" +#include "position.h" +#include "search.h" +#include "syzygy/tbprobe.h" +#include "types.h" +#include "uci.h" +#include "ucioption.h" + +namespace Stockfish { + +namespace NN = Eval::NNUE; + +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency())); + +Engine::Engine(std::optional path) : + binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""), + numaContext(NumaConfig::from_system()), + states(new std::deque(1)), + threads(), + networks( + numaContext, + NN::Networks( + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { + pos.set(StartFEN, false, &states->back()); + + + options.add( // + "Debug Log File", Option("", [](const Option& o) { + start_logger(o); + return std::nullopt; + })); + + options.add( // + "NumaPolicy", Option("auto", [this](const Option& o) { + set_numa_config_from_option(o); + return numa_config_information_as_string() + "\n" + + thread_allocation_information_as_string(); + })); + + options.add( // + "Threads", Option(1, 1, MaxThreads, [this](const Option&) { + resize_threads(); + return thread_allocation_information_as_string(); + })); + + options.add( // + "Hash", Option(16, 1, MaxHashMB, [this](const Option& o) { + set_tt_size(o); + return std::nullopt; + })); + + options.add( // + "Clear Hash", Option([this](const Option&) { + search_clear(); + return std::nullopt; + })); + + options.add( // + "Ponder", Option(false)); + + options.add( // + "MultiPV", Option(1, 1, MAX_MOVES)); + + options.add("Skill Level", Option(20, 0, 20)); + + options.add("Move Overhead", Option(10, 0, 5000)); + + options.add("nodestime", Option(0, 0, 10000)); + + options.add("UCI_Chess960", Option(false)); + + options.add("UCI_LimitStrength", Option(false)); + + options.add("UCI_Elo", + Option(Stockfish::Search::Skill::LowestElo, Stockfish::Search::Skill::LowestElo, + Stockfish::Search::Skill::HighestElo)); + + options.add("UCI_ShowWDL", Option(false)); + + options.add( // + "SyzygyPath", Option("", [](const Option& o) { + Tablebases::init(o); + return std::nullopt; + })); + + options.add("SyzygyProbeDepth", Option(1, 1, 100)); + + options.add("Syzygy50MoveRule", Option(true)); + + options.add("SyzygyProbeLimit", Option(7, 0, 7)); + + options.add( // + "EvalFile", Option(EvalFileDefaultNameBig, [this](const Option& o) { + load_big_network(o); + return std::nullopt; + })); + + options.add( // + "EvalFileSmall", Option(EvalFileDefaultNameSmall, [this](const Option& o) { + load_small_network(o); + return std::nullopt; + })); + + load_networks(); + resize_threads(); +} + +std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { + verify_networks(); + + return Benchmark::perft(fen, depth, isChess960); +} + +void Engine::go(Search::LimitsType& limits) { + assert(limits.perft == 0); + verify_networks(); + + threads.start_thinking(options, pos, states, limits); +} +void Engine::stop() { threads.stop = true; } + +void Engine::search_clear() { + wait_for_search_finished(); + + tt.clear(threads); + threads.clear(); + + // @TODO wont work with multiple instances + Tablebases::init(options["SyzygyPath"]); // Free mapped files +} + +void Engine::set_on_update_no_moves(std::function&& f) { + updateContext.onUpdateNoMoves = std::move(f); +} + +void Engine::set_on_update_full(std::function&& f) { + updateContext.onUpdateFull = std::move(f); +} + +void Engine::set_on_iter(std::function&& f) { + updateContext.onIter = std::move(f); +} + +void Engine::set_on_bestmove(std::function&& f) { + updateContext.onBestmove = std::move(f); +} + +void Engine::set_on_verify_networks(std::function&& f) { + onVerifyNetworks = std::move(f); +} + +void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); } + +void Engine::set_position(const std::string& fen, const std::vector& moves) { + // Drop the old state and create a new one + states = StateListPtr(new std::deque(1)); + pos.set(fen, options["UCI_Chess960"], &states->back()); + + for (const auto& move : moves) + { + auto m = UCIEngine::to_move(pos, move); + + if (m == Move::none()) + break; + + states->emplace_back(); + pos.do_move(m, states->back()); + } +} + +// modifiers + +void Engine::set_numa_config_from_option(const std::string& o) { + if (o == "auto" || o == "system") + { + numaContext.set_numa_config(NumaConfig::from_system()); + } + else if (o == "hardware") + { + // Don't respect affinity set in the system. + numaContext.set_numa_config(NumaConfig::from_system(false)); + } + else if (o == "none") + { + numaContext.set_numa_config(NumaConfig{}); + } + else + { + numaContext.set_numa_config(NumaConfig::from_string(o)); + } + + // Force reallocation of threads in case affinities need to change. + resize_threads(); + threads.ensure_network_replicated(); +} + +void Engine::resize_threads() { + threads.wait_for_search_finished(); + threads.set(numaContext.get_numa_config(), {options, threads, tt, networks}, updateContext); + + // Reallocate the hash with the new threadpool size + set_tt_size(options["Hash"]); + threads.ensure_network_replicated(); +} + +void Engine::set_tt_size(size_t mb) { + wait_for_search_finished(); + tt.resize(mb, threads); +} + +void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } + +// network related + +void Engine::verify_networks() const { + networks->big.verify(options["EvalFile"], onVerifyNetworks); + networks->small.verify(options["EvalFileSmall"], onVerifyNetworks); +} + +void Engine::load_networks() { + networks.modify_and_replicate([this](NN::Networks& networks_) { + networks_.big.load(binaryDirectory, options["EvalFile"]); + networks_.small.load(binaryDirectory, options["EvalFileSmall"]); + }); + threads.clear(); + threads.ensure_network_replicated(); +} + +void Engine::load_big_network(const std::string& file) { + networks.modify_and_replicate( + [this, &file](NN::Networks& networks_) { networks_.big.load(binaryDirectory, file); }); + threads.clear(); + threads.ensure_network_replicated(); +} + +void Engine::load_small_network(const std::string& file) { + networks.modify_and_replicate( + [this, &file](NN::Networks& networks_) { networks_.small.load(binaryDirectory, file); }); + threads.clear(); + threads.ensure_network_replicated(); +} + +void Engine::save_network(const std::pair, std::string> files[2]) { + networks.modify_and_replicate([&files](NN::Networks& networks_) { + networks_.big.save(files[0].first); + networks_.small.save(files[1].first); + }); +} + +// utility functions + +void Engine::trace_eval() const { + StateListPtr trace_states(new std::deque(1)); + Position p; + p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back()); + + verify_networks(); + + sync_cout << "\n" << Eval::trace(p, *networks) << sync_endl; +} + +const OptionsMap& Engine::get_options() const { return options; } +OptionsMap& Engine::get_options() { return options; } + +std::string Engine::fen() const { return pos.fen(); } + +void Engine::flip() { pos.flip(); } + +std::string Engine::visualize() const { + std::stringstream ss; + ss << pos; + return ss.str(); +} + +int Engine::get_hashfull(int maxAge) const { return tt.hashfull(maxAge); } + +std::vector> Engine::get_bound_thread_count_by_numa_node() const { + auto counts = threads.get_bound_thread_count_by_numa_node(); + const NumaConfig& cfg = numaContext.get_numa_config(); + std::vector> ratios; + NumaIndex n = 0; + for (; n < counts.size(); ++n) + ratios.emplace_back(counts[n], cfg.num_cpus_in_numa_node(n)); + if (!counts.empty()) + for (; n < cfg.num_numa_nodes(); ++n) + ratios.emplace_back(0, cfg.num_cpus_in_numa_node(n)); + return ratios; +} + +std::string Engine::get_numa_config_as_string() const { + return numaContext.get_numa_config().to_string(); +} + +std::string Engine::numa_config_information_as_string() const { + auto cfgStr = get_numa_config_as_string(); + return "Available processors: " + cfgStr; +} + +std::string Engine::thread_binding_information_as_string() const { + auto boundThreadsByNode = get_bound_thread_count_by_numa_node(); + std::stringstream ss; + if (boundThreadsByNode.empty()) + return ss.str(); + + bool isFirst = true; + + for (auto&& [current, total] : boundThreadsByNode) + { + if (!isFirst) + ss << ":"; + ss << current << "/" << total; + isFirst = false; + } + + return ss.str(); +} + +std::string Engine::thread_allocation_information_as_string() const { + std::stringstream ss; + + size_t threadsSize = threads.size(); + ss << "Using " << threadsSize << (threadsSize > 1 ? " threads" : " thread"); + + auto boundThreadsByNodeStr = thread_binding_information_as_string(); + if (boundThreadsByNodeStr.empty()) + return ss.str(); + + ss << " with NUMA node thread binding: "; + ss << boundThreadsByNodeStr; + + return ss.str(); +} +} diff --git a/stockfish/src/engine.h b/stockfish/src/engine.h new file mode 100644 index 0000000000000000000000000000000000000000..d26844f4ce4a96f0da8d76a25efd54cc1574113f --- /dev/null +++ b/stockfish/src/engine.h @@ -0,0 +1,130 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef ENGINE_H_INCLUDED +#define ENGINE_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nnue/network.h" +#include "numa.h" +#include "position.h" +#include "search.h" +#include "syzygy/tbprobe.h" // for Stockfish::Depth +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +namespace Stockfish { + +class Engine { + public: + using InfoShort = Search::InfoShort; + using InfoFull = Search::InfoFull; + using InfoIter = Search::InfoIteration; + + Engine(std::optional path = std::nullopt); + + // Cannot be movable due to components holding backreferences to fields + Engine(const Engine&) = delete; + Engine(Engine&&) = delete; + Engine& operator=(const Engine&) = delete; + Engine& operator=(Engine&&) = delete; + + ~Engine() { wait_for_search_finished(); } + + std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960); + + // non blocking call to start searching + void go(Search::LimitsType&); + // non blocking call to stop searching + void stop(); + + // blocking call to wait for search to finish + void wait_for_search_finished(); + // set a new position, moves are in UCI format + void set_position(const std::string& fen, const std::vector& moves); + + // modifiers + + void set_numa_config_from_option(const std::string& o); + void resize_threads(); + void set_tt_size(size_t mb); + void set_ponderhit(bool); + void search_clear(); + + void set_on_update_no_moves(std::function&&); + void set_on_update_full(std::function&&); + void set_on_iter(std::function&&); + void set_on_bestmove(std::function&&); + void set_on_verify_networks(std::function&&); + + // network related + + void verify_networks() const; + void load_networks(); + void load_big_network(const std::string& file); + void load_small_network(const std::string& file); + void save_network(const std::pair, std::string> files[2]); + + // utility functions + + void trace_eval() const; + + const OptionsMap& get_options() const; + OptionsMap& get_options(); + + int get_hashfull(int maxAge = 0) const; + + std::string fen() const; + void flip(); + std::string visualize() const; + std::vector> get_bound_thread_count_by_numa_node() const; + std::string get_numa_config_as_string() const; + std::string numa_config_information_as_string() const; + std::string thread_allocation_information_as_string() const; + std::string thread_binding_information_as_string() const; + + private: + const std::string binaryDirectory; + + NumaReplicationContext numaContext; + + Position pos; + StateListPtr states; + + OptionsMap options; + ThreadPool threads; + TranspositionTable tt; + LazyNumaReplicated networks; + + Search::SearchManager::UpdateContext updateContext; + std::function onVerifyNetworks; +}; + +} // namespace Stockfish + + +#endif // #ifndef ENGINE_H_INCLUDED diff --git a/stockfish/src/evaluate.cpp b/stockfish/src/evaluate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ccb089d977711a63f88da4b5b3c1ac578555c0de --- /dev/null +++ b/stockfish/src/evaluate.cpp @@ -0,0 +1,128 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "evaluate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nnue/network.h" +#include "nnue/nnue_misc.h" +#include "position.h" +#include "types.h" +#include "uci.h" +#include "nnue/nnue_accumulator.h" + +namespace Stockfish { + +// Returns a static, purely materialistic evaluation of the position from +// the point of view of the given color. It can be divided by PawnValue to get +// an approximation of the material advantage on the board in terms of pawns. +int Eval::simple_eval(const Position& pos, Color c) { + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); +} + +bool Eval::use_smallnet(const Position& pos) { + int simpleEval = simple_eval(pos, pos.side_to_move()); + return std::abs(simpleEval) > 962; +} + +// Evaluate is the evaluator for the outer world. It returns a static evaluation +// of the position from the point of view of the side to move. +Value Eval::evaluate(const Eval::NNUE::Networks& networks, + const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, + Eval::NNUE::AccumulatorCaches& caches, + int optimism) { + + assert(!pos.checkers()); + + bool smallNet = use_smallnet(pos); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small) + : networks.big.evaluate(pos, accumulators, &caches.big); + + Value nnue = (125 * psqt + 131 * positional) / 128; + + // Re-evaluate the position when higher eval accuracy is worth the time spent + if (smallNet && (std::abs(nnue) < 236)) + { + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + nnue = (125 * psqt + 131 * positional) / 128; + smallNet = false; + } + + // Blend optimism and eval with nnue complexity + int nnueComplexity = std::abs(psqt - positional); + optimism += optimism * nnueComplexity / 468; + nnue -= nnue * nnueComplexity / 18000; + + int material = 535 * pos.count() + pos.non_pawn_material(); + int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; + + // Damp down the evaluation linearly when shuffling + v -= v * pos.rule50_count() / 212; + + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return v; +} + +// Like evaluate(), but instead of returning a value, it returns +// a string (suitable for outputting to stdout) that contains the detailed +// descriptions and values of each evaluation term. Useful for debugging. +// Trace scores are from white's point of view +std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { + + if (pos.checkers()) + return "Final evaluation: none (in check)"; + + Eval::NNUE::AccumulatorStack accumulators; + auto caches = std::make_unique(networks); + + accumulators.reset(pos, networks, *caches); + + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; + + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches->big); + Value v = psqt + positional; + v = pos.side_to_move() == WHITE ? v : -v; + ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; + + v = evaluate(networks, pos, accumulators, *caches, VALUE_ZERO); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; + ss << " [with scaled NNUE, ...]"; + ss << "\n"; + + return ss.str(); +} + +} // namespace Stockfish diff --git a/stockfish/src/evaluate.h b/stockfish/src/evaluate.h new file mode 100644 index 0000000000000000000000000000000000000000..07b914007ea2688e9803696a175f527671caffea --- /dev/null +++ b/stockfish/src/evaluate.h @@ -0,0 +1,58 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef EVALUATE_H_INCLUDED +#define EVALUATE_H_INCLUDED + +#include + +#include "types.h" + +namespace Stockfish { + +class Position; + +namespace Eval { + +// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue +// for the build process (profile-build and fishtest) to work. Do not change the +// name of the macro or the location where this macro is defined, as it is used +// in the Makefile/Fishtest. +#define EvalFileDefaultNameBig "nn-1c0000000000.nnue" +#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" + +namespace NNUE { +struct Networks; +struct AccumulatorCaches; +class AccumulatorStack; +} + +std::string trace(Position& pos, const Eval::NNUE::Networks& networks); + +int simple_eval(const Position& pos, Color c); +bool use_smallnet(const Position& pos); +Value evaluate(const NNUE::Networks& networks, + const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, + Eval::NNUE::AccumulatorCaches& caches, + int optimism); +} // namespace Eval + +} // namespace Stockfish + +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/stockfish/src/history.h b/stockfish/src/history.h new file mode 100644 index 0000000000000000000000000000000000000000..ec245230a76b697802169558acaf2390003a2436 --- /dev/null +++ b/stockfish/src/history.h @@ -0,0 +1,171 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef HISTORY_H_INCLUDED +#define HISTORY_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep + +#include "misc.h" +#include "position.h" + +namespace Stockfish { + +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; +constexpr int LOW_PLY_HISTORY_SIZE = 4; + +static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, + "PAWN_HISTORY_SIZE has to be a power of 2"); + +static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, + "CORRECTION_HISTORY_SIZE has to be a power of 2"); + +enum PawnHistoryType { + Normal, + Correction +}; + +template +inline int pawn_structure_index(const Position& pos) { + return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); +} + +inline int minor_piece_index(const Position& pos) { + return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); +} + +template +inline int non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); +} + +// StatsEntry is the container of various numerical statistics. We use a class +// instead of a naked value to directly call history update operator<<() on +// the entry. The first template parameter T is the base type of the array, +// and the second template parameter D limits the range of updates in [-D, D] +// when we update values with the << operator +template +class StatsEntry { + + static_assert(std::is_arithmetic_v, "Not an arithmetic type"); + static_assert(D <= std::numeric_limits::max(), "D overflows T"); + + T entry; + + public: + StatsEntry& operator=(const T& v) { + entry = v; + return *this; + } + operator const T&() const { return entry; } + + void operator<<(int bonus) { + // Make sure that bonus is in range [-D, D] + int clampedBonus = std::clamp(bonus, -D, D); + entry += clampedBonus - entry * std::abs(clampedBonus) / D; + + assert(std::abs(entry) <= D); + } +}; + +enum StatsType { + NoCaptures, + Captures +}; + +template +using Stats = MultiArray, Sizes...>; + +// ButterflyHistory records how often quiet moves have been successful or unsuccessful +// during the current search, and is used for reduction and move ordering decisions. +// It uses 2 tables (one for each color) indexed by the move's from and to squares, +// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) +using ButterflyHistory = Stats; + +// LowPlyHistory is adressed by play and move's from and to squares, used +// to improve move ordering near the root +using LowPlyHistory = + Stats; + +// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] +using CapturePieceToHistory = Stats; + +// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] +using PieceToHistory = Stats; + +// ContinuationHistory is the combined history of a given pair of moves, usually +// the current one given a previous one. The nested history table is based on +// PieceToHistory instead of ButterflyBoards. +// (~63 elo) +using ContinuationHistory = MultiArray; + +// PawnHistory is addressed by the pawn structure and a move's [piece][to] +using PawnHistory = Stats; + +// Correction histories record differences between the static evaluation of +// positions and their search score. It is used to improve the static evaluation +// used by some search heuristics. +// see https://www.chessprogramming.org/Static_Evaluation_Correction_History +enum CorrHistType { + Pawn, // By color and pawn structure + Minor, // By color and positions of minor pieces (Knight, Bishop) + NonPawn, // By non-pawn material positions and color + PieceTo, // By [piece][to] move + Continuation, // Combined history of move pairs +}; + +namespace Detail { + +template +struct CorrHistTypedef { + using type = Stats; +}; + +template<> +struct CorrHistTypedef { + using type = Stats; +}; + +template<> +struct CorrHistTypedef { + using type = MultiArray::type, PIECE_NB, SQUARE_NB>; +}; + +template<> +struct CorrHistTypedef { + using type = + Stats; +}; + +} + +template +using CorrectionHistory = typename Detail::CorrHistTypedef::type; + +} // namespace Stockfish + +#endif // #ifndef HISTORY_H_INCLUDED diff --git a/stockfish/src/incbin/UNLICENCE b/stockfish/src/incbin/UNLICENCE new file mode 100644 index 0000000000000000000000000000000000000000..32484ab5e7026f9a1f15c2f8c08b1418802e02a8 --- /dev/null +++ b/stockfish/src/incbin/UNLICENCE @@ -0,0 +1,26 @@ +The file "incbin.h" is free and unencumbered software released into +the public domain by Dale Weiler, see: + + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/stockfish/src/incbin/incbin.h b/stockfish/src/incbin/incbin.h new file mode 100644 index 0000000000000000000000000000000000000000..3f662e15dad1864dc6031c6491a21a6815994f1a --- /dev/null +++ b/stockfish/src/incbin/incbin.h @@ -0,0 +1,476 @@ +/** + * @file incbin.h + * @author Dale Weiler + * @brief Utility for including binary files + * + * Facilities for including binary files into the current translation unit and + * making use from them externally in other translation units. + */ +#ifndef INCBIN_HDR +#define INCBIN_HDR +#include +#if defined(__AVX512BW__) || \ + defined(__AVX512CD__) || \ + defined(__AVX512DQ__) || \ + defined(__AVX512ER__) || \ + defined(__AVX512PF__) || \ + defined(__AVX512VL__) || \ + defined(__AVX512F__) +# define INCBIN_ALIGNMENT_INDEX 6 +#elif defined(__AVX__) || \ + defined(__AVX2__) +# define INCBIN_ALIGNMENT_INDEX 5 +#elif defined(__SSE__) || \ + defined(__SSE2__) || \ + defined(__SSE3__) || \ + defined(__SSSE3__) || \ + defined(__SSE4_1__) || \ + defined(__SSE4_2__) || \ + defined(__neon__) || \ + defined(__ARM_NEON) || \ + defined(__ALTIVEC__) +# define INCBIN_ALIGNMENT_INDEX 4 +#elif ULONG_MAX != 0xffffffffu +# define INCBIN_ALIGNMENT_INDEX 3 +# else +# define INCBIN_ALIGNMENT_INDEX 2 +#endif + +/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */ +#define INCBIN_ALIGN_SHIFT_0 1 +#define INCBIN_ALIGN_SHIFT_1 2 +#define INCBIN_ALIGN_SHIFT_2 4 +#define INCBIN_ALIGN_SHIFT_3 8 +#define INCBIN_ALIGN_SHIFT_4 16 +#define INCBIN_ALIGN_SHIFT_5 32 +#define INCBIN_ALIGN_SHIFT_6 64 + +/* Actual alignment value */ +#define INCBIN_ALIGNMENT \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \ + INCBIN_ALIGNMENT_INDEX) + +/* Stringize */ +#define INCBIN_STR(X) \ + #X +#define INCBIN_STRINGIZE(X) \ + INCBIN_STR(X) +/* Concatenate */ +#define INCBIN_CAT(X, Y) \ + X ## Y +#define INCBIN_CONCATENATE(X, Y) \ + INCBIN_CAT(X, Y) +/* Deferred macro expansion */ +#define INCBIN_EVAL(X) \ + X +#define INCBIN_INVOKE(N, ...) \ + INCBIN_EVAL(N(__VA_ARGS__)) +/* Variable argument count for overloading by arity */ +#define INCBIN_VA_ARG_COUNTER(_1, _2, _3, N, ...) N +#define INCBIN_VA_ARGC(...) INCBIN_VA_ARG_COUNTER(__VA_ARGS__, 3, 2, 1, 0) + +/* Green Hills uses a different directive for including binary data */ +#if defined(__ghs__) +# if (__ghs_asm == 2) +# define INCBIN_MACRO ".file" +/* Or consider the ".myrawdata" entry in the ld file */ +# else +# define INCBIN_MACRO "\tINCBIN" +# endif +#else +# define INCBIN_MACRO ".incbin" +#endif + +#ifndef _MSC_VER +# define INCBIN_ALIGN \ + __attribute__((aligned(INCBIN_ALIGNMENT))) +#else +# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT)) +#endif + +#if defined(__arm__) || /* GNU C and RealView */ \ + defined(__arm) || /* Diab */ \ + defined(_ARM) /* ImageCraft */ +# define INCBIN_ARM +#endif + +#ifdef __GNUC__ +/* Utilize .balign where supported */ +# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".balign 1\n" +#elif defined(INCBIN_ARM) +/* + * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is + * the shift count. This is the value passed to `.align' + */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n" +# define INCBIN_ALIGN_BYTE ".align 0\n" +#else +/* We assume other inline assembler's treat `.align' as `.balign' */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".align 1\n" +#endif + +/* INCBIN_CONST is used by incbin.c generated files */ +#if defined(__cplusplus) +# define INCBIN_EXTERNAL extern "C" +# define INCBIN_CONST extern const +#else +# define INCBIN_EXTERNAL extern +# define INCBIN_CONST const +#endif + +/** + * @brief Optionally override the linker section into which size and data is + * emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + */ +#if !defined(INCBIN_OUTPUT_SECTION) +# if defined(__APPLE__) +# define INCBIN_OUTPUT_SECTION ".const_data" +# else +# define INCBIN_OUTPUT_SECTION ".rodata" +# endif +#endif + +/** + * @brief Optionally override the linker section into which data is emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + */ +#if !defined(INCBIN_OUTPUT_DATA_SECTION) +# define INCBIN_OUTPUT_DATA_SECTION INCBIN_OUTPUT_SECTION +#endif + +/** + * @brief Optionally override the linker section into which size is emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + * + * @note This is useful for Harvard architectures where program memory cannot + * be directly read from the program without special instructions. With this you + * can chose to put the size variable in RAM rather than ROM. + */ +#if !defined(INCBIN_OUTPUT_SIZE_SECTION) +# define INCBIN_OUTPUT_SIZE_SECTION INCBIN_OUTPUT_SECTION +#endif + +#if defined(__APPLE__) +# include "TargetConditionals.h" +# if defined(TARGET_OS_IPHONE) && !defined(INCBIN_SILENCE_BITCODE_WARNING) +# warning "incbin is incompatible with bitcode. Using the library will break upload to App Store if you have bitcode enabled. Add `#define INCBIN_SILENCE_BITCODE_WARNING` before including this header to silence this warning." +# endif +/* The directives are different for Apple branded compilers */ +# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# define INCBIN_INT ".long " +# define INCBIN_MANGLE "_" +# define INCBIN_BYTE ".byte " +# define INCBIN_TYPE(...) +#else +# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# if defined(__ghs__) +# define INCBIN_INT ".word " +# else +# define INCBIN_INT ".int " +# endif +# if defined(__USER_LABEL_PREFIX__) +# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__) +# else +# define INCBIN_MANGLE "" +# endif +# if defined(INCBIN_ARM) +/* On arm assemblers, `@' is used as a line comment token */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n" +# elif defined(__MINGW32__) || defined(__MINGW64__) +/* Mingw doesn't support this directive either */ +# define INCBIN_TYPE(NAME) +# else +/* It's safe to use `@' on other architectures */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n" +# endif +# define INCBIN_BYTE ".byte " +#endif + +/* List of style types used for symbol names */ +#define INCBIN_STYLE_CAMEL 0 +#define INCBIN_STYLE_SNAKE 1 + +/** + * @brief Specify the prefix to use for symbol names. + * + * @note By default this is "g". + * + * @code + * #define INCBIN_PREFIX incbin + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols instead: + * // const unsigned char incbinFoo[]; + * // const unsigned char *const incbinFoo; + * // const unsigned int incbinFoo; + * @endcode + */ +#if !defined(INCBIN_PREFIX) +# define INCBIN_PREFIX g +#endif + +/** + * @brief Specify the style used for symbol names. + * + * Possible options are + * - INCBIN_STYLE_CAMEL "CamelCase" + * - INCBIN_STYLE_SNAKE "snake_case" + * + * @note By default this is INCBIN_STYLE_CAMEL + * + * @code + * #define INCBIN_STYLE INCBIN_STYLE_SNAKE + * #include "incbin.h" + * INCBIN(foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char foo_data[]; + * // const unsigned char *const foo_end; + * // const unsigned int foo_size; + * @endcode + */ +#if !defined(INCBIN_STYLE) +# define INCBIN_STYLE INCBIN_STYLE_CAMEL +#endif + +/* Style lookup tables */ +#define INCBIN_STYLE_0_DATA Data +#define INCBIN_STYLE_0_END End +#define INCBIN_STYLE_0_SIZE Size +#define INCBIN_STYLE_1_DATA _data +#define INCBIN_STYLE_1_END _end +#define INCBIN_STYLE_1_SIZE _size + +/* Style lookup: returning identifier */ +#define INCBIN_STYLE_IDENT(TYPE) \ + INCBIN_CONCATENATE( \ + INCBIN_STYLE_, \ + INCBIN_CONCATENATE( \ + INCBIN_EVAL(INCBIN_STYLE), \ + INCBIN_CONCATENATE(_, TYPE))) + +/* Style lookup: returning string literal */ +#define INCBIN_STYLE_STRING(TYPE) \ + INCBIN_STRINGIZE( \ + INCBIN_STYLE_IDENT(TYPE)) \ + +/* Generate the global labels by indirectly invoking the macro with our style + * type and concatenating the name against them. */ +#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ + INCBIN_INVOKE( \ + INCBIN_GLOBAL, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) \ + INCBIN_INVOKE( \ + INCBIN_TYPE, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) + +/** + * @brief Externally reference binary data included in another translation unit. + * + * Produces three external symbols that reference the binary data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`. + * @param NAME The name given for the binary data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const unsigned char Foo[]; + * // extern const unsigned char *const Foo; + * // extern const unsigned int Foo; + * @endcode + * + * You may specify a custom optional data type as well as the first argument. + * @code + * INCBIN_EXTERN(custom_type, Foo); + * + * // Now you have the following symbols: + * // extern const custom_type Foo[]; + * // extern const custom_type *const Foo; + * // extern const unsigned int Foo; + * @endcode + */ +#define INCBIN_EXTERN(...) \ + INCBIN_CONCATENATE(INCBIN_EXTERN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__) +#define INCBIN_EXTERN_1(NAME, ...) \ + INCBIN_EXTERN_2(unsigned char, NAME) +#define INCBIN_EXTERN_2(TYPE, NAME) \ + INCBIN_EXTERNAL const INCBIN_ALIGN TYPE \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(DATA))[]; \ + INCBIN_EXTERNAL const INCBIN_ALIGN TYPE *const \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(END)); \ + INCBIN_EXTERNAL const unsigned int \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(SIZE)) + +/** + * @brief Externally reference textual data included in another translation unit. + * + * Produces three external symbols that reference the textual data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name given for the textual data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const char Foo[]; + * // extern const char *const Foo; + * // extern const unsigned int Foo; + * @endcode + */ +#define INCTXT_EXTERN(NAME) \ + INCBIN_EXTERN_2(char, NAME) + +/** + * @brief Include a binary file into the current translation unit. + * + * Includes a binary file into the current translation unit, producing three symbols + * for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`. + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCBIN(Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const unsigned char Icon[]; + * // const unsigned char *const Icon; + * // const unsigned int Icon; + * @endcode + * + * You may specify a custom optional data type as well as the first argument. + * These macros are specialized by arity. + * @code + * INCBIN(custom_type, Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const custom_type Icon[]; + * // const custom_type *const Icon; + * // const unsigned int Icon; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#ifdef _MSC_VER +# define INCBIN(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +# define INCBIN(...) \ + INCBIN_CONCATENATE(INCBIN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__) +# if defined(__GNUC__) +# define INCBIN_1(...) _Pragma("GCC error \"Single argument INCBIN not allowed\"") +# elif defined(__clang__) +# define INCBIN_1(...) _Pragma("clang error \"Single argument INCBIN not allowed\"") +# else +# define INCBIN_1(...) /* Cannot do anything here */ +# endif +# define INCBIN_2(NAME, FILENAME) \ + INCBIN_3(unsigned char, NAME, FILENAME) +# define INCBIN_3(TYPE, NAME, FILENAME) INCBIN_COMMON(TYPE, NAME, FILENAME, /* No terminator for binary data */) +# define INCBIN_COMMON(TYPE, NAME, FILENAME, TERMINATOR) \ + __asm__(INCBIN_SECTION \ + INCBIN_GLOBAL_LABELS(NAME, DATA) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \ + INCBIN_MACRO " \"" FILENAME "\"\n" \ + TERMINATOR \ + INCBIN_GLOBAL_LABELS(NAME, END) \ + INCBIN_ALIGN_BYTE \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \ + INCBIN_BYTE "1\n" \ + INCBIN_GLOBAL_LABELS(NAME, SIZE) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \ + INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \ + INCBIN_ALIGN_HOST \ + ".text\n" \ + ); \ + INCBIN_EXTERN(TYPE, NAME) +#endif + +/** + * @brief Include a textual file into the current translation unit. + * + * This behaves the same as INCBIN except it produces char compatible arrays + * and implicitly adds a null-terminator byte, thus the size of data included + * by this is one byte larger than that of INCBIN. + * + * Includes a textual file into the current translation unit, producing three + * symbols for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCTXT(Readme, "readme.txt"); + * + * // Now you have the following symbols: + * // const char Readme[]; + * // const char *const Readme; + * // const unsigned int Readme; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#if defined(_MSC_VER) +# define INCTXT(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +# define INCTXT(NAME, FILENAME) \ + INCBIN_COMMON(char, NAME, FILENAME, INCBIN_BYTE "0\n") +#endif + +#endif \ No newline at end of file diff --git a/stockfish/src/main.cpp b/stockfish/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e262f387576a61577836de95b65a2403d2224fe0 --- /dev/null +++ b/stockfish/src/main.cpp @@ -0,0 +1,44 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "misc.h" +#include "position.h" +#include "types.h" +#include "uci.h" +#include "tune.h" + +using namespace Stockfish; + +int main(int argc, char* argv[]) { + + std::cout << engine_info() << std::endl; + + Bitboards::init(); + Position::init(); + + UCIEngine uci(argc, argv); + + Tune::init(uci.engine_options()); + + uci.loop(); + + return 0; +} diff --git a/stockfish/src/memory.cpp b/stockfish/src/memory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..92cb650f26fb66d5afaabb1f179fdaee133e4e65 --- /dev/null +++ b/stockfish/src/memory.cpp @@ -0,0 +1,268 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "memory.h" + +#include + +#if __has_include("features.h") + #include +#endif + +#if defined(__linux__) && !defined(__ANDROID__) + #include +#endif + +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \ + || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \ + || defined(__e2k__) + #define POSIXALIGNEDALLOC + #include +#endif + +#ifdef _WIN32 + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif + + #ifndef NOMINMAX + #define NOMINMAX + #endif + + #include // std::hex, std::dec + #include // std::cerr + #include // std::endl + #include + +// The needed Windows API for processor groups could be missed from old Windows +// versions, so instead of calling them directly (forcing the linker to resolve +// the calls at compile time), try to load them at runtime. To do this we need +// first to define the corresponding function pointers. + +extern "C" { +using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); +using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using AdjustTokenPrivileges_t = + bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +} +#endif + + +namespace Stockfish { + +// Wrappers for systems where the c++17 implementation does not guarantee the +// availability of aligned_alloc(). Memory allocated with std_aligned_alloc() +// must be freed with std_aligned_free(). + +void* std_aligned_alloc(size_t alignment, size_t size) { +#if defined(_ISOC11_SOURCE) + return aligned_alloc(alignment, size); +#elif defined(POSIXALIGNEDALLOC) + void* mem = nullptr; + posix_memalign(&mem, alignment, size); + return mem; +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) + return _mm_malloc(size, alignment); +#elif defined(_WIN32) + return _aligned_malloc(size, alignment); +#else + return std::aligned_alloc(alignment, size); +#endif +} + +void std_aligned_free(void* ptr) { + +#if defined(POSIXALIGNEDALLOC) + free(ptr); +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) + _mm_free(ptr); +#elif defined(_WIN32) + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +// aligned_large_pages_alloc() will return suitably aligned memory, +// if possible using large pages. + +#if defined(_WIN32) + +static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { + + #if !defined(_WIN64) + return nullptr; + #else + + HANDLE hProcessToken{}; + LUID luid{}; + void* mem = nullptr; + + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return nullptr; + + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + + auto OpenProcessToken_f = + OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!OpenProcessToken_f) + return nullptr; + auto LookupPrivilegeValueA_f = + LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!LookupPrivilegeValueA_f) + return nullptr; + auto AdjustTokenPrivileges_f = + AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!AdjustTokenPrivileges_f) + return nullptr; + + // We need SeLockMemoryPrivilege, so try to enable it for the process + + if (!OpenProcessToken_f( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; + + if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) + { + TOKEN_PRIVILEGES tp{}; + TOKEN_PRIVILEGES prevTp{}; + DWORD prevTpLen = 0; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() + // succeeds, we still need to query GetLastError() to ensure that the privileges + // were actually obtained. + + if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, + &prevTpLen) + && GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, + PAGE_READWRITE); + + // Privilege no longer needed, restore previous state + AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + } + } + + CloseHandle(hProcessToken); + + return mem; + + #endif +} + +void* aligned_large_pages_alloc(size_t allocSize) { + + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_windows(allocSize); + + // Fall back to regular, page-aligned, allocation if necessary + if (!mem) + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + + return mem; +} + +#else + +void* aligned_large_pages_alloc(size_t allocSize) { + + #if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // 2MB page size assumed + #else + constexpr size_t alignment = 4096; // small page size assumed + #endif + + // Round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void* mem = std_aligned_alloc(alignment, size); + #if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); + #endif + return mem; +} + +#endif + +bool has_large_pages() { + +#if defined(_WIN32) + + constexpr size_t page_size = 2 * 1024 * 1024; // 2MB page size assumed + void* mem = aligned_large_pages_alloc_windows(page_size); + if (mem == nullptr) + { + return false; + } + else + { + aligned_large_pages_free(mem); + return true; + } + +#elif defined(__linux__) + + #if defined(MADV_HUGEPAGE) + return true; + #else + return false; + #endif + +#else + + return false; + +#endif +} + + +// aligned_large_pages_free() will free the previously memory allocated +// by aligned_large_pages_alloc(). The effect is a nop if mem == nullptr. + +#if defined(_WIN32) + +void aligned_large_pages_free(void* mem) { + + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err + << std::dec << std::endl; + exit(EXIT_FAILURE); + } +} + +#else + +void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } + +#endif +} // namespace Stockfish diff --git a/stockfish/src/memory.h b/stockfish/src/memory.h new file mode 100644 index 0000000000000000000000000000000000000000..4dc23287835b85983ea24a062a91bbc0f01fd2f0 --- /dev/null +++ b/stockfish/src/memory.h @@ -0,0 +1,218 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MEMORY_H_INCLUDED +#define MEMORY_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" + +namespace Stockfish { + +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); + +// Memory aligned by page size, min alignment: 4096 bytes +void* aligned_large_pages_alloc(size_t size); +void aligned_large_pages_free(void* mem); + +bool has_large_pages(); + +// Frees memory which was placed there with placement new. +// Works for both single objects and arrays of unknown bound. +template +void memory_deleter(T* ptr, FREE_FUNC free_func) { + if (!ptr) + return; + + // Explicitly needed to call the destructor + if constexpr (!std::is_trivially_destructible_v) + ptr->~T(); + + free_func(ptr); + return; +} + +// Frees memory which was placed there with placement new. +// Works for both single objects and arrays of unknown bound. +template +void memory_deleter_array(T* ptr, FREE_FUNC free_func) { + if (!ptr) + return; + + + // Move back on the pointer to where the size is allocated + const size_t array_offset = std::max(sizeof(size_t), alignof(T)); + char* raw_memory = reinterpret_cast(ptr) - array_offset; + + if constexpr (!std::is_trivially_destructible_v) + { + const size_t size = *reinterpret_cast(raw_memory); + + // Explicitly call the destructor for each element in reverse order + for (size_t i = size; i-- > 0;) + ptr[i].~T(); + } + + free_func(raw_memory); +} + +// Allocates memory for a single object and places it there with placement new +template +inline std::enable_if_t, T*> memory_allocator(ALLOC_FUNC alloc_func, + Args&&... args) { + void* raw_memory = alloc_func(sizeof(T)); + ASSERT_ALIGNED(raw_memory, alignof(T)); + return new (raw_memory) T(std::forward(args)...); +} + +// Allocates memory for an array of unknown bound and places it there with placement new +template +inline std::enable_if_t, std::remove_extent_t*> +memory_allocator(ALLOC_FUNC alloc_func, size_t num) { + using ElementType = std::remove_extent_t; + + const size_t array_offset = std::max(sizeof(size_t), alignof(ElementType)); + + // Save the array size in the memory location + char* raw_memory = + reinterpret_cast(alloc_func(array_offset + num * sizeof(ElementType))); + ASSERT_ALIGNED(raw_memory, alignof(T)); + + new (raw_memory) size_t(num); + + for (size_t i = 0; i < num; ++i) + new (raw_memory + array_offset + i * sizeof(ElementType)) ElementType(); + + // Need to return the pointer at the start of the array so that + // the indexing in unique_ptr works. + return reinterpret_cast(raw_memory + array_offset); +} + +// +// +// aligned large page unique ptr +// +// + +template +struct LargePageDeleter { + void operator()(T* ptr) const { return memory_deleter(ptr, aligned_large_pages_free); } +}; + +template +struct LargePageArrayDeleter { + void operator()(T* ptr) const { return memory_deleter_array(ptr, aligned_large_pages_free); } +}; + +template +using LargePagePtr = + std::conditional_t, + std::unique_ptr>>, + std::unique_ptr>>; + +// make_unique_large_page for single objects +template +std::enable_if_t, LargePagePtr> make_unique_large_page(Args&&... args) { + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + + T* obj = memory_allocator(aligned_large_pages_alloc, std::forward(args)...); + + return LargePagePtr(obj); +} + +// make_unique_large_page for arrays of unknown bound +template +std::enable_if_t, LargePagePtr> make_unique_large_page(size_t num) { + using ElementType = std::remove_extent_t; + + static_assert(alignof(ElementType) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + + ElementType* memory = memory_allocator(aligned_large_pages_alloc, num); + + return LargePagePtr(memory); +} + +// +// +// aligned unique ptr +// +// + +template +struct AlignedDeleter { + void operator()(T* ptr) const { return memory_deleter(ptr, std_aligned_free); } +}; + +template +struct AlignedArrayDeleter { + void operator()(T* ptr) const { return memory_deleter_array(ptr, std_aligned_free); } +}; + +template +using AlignedPtr = + std::conditional_t, + std::unique_ptr>>, + std::unique_ptr>>; + +// make_unique_aligned for single objects +template +std::enable_if_t, AlignedPtr> make_unique_aligned(Args&&... args) { + const auto func = [](size_t size) { return std_aligned_alloc(alignof(T), size); }; + T* obj = memory_allocator(func, std::forward(args)...); + + return AlignedPtr(obj); +} + +// make_unique_aligned for arrays of unknown bound +template +std::enable_if_t, AlignedPtr> make_unique_aligned(size_t num) { + using ElementType = std::remove_extent_t; + + const auto func = [](size_t size) { return std_aligned_alloc(alignof(ElementType), size); }; + ElementType* memory = memory_allocator(func, num); + + return AlignedPtr(memory); +} + + +// Get the first aligned element of an array. +// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, +// where N is the number of elements in the array. +template +T* align_ptr_up(T* ptr) { + static_assert(alignof(T) < Alignment); + + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast( + reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); +} + + +} // namespace Stockfish + +#endif // #ifndef MEMORY_H_INCLUDED diff --git a/stockfish/src/misc.cpp b/stockfish/src/misc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c5ac45f5cb5eb6a3c6d6374cd58ad9903766f580 --- /dev/null +++ b/stockfish/src/misc.cpp @@ -0,0 +1,524 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" + +namespace Stockfish { + +namespace { + +// Version number or dev. +constexpr std::string_view version = "17.1"; + +// Our fancy logging facility. The trick here is to replace cin.rdbuf() and +// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We +// can toggle the logging of std::cout and std:cin at runtime whilst preserving +// usual I/O functionality, all without changing a single line of code! +// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 + +struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout + + Tie(std::streambuf* b, std::streambuf* l) : + buf(b), + logBuf(l) {} + + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } + + std::streambuf *buf, *logBuf; + + int log(int c, const char* prefix) { + + static int last = '\n'; // Single log file + + if (last == '\n') + logBuf->sputn(prefix, 3); + + return last = logBuf->sputc(char(c)); + } +}; + +class Logger { + + Logger() : + in(std::cin.rdbuf(), file.rdbuf()), + out(std::cout.rdbuf(), file.rdbuf()) {} + ~Logger() { start(""); } + + std::ofstream file; + Tie in, out; + + public: + static void start(const std::string& fname) { + + static Logger l; + + if (l.file.is_open()) + { + std::cout.rdbuf(l.out.buf); + std::cin.rdbuf(l.in.buf); + l.file.close(); + } + + if (!fname.empty()) + { + l.file.open(fname, std::ifstream::out); + + if (!l.file.is_open()) + { + std::cerr << "Unable to open debug log file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + std::cin.rdbuf(&l.in); + std::cout.rdbuf(&l.out); + } + } +}; + +} // namespace + + +// Returns the full name of the current Stockfish version. +// +// For local dev compiles we try to append the commit SHA and +// commit date from git. If that fails only the local compilation +// date is set and "nogit" is specified: +// Stockfish dev-YYYYMMDD-SHA +// or +// Stockfish dev-YYYYMMDD-nogit +// +// For releases (non-dev builds) we only include the version number: +// Stockfish version +std::string engine_version_info() { + std::stringstream ss; + ss << "Stockfish " << version << std::setfill('0'); + + if constexpr (version == "dev") + { + ss << "-"; +#ifdef GIT_DATE + ss << stringify(GIT_DATE); +#else + constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + + std::string month, day, year; + std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" + + date >> month >> day >> year; + ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) + << std::setw(2) << std::setfill('0') << day; +#endif + + ss << "-"; + +#ifdef GIT_SHA + ss << stringify(GIT_SHA); +#else + ss << "nogit"; +#endif + } + + return ss.str(); +} + +std::string engine_info(bool to_uci) { + return engine_version_info() + (to_uci ? "\nid author " : " by ") + + "the Stockfish developers (see AUTHORS file)"; +} + + +// Returns a string trying to describe the compiler we use +std::string compiler_info() { + +#define make_version_string(major, minor, patch) \ + stringify(major) "." stringify(minor) "." stringify(patch) + + // Predefined macros hell: + // + // __GNUC__ Compiler is GCC, Clang or ICX + // __clang__ Compiler is Clang or ICX + // __INTEL_LLVM_COMPILER Compiler is ICX + // _MSC_VER Compiler is MSVC + // _WIN32 Building on Windows (any) + // _WIN64 Building on Windows 64 bit + + std::string compiler = "\nCompiled by : "; + +#if defined(__INTEL_LLVM_COMPILER) + compiler += "ICX "; + compiler += stringify(__INTEL_LLVM_COMPILER); +#elif defined(__clang__) + compiler += "clang++ "; + compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); +#elif _MSC_VER + compiler += "MSVC "; + compiler += "(version "; + compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); + compiler += ")"; +#elif defined(__e2k__) && defined(__LCC__) + #define dot_ver2(n) \ + compiler += char('.'); \ + compiler += char('0' + (n) / 10); \ + compiler += char('0' + (n) % 10); + + compiler += "MCST LCC "; + compiler += "(version "; + compiler += std::to_string(__LCC__ / 100); + dot_ver2(__LCC__ % 100) dot_ver2(__LCC_MINOR__) compiler += ")"; +#elif __GNUC__ + compiler += "g++ (GNUC) "; + compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); +#else + compiler += "Unknown compiler "; + compiler += "(unknown version)"; +#endif + +#if defined(__APPLE__) + compiler += " on Apple"; +#elif defined(__CYGWIN__) + compiler += " on Cygwin"; +#elif defined(__MINGW64__) + compiler += " on MinGW64"; +#elif defined(__MINGW32__) + compiler += " on MinGW32"; +#elif defined(__ANDROID__) + compiler += " on Android"; +#elif defined(__linux__) + compiler += " on Linux"; +#elif defined(_WIN64) + compiler += " on Microsoft Windows 64-bit"; +#elif defined(_WIN32) + compiler += " on Microsoft Windows 32-bit"; +#else + compiler += " on unknown system"; +#endif + + compiler += "\nCompilation architecture : "; +#if defined(ARCH) + compiler += stringify(ARCH); +#else + compiler += "(undefined architecture)"; +#endif + + compiler += "\nCompilation settings : "; + compiler += (Is64Bit ? "64bit" : "32bit"); +#if defined(USE_VNNI) + compiler += " VNNI"; +#endif +#if defined(USE_AVX512) + compiler += " AVX512"; +#endif + compiler += (HasPext ? " BMI2" : ""); +#if defined(USE_AVX2) + compiler += " AVX2"; +#endif +#if defined(USE_SSE41) + compiler += " SSE41"; +#endif +#if defined(USE_SSSE3) + compiler += " SSSE3"; +#endif +#if defined(USE_SSE2) + compiler += " SSE2"; +#endif + compiler += (HasPopCnt ? " POPCNT" : ""); +#if defined(USE_NEON_DOTPROD) + compiler += " NEON_DOTPROD"; +#elif defined(USE_NEON) + compiler += " NEON"; +#endif + +#if !defined(NDEBUG) + compiler += " DEBUG"; +#endif + + compiler += "\nCompiler __VERSION__ macro : "; +#ifdef __VERSION__ + compiler += __VERSION__; +#else + compiler += "(undefined macro)"; +#endif + + compiler += "\n"; + + return compiler; +} + + +// Debug functions used mainly to collect run-time statistics +constexpr int MaxDebugSlots = 32; + +namespace { + +template +struct DebugInfo { + std::array, N> data = {0}; + + [[nodiscard]] constexpr std::atomic& operator[](size_t index) { + assert(index < N); + return data[index]; + } + + constexpr DebugInfo& operator=(const DebugInfo& other) { + for (size_t i = 0; i < N; i++) + data[i].store(other.data[i].load()); + return *this; + } +}; + +struct DebugExtremes: public DebugInfo<3> { + DebugExtremes() { + data[1] = std::numeric_limits::min(); + data[2] = std::numeric_limits::max(); + } +}; + +std::array, MaxDebugSlots> hit; +std::array, MaxDebugSlots> mean; +std::array, MaxDebugSlots> stdev; +std::array, MaxDebugSlots> correl; +std::array extremes; + +} // namespace + +void dbg_hit_on(bool cond, int slot) { + + ++hit.at(slot)[0]; + if (cond) + ++hit.at(slot)[1]; +} + +void dbg_mean_of(int64_t value, int slot) { + + ++mean.at(slot)[0]; + mean.at(slot)[1] += value; +} + +void dbg_stdev_of(int64_t value, int slot) { + + ++stdev.at(slot)[0]; + stdev.at(slot)[1] += value; + stdev.at(slot)[2] += value * value; +} + +void dbg_extremes_of(int64_t value, int slot) { + ++extremes.at(slot)[0]; + + int64_t current_max = extremes.at(slot)[1].load(); + while (current_max < value && !extremes.at(slot)[1].compare_exchange_weak(current_max, value)) + {} + + int64_t current_min = extremes.at(slot)[2].load(); + while (current_min > value && !extremes.at(slot)[2].compare_exchange_weak(current_min, value)) + {} +} + +void dbg_correl_of(int64_t value1, int64_t value2, int slot) { + + ++correl.at(slot)[0]; + correl.at(slot)[1] += value1; + correl.at(slot)[2] += value1 * value1; + correl.at(slot)[3] += value2; + correl.at(slot)[4] += value2 * value2; + correl.at(slot)[5] += value1 * value2; +} + +void dbg_print() { + + int64_t n; + auto E = [&n](int64_t x) { return double(x) / n; }; + auto sqr = [](double x) { return x * x; }; + + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = hit[i][0])) + std::cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1] + << " Hit Rate (%) " << 100.0 * E(hit[i][1]) << std::endl; + + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = mean[i][0])) + { + std::cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) << std::endl; + } + + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = stdev[i][0])) + { + double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1]))); + std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl; + } + + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = extremes[i][0])) + { + std::cerr << "Extremity #" << i << ": Total " << n << " Min " << extremes[i][2] + << " Max " << extremes[i][1] << std::endl; + } + + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = correl[i][0])) + { + double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3])) + / (sqrt(E(correl[i][2]) - sqr(E(correl[i][1]))) + * sqrt(E(correl[i][4]) - sqr(E(correl[i][3])))); + std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::endl; + } +} + +void dbg_clear() { + hit.fill({}); + mean.fill({}); + stdev.fill({}); + correl.fill({}); + extremes.fill({}); +} + +// Used to serialize access to std::cout +// to avoid multiple threads writing at the same time. +std::ostream& operator<<(std::ostream& os, SyncCout sc) { + + static std::mutex m; + + if (sc == IO_LOCK) + m.lock(); + + if (sc == IO_UNLOCK) + m.unlock(); + + return os; +} + +void sync_cout_start() { std::cout << IO_LOCK; } +void sync_cout_end() { std::cout << IO_UNLOCK; } + +// Trampoline helper to avoid moving Logger to misc.h +void start_logger(const std::string& fname) { Logger::start(fname); } + + +#ifdef NO_PREFETCH + +void prefetch(const void*) {} + +#else + +void prefetch(const void* addr) { + + #if defined(_MSC_VER) + _mm_prefetch((char const*) addr, _MM_HINT_T0); + #else + __builtin_prefetch(addr); + #endif +} + +#endif + +#ifdef _WIN32 + #include + #define GETCWD _getcwd +#else + #include + #define GETCWD getcwd +#endif + +size_t str_to_size_t(const std::string& s) { + unsigned long long value = std::stoull(s); + if (value > std::numeric_limits::max()) + std::exit(EXIT_FAILURE); + return static_cast(value); +} + +std::optional read_file_to_string(const std::string& path) { + std::ifstream f(path, std::ios_base::binary); + if (!f) + return std::nullopt; + return std::string(std::istreambuf_iterator(f), std::istreambuf_iterator()); +} + +void remove_whitespace(std::string& s) { + s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return std::isspace(c); }), s.end()); +} + +bool is_whitespace(std::string_view s) { + return std::all_of(s.begin(), s.end(), [](char c) { return std::isspace(c); }); +} + +std::string CommandLine::get_binary_directory(std::string argv0) { + std::string pathSeparator; + +#ifdef _WIN32 + pathSeparator = "\\"; + #ifdef _MSC_VER + // Under windows argv[0] may not have the extension. Also _get_pgmptr() had + // issues in some Windows 10 versions, so check returned values carefully. + char* pgmptr = nullptr; + if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) + argv0 = pgmptr; + #endif +#else + pathSeparator = "/"; +#endif + + // Extract the working directory + auto workingDirectory = CommandLine::get_working_directory(); + + // Extract the binary directory path from argv0 + auto binaryDirectory = argv0; + size_t pos = binaryDirectory.find_last_of("\\/"); + if (pos == std::string::npos) + binaryDirectory = "." + pathSeparator; + else + binaryDirectory.resize(pos + 1); + + // Pattern replacement: "./" at the start of path is replaced by the working directory + if (binaryDirectory.find("." + pathSeparator) == 0) + binaryDirectory.replace(0, 1, workingDirectory); + + return binaryDirectory; +} + +std::string CommandLine::get_working_directory() { + std::string workingDirectory = ""; + char buff[40000]; + char* cwd = GETCWD(buff, 40000); + if (cwd) + workingDirectory = cwd; + + return workingDirectory; +} + + +} // namespace Stockfish diff --git a/stockfish/src/misc.h b/stockfish/src/misc.h new file mode 100644 index 0000000000000000000000000000000000000000..84f11d6de38fd4e9110b0d1e0b696077af343540 --- /dev/null +++ b/stockfish/src/misc.h @@ -0,0 +1,322 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MISC_H_INCLUDED +#define MISC_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define stringify2(x) #x +#define stringify(x) stringify2(x) + +namespace Stockfish { + +std::string engine_version_info(); +std::string engine_info(bool to_uci = false); +std::string compiler_info(); + +// Preloads the given address in L1/L2 cache. This is a non-blocking +// function that doesn't stall the CPU waiting for data to be loaded from memory, +// which can be quite slow. +void prefetch(const void* addr); + +void start_logger(const std::string& fname); + +size_t str_to_size_t(const std::string& s); + +#if defined(__linux__) + +struct PipeDeleter { + void operator()(FILE* file) const { + if (file != nullptr) + { + pclose(file); + } + } +}; + +#endif + +// Reads the file as bytes. +// Returns std::nullopt if the file does not exist. +std::optional read_file_to_string(const std::string& path); + +void dbg_hit_on(bool cond, int slot = 0); +void dbg_mean_of(int64_t value, int slot = 0); +void dbg_stdev_of(int64_t value, int slot = 0); +void dbg_extremes_of(int64_t value, int slot = 0); +void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); +void dbg_print(); +void dbg_clear(); + +using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds +static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); +inline TimePoint now() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} + +inline std::vector split(std::string_view s, std::string_view delimiter) { + std::vector res; + + if (s.empty()) + return res; + + size_t begin = 0; + for (;;) + { + const size_t end = s.find(delimiter, begin); + if (end == std::string::npos) + break; + + res.emplace_back(s.substr(begin, end - begin)); + begin = end + delimiter.size(); + } + + res.emplace_back(s.substr(begin)); + + return res; +} + +void remove_whitespace(std::string& s); +bool is_whitespace(std::string_view s); + +enum SyncCout { + IO_LOCK, + IO_UNLOCK +}; +std::ostream& operator<<(std::ostream&, SyncCout); + +#define sync_cout std::cout << IO_LOCK +#define sync_endl std::endl << IO_UNLOCK + +void sync_cout_start(); +void sync_cout_end(); + +// True if and only if the binary is compiled on a little-endian machine +static inline const std::uint16_t Le = 1; +static inline const bool IsLittleEndian = *reinterpret_cast(&Le) == 1; + + +template +class ValueList { + + public: + std::size_t size() const { return size_; } + void push_back(const T& value) { values_[size_++] = value; } + const T* begin() const { return values_; } + const T* end() const { return values_ + size_; } + const T& operator[](int index) const { return values_[index]; } + + private: + T values_[MaxSize]; + std::size_t size_ = 0; +}; + + +template +class MultiArray; + +namespace Detail { + +template +struct MultiArrayHelper { + using ChildType = MultiArray; +}; + +template +struct MultiArrayHelper { + using ChildType = T; +}; + +template +constexpr bool is_strictly_assignable_v = + std::is_assignable_v && (std::is_same_v || !std::is_convertible_v); + +} + +// MultiArray is a generic N-dimensional array. +// The template parameters (Size and Sizes) encode the dimensions of the array. +template +class MultiArray { + using ChildType = typename Detail::MultiArrayHelper::ChildType; + using ArrayType = std::array; + ArrayType data_; + + public: + using value_type = typename ArrayType::value_type; + using size_type = typename ArrayType::size_type; + using difference_type = typename ArrayType::difference_type; + using reference = typename ArrayType::reference; + using const_reference = typename ArrayType::const_reference; + using pointer = typename ArrayType::pointer; + using const_pointer = typename ArrayType::const_pointer; + using iterator = typename ArrayType::iterator; + using const_iterator = typename ArrayType::const_iterator; + using reverse_iterator = typename ArrayType::reverse_iterator; + using const_reverse_iterator = typename ArrayType::const_reverse_iterator; + + constexpr auto& at(size_type index) noexcept { return data_.at(index); } + constexpr const auto& at(size_type index) const noexcept { return data_.at(index); } + + constexpr auto& operator[](size_type index) noexcept { return data_[index]; } + constexpr const auto& operator[](size_type index) const noexcept { return data_[index]; } + + constexpr auto& front() noexcept { return data_.front(); } + constexpr const auto& front() const noexcept { return data_.front(); } + constexpr auto& back() noexcept { return data_.back(); } + constexpr const auto& back() const noexcept { return data_.back(); } + + auto* data() { return data_.data(); } + const auto* data() const { return data_.data(); } + + constexpr auto begin() noexcept { return data_.begin(); } + constexpr auto end() noexcept { return data_.end(); } + constexpr auto begin() const noexcept { return data_.begin(); } + constexpr auto end() const noexcept { return data_.end(); } + constexpr auto cbegin() const noexcept { return data_.cbegin(); } + constexpr auto cend() const noexcept { return data_.cend(); } + + constexpr auto rbegin() noexcept { return data_.rbegin(); } + constexpr auto rend() noexcept { return data_.rend(); } + constexpr auto rbegin() const noexcept { return data_.rbegin(); } + constexpr auto rend() const noexcept { return data_.rend(); } + constexpr auto crbegin() const noexcept { return data_.crbegin(); } + constexpr auto crend() const noexcept { return data_.crend(); } + + constexpr bool empty() const noexcept { return data_.empty(); } + constexpr size_type size() const noexcept { return data_.size(); } + constexpr size_type max_size() const noexcept { return data_.max_size(); } + + template + void fill(const U& v) { + static_assert(Detail::is_strictly_assignable_v, + "Cannot assign fill value to entry type"); + for (auto& ele : data_) + { + if constexpr (sizeof...(Sizes) == 0) + ele = v; + else + ele.fill(v); + } + } + + constexpr void swap(MultiArray& other) noexcept { data_.swap(other.data_); } +}; + + +// xorshift64star Pseudo-Random Number Generator +// This class is based on original code written and dedicated +// to the public domain by Sebastiano Vigna (2014). +// It has the following characteristics: +// +// - Outputs 64-bit numbers +// - Passes Dieharder and SmallCrush test batteries +// - Does not require warm-up, no zeroland to escape +// - Internal state is a single 64-bit integer +// - Period is 2^64 - 1 +// - Speed: 1.60 ns/call (Core i7 @3.40GHz) +// +// For further analysis see +// + +class PRNG { + + uint64_t s; + + uint64_t rand64() { + + s ^= s >> 12, s ^= s << 25, s ^= s >> 27; + return s * 2685821657736338717LL; + } + + public: + PRNG(uint64_t seed) : + s(seed) { + assert(seed); + } + + template + T rand() { + return T(rand64()); + } + + // Special generator used to fast init magic numbers. + // Output values only have 1/8th of their bits set on average. + template + T sparse_rand() { + return T(rand64() & rand64() & rand64()); + } +}; + +inline uint64_t mul_hi64(uint64_t a, uint64_t b) { +#if defined(__GNUC__) && defined(IS_64BIT) + __extension__ using uint128 = unsigned __int128; + return (uint128(a) * uint128(b)) >> 64; +#else + uint64_t aL = uint32_t(a), aH = a >> 32; + uint64_t bL = uint32_t(b), bH = b >> 32; + uint64_t c1 = (aL * bL) >> 32; + uint64_t c2 = aH * bL + c1; + uint64_t c3 = aL * bH + uint32_t(c2); + return aH * bH + (c2 >> 32) + (c3 >> 32); +#endif +} + + +struct CommandLine { + public: + CommandLine(int _argc, char** _argv) : + argc(_argc), + argv(_argv) {} + + static std::string get_binary_directory(std::string argv0); + static std::string get_working_directory(); + + int argc; + char** argv; +}; + +namespace Utility { + +template +void move_to_front(std::vector& vec, Predicate pred) { + auto it = std::find_if(vec.begin(), vec.end(), pred); + + if (it != vec.end()) + { + std::rotate(vec.begin(), it, it + 1); + } +} +} + +} // namespace Stockfish + +#endif // #ifndef MISC_H_INCLUDED diff --git a/stockfish/src/movegen.cpp b/stockfish/src/movegen.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8653a828fde034bda30353e82795bae9c5bf28b3 --- /dev/null +++ b/stockfish/src/movegen.cpp @@ -0,0 +1,256 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "movegen.h" + +#include +#include + +#include "bitboard.h" +#include "position.h" + +namespace Stockfish { + +namespace { + +template +ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { + + constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS; + + if constexpr (Type == CAPTURES || all) + *moveList++ = Move::make(to - D, to, QUEEN); + + if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all) + { + *moveList++ = Move::make(to - D, to, ROOK); + *moveList++ = Move::make(to - D, to, BISHOP); + *moveList++ = Move::make(to - D, to, KNIGHT); + } + + return moveList; +} + + +template +ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { + + constexpr Color Them = ~Us; + constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + constexpr Direction Up = pawn_push(Us); + constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + + const Bitboard emptySquares = ~pos.pieces(); + const Bitboard enemies = Type == EVASIONS ? pos.checkers() : pos.pieces(Them); + + Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; + Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; + + // Single and double pawn pushes, no promotions + if constexpr (Type != CAPTURES) + { + Bitboard b1 = shift(pawnsNotOn7) & emptySquares; + Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; + + if constexpr (Type == EVASIONS) // Consider only blocking squares + { + b1 &= target; + b2 &= target; + } + + while (b1) + { + Square to = pop_lsb(b1); + *moveList++ = Move(to - Up, to); + } + + while (b2) + { + Square to = pop_lsb(b2); + *moveList++ = Move(to - Up - Up, to); + } + } + + // Promotions and underpromotions + if (pawnsOn7) + { + Bitboard b1 = shift(pawnsOn7) & enemies; + Bitboard b2 = shift(pawnsOn7) & enemies; + Bitboard b3 = shift(pawnsOn7) & emptySquares; + + if constexpr (Type == EVASIONS) + b3 &= target; + + while (b1) + moveList = make_promotions(moveList, pop_lsb(b1)); + + while (b2) + moveList = make_promotions(moveList, pop_lsb(b2)); + + while (b3) + moveList = make_promotions(moveList, pop_lsb(b3)); + } + + // Standard and en passant captures + if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { + Bitboard b1 = shift(pawnsNotOn7) & enemies; + Bitboard b2 = shift(pawnsNotOn7) & enemies; + + while (b1) + { + Square to = pop_lsb(b1); + *moveList++ = Move(to - UpRight, to); + } + + while (b2) + { + Square to = pop_lsb(b2); + *moveList++ = Move(to - UpLeft, to); + } + + if (pos.ep_square() != SQ_NONE) + { + assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); + + // An en passant capture cannot resolve a discovered check + if (Type == EVASIONS && (target & (pos.ep_square() + Up))) + return moveList; + + b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); + + assert(b1); + + while (b1) + *moveList++ = Move::make(pop_lsb(b1), pos.ep_square()); + } + } + + return moveList; +} + + +template +ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { + + static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); + + Bitboard bb = pos.pieces(Us, Pt); + + while (bb) + { + Square from = pop_lsb(bb); + Bitboard b = attacks_bb(from, pos.pieces()) & target; + + while (b) + *moveList++ = Move(from, pop_lsb(b)); + } + + return moveList; +} + + +template +ExtMove* generate_all(const Position& pos, ExtMove* moveList) { + + static_assert(Type != LEGAL, "Unsupported type in generate_all()"); + + const Square ksq = pos.square(Us); + Bitboard target; + + // Skip generating non-king moves when in double check + if (Type != EVASIONS || !more_than_one(pos.checkers())) + { + target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) + : Type == NON_EVASIONS ? ~pos.pieces(Us) + : Type == CAPTURES ? pos.pieces(~Us) + : ~pos.pieces(); // QUIETS + + moveList = generate_pawn_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + } + + Bitboard b = attacks_bb(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target); + + while (b) + *moveList++ = Move(ksq, pop_lsb(b)); + + if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) + for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) + if (!pos.castling_impeded(cr) && pos.can_castle(cr)) + *moveList++ = Move::make(ksq, pos.castling_rook_square(cr)); + + return moveList; +} + +} // namespace + + +// Generates all pseudo-legal captures plus queen promotions +// Generates all pseudo-legal non-captures and underpromotions +// Generates all pseudo-legal check evasions +// Generates all pseudo-legal captures and non-captures +// +// Returns a pointer to the end of the move list. +template +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + static_assert(Type != LEGAL, "Unsupported type in generate()"); + assert((Type == EVASIONS) == bool(pos.checkers())); + + Color us = pos.side_to_move(); + + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); +} + +// Explicit template instantiations +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); + + +// generate generates all the legal moves in the given position + +template<> +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + Color us = pos.side_to_move(); + Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); + Square ksq = pos.square(us); + ExtMove* cur = moveList; + + moveList = + pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); + while (cur != moveList) + if (((pinned & cur->from_sq()) || cur->from_sq() == ksq || cur->type_of() == EN_PASSANT) + && !pos.legal(*cur)) + *cur = *(--moveList); + else + ++cur; + + return moveList; +} + +} // namespace Stockfish diff --git a/stockfish/src/movegen.h b/stockfish/src/movegen.h new file mode 100644 index 0000000000000000000000000000000000000000..7c6cceb7c3dea848d1eeb8d0d302dec0690e6286 --- /dev/null +++ b/stockfish/src/movegen.h @@ -0,0 +1,73 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MOVEGEN_H_INCLUDED +#define MOVEGEN_H_INCLUDED + +#include // IWYU pragma: keep +#include + +#include "types.h" + +namespace Stockfish { + +class Position; + +enum GenType { + CAPTURES, + QUIETS, + EVASIONS, + NON_EVASIONS, + LEGAL +}; + +struct ExtMove: public Move { + int value; + + void operator=(Move m) { data = m.raw(); } + + // Inhibit unwanted implicit conversions to Move + // with an ambiguity that yields to a compile error. + operator float() const = delete; +}; + +inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; } + +template +ExtMove* generate(const Position& pos, ExtMove* moveList); + +// The MoveList struct wraps the generate() function and returns a convenient +// list of moves. Using MoveList is sometimes preferable to directly calling +// the lower level generate() function. +template +struct MoveList { + + explicit MoveList(const Position& pos) : + last(generate(pos, moveList)) {} + const ExtMove* begin() const { return moveList; } + const ExtMove* end() const { return last; } + size_t size() const { return last - moveList; } + bool contains(Move move) const { return std::find(begin(), end(), move) != end(); } + + private: + ExtMove moveList[MAX_MOVES], *last; +}; + +} // namespace Stockfish + +#endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/stockfish/src/movepick.cpp b/stockfish/src/movepick.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c762e7e453ce456380bac0f59efbebb7b5668b31 --- /dev/null +++ b/stockfish/src/movepick.cpp @@ -0,0 +1,320 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "movepick.h" + +#include +#include + +#include "bitboard.h" +#include "misc.h" +#include "position.h" + +namespace Stockfish { + +namespace { + +enum Stages { + // generate main search moves + MAIN_TT, + CAPTURE_INIT, + GOOD_CAPTURE, + QUIET_INIT, + GOOD_QUIET, + BAD_CAPTURE, + BAD_QUIET, + + // generate evasion moves + EVASION_TT, + EVASION_INIT, + EVASION, + + // generate probcut moves + PROBCUT_TT, + PROBCUT_INIT, + PROBCUT, + + // generate qsearch moves + QSEARCH_TT, + QCAPTURE_INIT, + QCAPTURE +}; + +// Sort moves in descending order up to and including a given limit. +// The order of moves smaller than the limit is left unspecified. +void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { + + for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) + if (p->value >= limit) + { + ExtMove tmp = *p, *q; + *p = *++sortedEnd; + for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) + *q = *(q - 1); + *q = tmp; + } +} + +} // namespace + + +// Constructors of the MovePicker class. As arguments, we pass information +// to decide which class of moves to emit, to help sorting the (presumably) +// good moves first, and how important move ordering is at the current node. + +// MovePicker constructor for the main search and for the quiescence search +MovePicker::MovePicker(const Position& p, + Move ttm, + Depth d, + const ButterflyHistory* mh, + const LowPlyHistory* lph, + const CapturePieceToHistory* cph, + const PieceToHistory** ch, + const PawnHistory* ph, + int pl) : + pos(p), + mainHistory(mh), + lowPlyHistory(lph), + captureHistory(cph), + continuationHistory(ch), + pawnHistory(ph), + ttMove(ttm), + depth(d), + ply(pl) { + + if (pos.checkers()) + stage = EVASION_TT + !(ttm && pos.pseudo_legal(ttm)); + + else + stage = (depth > 0 ? MAIN_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); +} + +// MovePicker constructor for ProbCut: we generate captures with Static Exchange +// Evaluation (SEE) greater than or equal to the given threshold. +MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) : + pos(p), + captureHistory(cph), + ttMove(ttm), + threshold(th) { + assert(!pos.checkers()); + + stage = PROBCUT_TT + + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); +} + +// Assigns a numerical value to each move in a list, used for sorting. +// Captures are ordered by Most Valuable Victim (MVV), preferring captures +// with a good history. Quiets moves are ordered using the history tables. +template +void MovePicker::score() { + + static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); + + [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, + threatenedPieces; + if constexpr (Type == QUIETS) + { + Color us = pos.side_to_move(); + + threatenedByPawn = pos.attacks_by(~us); + threatenedByMinor = + pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; + threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; + + // Pieces threatened by pieces of lesser material value + threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) + | (pos.pieces(us, ROOK) & threatenedByMinor) + | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); + } + + for (auto& m : *this) + if constexpr (Type == CAPTURES) + m.value = + 7 * int(PieceValue[pos.piece_on(m.to_sq())]) + + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]; + + else if constexpr (Type == QUIETS) + { + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pc); + Square from = m.from_sq(); + Square to = m.to_sq(); + + // histories + m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()]; + m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; + m.value += (*continuationHistory[0])[pc][to]; + m.value += (*continuationHistory[1])[pc][to]; + m.value += (*continuationHistory[2])[pc][to]; + m.value += (*continuationHistory[3])[pc][to]; + m.value += (*continuationHistory[4])[pc][to] / 3; + m.value += (*continuationHistory[5])[pc][to]; + + // bonus for checks + m.value += bool(pos.check_squares(pt) & to) * 16384; + + // bonus for escaping from capture + m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51700 + : pt == ROOK && !(to & threatenedByMinor) ? 25600 + : !(to & threatenedByPawn) ? 14450 + : 0) + : 0; + + // malus for putting piece en prise + m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 + : pt == ROOK && bool(to & threatenedByMinor) ? 24335 + : 0); + + if (ply < LOW_PLY_HISTORY_SIZE) + m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + 2 * ply); + } + + else // Type == EVASIONS + { + if (pos.capture_stage(m)) + m.value = PieceValue[pos.piece_on(m.to_sq())] + (1 << 28); + else + m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] + + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][m.to_sq()]; + } +} + +// Returns the next move satisfying a predicate function. +// This never returns the TT move, as it was emitted before. +template +Move MovePicker::select(Pred filter) { + + for (; cur < endMoves; ++cur) + if (*cur != ttMove && filter()) + return *cur++; + + return Move::none(); +} + +// This is the most important method of the MovePicker class. We emit one +// new pseudo-legal move on every call until there are no more moves left, +// picking the move with the highest score from a list of generated moves. +Move MovePicker::next_move() { + + auto quiet_threshold = [](Depth d) { return -3560 * d; }; + +top: + switch (stage) + { + + case MAIN_TT : + case EVASION_TT : + case QSEARCH_TT : + case PROBCUT_TT : + ++stage; + return ttMove; + + case CAPTURE_INIT : + case PROBCUT_INIT : + case QCAPTURE_INIT : + cur = endBadCaptures = moves; + endMoves = generate(pos, cur); + + score(); + partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); + ++stage; + goto top; + + case GOOD_CAPTURE : + if (select([&]() { + // Move losing capture to endBadCaptures to be tried later + return pos.see_ge(*cur, -cur->value / 18) ? true + : (*endBadCaptures++ = *cur, false); + })) + return *(cur - 1); + + ++stage; + [[fallthrough]]; + + case QUIET_INIT : + if (!skipQuiets) + { + cur = endBadCaptures; + endMoves = beginBadQuiets = endBadQuiets = generate(pos, cur); + + score(); + partial_insertion_sort(cur, endMoves, quiet_threshold(depth)); + } + + ++stage; + [[fallthrough]]; + + case GOOD_QUIET : + if (!skipQuiets && select([]() { return true; })) + { + if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) + return *(cur - 1); + + // Remaining quiets are bad + beginBadQuiets = cur - 1; + } + + // Prepare the pointers to loop over the bad captures + cur = moves; + endMoves = endBadCaptures; + + ++stage; + [[fallthrough]]; + + case BAD_CAPTURE : + if (select([]() { return true; })) + return *(cur - 1); + + // Prepare the pointers to loop over the bad quiets + cur = beginBadQuiets; + endMoves = endBadQuiets; + + ++stage; + [[fallthrough]]; + + case BAD_QUIET : + if (!skipQuiets) + return select([]() { return true; }); + + return Move::none(); + + case EVASION_INIT : + cur = moves; + endMoves = generate(pos, cur); + + score(); + partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); + ++stage; + [[fallthrough]]; + + case EVASION : + case QCAPTURE : + return select([]() { return true; }); + + case PROBCUT : + return select([&]() { return pos.see_ge(*cur, threshold); }); + } + + assert(false); + return Move::none(); // Silence warning +} + +void MovePicker::skip_quiet_moves() { skipQuiets = true; } + +} // namespace Stockfish diff --git a/stockfish/src/movepick.h b/stockfish/src/movepick.h new file mode 100644 index 0000000000000000000000000000000000000000..71078bdcf3db8ca4e1f8357695c70a13f50858fc --- /dev/null +++ b/stockfish/src/movepick.h @@ -0,0 +1,80 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MOVEPICK_H_INCLUDED +#define MOVEPICK_H_INCLUDED + +#include "history.h" +#include "movegen.h" +#include "types.h" + +namespace Stockfish { + +class Position; + +// The MovePicker class is used to pick one pseudo-legal move at a time from the +// current position. The most important method is next_move(), which emits one +// new pseudo-legal move on every call, until there are no moves left, when +// Move::none() is returned. In order to improve the efficiency of the alpha-beta +// algorithm, MovePicker attempts to return the moves which are most likely to get +// a cut-off first. +class MovePicker { + + public: + MovePicker(const MovePicker&) = delete; + MovePicker& operator=(const MovePicker&) = delete; + MovePicker(const Position&, + Move, + Depth, + const ButterflyHistory*, + const LowPlyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + const PawnHistory*, + int); + MovePicker(const Position&, Move, int, const CapturePieceToHistory*); + Move next_move(); + void skip_quiet_moves(); + + private: + template + Move select(Pred); + template + void score(); + ExtMove* begin() { return cur; } + ExtMove* end() { return endMoves; } + + const Position& pos; + const ButterflyHistory* mainHistory; + const LowPlyHistory* lowPlyHistory; + const CapturePieceToHistory* captureHistory; + const PieceToHistory** continuationHistory; + const PawnHistory* pawnHistory; + Move ttMove; + ExtMove * cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; + int stage; + int threshold; + Depth depth; + int ply; + bool skipQuiets = false; + ExtMove moves[MAX_MOVES]; +}; + +} // namespace Stockfish + +#endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/stockfish/src/nnue/features/half_ka_v2_hm.cpp b/stockfish/src/nnue/features/half_ka_v2_hm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eb3c7e6a7018a9c40667933a0346647e62e5f8cf --- /dev/null +++ b/stockfish/src/nnue/features/half_ka_v2_hm.cpp @@ -0,0 +1,84 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKAv2_hm of NNUE evaluation function + +#include "half_ka_v2_hm.h" + +#include "../../bitboard.h" +#include "../../position.h" +#include "../../types.h" +#include "../nnue_accumulator.h" + +namespace Stockfish::Eval::NNUE::Features { + +// Index of a feature for a given king position and another piece on some square +template +inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { + return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + + KingBuckets[Perspective][ksq]); +} + +// Get a list of indices for active features +template +void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { + Square ksq = pos.square(Perspective); + Bitboard bb = pos.pieces(); + while (bb) + { + Square s = pop_lsb(bb); + active.push_back(make_index(s, pos.piece_on(s), ksq)); + } +} + +// Explicit template instantiations +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); +template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); + +// Get a list of indices for recently changed features +template +void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added) { + for (int i = 0; i < dp.dirty_num; ++i) + { + if (dp.from[i] != SQ_NONE) + removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); + if (dp.to[i] != SQ_NONE) + added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); + } +} + +// Explicit template instantiations +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); + +bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) { + return dirtyPiece.piece[0] == make_piece(perspective, KING); +} + +} // namespace Stockfish::Eval::NNUE::Features diff --git a/stockfish/src/nnue/features/half_ka_v2_hm.h b/stockfish/src/nnue/features/half_ka_v2_hm.h new file mode 100644 index 0000000000000000000000000000000000000000..ba122adc8da84dd4987b4ed0a9af4ad1c2ffd01e --- /dev/null +++ b/stockfish/src/nnue/features/half_ka_v2_hm.h @@ -0,0 +1,144 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKP of NNUE evaluation function + +#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED +#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED + +#include + +#include "../../misc.h" +#include "../../types.h" +#include "../nnue_common.h" + +namespace Stockfish { +class Position; +} + +namespace Stockfish::Eval::NNUE::Features { + +// Feature HalfKAv2_hm: Combination of the position of own king and the +// position of pieces. Position mirrored such that king is always on e..h files. +class HalfKAv2_hm { + + // Unique number for each piece type on each square + enum { + PS_NONE = 0, + PS_W_PAWN = 0, + PS_B_PAWN = 1 * SQUARE_NB, + PS_W_KNIGHT = 2 * SQUARE_NB, + PS_B_KNIGHT = 3 * SQUARE_NB, + PS_W_BISHOP = 4 * SQUARE_NB, + PS_B_BISHOP = 5 * SQUARE_NB, + PS_W_ROOK = 6 * SQUARE_NB, + PS_B_ROOK = 7 * SQUARE_NB, + PS_W_QUEEN = 8 * SQUARE_NB, + PS_B_QUEEN = 9 * SQUARE_NB, + PS_KING = 10 * SQUARE_NB, + PS_NB = 11 * SQUARE_NB + }; + + static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { + // Convention: W - us, B - them + // Viewed from other side, W and B are reversed + {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE}, + {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}}; + + public: + // Feature name + static constexpr const char* Name = "HalfKAv2_hm(Friend)"; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t HashValue = 0x7f234cb8u; + + // Number of feature dimensions + static constexpr IndexType Dimensions = + static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; + +#define B(v) (v * PS_NB) + // clang-format off + static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = { + { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), + B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), + B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), + B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), + B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), + B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), + B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), + B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) }, + { B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0), + B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), + B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), + B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), + B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), + B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), + B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), + B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) } + }; + // clang-format on +#undef B + // clang-format off + // Orient a square according to perspective (rotates by 180 for black) + static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { + { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 }, + { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 } + }; + // clang-format on + + // Maximum number of simultaneously active features. + static constexpr IndexType MaxActiveDimensions = 32; + using IndexList = ValueList; + + // Index of a feature for a given king position and another piece on some square + template + static IndexType make_index(Square s, Piece pc, Square ksq); + + // Get a list of indices for active features + template + static void append_active_indices(const Position& pos, IndexList& active); + + // Get a list of indices for recently changed features + template + static void + append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); + + // Returns whether the change stored in this DirtyPiece means + // that a full accumulator refresh is required. + static bool requires_refresh(const DirtyPiece& dirtyPiece, Color perspective); +}; + +} // namespace Stockfish::Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED diff --git a/stockfish/src/nnue/layers/affine_transform.h b/stockfish/src/nnue/layers/affine_transform.h new file mode 100644 index 0000000000000000000000000000000000000000..dac727e23bcd04200666938a62af24f197444592 --- /dev/null +++ b/stockfish/src/nnue/layers/affine_transform.h @@ -0,0 +1,306 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer AffineTransform of NNUE evaluation function + +#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED + +#include +#include + +#include "../nnue_common.h" +#include "simd.h" + +/* + This file contains the definition for a fully connected layer (aka affine transform). + + - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32. + - that's why AVX512 is hard to implement + - expected use-case is small layers + - inputs are processed in chunks of 4, weights are respectively transposed + - accumulation happens directly to int32s +*/ + +namespace Stockfish::Eval::NNUE::Layers { + +#if defined(USE_SSSE3) || defined(USE_NEON_DOTPROD) + #define ENABLE_SEQ_OPT +#endif + +// Fallback implementation for older/other architectures. +// Requires the input to be padded to at least 16 values. +#ifndef ENABLE_SEQ_OPT + +template +static void affine_transform_non_ssse3(std::int32_t* output, + const std::int8_t* weights, + const std::int32_t* biases, + const std::uint8_t* input) { + #if defined(USE_SSE2) || defined(USE_NEON) + #if defined(USE_SSE2) + // At least a multiple of 16, with SSE2. + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const __m128i Zeros = _mm_setzero_si128(); + const auto inputVector = reinterpret_cast(input); + + #elif defined(USE_NEON) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); + #endif + + for (IndexType i = 0; i < OutputDimensions; ++i) + { + const IndexType offset = i * PaddedInputDimensions; + + #if defined(USE_SSE2) + __m128i sumLo = _mm_cvtsi32_si128(biases[i]); + __m128i sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m128i row_j = _mm_load_si128(&row[j]); + __m128i input_j = _mm_load_si128(&inputVector[j]); + __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); + __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); + __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); + __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); + __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); + __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_epi32(sumLo, productLo); + sumHi = _mm_add_epi32(sumHi, productHi); + } + __m128i sum = _mm_add_epi32(sumLo, sumHi); + __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sumHigh_64); + __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sum_second_32); + output[i] = _mm_cvtsi128_si32(sum); + + #elif defined(USE_NEON) + + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); + product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); + sum = vpadalq_s16(sum, product); + } + output[i] = Simd::neon_m128_reduce_add_epi32(sum); + + #endif + } + #else + std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); + + // Traverse weights in transpose order to take advantage of input sparsity + for (IndexType i = 0; i < InputDimensions; ++i) + if (input[i]) + { + const std::int8_t* w = &weights[i]; + const int in = input[i]; + for (IndexType j = 0; j < OutputDimensions; ++j) + output[j] += w[j * PaddedInputDimensions] * in; + } + #endif +} + +#endif // !ENABLE_SEQ_OPT + +template +class AffineTransform { + public: + // Input/output type + using InputType = std::uint8_t; + using OutputType = std::int32_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = OutDims; + + static constexpr IndexType PaddedInputDimensions = + ceil_to_multiple(InputDimensions, MaxSimdWidth); + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, MaxSimdWidth); + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; + } + + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + i % 4; + } + + static constexpr IndexType get_weight_index(IndexType i) { +#ifdef ENABLE_SEQ_OPT + return get_weight_index_scrambled(i); +#else + return i; +#endif + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); + + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + write_little_endian(stream, biases, OutputDimensions); + + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); + + return !stream.fail(); + } + // Forward propagation + void propagate(const InputType* input, OutputType* output) const { + +#ifdef ENABLE_SEQ_OPT + + if constexpr (OutputDimensions > 1) + { + #if defined(USE_AVX512) + using vec_t = __m512i; + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #elif defined(USE_AVX2) + using vec_t = __m256i; + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #elif defined(USE_NEON_DOTPROD) + using vec_t = int32x4_t; + #define vec_set_32 vdupq_n_s32 + #define vec_add_dpbusd_32(acc, a, b) \ + Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ + vreinterpretq_s8_s32(b)) + #endif + + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + + static_assert(OutputDimensions % OutputSimdWidth == 0); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + + const auto input32 = reinterpret_cast(input); + const vec_t* biasvec = reinterpret_cast(biases); + vec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType i = 0; i < NumChunks; ++i) + { + const vec_t in0 = vec_set_32(input32[i]); + const auto col0 = + reinterpret_cast(&weights[i * OutputDimensions * 4]); + + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in0, col0[k]); + } + + vec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + + #undef vec_set_32 + #undef vec_add_dpbusd_32 + } + else if constexpr (OutputDimensions == 1) + { + // We cannot use AVX512 for the last layer because there are only 32 inputs + // and the buffer is not padded to 64 elements. + #if defined(USE_AVX2) + using vec_t = __m256i; + #define vec_setzero() _mm256_setzero_si256() + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_hadd Simd::m256_hadd + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero() _mm_setzero_si128() + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_hadd Simd::m128_hadd + #elif defined(USE_NEON_DOTPROD) + using vec_t = int32x4_t; + #define vec_setzero() vdupq_n_s32(0) + #define vec_set_32 vdupq_n_s32 + #define vec_add_dpbusd_32(acc, a, b) \ + Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ + vreinterpretq_s8_s32(b)) + #define vec_hadd Simd::neon_m128_hadd + #endif + + const auto inputVector = reinterpret_cast(input); + + static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); + + static_assert(PaddedInputDimensions % InputSimdWidth == 0); + + constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; + vec_t sum0 = vec_setzero(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < int(NumChunks); ++j) + { + const vec_t in = inputVector[j]; + vec_add_dpbusd_32(sum0, in, row0[j]); + } + output[0] = vec_hadd(sum0, biases[0]); + + #undef vec_setzero + #undef vec_set_32 + #undef vec_add_dpbusd_32 + #undef vec_hadd + } +#else + // Use old implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); +#endif + } + + private: + using BiasType = OutputType; + using WeightType = std::int8_t; + + alignas(CacheLineSize) BiasType biases[OutputDimensions]; + alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/stockfish/src/nnue/layers/affine_transform_sparse_input.h b/stockfish/src/nnue/layers/affine_transform_sparse_input.h new file mode 100644 index 0000000000000000000000000000000000000000..be5e30b5e2fd63f937bef1b3334d1ab395330990 --- /dev/null +++ b/stockfish/src/nnue/layers/affine_transform_sparse_input.h @@ -0,0 +1,306 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer AffineTransformSparseInput of NNUE evaluation function + +#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED +#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED + +#include +#include +#include +#include + +#include "../../bitboard.h" +#include "../nnue_common.h" +#include "affine_transform.h" +#include "simd.h" + +/* + This file contains the definition for a fully connected layer (aka affine transform) with block sparse input. +*/ + +namespace Stockfish::Eval::NNUE::Layers { + +#if (USE_SSSE3 | (USE_NEON >= 8)) +static constexpr int lsb_index64[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, + 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, + 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63}; + +constexpr int constexpr_lsb(uint64_t bb) { + assert(bb != 0); + constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL; + return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58]; +} + +alignas(CacheLineSize) static constexpr struct OffsetIndices { + + #if (USE_SSE41) + std::uint8_t offset_indices[256][8]; + #else + std::uint16_t offset_indices[256][8]; + #endif + + constexpr OffsetIndices() : + offset_indices() { + for (int i = 0; i < 256; ++i) + { + std::uint64_t j = i, k = 0; + while (j) + { + offset_indices[i][k++] = constexpr_lsb(j); + j &= j - 1; + } + while (k < 8) + offset_indices[i][k++] = 0; + } + } + +} Lookup; + +// Find indices of nonzero numbers in an int32_t array +template +void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { + #if defined(USE_SSSE3) + #if defined(USE_AVX512) + using vec_t = __m512i; + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) + #elif defined(USE_AVX2) + using vec_t = __m256i; + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) \ + _mm256_movemask_ps( \ + _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_nnz(a) \ + _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) + #endif + using vec128_t = __m128i; + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + #elif defined(USE_NEON) + using vec_t = uint32x4_t; + static const std::uint32_t Mask[4] = {1, 2, 4, 8}; + #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) + using vec128_t = uint16x8_t; + #define vec128_zero vdupq_n_u16(0) + #define vec128_set_16(a) vdupq_n_u16(a) + #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) + #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) + #define vec128_add(a, b) vaddq_u16(a, b) + #endif + constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); + // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) + constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); + constexpr IndexType NumChunks = InputDimensions / ChunkSize; + constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; + constexpr IndexType OutputsPerChunk = ChunkSize / 8; + + const auto inputVector = reinterpret_cast(input); + IndexType count = 0; + vec128_t base = vec128_zero; + const vec128_t increment = vec128_set_16(8); + for (IndexType i = 0; i < NumChunks; ++i) + { + // bitmask of nonzero values in this chunk + unsigned nnz = 0; + for (IndexType j = 0; j < InputsPerChunk; ++j) + { + const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; + nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); + } + for (IndexType j = 0; j < OutputsPerChunk; ++j) + { + const unsigned lookup = (nnz >> (j * 8)) & 0xFF; + const vec128_t offsets = + vec128_load(reinterpret_cast(&Lookup.offset_indices[lookup])); + vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); + count += popcount(lookup); + base = vec128_add(base, increment); + } + } + count_out = count; +} + #undef vec_nnz + #undef vec128_zero + #undef vec128_set_16 + #undef vec128_load + #undef vec128_storeu + #undef vec128_add +#endif + +// Sparse input implementation +template +class AffineTransformSparseInput { + public: + // Input/output type + using InputType = std::uint8_t; + using OutputType = std::int32_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = OutDims; + + static_assert(OutputDimensions % 16 == 0, + "Only implemented for OutputDimensions divisible by 16."); + + static constexpr IndexType PaddedInputDimensions = + ceil_to_multiple(InputDimensions, MaxSimdWidth); + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, MaxSimdWidth); + +#if (USE_SSSE3 | (USE_NEON >= 8)) + static constexpr IndexType ChunkSize = 4; +#else + static constexpr IndexType ChunkSize = 1; +#endif + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; + } + + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + + i / PaddedInputDimensions * ChunkSize + i % ChunkSize; + } + + static constexpr IndexType get_weight_index(IndexType i) { +#if (USE_SSSE3 | (USE_NEON >= 8)) + return get_weight_index_scrambled(i); +#else + return i; +#endif + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); + + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + write_little_endian(stream, biases, OutputDimensions); + + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); + + return !stream.fail(); + } + // Forward propagation + void propagate(const InputType* input, OutputType* output) const { + +#if (USE_SSSE3 | (USE_NEON >= 8)) + #if defined(USE_AVX512) + using invec_t = __m512i; + using outvec_t = __m512i; + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #elif defined(USE_AVX2) + using invec_t = __m256i; + using outvec_t = __m256i; + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #elif defined(USE_SSSE3) + using invec_t = __m128i; + using outvec_t = __m128i; + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #elif defined(USE_NEON_DOTPROD) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 + #elif defined(USE_NEON) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 + #endif + static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + std::uint16_t nnz[NumChunks]; + IndexType count; + + const auto input32 = reinterpret_cast(input); + + // Find indices of nonzero 32-bit blocks + find_nnz(input32, nnz, count); + + const outvec_t* biasvec = reinterpret_cast(biases); + outvec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType j = 0; j < count; ++j) + { + const auto i = nnz[j]; + const invec_t in = vec_set_32(input32[i]); + const auto col = + reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in, col[k]); + } + + outvec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + #undef vec_set_32 + #undef vec_add_dpbusd_32 +#else + // Use dense implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); +#endif + } + + private: + using BiasType = OutputType; + using WeightType = std::int8_t; + + alignas(CacheLineSize) BiasType biases[OutputDimensions]; + alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED diff --git a/stockfish/src/nnue/layers/clipped_relu.h b/stockfish/src/nnue/layers/clipped_relu.h new file mode 100644 index 0000000000000000000000000000000000000000..2ad5a86a12c8a18739b5367addc87a594132488c --- /dev/null +++ b/stockfish/src/nnue/layers/clipped_relu.h @@ -0,0 +1,164 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer ClippedReLU of NNUE evaluation function + +#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED + +#include +#include +#include + +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Layers { + +// Clipped ReLU +template +class ClippedReLU { + public: + // Input/output type + using InputType = std::int32_t; + using OutputType = std::uint8_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = InputDimensions; + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, 32); + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream&) { return true; } + + // Write network parameters + bool write_parameters(std::ostream&) const { return true; } + + // Forward propagation + void propagate(const InputType* input, OutputType* output) const { + +#if defined(USE_AVX2) + if constexpr (InputDimensions % SimdWidth == 0) + { + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m256i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m256i words0 = + _mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 0]), + _mm256_load_si256(&in[i * 4 + 1])), + WeightScaleBits); + const __m256i words1 = + _mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 2]), + _mm256_load_si256(&in[i * 4 + 3])), + WeightScaleBits); + _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32( + _mm256_packs_epi16(words0, words1), Offsets)); + } + } + else + { + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m128i words0 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + } + } + constexpr IndexType Start = InputDimensions % SimdWidth == 0 + ? InputDimensions / SimdWidth * SimdWidth + : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); + +#elif defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + + #ifndef USE_SSE41 + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + #if defined(USE_SSE41) + const __m128i words0 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + #else + const __m128i words0 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)); + #endif + } + constexpr IndexType Start = NumChunks * SimdWidth; + +#elif defined(USE_NEON) + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + int16x8_t shifted; + const auto pack = reinterpret_cast(&shifted); + pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); + pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); + out[i] = vmax_s8(vqmovn_s16(shifted), Zero); + } + constexpr IndexType Start = NumChunks * (SimdWidth / 2); +#else + constexpr IndexType Start = 0; +#endif + + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast(std::clamp(input[i] >> WeightScaleBits, 0, 127)); + } + } +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/stockfish/src/nnue/layers/simd.h b/stockfish/src/nnue/layers/simd.h new file mode 100644 index 0000000000000000000000000000000000000000..70ca68a0c708596e8447cb53c716500179467b6d --- /dev/null +++ b/stockfish/src/nnue/layers/simd.h @@ -0,0 +1,134 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef STOCKFISH_SIMD_H_INCLUDED +#define STOCKFISH_SIMD_H_INCLUDED + +#if defined(USE_AVX2) + #include + +#elif defined(USE_SSE41) + #include + +#elif defined(USE_SSSE3) + #include + +#elif defined(USE_SSE2) + #include + +#elif defined(USE_NEON) + #include +#endif + +namespace Stockfish::Simd { + +#if defined(USE_AVX512) + +[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { + return _mm512_reduce_add_epi32(sum) + bias; +} + +[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) { + + #if defined(USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a, b); + #else + __m512i product0 = _mm512_maddubs_epi16(a, b); + product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, product0); + #endif +} + +#endif + +#if defined(USE_AVX2) + +[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { + __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); + return _mm_cvtsi128_si32(sum128) + bias; +} + +[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) { + + #if defined(USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a, b); + #else + __m256i product0 = _mm256_maddubs_epi16(a, b); + product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, product0); + #endif +} + +#endif + +#if defined(USE_SSSE3) + +[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB + return _mm_cvtsi128_si32(sum) + bias; +} + +[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) { + + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, product0); +} + +#endif + +#if defined(USE_NEON_DOTPROD) + +[[maybe_unused]] static void +dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + acc = vdotq_s32(acc, a, b); +} +#endif + +#if defined(USE_NEON) + +[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { + #if USE_NEON >= 8 + return vaddvq_s32(s); + #else + return s[0] + s[1] + s[2] + s[3]; + #endif +} + +[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { + return neon_m128_reduce_add_epi32(sum) + bias; +} + +#endif + +#if USE_NEON >= 8 +[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); + int16x8_t product1 = vmull_high_s8(a, b); + int16x8_t sum = vpaddq_s16(product0, product1); + acc = vpadalq_s16(acc, sum); +} +#endif +} + +#endif // STOCKFISH_SIMD_H_INCLUDED diff --git a/stockfish/src/nnue/layers/sqr_clipped_relu.h b/stockfish/src/nnue/layers/sqr_clipped_relu.h new file mode 100644 index 0000000000000000000000000000000000000000..d14f1e0aba3c37b21d41d1a9930e58063c8af3f1 --- /dev/null +++ b/stockfish/src/nnue/layers/sqr_clipped_relu.h @@ -0,0 +1,103 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer ClippedReLU of NNUE evaluation function + +#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED + +#include +#include +#include + +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Layers { + +// Clipped ReLU +template +class SqrClippedReLU { + public: + // Input/output type + using InputType = std::int32_t; + using OutputType = std::uint8_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = InputDimensions; + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, 32); + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream&) { return true; } + + // Write network parameters + bool write_parameters(std::ostream&) const { return true; } + + // Forward propagation + void propagate(const InputType* input, OutputType* output) const { + +#if defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / 16; + + static_assert(WeightScaleBits == 6); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + __m128i words0 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])); + __m128i words1 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])); + + // We shift by WeightScaleBits * 2 = 12 and divide by 128 + // which is an additional shift-right of 7, meaning 19 in total. + // MulHi strips the lower 16 bits so we need to shift out 3 more to match. + words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); + words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); + + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + } + constexpr IndexType Start = NumChunks * 16; + +#else + constexpr IndexType Start = 0; +#endif + + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast( + // Really should be /127 but we need to make it fast so we right-shift + // by an extra 7 bits instead. Needs to be accounted for in the trainer. + std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7))); + } + } +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED diff --git a/stockfish/src/nnue/network.cpp b/stockfish/src/nnue/network.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cba3abc63014ae00bda000d7f8929be1e0cb12ab --- /dev/null +++ b/stockfish/src/nnue/network.cpp @@ -0,0 +1,463 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "network.h" + +#include +#include +#include +#include +#include +#include +#include + +#define INCBIN_SILENCE_BITCODE_WARNING +#include "../incbin/incbin.h" + +#include "../evaluate.h" +#include "../memory.h" +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_common.h" +#include "nnue_misc.h" + +// Macro to embed the default efficiently updatable neural network (NNUE) file +// data in the engine binary (using incbin.h, by Dale Weiler). +// This macro invocation will declare the following three variables +// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data +// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end +// const unsigned int gEmbeddedNNUESize; // the size of the embedded file +// Note that this does not work in Microsoft Visual Studio. +#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) +INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); +INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); +#else +const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; +const unsigned int gEmbeddedNNUEBigSize = 1; +const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; +const unsigned int gEmbeddedNNUESmallSize = 1; +#endif + +namespace { + +struct EmbeddedNNUE { + EmbeddedNNUE(const unsigned char* embeddedData, + const unsigned char* embeddedEnd, + const unsigned int embeddedSize) : + data(embeddedData), + end(embeddedEnd), + size(embeddedSize) {} + const unsigned char* data; + const unsigned char* end; + const unsigned int size; +}; + +using namespace Stockfish::Eval::NNUE; + +EmbeddedNNUE get_embedded(EmbeddedNNUEType type) { + if (type == EmbeddedNNUEType::BIG) + return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); + else + return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); +} + +} + + +namespace Stockfish::Eval::NNUE { + + +namespace Detail { + +// Read evaluation function parameters +template +bool read_parameters(std::istream& stream, T& reference) { + + std::uint32_t header; + header = read_little_endian(stream); + if (!stream || header != T::get_hash_value()) + return false; + return reference.read_parameters(stream); +} + +// Write evaluation function parameters +template +bool write_parameters(std::ostream& stream, T& reference) { + + write_little_endian(stream, T::get_hash_value()); + return reference.write_parameters(stream); +} + +} // namespace Detail + +template +Network::Network(const Network& other) : + evalFile(other.evalFile), + embeddedType(other.embeddedType) { + + if (other.featureTransformer) + featureTransformer = make_unique_large_page(*other.featureTransformer); + + network = make_unique_aligned(LayerStacks); + + if (!other.network) + return; + + for (std::size_t i = 0; i < LayerStacks; ++i) + network[i] = other.network[i]; +} + +template +Network& +Network::operator=(const Network& other) { + evalFile = other.evalFile; + embeddedType = other.embeddedType; + + if (other.featureTransformer) + featureTransformer = make_unique_large_page(*other.featureTransformer); + + network = make_unique_aligned(LayerStacks); + + if (!other.network) + return *this; + + for (std::size_t i = 0; i < LayerStacks; ++i) + network[i] = other.network[i]; + + return *this; +} + +template +void Network::load(const std::string& rootDirectory, std::string evalfilePath) { +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", rootDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", rootDirectory}; +#endif + + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + for (const auto& directory : dirs) + { + if (evalFile.current != evalfilePath) + { + if (directory != "") + { + load_user_net(directory, evalfilePath); + } + + if (directory == "" && evalfilePath == evalFile.defaultName) + { + load_internal(); + } + } + } +} + + +template +bool Network::save(const std::optional& filename) const { + std::string actualFilename; + std::string msg; + + if (filename.has_value()) + actualFilename = filename.value(); + else + { + if (evalFile.current != evalFile.defaultName) + { + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; + + sync_cout << msg << sync_endl; + return false; + } + + actualFilename = evalFile.defaultName; + } + + std::ofstream stream(actualFilename, std::ios_base::binary); + bool saved = save(stream, evalFile.current, evalFile.netDescription); + + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; + + sync_cout << msg << sync_endl; + return saved; +} + + +template +NetworkOutput +Network::evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache) const { + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); +#else + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + + const int bucket = (pos.count() - 1) / 4; + const auto psqt = + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); + const auto positional = network[bucket].propagate(transformedFeatures); + return {static_cast(psqt / OutputScale), static_cast(positional / OutputScale)}; +} + + +template +void Network::verify(std::string evalfilePath, + const std::function& f) const { + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + if (evalFile.current != evalfilePath) + { + if (f) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + evalFile.defaultName; + std::string msg5 = "The engine will be terminated now."; + + std::string msg = "ERROR: " + msg1 + '\n' + "ERROR: " + msg2 + '\n' + "ERROR: " + msg3 + + '\n' + "ERROR: " + msg4 + '\n' + "ERROR: " + msg5 + '\n'; + + f(msg); + } + + exit(EXIT_FAILURE); + } + + if (f) + { + size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks; + f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024)) + + "MiB, (" + std::to_string(featureTransformer->InputDimensions) + ", " + + std::to_string(network[0].TransformedFeatureDimensions) + ", " + + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + + ", 1))"); + } +} + + +template +NnueEvalTrace +Network::trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache) const { + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); +#else + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + + NnueEvalTrace t{}; + t.correctBucket = (pos.count() - 1) / 4; + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) + { + const auto materialist = + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); + const auto positional = network[bucket].propagate(transformedFeatures); + + t.psqt[bucket] = static_cast(materialist / OutputScale); + t.positional[bucket] = static_cast(positional / OutputScale); + } + + return t; +} + + +template +void Network::load_user_net(const std::string& dir, + const std::string& evalfilePath) { + std::ifstream stream(dir + evalfilePath, std::ios::binary); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalfilePath; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::load_internal() { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } + }; + + const auto embedded = get_embedded(embeddedType); + + MemoryBuffer buffer(const_cast(reinterpret_cast(embedded.data)), + size_t(embedded.size)); + + std::istream stream(&buffer); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalFile.defaultName; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::initialize() { + featureTransformer = make_unique_large_page(); + network = make_unique_aligned(LayerStacks); +} + + +template +bool Network::save(std::ostream& stream, + const std::string& name, + const std::string& netDescription) const { + if (name.empty() || name == "None") + return false; + + return write_parameters(stream, netDescription); +} + + +template +std::optional Network::load(std::istream& stream) { + initialize(); + std::string description; + + return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt; +} + + +// Read network header +template +bool Network::read_header(std::istream& stream, + std::uint32_t* hashValue, + std::string* desc) const { + std::uint32_t version, size; + + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; + desc->resize(size); + stream.read(&(*desc)[0], size); + return !stream.fail(); +} + + +// Write network header +template +bool Network::write_header(std::ostream& stream, + std::uint32_t hashValue, + const std::string& desc) const { + write_little_endian(stream, Version); + write_little_endian(stream, hashValue); + write_little_endian(stream, std::uint32_t(desc.size())); + stream.write(&desc[0], desc.size()); + return !stream.fail(); +} + + +template +bool Network::read_parameters(std::istream& stream, + std::string& netDescription) const { + std::uint32_t hashValue; + if (!read_header(stream, &hashValue, &netDescription)) + return false; + if (hashValue != Network::hash) + return false; + if (!Detail::read_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::read_parameters(stream, network[i])) + return false; + } + return stream && stream.peek() == std::ios::traits_type::eof(); +} + + +template +bool Network::write_parameters(std::ostream& stream, + const std::string& netDescription) const { + if (!write_header(stream, Network::hash, netDescription)) + return false; + if (!Detail::write_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::write_parameters(stream, network[i])) + return false; + } + return bool(stream); +} + +// Explicit template instantiations + +template class Network< + NetworkArchitecture, + FeatureTransformer>; + +template class Network< + NetworkArchitecture, + FeatureTransformer>; + +} // namespace Stockfish::Eval::NNUE diff --git a/stockfish/src/nnue/network.h b/stockfish/src/nnue/network.h new file mode 100644 index 0000000000000000000000000000000000000000..21df4b0a14e33f243b7ab4ce8807240b9fb47030 --- /dev/null +++ b/stockfish/src/nnue/network.h @@ -0,0 +1,138 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NETWORK_H_INCLUDED +#define NETWORK_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../memory.h" +#include "../types.h" +#include "nnue_accumulator.h" +#include "nnue_architecture.h" +#include "nnue_feature_transformer.h" +#include "nnue_misc.h" + +namespace Stockfish { +class Position; +} + +namespace Stockfish::Eval::NNUE { + +enum class EmbeddedNNUEType { + BIG, + SMALL, +}; + +using NetworkOutput = std::tuple; + +template +class Network { + static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions; + + public: + Network(EvalFile file, EmbeddedNNUEType type) : + evalFile(file), + embeddedType(type) {} + + Network(const Network& other); + Network(Network&& other) = default; + + Network& operator=(const Network& other); + Network& operator=(Network&& other) = default; + + void load(const std::string& rootDirectory, std::string evalfilePath); + bool save(const std::optional& filename) const; + + NetworkOutput evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache) const; + + + void verify(std::string evalfilePath, const std::function&) const; + NnueEvalTrace trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache) const; + + private: + void load_user_net(const std::string&, const std::string&); + void load_internal(); + + void initialize(); + + bool save(std::ostream&, const std::string&, const std::string&) const; + std::optional load(std::istream&); + + bool read_header(std::istream&, std::uint32_t*, std::string*) const; + bool write_header(std::ostream&, std::uint32_t, const std::string&) const; + + bool read_parameters(std::istream&, std::string&) const; + bool write_parameters(std::ostream&, const std::string&) const; + + // Input feature converter + LargePagePtr featureTransformer; + + // Evaluation function + AlignedPtr network; + + EvalFile evalFile; + EmbeddedNNUEType embeddedType; + + // Hash value of evaluation function structure + static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); + + template + friend struct AccumulatorCaches::Cache; + + friend class AccumulatorStack; +}; + +// Definitions of the network types +using SmallFeatureTransformer = + FeatureTransformer; +using SmallNetworkArchitecture = + NetworkArchitecture; + +using BigFeatureTransformer = + FeatureTransformer; +using BigNetworkArchitecture = NetworkArchitecture; + +using NetworkBig = Network; +using NetworkSmall = Network; + + +struct Networks { + Networks(NetworkBig&& nB, NetworkSmall&& nS) : + big(std::move(nB)), + small(std::move(nS)) {} + + NetworkBig big; + NetworkSmall small; +}; + + +} // namespace Stockfish + +#endif diff --git a/stockfish/src/nnue/nnue_accumulator.cpp b/stockfish/src/nnue/nnue_accumulator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d693cc03129f37d7fef9ef052313a6e486fc1806 --- /dev/null +++ b/stockfish/src/nnue/nnue_accumulator.cpp @@ -0,0 +1,619 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "nnue_accumulator.h" + +#include +#include +#include + +#include "../bitboard.h" +#include "../position.h" +#include "../types.h" +#include "network.h" +#include "nnue_architecture.h" +#include "nnue_common.h" +#include "nnue_feature_transformer.h" + +namespace Stockfish::Eval::NNUE { + +#if defined(__GNUC__) && !defined(__clang__) + #define sf_assume(cond) \ + do \ + { \ + if (!(cond)) \ + __builtin_unreachable(); \ + } while (0) +#else + // do nothing for other compilers + #define sf_assume(cond) +#endif + +namespace { + +template AccumulatorState::*accPtr> +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed); + +template AccumulatorState::*accPtr> +void update_accumulator_refresh_cache( + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache); + +} + +void AccumulatorState::reset(const DirtyPiece& dp) noexcept { + dirtyPiece = dp; + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); +} + +const AccumulatorState& AccumulatorStack::latest() const noexcept { + return m_accumulators[m_current_idx - 1]; +} + +AccumulatorState& AccumulatorStack::mut_latest() noexcept { + return m_accumulators[m_current_idx - 1]; +} + +void AccumulatorStack::reset(const Position& rootPos, + const Networks& networks, + AccumulatorCaches& caches) noexcept { + m_current_idx = 1; + + update_accumulator_refresh_cache( + *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); + update_accumulator_refresh_cache( + *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); + + update_accumulator_refresh_cache( + *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); + update_accumulator_refresh_cache( + *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); +} + +void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { + assert(m_current_idx + 1 < m_accumulators.size()); + m_accumulators[m_current_idx].reset(dirtyPiece); + m_current_idx++; +} + +void AccumulatorStack::pop() noexcept { + assert(m_current_idx > 1); + m_current_idx--; +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + evaluate_side(pos, featureTransformer, cache); + evaluate_side(pos, featureTransformer, cache); +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::evaluate_side( + const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + const auto last_usable_accum = find_last_usable_accumulator(); + + if ((m_accumulators[last_usable_accum].*accPtr).computed[Perspective]) + forward_update_incremental(pos, featureTransformer, last_usable_accum); + + else + { + update_accumulator_refresh_cache(featureTransformer, pos, mut_latest(), cache); + backward_update_incremental(pos, featureTransformer, last_usable_accum); + } +} + +// Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator +// state just before a change that requires full refresh. +template AccumulatorState::*accPtr> +std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { + + for (std::size_t curr_idx = m_current_idx - 1; curr_idx > 0; curr_idx--) + { + if ((m_accumulators[curr_idx].*accPtr).computed[Perspective]) + return curr_idx; + + if (FeatureSet::requires_refresh(m_accumulators[curr_idx].dirtyPiece, Perspective)) + return curr_idx; + } + + return 0; +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::forward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept { + + assert(begin < m_accumulators.size()); + assert((m_accumulators[begin].*accPtr).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = begin + 1; next < m_current_idx; next++) + update_accumulator_incremental(featureTransformer, ksq, m_accumulators[next], + m_accumulators[next - 1]); + + assert((latest().*accPtr).computed[Perspective]); +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::backward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept { + + assert(end < m_accumulators.size()); + assert(end < m_current_idx); + assert((latest().*accPtr).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = m_current_idx - 2; next >= end; next--) + update_accumulator_incremental( + featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); + + assert((m_accumulators[end].*accPtr).computed[Perspective]); +} + +// Explicit template instantiations +template void +AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& + featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; +template void +AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& + featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + +namespace { + +template AccumulatorState::*accPtr> +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed) { + [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; + [[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS; + + assert(Forward != Backwards); + + assert((computed.*accPtr).computed[Perspective]); + assert(!(target_state.*accPtr).computed[Perspective]); + + // The size must be enough to contain the largest possible update. + // That might depend on the feature set and generally relies on the + // feature set's update cost calculation to be correct and never allow + // updates with more added/removed features than MaxActiveDimensions. + // In this case, the maximum size of both feature addition and removal + // is 2, since we are incrementally updating one move at a time. + FeatureSet::IndexList removed, added; + if constexpr (Forward) + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, + added); + else + FeatureSet::append_changed_indices(ksq, computed.dirtyPiece, added, removed); + + if (removed.size() == 0 && added.size() == 0) + { + std::memcpy((target_state.*accPtr).accumulation[Perspective], + (computed.*accPtr).accumulation[Perspective], + TransformedFeatureDimensions * sizeof(BiasType)); + std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], + (computed.*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + } + else + { + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); + + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1 || added.size() == 2); + sf_assume(removed.size() == 1 || removed.size() == 2); + +#ifdef VECTOR + auto* accIn = + reinterpret_cast(&(computed.*accPtr).accumulation[Perspective][0]); + auto* accOut = + reinterpret_cast(&(target_state.*accPtr).accumulation[Perspective][0]); + + const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; + auto* columnA0 = reinterpret_cast(&featureTransformer.weights[offsetA0]); + const IndexType offsetR0 = TransformedFeatureDimensions * removed[0]; + auto* columnR0 = reinterpret_cast(&featureTransformer.weights[offsetR0]); + + if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) + { + assert(added.size() == 1 && removed.size() == 1); + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), + vec_add_16(columnR0[i], columnR1[i])); + } + else if (Backwards && removed.size() == 1) + { + assert(added.size() == 2); + const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), + vec_sub_16(columnA1[i], columnR0[i])); + } + else + { + assert(added.size() == 2 && removed.size() == 2); + const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); + const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), + vec_add_16(columnR0[i], columnR1[i]))); + } + + auto* accPsqtIn = + reinterpret_cast(&(computed.*accPtr).psqtAccumulation[Perspective][0]); + auto* accPsqtOut = + reinterpret_cast(&(target_state.*accPtr).psqtAccumulation[Perspective][0]); + + const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; + auto* columnPsqtA0 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA0]); + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; + auto* columnPsqtR0 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR0]); + + if ((Forward && removed.size() == 1) + || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 + { + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = + vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), columnPsqtA0[i]); + } + else if (Forward && added.size() == 1) + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); + } + else if (Backwards && removed.size() == 1) + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); + } + else + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_add_psqt_32( + accPsqtIn[i], vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); + } +#else + std::memcpy((target_state.*accPtr).accumulation[Perspective], + (computed.*accPtr).accumulation[Perspective], + TransformedFeatureDimensions * sizeof(BiasType)); + std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], + (computed.*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + + // Difference calculation for the deactivated features + for (const auto index : removed) + { + const IndexType offset = TransformedFeatureDimensions * index; + for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) + (target_state.*accPtr).accumulation[Perspective][i] -= + featureTransformer.weights[offset + i]; + + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (target_state.*accPtr).psqtAccumulation[Perspective][i] -= + featureTransformer.psqtWeights[index * PSQTBuckets + i]; + } + + // Difference calculation for the activated features + for (const auto index : added) + { + const IndexType offset = TransformedFeatureDimensions * index; + for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) + (target_state.*accPtr).accumulation[Perspective][i] += + featureTransformer.weights[offset + i]; + + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (target_state.*accPtr).psqtAccumulation[Perspective][i] += + featureTransformer.psqtWeights[index * PSQTBuckets + i]; + } +#endif + } + + (target_state.*accPtr).computed[Perspective] = true; +} + +template AccumulatorState::*accPtr> +void update_accumulator_refresh_cache( + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache) { + using Tiling [[maybe_unused]] = SIMDTiling; + + const Square ksq = pos.square(Perspective); + auto& entry = cache[ksq][Perspective]; + FeatureSet::IndexList removed, added; + + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + } + } + + auto& accumulator = accumulatorState.*accPtr; + accumulator.computed[Perspective] = true; + +#ifdef VECTOR + const bool combineLast3 = + std::abs((int) removed.size() - (int) added.size()) == 1 && removed.size() + added.size() > 2; + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; + + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) + { + auto* accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = entryTile[k]; + + std::size_t i = 0; + for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); + } + if (combineLast3) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + if (removed.size() > added.size()) + { + IndexType indexR2 = removed[i + 1]; + const IndexType offsetR2 = Dimensions * indexR2 + j * Tiling::TileHeight; + auto* columnR2 = + reinterpret_cast(&featureTransformer.weights[offsetR2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), + vec_add_16(columnR[k], columnR2[k])); + } + else + { + IndexType indexA2 = added[i + 1]; + const IndexType offsetA2 = Dimensions * indexA2 + j * Tiling::TileHeight; + auto* columnA2 = + reinterpret_cast(&featureTransformer.weights[offsetA2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), + vec_add_16(columnA[k], columnA2[k])); + } + } + else + { + for (; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + } + + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) + { + auto* accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + auto* entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = entryTilePsqt[k]; + + for (std::size_t i = 0; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + for (std::size_t i = 0; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&entryTilePsqt[k], psqt[k]); + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + +#else + + for (const auto index : removed) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] -= featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] -= featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + for (const auto index : added) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] += featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] += featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + + // The accumulator of the refresh entry has been updated. + // Now copy its content to the actual accumulator we were refreshing. + + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, + sizeof(BiasType) * Dimensions); + + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, + sizeof(int32_t) * PSQTBuckets); +#endif + + for (Color c : {WHITE, BLACK}) + entry.byColorBB[c] = pos.pieces(c); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.byTypeBB[pt] = pos.pieces(pt); +} + +} + +} diff --git a/stockfish/src/nnue/nnue_accumulator.h b/stockfish/src/nnue/nnue_accumulator.h new file mode 100644 index 0000000000000000000000000000000000000000..362ea83e30a105fe0837f1d8d930d7bd11986dfd --- /dev/null +++ b/stockfish/src/nnue/nnue_accumulator.h @@ -0,0 +1,183 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Class for difference calculation of NNUE evaluation function + +#ifndef NNUE_ACCUMULATOR_H_INCLUDED +#define NNUE_ACCUMULATOR_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_common.h" + +namespace Stockfish { +class Position; +} + +namespace Stockfish::Eval::NNUE { + +using BiasType = std::int16_t; +using PSQTWeightType = std::int32_t; +using IndexType = std::uint32_t; + +struct Networks; + +template +struct alignas(CacheLineSize) Accumulator; + +struct AccumulatorState; + +template AccumulatorState::*accPtr> +class FeatureTransformer; + +// Class that holds the result of affine transformation of input features +template +struct alignas(CacheLineSize) Accumulator { + std::int16_t accumulation[COLOR_NB][Size]; + std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; + std::array computed; +}; + + +// AccumulatorCaches struct provides per-thread accumulator caches, where each +// cache contains multiple entries for each of the possible king squares. +// When the accumulator needs to be refreshed, the cached entry is used to more +// efficiently update the accumulator, instead of rebuilding it from scratch. +// This idea, was first described by Luecx (author of Koivisto) and +// is commonly referred to as "Finny Tables". +struct AccumulatorCaches { + + template + AccumulatorCaches(const Networks& networks) { + clear(networks); + } + + template + struct alignas(CacheLineSize) Cache { + + struct alignas(CacheLineSize) Entry { + BiasType accumulation[Size]; + PSQTWeightType psqtAccumulation[PSQTBuckets]; + Bitboard byColorBB[COLOR_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + + // To initialize a refresh entry, we set all its bitboards empty, + // so we put the biases in the accumulation, without any weights on top + void clear(const BiasType* biases) { + + std::memcpy(accumulation, biases, sizeof(accumulation)); + std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, + sizeof(Entry) - offsetof(Entry, psqtAccumulation)); + } + }; + + template + void clear(const Network& network) { + for (auto& entries1D : entries) + for (auto& entry : entries1D) + entry.clear(network.featureTransformer->biases); + } + + std::array& operator[](Square sq) { return entries[sq]; } + + std::array, SQUARE_NB> entries; + }; + + template + void clear(const Networks& networks) { + big.clear(networks.big); + small.clear(networks.small); + } + + Cache big; + Cache small; +}; + + +struct AccumulatorState { + Accumulator accumulatorBig; + Accumulator accumulatorSmall; + DirtyPiece dirtyPiece; + + void reset(const DirtyPiece& dp) noexcept; +}; + + +class AccumulatorStack { + public: + AccumulatorStack() : + m_accumulators(MAX_PLY + 1), + m_current_idx{} {} + + [[nodiscard]] const AccumulatorState& latest() const noexcept; + + void + reset(const Position& rootPos, const Networks& networks, AccumulatorCaches& caches) noexcept; + void push(const DirtyPiece& dirtyPiece) noexcept; + void pop() noexcept; + + template AccumulatorState::*accPtr> + void evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + private: + [[nodiscard]] AccumulatorState& mut_latest() noexcept; + + template AccumulatorState::*accPtr> + void evaluate_side(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + template AccumulatorState::*accPtr> + [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + + template AccumulatorState::*accPtr> + void + forward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept; + + template AccumulatorState::*accPtr> + void + backward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept; + + std::vector m_accumulators; + std::size_t m_current_idx; +}; + +} // namespace Stockfish::Eval::NNUE + +#endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/stockfish/src/nnue/nnue_architecture.h b/stockfish/src/nnue/nnue_architecture.h new file mode 100644 index 0000000000000000000000000000000000000000..0c9f097dc601d385f229a5784e38eb037916fd24 --- /dev/null +++ b/stockfish/src/nnue/nnue_architecture.h @@ -0,0 +1,137 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Input features and network structure used in NNUE evaluation function + +#ifndef NNUE_ARCHITECTURE_H_INCLUDED +#define NNUE_ARCHITECTURE_H_INCLUDED + +#include +#include +#include + +#include "features/half_ka_v2_hm.h" +#include "layers/affine_transform.h" +#include "layers/affine_transform_sparse_input.h" +#include "layers/clipped_relu.h" +#include "layers/sqr_clipped_relu.h" +#include "nnue_common.h" + +namespace Stockfish::Eval::NNUE { + +// Input features used in evaluation function +using FeatureSet = Features::HalfKAv2_hm; + +// Number of input feature dimensions after conversion +constexpr IndexType TransformedFeatureDimensionsBig = 3072; +constexpr int L2Big = 15; +constexpr int L3Big = 32; + +constexpr IndexType TransformedFeatureDimensionsSmall = 128; +constexpr int L2Small = 15; +constexpr int L3Small = 32; + +constexpr IndexType PSQTBuckets = 8; +constexpr IndexType LayerStacks = 8; + +template +struct NetworkArchitecture { + static constexpr IndexType TransformedFeatureDimensions = L1; + static constexpr int FC_0_OUTPUTS = L2; + static constexpr int FC_1_OUTPUTS = L3; + + Layers::AffineTransformSparseInput fc_0; + Layers::SqrClippedReLU ac_sqr_0; + Layers::ClippedReLU ac_0; + Layers::AffineTransform fc_1; + Layers::ClippedReLU ac_1; + Layers::AffineTransform fc_2; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + // input slice hash + std::uint32_t hashValue = 0xEC42E90Du; + hashValue ^= TransformedFeatureDimensions * 2; + + hashValue = decltype(fc_0)::get_hash_value(hashValue); + hashValue = decltype(ac_0)::get_hash_value(hashValue); + hashValue = decltype(fc_1)::get_hash_value(hashValue); + hashValue = decltype(ac_1)::get_hash_value(hashValue); + hashValue = decltype(fc_2)::get_hash_value(hashValue); + + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + return fc_0.read_parameters(stream) && ac_0.read_parameters(stream) + && fc_1.read_parameters(stream) && ac_1.read_parameters(stream) + && fc_2.read_parameters(stream); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + return fc_0.write_parameters(stream) && ac_0.write_parameters(stream) + && fc_1.write_parameters(stream) && ac_1.write_parameters(stream) + && fc_2.write_parameters(stream); + } + + std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { + struct alignas(CacheLineSize) Buffer { + alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out; + alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType + ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; + alignas(CacheLineSize) typename decltype(ac_0)::OutputBuffer ac_0_out; + alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out; + alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out; + alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out; + + Buffer() { std::memset(this, 0, sizeof(*this)); } + }; + +#if defined(__clang__) && (__APPLE__) + // workaround for a bug reported with xcode 12 + static thread_local auto tlsBuffer = std::make_unique(); + // Access TLS only once, cache result. + Buffer& buffer = *tlsBuffer; +#else + alignas(CacheLineSize) static thread_local Buffer buffer; +#endif + + fc_0.propagate(transformedFeatures, buffer.fc_0_out); + ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); + ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); + std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, + FC_0_OUTPUTS * sizeof(typename decltype(ac_0)::OutputType)); + fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); + ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); + fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); + + // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<. +*/ + +// Constants used in NNUE evaluation function + +#ifndef NNUE_COMMON_H_INCLUDED +#define NNUE_COMMON_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "../misc.h" + +#if defined(USE_AVX2) + #include + +#elif defined(USE_SSE41) + #include + +#elif defined(USE_SSSE3) + #include + +#elif defined(USE_SSE2) + #include + +#elif defined(USE_NEON) + #include +#endif + +namespace Stockfish::Eval::NNUE { + +// Version of the evaluation file +constexpr std::uint32_t Version = 0x7AF32F20u; + +// Constant used in evaluation value calculation +constexpr int OutputScale = 16; +constexpr int WeightScaleBits = 6; + +// Size of cache line (in bytes) +constexpr std::size_t CacheLineSize = 64; + +constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; +constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; + +// SIMD width (in bytes) +#if defined(USE_AVX2) +constexpr std::size_t SimdWidth = 32; + +#elif defined(USE_SSE2) +constexpr std::size_t SimdWidth = 16; + +#elif defined(USE_NEON) +constexpr std::size_t SimdWidth = 16; +#endif + +constexpr std::size_t MaxSimdWidth = 32; + +// Type of input feature after conversion +using TransformedFeatureType = std::uint8_t; +using IndexType = std::uint32_t; + +// Round n up to be a multiple of base +template +constexpr IntType ceil_to_multiple(IntType n, IntType base) { + return (n + base - 1) / base * base; +} + + +// Utility to read an integer (signed or unsigned, any size) +// from a stream in little-endian order. We swap the byte order after the read if +// necessary to return a result with the byte ordering of the compiling machine. +template +inline IntType read_little_endian(std::istream& stream) { + IntType result; + + if (IsLittleEndian) + stream.read(reinterpret_cast(&result), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = 0; + + stream.read(reinterpret_cast(u), sizeof(IntType)); + for (std::size_t i = 0; i < sizeof(IntType); ++i) + v = (v << 8) | u[sizeof(IntType) - i - 1]; + + std::memcpy(&result, &v, sizeof(IntType)); + } + + return result; +} + + +// Utility to write an integer (signed or unsigned, any size) +// to a stream in little-endian order. We swap the byte order before the write if +// necessary to always write in little-endian order, independently of the byte +// ordering of the compiling machine. +template +inline void write_little_endian(std::ostream& stream, IntType value) { + + if (IsLittleEndian) + stream.write(reinterpret_cast(&value), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = value; + + std::size_t i = 0; + // if constexpr to silence the warning about shift by 8 + if constexpr (sizeof(IntType) > 1) + { + for (; i + 1 < sizeof(IntType); ++i) + { + u[i] = std::uint8_t(v); + v >>= 8; + } + } + u[i] = std::uint8_t(v); + + stream.write(reinterpret_cast(u), sizeof(IntType)); + } +} + + +// Read integers in bulk from a little-endian stream. +// This reads N integers from stream s and puts them in array out. +template +inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { + if (IsLittleEndian) + stream.read(reinterpret_cast(out), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + out[i] = read_little_endian(stream); +} + + +// Write integers in bulk to a little-endian stream. +// This takes N integers from array values and writes them on stream s. +template +inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { + if (IsLittleEndian) + stream.write(reinterpret_cast(values), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + write_little_endian(stream, values[i]); +} + + +// Read N signed integers from the stream s, putting them in the array out. +// The stream is assumed to be compressed using the signed LEB128 format. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { + + // Check the presence of our LEB128 magic string + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + + auto bytes_left = read_little_endian(stream); + + std::uint32_t buf_pos = BUF_SIZE; + for (std::size_t i = 0; i < count; ++i) + { + IntType result = 0; + size_t shift = 0; + do + { + if (buf_pos == BUF_SIZE) + { + stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); + buf_pos = 0; + } + + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << shift; + shift += 7; + + if ((byte & 0x80) == 0) + { + out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) + ? result + : result | ~((1 << shift) - 1); + break; + } + } while (shift < sizeof(IntType) * 8); + } + + assert(bytes_left == 0); +} + + +// Write signed integers to a stream with LEB128 compression. +// This takes N integers from array values, compresses them with +// the LEB128 algorithm and writes the result on the stream s. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { + + // Write our LEB128 magic string + stream.write(Leb128MagicString, Leb128MagicStringSize); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + std::uint32_t byte_count = 0; + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + std::uint8_t byte; + do + { + byte = value & 0x7f; + value >>= 7; + ++byte_count; + } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + + write_little_endian(stream, byte_count); + + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + std::uint32_t buf_pos = 0; + + auto flush = [&]() { + if (buf_pos > 0) + { + stream.write(reinterpret_cast(buf), buf_pos); + buf_pos = 0; + } + }; + + auto write = [&](std::uint8_t byte) { + buf[buf_pos++] = byte; + if (buf_pos == BUF_SIZE) + flush(); + }; + + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + while (true) + { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((byte & 0x40) == 0 ? value == 0 : value == -1) + { + write(byte); + break; + } + write(byte | 0x80); + } + } + + flush(); +} + +enum IncUpdateDirection { + FORWARD, + BACKWARDS +}; + +} // namespace Stockfish::Eval::NNUE + +#endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/stockfish/src/nnue/nnue_feature_transformer.h b/stockfish/src/nnue/nnue_feature_transformer.h new file mode 100644 index 0000000000000000000000000000000000000000..20e85be3c999375a21f94f1d629a0f8b40ab1daf --- /dev/null +++ b/stockfish/src/nnue/nnue_feature_transformer.h @@ -0,0 +1,474 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// A class that converts the input features of the NNUE evaluation function + +#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED +#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED + +#include +#include +#include +#include + +#include "../position.h" +#include "../types.h" +#include "nnue_accumulator.h" +#include "nnue_architecture.h" +#include "nnue_common.h" + +namespace Stockfish::Eval::NNUE { + +using BiasType = std::int16_t; +using WeightType = std::int16_t; +using PSQTWeightType = std::int32_t; + +// If vector instructions are enabled, we update and refresh the +// accumulator tile by tile such that each tile fits in the CPU's +// vector registers. +#define VECTOR + +static_assert(PSQTBuckets % 8 == 0, + "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); + +#ifdef USE_AVX512 +using vec_t = __m512i; +using psqt_vec_t = __m256i; + #define vec_load(a) _mm512_load_si512(a) + #define vec_store(a, b) _mm512_store_si512(a, b) + #define vec_add_16(a, b) _mm512_add_epi16(a, b) + #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b) + #define vec_zero() _mm512_setzero_epi32() + #define vec_set_16(a) _mm512_set1_epi16(a) + #define vec_max_16(a, b) _mm512_max_epi16(a, b) + #define vec_min_16(a, b) _mm512_min_epi16(a, b) + #define vec_slli_16(a, b) _mm512_slli_epi16(a, b) + // Inverse permuted at load time + #define vec_packus_16(a, b) _mm512_packus_epi16(a, b) + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + #define MaxChunkSize 64 + +#elif USE_AVX2 +using vec_t = __m256i; +using psqt_vec_t = __m256i; + #define vec_load(a) _mm256_load_si256(a) + #define vec_store(a, b) _mm256_store_si256(a, b) + #define vec_add_16(a, b) _mm256_add_epi16(a, b) + #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b) + #define vec_zero() _mm256_setzero_si256() + #define vec_set_16(a) _mm256_set1_epi16(a) + #define vec_max_16(a, b) _mm256_max_epi16(a, b) + #define vec_min_16(a, b) _mm256_min_epi16(a, b) + #define vec_slli_16(a, b) _mm256_slli_epi16(a, b) + // Inverse permuted at load time + #define vec_packus_16(a, b) _mm256_packus_epi16(a, b) + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + #define MaxChunkSize 32 + +#elif USE_SSE2 +using vec_t = __m128i; +using psqt_vec_t = __m128i; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) _mm_add_epi16(a, b) + #define vec_sub_16(a, b) _mm_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm_mulhi_epi16(a, b) + #define vec_zero() _mm_setzero_si128() + #define vec_set_16(a) _mm_set1_epi16(a) + #define vec_max_16(a, b) _mm_max_epi16(a, b) + #define vec_min_16(a, b) _mm_min_epi16(a, b) + #define vec_slli_16(a, b) _mm_slli_epi16(a, b) + #define vec_packus_16(a, b) _mm_packus_epi16(a, b) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b) + #define vec_zero_psqt() _mm_setzero_si128() + #define NumRegistersSIMD (Is64Bit ? 16 : 8) + #define MaxChunkSize 16 + +#elif USE_NEON +using vec_t = int16x8_t; +using psqt_vec_t = int32x4_t; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) vaddq_s16(a, b) + #define vec_sub_16(a, b) vsubq_s16(a, b) + #define vec_mulhi_16(a, b) vqdmulhq_s16(a, b) + #define vec_zero() \ + vec_t { 0 } + #define vec_set_16(a) vdupq_n_s16(a) + #define vec_max_16(a, b) vmaxq_s16(a, b) + #define vec_min_16(a, b) vminq_s16(a, b) + #define vec_slli_16(a, b) vshlq_s16(a, vec_set_16(b)) + #define vec_packus_16(a, b) reinterpret_cast(vcombine_u8(vqmovun_s16(a), vqmovun_s16(b))) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) vaddq_s32(a, b) + #define vec_sub_psqt_32(a, b) vsubq_s32(a, b) + #define vec_zero_psqt() \ + psqt_vec_t { 0 } + #define NumRegistersSIMD 16 + #define MaxChunkSize 16 + +#else + #undef VECTOR + +#endif + +// Returns the inverse of a permutation +template +constexpr std::array +invert_permutation(const std::array& order) { + std::array inverse{}; + for (std::size_t i = 0; i < order.size(); i++) + inverse[order[i]] = i; + return inverse; +} + +// Divide a byte region of size TotalSize to chunks of size +// BlockSize, and permute the blocks by a given order +template +void permute(T (&data)[N], const std::array& order) { + constexpr std::size_t TotalSize = N * sizeof(T); + + static_assert(TotalSize % (BlockSize * OrderSize) == 0, + "ChunkSize * OrderSize must perfectly divide TotalSize"); + + constexpr std::size_t ProcessChunkSize = BlockSize * OrderSize; + + std::array buffer{}; + + std::byte* const bytes = reinterpret_cast(data); + + for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize) + { + std::byte* const values = &bytes[i]; + + for (std::size_t j = 0; j < OrderSize; j++) + { + auto* const buffer_chunk = &buffer[j * BlockSize]; + auto* const value_chunk = &values[order[j] * BlockSize]; + + std::copy(value_chunk, value_chunk + BlockSize, buffer_chunk); + } + + std::copy(std::begin(buffer), std::end(buffer), values); + } +} + +// Compute optimal SIMD register count for feature transformer accumulation. +template +class SIMDTiling { +#ifdef VECTOR + // We use __m* types as template arguments, which causes GCC to emit warnings + // about losing some attribute information. This is irrelevant to us as we + // only take their size, so the following pragma are harmless. + #if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wignored-attributes" + #endif + + template + static constexpr int BestRegisterCount() { + constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType); + constexpr std::size_t LaneSize = sizeof(LaneType); + + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); + + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; + + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; + + return 1; + } + + #if defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + + public: + static constexpr int NumRegs = + BestRegisterCount(); + static constexpr int NumPsqtRegs = + BestRegisterCount(); + + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; + static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; + + static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); + static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); +#endif +}; + + +// Input feature converter +template AccumulatorState::*accPtr> +class FeatureTransformer { + + // Number of output dimensions for one side + static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; + + public: + // Output type + using OutputType = TransformedFeatureType; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = FeatureSet::Dimensions; + static constexpr IndexType OutputDimensions = HalfDimensions; + + // Size of forward propagation buffer + static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType); + + // Store the order by which 128-bit blocks of a 1024-bit data must + // be permuted so that calling packus on adjacent vectors of 16-bit + // integers loaded from the data results in the pre-permutation order + static constexpr auto PackusEpi16Order = []() -> std::array { +#if defined(USE_AVX512) + // _mm512_packus_epi16 after permutation: + // | 0 | 2 | 4 | 6 | // Vector 0 + // | 1 | 3 | 5 | 7 | // Vector 1 + // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // Packed Result + return {0, 2, 4, 6, 1, 3, 5, 7}; +#elif defined(USE_AVX2) + // _mm256_packus_epi16 after permutation: + // | 0 | 2 | | 4 | 6 | // Vector 0, 2 + // | 1 | 3 | | 5 | 7 | // Vector 1, 3 + // | 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | // Packed Result + return {0, 2, 1, 3, 4, 6, 5, 7}; +#else + return {0, 1, 2, 3, 4, 5, 6, 7}; +#endif + }(); + + static constexpr auto InversePackusEpi16Order = invert_permutation(PackusEpi16Order); + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + return FeatureSet::HashValue ^ (OutputDimensions * 2); + } + + void permute_weights() { + permute<16>(biases, PackusEpi16Order); + permute<16>(weights, PackusEpi16Order); + } + + void unpermute_weights() { + permute<16>(biases, InversePackusEpi16Order); + permute<16>(weights, InversePackusEpi16Order); + } + + inline void scale_weights(bool read) { + for (IndexType j = 0; j < InputDimensions; ++j) + { + WeightType* w = &weights[j * HalfDimensions]; + for (IndexType i = 0; i < HalfDimensions; ++i) + w[i] = read ? w[i] * 2 : w[i] / 2; + } + + for (IndexType i = 0; i < HalfDimensions; ++i) + biases[i] = read ? biases[i] * 2 : biases[i] / 2; + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + + read_leb_128(stream, biases, HalfDimensions); + read_leb_128(stream, weights, HalfDimensions * InputDimensions); + read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + + permute_weights(); + scale_weights(true); + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) { + + unpermute_weights(); + scale_weights(false); + + write_leb_128(stream, biases, HalfDimensions); + write_leb_128(stream, weights, HalfDimensions * InputDimensions); + write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + + permute_weights(); + scale_weights(true); + return !stream.fail(); + } + + // Convert input features + std::int32_t transform(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache, + OutputType* output, + int bucket) const { + + accumulatorStack.evaluate(pos, *this, *cache); + const auto& accumulatorState = accumulatorStack.latest(); + + const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; + const auto& psqtAccumulation = (accumulatorState.*accPtr).psqtAccumulation; + const auto psqt = + (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) + / 2; + + const auto& accumulation = (accumulatorState.*accPtr).accumulation; + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = (HalfDimensions / 2) * p; + +#if defined(VECTOR) + + constexpr IndexType OutputChunkSize = MaxChunkSize; + static_assert((HalfDimensions / 2) % OutputChunkSize == 0); + constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; + + const vec_t Zero = vec_zero(); + const vec_t One = vec_set_16(127 * 2); + + const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); + const vec_t* in1 = + reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); + vec_t* out = reinterpret_cast(output + offset); + + // Per the NNUE architecture, here we want to multiply pairs of + // clipped elements and divide the product by 128. To do this, + // we can naively perform min/max operation to clip each of the + // four int16 vectors, mullo pairs together, then pack them into + // one int8 vector. However, there exists a faster way. + + // The idea here is to use the implicit clipping from packus to + // save us two vec_max_16 instructions. This clipping works due + // to the fact that any int16 integer below zero will be zeroed + // on packus. + + // Consider the case where the second element is negative. + // If we do standard clipping, that element will be zero, which + // means our pairwise product is zero. If we perform packus and + // remove the lower-side clip for the second element, then our + // product before packus will be negative, and is zeroed on pack. + // The two operation produce equivalent results, but the second + // one (using packus) saves one max operation per pair. + + // But here we run into a problem: mullo does not preserve the + // sign of the multiplication. We can get around this by doing + // mulhi, which keeps the sign. But that requires an additional + // tweak. + + // mulhi cuts off the last 16 bits of the resulting product, + // which is the same as performing a rightward shift of 16 bits. + // We can use this to our advantage. Recall that we want to + // divide the final product by 128, which is equivalent to a + // 7-bit right shift. Intuitively, if we shift the clipped + // value left by 9, and perform mulhi, which shifts the product + // right by 16 bits, then we will net a right shift of 7 bits. + // However, this won't work as intended. Since we clip the + // values to have a maximum value of 127, shifting it by 9 bits + // might occupy the signed bit, resulting in some positive + // values being interpreted as negative after the shift. + + // There is a way, however, to get around this limitation. When + // loading the network, scale accumulator weights and biases by + // 2. To get the same pairwise multiplication result as before, + // we need to divide the product by 128 * 2 * 2 = 512, which + // amounts to a right shift of 9 bits. So now we only have to + // shift left by 7 bits, perform mulhi (shifts right by 16 bits) + // and net a 9 bit right shift. Since we scaled everything by + // two, the values are clipped at 127 * 2 = 254, which occupies + // 8 bits. Shifting it by 7 bits left will no longer occupy the + // signed bit, so we are safe. + + // Note that on NEON processors, we shift left by 6 instead + // because the instruction "vqdmulhq_s16" also doubles the + // return value after the multiplication, adding an extra shift + // to the left by 1, so we compensate by shifting less before + // the multiplication. + + constexpr int shift = + #if defined(USE_SSE2) + 7; + #else + 6; + #endif + + for (IndexType j = 0; j < NumOutputChunks; ++j) + { + const vec_t sum0a = + vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift); + const vec_t sum0b = + vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift); + const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One); + const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One); + + const vec_t pa = vec_mulhi_16(sum0a, sum1a); + const vec_t pb = vec_mulhi_16(sum0b, sum1b); + + out[j] = vec_packus_16(pa, pb); + } + +#else + + for (IndexType j = 0; j < HalfDimensions / 2; ++j) + { + BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; + BiasType sum1 = + accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; + sum0 = std::clamp(sum0, 0, 127 * 2); + sum1 = std::clamp(sum1, 0, 127 * 2); + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 512); + } + +#endif + } + + return psqt; + } // end of function transform() + + alignas(CacheLineSize) BiasType biases[HalfDimensions]; + alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; + alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; +}; + +} // namespace Stockfish::Eval::NNUE + +#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/stockfish/src/nnue/nnue_misc.cpp b/stockfish/src/nnue/nnue_misc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..809d454b5ff816560dda19f69065f264877d1ded --- /dev/null +++ b/stockfish/src/nnue/nnue_misc.cpp @@ -0,0 +1,194 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Code for calculating NNUE evaluation function + +#include "nnue_misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../position.h" +#include "../types.h" +#include "../uci.h" +#include "network.h" +#include "nnue_accumulator.h" + +namespace Stockfish::Eval::NNUE { + + +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); + + +namespace { +// Converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +void format_cp_compact(Value v, char* buffer, const Position& pos) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(UCIEngine::to_cp(v, pos)); + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; + buffer[3] = '0' + cp / 100; + buffer[4] = ' '; + } + else if (cp >= 1000) + { + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; + buffer[3] = '.'; + buffer[4] = '0' + cp / 10; + } + else + { + buffer[1] = '0' + cp / 100; + cp %= 100; + buffer[2] = '.'; + buffer[3] = '0' + cp / 10; + cp %= 10; + buffer[4] = '0' + cp / 1; + } +} + + +// Converts a Value into pawns, always keeping two decimals +void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) { + + const double pawns = std::abs(0.01 * UCIEngine::to_cp(v, pos)); + + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} +} + + +// Returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string +trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) { + + std::stringstream ss; + + char board[3 * 8 + 1][8 * 8 + 2]; + std::memset(board, ' ', sizeof(board)); + for (int row = 0; row < 3 * 8 + 1; ++row) + board[row][8 * 8 + 1] = '\0'; + + // A lambda to output one box of the board + auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) { + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (is_valid(value)) + format_cp_compact(value, &board[y + 2][x + 2], pos); + }; + + AccumulatorStack accumulators; + accumulators.reset(pos, networks, caches); + + // We estimate the value of each piece by doing a differential evaluation from + // the current base eval, simulating the removal of the piece from its square. + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches.big); + Value base = psqt + positional; + base = pos.side_to_move() == WHITE ? base : -base; + + for (File f = FILE_A; f <= FILE_H; ++f) + for (Rank r = RANK_1; r <= RANK_8; ++r) + { + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; + + if (pc != NO_PIECE && type_of(pc) != KING) + { + pos.remove_piece(sq); + + accumulators.reset(pos, networks, caches); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + Value eval = psqt + positional; + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + } + + writeSquare(f, r, pc, v); + } + + ss << " NNUE derived piece values:\n"; + for (int row = 0; row < 3 * 8 + 1; ++row) + ss << board[row] << '\n'; + ss << '\n'; + + accumulators.reset(pos, networks, caches); + auto t = networks.big.trace_evaluate(pos, accumulators, &caches.big); + + ss << " NNUE network contributions " + << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl + << "+------------+------------+------------+------------+\n" + << "| Bucket | Material | Positional | Total |\n" + << "| | (PSQT) | (Layers) | |\n" + << "+------------+------------+------------+------------+\n"; + + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) + { + ss << "| " << bucket << " " // + << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss, pos); + ss << " " // + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss, pos); + ss << " " // + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); + ss << " " // + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; + } + + ss << "+------------+------------+------------+------------+\n"; + + return ss.str(); +} + + +} // namespace Stockfish::Eval::NNUE diff --git a/stockfish/src/nnue/nnue_misc.h b/stockfish/src/nnue/nnue_misc.h new file mode 100644 index 0000000000000000000000000000000000000000..02212160a124d3d46d7ae5637a8c176be079766e --- /dev/null +++ b/stockfish/src/nnue/nnue_misc.h @@ -0,0 +1,61 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NNUE_MISC_H_INCLUDED +#define NNUE_MISC_H_INCLUDED + +#include +#include + +#include "../types.h" +#include "nnue_architecture.h" + +namespace Stockfish { + +class Position; + +namespace Eval::NNUE { + +struct EvalFile { + // Default net name, will use one of the EvalFileDefaultName* macros defined + // in evaluate.h + std::string defaultName; + // Selected net name, either via uci option or default + std::string current; + // Net description extracted from the net file + std::string netDescription; +}; + + +struct NnueEvalTrace { + static_assert(LayerStacks == PSQTBuckets); + + Value psqt[LayerStacks]; + Value positional[LayerStacks]; + std::size_t correctBucket; +}; + +struct Networks; +struct AccumulatorCaches; + +std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches); + +} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish + +#endif // #ifndef NNUE_MISC_H_INCLUDED diff --git a/stockfish/src/numa.h b/stockfish/src/numa.h new file mode 100644 index 0000000000000000000000000000000000000000..5cadbc726fc5632aa4b43bc6a66bb33f347809f9 --- /dev/null +++ b/stockfish/src/numa.h @@ -0,0 +1,1346 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NUMA_H_INCLUDED +#define NUMA_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "memory.h" + +// We support linux very well, but we explicitly do NOT support Android, +// because there is no affected systems, not worth maintaining. +#if defined(__linux__) && !defined(__ANDROID__) + #if !defined(_GNU_SOURCE) + #define _GNU_SOURCE + #endif + #include +#elif defined(_WIN64) + + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif + +// On Windows each processor group can have up to 64 processors. +// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups +static constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64; + + #if !defined(NOMINMAX) + #define NOMINMAX + #endif + #include + #if defined small + #undef small + #endif + +// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadselectedcpusetmasks +using SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT); + +// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadselectedcpusetmasks +using GetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT, PUSHORT); + +#endif + +#include "misc.h" + +namespace Stockfish { + +using CpuIndex = size_t; +using NumaIndex = size_t; + +inline CpuIndex get_hardware_concurrency() { + CpuIndex concurrency = std::thread::hardware_concurrency(); + + // Get all processors across all processor groups on windows, since + // hardware_concurrency() only returns the number of processors in + // the first group, because only these are available to std::thread. +#ifdef _WIN64 + concurrency = std::max(concurrency, GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)); +#endif + + return concurrency; +} + +inline const CpuIndex SYSTEM_THREADS_NB = std::max(1, get_hardware_concurrency()); + +#if defined(_WIN64) + +struct WindowsAffinity { + std::optional> oldApi; + std::optional> newApi; + + // We also provide diagnostic for when the affinity is set to nullopt + // whether it was due to being indeterminate. If affinity is indeterminate + // it is best to assume it is not set at all, so consistent with the meaning + // of the nullopt affinity. + bool isNewDeterminate = true; + bool isOldDeterminate = true; + + std::optional> get_combined() const { + if (!oldApi.has_value()) + return newApi; + if (!newApi.has_value()) + return oldApi; + + std::set intersect; + std::set_intersection(oldApi->begin(), oldApi->end(), newApi->begin(), newApi->end(), + std::inserter(intersect, intersect.begin())); + return intersect; + } + + // Since Windows 11 and Windows Server 2022 thread affinities can span + // processor groups and can be set as such by a new WinAPI function. However, + // we may need to force using the old API if we detect that the process has + // affinity set by the old API already and we want to override that. Due to the + // limitations of the old API we cannot detect its use reliably. There will be + // cases where we detect not use but it has actually been used and vice versa. + + bool likely_used_old_api() const { return oldApi.has_value() || !isOldDeterminate; } +}; + +inline std::pair> get_process_group_affinity() { + + // GetProcessGroupAffinity requires the GroupArray argument to be + // aligned to 4 bytes instead of just 2. + static constexpr size_t GroupArrayMinimumAlignment = 4; + static_assert(GroupArrayMinimumAlignment >= alignof(USHORT)); + + // The function should succeed the second time, but it may fail if the group + // affinity has changed between GetProcessGroupAffinity calls. In such case + // we consider this a hard error, as we Cannot work with unstable affinities + // anyway. + static constexpr int MAX_TRIES = 2; + USHORT GroupCount = 1; + for (int i = 0; i < MAX_TRIES; ++i) + { + auto GroupArray = std::make_unique( + GroupCount + (GroupArrayMinimumAlignment / alignof(USHORT) - 1)); + + USHORT* GroupArrayAligned = align_ptr_up(GroupArray.get()); + + const BOOL status = + GetProcessGroupAffinity(GetCurrentProcess(), &GroupCount, GroupArrayAligned); + + if (status == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + break; + } + + if (status != 0) + { + return std::make_pair(status, + std::vector(GroupArrayAligned, GroupArrayAligned + GroupCount)); + } + } + + return std::make_pair(0, std::vector()); +} + +// On Windows there are two ways to set affinity, and therefore 2 ways to get it. +// These are not consistent, so we have to check both. In some cases it is actually +// not possible to determine affinity. For example when two different threads have +// affinity on different processor groups, set using SetThreadAffinityMask, we cannot +// retrieve the actual affinities. +// From documentation on GetProcessAffinityMask: +// > If the calling process contains threads in multiple groups, +// > the function returns zero for both affinity masks. +// In such cases we just give up and assume we have affinity for all processors. +// nullopt means no affinity is set, that is, all processors are allowed +inline WindowsAffinity get_process_affinity() { + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto GetThreadSelectedCpuSetMasks_f = GetThreadSelectedCpuSetMasks_t( + (void (*)()) GetProcAddress(k32, "GetThreadSelectedCpuSetMasks")); + + BOOL status = 0; + + WindowsAffinity affinity; + + if (GetThreadSelectedCpuSetMasks_f != nullptr) + { + USHORT RequiredMaskCount; + status = GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), nullptr, 0, &RequiredMaskCount); + + // We expect ERROR_INSUFFICIENT_BUFFER from GetThreadSelectedCpuSetMasks, + // but other failure is an actual error. + if (status == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + affinity.isNewDeterminate = false; + } + else if (RequiredMaskCount > 0) + { + // If RequiredMaskCount then these affinities were never set, but it's + // not consistent so GetProcessAffinityMask may still return some affinity. + auto groupAffinities = std::make_unique(RequiredMaskCount); + + status = GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(), + RequiredMaskCount, &RequiredMaskCount); + + if (status == 0) + { + affinity.isNewDeterminate = false; + } + else + { + std::set cpus; + + for (USHORT i = 0; i < RequiredMaskCount; ++i) + { + const size_t procGroupIndex = groupAffinities[i].Group; + + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (groupAffinities[i].Mask & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + } + + affinity.newApi = std::move(cpus); + } + } + } + + // NOTE: There is no way to determine full affinity using the old API if + // individual threads set affinity on different processor groups. + + DWORD_PTR proc, sys; + status = GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys); + + // If proc == 0 then we cannot determine affinity because it spans processor groups. + // On Windows 11 and Server 2022 it will instead + // > If, however, hHandle specifies a handle to the current process, the function + // > always uses the calling thread's primary group (which by default is the same + // > as the process' primary group) in order to set the + // > lpProcessAffinityMask and lpSystemAffinityMask. + // So it will never be indeterminate here. We can only make assumptions later. + if (status == 0 || proc == 0) + { + affinity.isOldDeterminate = false; + return affinity; + } + + // If SetProcessAffinityMask was never called the affinity must span + // all processor groups, but if it was called it must only span one. + + std::vector groupAffinity; // We need to capture this later and capturing + // from structured bindings requires c++20. + + std::tie(status, groupAffinity) = get_process_group_affinity(); + if (status == 0) + { + affinity.isOldDeterminate = false; + return affinity; + } + + if (groupAffinity.size() == 1) + { + // We detect the case when affinity is set to all processors and correctly + // leave affinity.oldApi as nullopt. + if (GetActiveProcessorGroupCount() != 1 || proc != sys) + { + std::set cpus; + + const size_t procGroupIndex = groupAffinity[0]; + + const uint64_t mask = static_cast(proc); + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (mask & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + + affinity.oldApi = std::move(cpus); + } + } + else + { + // If we got here it means that either SetProcessAffinityMask was never set + // or we're on Windows 11/Server 2022. + + // Since Windows 11 and Windows Server 2022 the behaviour of + // GetProcessAffinityMask changed: + // > If, however, hHandle specifies a handle to the current process, + // > the function always uses the calling thread's primary group + // > (which by default is the same as the process' primary group) + // > in order to set the lpProcessAffinityMask and lpSystemAffinityMask. + // In which case we can actually retrieve the full affinity. + + if (GetThreadSelectedCpuSetMasks_f != nullptr) + { + std::thread th([&]() { + std::set cpus; + bool isAffinityFull = true; + + for (auto procGroupIndex : groupAffinity) + { + const int numActiveProcessors = + GetActiveProcessorCount(static_cast(procGroupIndex)); + + // We have to schedule to two different processors + // and & the affinities we get. Otherwise our processor + // choice could influence the resulting affinity. + // We assume the processor IDs within the group are + // filled sequentially from 0. + uint64_t procCombined = std::numeric_limits::max(); + uint64_t sysCombined = std::numeric_limits::max(); + + for (int i = 0; i < std::min(numActiveProcessors, 2); ++i) + { + GROUP_AFFINITY GroupAffinity; + std::memset(&GroupAffinity, 0, sizeof(GROUP_AFFINITY)); + GroupAffinity.Group = static_cast(procGroupIndex); + + GroupAffinity.Mask = static_cast(1) << i; + + status = + SetThreadGroupAffinity(GetCurrentThread(), &GroupAffinity, nullptr); + if (status == 0) + { + affinity.isOldDeterminate = false; + return; + } + + SwitchToThread(); + + DWORD_PTR proc2, sys2; + status = GetProcessAffinityMask(GetCurrentProcess(), &proc2, &sys2); + if (status == 0) + { + affinity.isOldDeterminate = false; + return; + } + + procCombined &= static_cast(proc2); + sysCombined &= static_cast(sys2); + } + + if (procCombined != sysCombined) + isAffinityFull = false; + + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (procCombined & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + } + + // We have to detect the case where the affinity was not set, + // or is set to all processors so that we correctly produce as + // std::nullopt result. + if (!isAffinityFull) + { + affinity.oldApi = std::move(cpus); + } + }); + + th.join(); + } + } + + return affinity; +} + +#endif + +#if defined(__linux__) && !defined(__ANDROID__) + +inline std::set get_process_affinity() { + + std::set cpus; + + // For unsupported systems, or in case of a soft error, we may assume + // all processors are available for use. + [[maybe_unused]] auto set_to_all_cpus = [&]() { + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + cpus.insert(c); + }; + + // cpu_set_t by default holds 1024 entries. This may not be enough soon, + // but there is no easy way to determine how many threads there actually + // is. In this case we just choose a reasonable upper bound. + static constexpr CpuIndex MaxNumCpus = 1024 * 64; + + cpu_set_t* mask = CPU_ALLOC(MaxNumCpus); + if (mask == nullptr) + std::exit(EXIT_FAILURE); + + const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus); + + CPU_ZERO_S(masksize, mask); + + const int status = sched_getaffinity(0, masksize, mask); + + if (status != 0) + { + CPU_FREE(mask); + std::exit(EXIT_FAILURE); + } + + for (CpuIndex c = 0; c < MaxNumCpus; ++c) + if (CPU_ISSET_S(c, masksize, mask)) + cpus.insert(c); + + CPU_FREE(mask); + + return cpus; +} + +#endif + +#if defined(__linux__) && !defined(__ANDROID__) + +inline static const auto STARTUP_PROCESSOR_AFFINITY = get_process_affinity(); + +#elif defined(_WIN64) + +inline static const auto STARTUP_PROCESSOR_AFFINITY = get_process_affinity(); +inline static const auto STARTUP_USE_OLD_AFFINITY_API = + STARTUP_PROCESSOR_AFFINITY.likely_used_old_api(); + +#endif + +// We want to abstract the purpose of storing the numa node index somewhat. +// Whoever is using this does not need to know the specifics of the replication +// machinery to be able to access NUMA replicated memory. +class NumaReplicatedAccessToken { + public: + NumaReplicatedAccessToken() : + n(0) {} + + explicit NumaReplicatedAccessToken(NumaIndex idx) : + n(idx) {} + + NumaIndex get_numa_index() const { return n; } + + private: + NumaIndex n; +}; + +// Designed as immutable, because there is no good reason to alter an already +// existing config in a way that doesn't require recreating it completely, and +// it would be complex and expensive to maintain class invariants. +// The CPU (processor) numbers always correspond to the actual numbering used +// by the system. The NUMA node numbers MAY NOT correspond to the system's +// numbering of the NUMA nodes. In particular, empty nodes may be removed, or +// the user may create custom nodes. It is guaranteed that NUMA nodes are NOT +// empty: every node exposed by NumaConfig has at least one processor assigned. +// +// We use startup affinities so as not to modify its own behaviour in time. +// +// Since Stockfish doesn't support exceptions all places where an exception +// should be thrown are replaced by std::exit. +class NumaConfig { + public: + NumaConfig() : + highestCpuIndex(0), + customAffinity(false) { + const auto numCpus = SYSTEM_THREADS_NB; + add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1); + } + + // This function queries the system for the mapping of processors to NUMA nodes. + // On Linux we read from standardized kernel sysfs, with a fallback to single NUMA + // node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see + // comment for Windows implementation of get_process_affinity. + static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) { + NumaConfig cfg = empty(); + +#if defined(__linux__) && !defined(__ANDROID__) + + std::set allowedCpus; + + if (respectProcessAffinity) + allowedCpus = STARTUP_PROCESSOR_AFFINITY; + + auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) { + return !respectProcessAffinity || allowedCpus.count(c) == 1; + }; + + // On Linux things are straightforward, since there's no processor groups and + // any thread can be scheduled on all processors. + // We try to gather this information from the sysfs first + // https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node + + bool useFallback = false; + auto fallback = [&]() { + useFallback = true; + cfg = empty(); + }; + + // /sys/devices/system/node/online contains information about active NUMA nodes + auto nodeIdsStr = read_file_to_string("/sys/devices/system/node/online"); + if (!nodeIdsStr.has_value() || nodeIdsStr->empty()) + { + fallback(); + } + else + { + remove_whitespace(*nodeIdsStr); + for (size_t n : indices_from_shortened_string(*nodeIdsStr)) + { + // /sys/devices/system/node/node.../cpulist + std::string path = + std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist"; + auto cpuIdsStr = read_file_to_string(path); + // Now, we only bail if the file does not exist. Some nodes may be + // empty, that's fine. An empty node still has a file that appears + // to have some whitespace, so we need to handle that. + if (!cpuIdsStr.has_value()) + { + fallback(); + break; + } + else + { + remove_whitespace(*cpuIdsStr); + for (size_t c : indices_from_shortened_string(*cpuIdsStr)) + { + if (is_cpu_allowed(c)) + cfg.add_cpu_to_node(n, c); + } + } + } + } + + if (useFallback) + { + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + if (is_cpu_allowed(c)) + cfg.add_cpu_to_node(NumaIndex{0}, c); + } + +#elif defined(_WIN64) + + std::optional> allowedCpus; + + if (respectProcessAffinity) + allowedCpus = STARTUP_PROCESSOR_AFFINITY.get_combined(); + + // The affinity cannot be determined in all cases on Windows, + // but we at least guarantee that the number of allowed processors + // is >= number of processors in the affinity mask. In case the user + // is not satisfied they must set the processor numbers explicitly. + auto is_cpu_allowed = [&allowedCpus](CpuIndex c) { + return !allowedCpus.has_value() || allowedCpus->count(c) == 1; + }; + + WORD numProcGroups = GetActiveProcessorGroupCount(); + for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup) + { + for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number) + { + PROCESSOR_NUMBER procnum; + procnum.Group = procGroup; + procnum.Number = number; + procnum.Reserved = 0; + USHORT nodeNumber; + + const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber); + const CpuIndex c = static_cast(procGroup) * WIN_PROCESSOR_GROUP_SIZE + + static_cast(number); + if (status != 0 && nodeNumber != std::numeric_limits::max() + && is_cpu_allowed(c)) + { + cfg.add_cpu_to_node(nodeNumber, c); + } + } + } + + // Split the NUMA nodes to be contained within a group if necessary. + // This is needed between Windows 10 Build 20348 and Windows 11, because + // the new NUMA allocation behaviour was introduced while there was + // still no way to set thread affinity spanning multiple processor groups. + // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support + // We also do this is if need to force old API for some reason. + // + // 2024-08-26: It appears that we need to actually always force this behaviour. + // While Windows allows this to work now, such assignments have bad interaction + // with the scheduler - in particular it still prefers scheduling on the thread's + // "primary" node, even if it means scheduling SMT processors first. + // See https://github.com/official-stockfish/Stockfish/issues/5551 + // See https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups + // + // Each process is assigned a primary group at creation, and by default all + // of its threads' primary group is the same. Each thread's ideal processor + // is in the thread's primary group, so threads will preferentially be + // scheduled to processors on their primary group, but they are able to + // be scheduled to processors on any other group. + // + // used to be guarded by if (STARTUP_USE_OLD_AFFINITY_API) + { + NumaConfig splitCfg = empty(); + + NumaIndex splitNodeIndex = 0; + for (const auto& cpus : cfg.nodes) + { + if (cpus.empty()) + continue; + + size_t lastProcGroupIndex = *(cpus.begin()) / WIN_PROCESSOR_GROUP_SIZE; + for (CpuIndex c : cpus) + { + const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE; + if (procGroupIndex != lastProcGroupIndex) + { + splitNodeIndex += 1; + lastProcGroupIndex = procGroupIndex; + } + splitCfg.add_cpu_to_node(splitNodeIndex, c); + } + splitNodeIndex += 1; + } + + cfg = std::move(splitCfg); + } + +#else + + // Fallback for unsupported systems. + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + cfg.add_cpu_to_node(NumaIndex{0}, c); + +#endif + + // We have to ensure no empty NUMA nodes persist. + cfg.remove_empty_numa_nodes(); + + // If the user explicitly opts out from respecting the current process affinity + // then it may be inconsistent with the current affinity (obviously), so we + // consider it custom. + if (!respectProcessAffinity) + cfg.customAffinity = true; + + return cfg; + } + + // ':'-separated numa nodes + // ','-separated cpu indices + // supports "first-last" range syntax for cpu indices + // For example "0-15,128-143:16-31,144-159:32-47,160-175:48-63,176-191" + static NumaConfig from_string(const std::string& s) { + NumaConfig cfg = empty(); + + NumaIndex n = 0; + for (auto&& nodeStr : split(s, ":")) + { + auto indices = indices_from_shortened_string(std::string(nodeStr)); + if (!indices.empty()) + { + for (auto idx : indices) + { + if (!cfg.add_cpu_to_node(n, CpuIndex(idx))) + std::exit(EXIT_FAILURE); + } + + n += 1; + } + } + + cfg.customAffinity = true; + + return cfg; + } + + NumaConfig(const NumaConfig&) = delete; + NumaConfig(NumaConfig&&) = default; + NumaConfig& operator=(const NumaConfig&) = delete; + NumaConfig& operator=(NumaConfig&&) = default; + + bool is_cpu_assigned(CpuIndex n) const { return nodeByCpu.count(n) == 1; } + + NumaIndex num_numa_nodes() const { return nodes.size(); } + + CpuIndex num_cpus_in_numa_node(NumaIndex n) const { + assert(n < nodes.size()); + return nodes[n].size(); + } + + CpuIndex num_cpus() const { return nodeByCpu.size(); } + + bool requires_memory_replication() const { return customAffinity || nodes.size() > 1; } + + std::string to_string() const { + std::string str; + + bool isFirstNode = true; + for (auto&& cpus : nodes) + { + if (!isFirstNode) + str += ":"; + + bool isFirstSet = true; + auto rangeStart = cpus.begin(); + for (auto it = cpus.begin(); it != cpus.end(); ++it) + { + auto next = std::next(it); + if (next == cpus.end() || *next != *it + 1) + { + // cpus[i] is at the end of the range (may be of size 1) + if (!isFirstSet) + str += ","; + + const CpuIndex last = *it; + + if (it != rangeStart) + { + const CpuIndex first = *rangeStart; + + str += std::to_string(first); + str += "-"; + str += std::to_string(last); + } + else + str += std::to_string(last); + + rangeStart = next; + isFirstSet = false; + } + } + + isFirstNode = false; + } + + return str; + } + + bool suggests_binding_threads(CpuIndex numThreads) const { + // If we can reasonably determine that the threads cannot be contained + // by the OS within the first NUMA node then we advise distributing + // and binding threads. When the threads are not bound we can only use + // NUMA memory replicated objects from the first node, so when the OS + // has to schedule on other nodes we lose performance. We also suggest + // binding if there's enough threads to distribute among nodes with minimal + // disparity. We try to ignore small nodes, in particular the empty ones. + + // If the affinity set by the user does not match the affinity given by + // the OS then binding is necessary to ensure the threads are running on + // correct processors. + if (customAffinity) + return true; + + // We obviously cannot distribute a single thread, so a single thread + // should never be bound. + if (numThreads <= 1) + return false; + + size_t largestNodeSize = 0; + for (auto&& cpus : nodes) + if (cpus.size() > largestNodeSize) + largestNodeSize = cpus.size(); + + auto is_node_small = [largestNodeSize](const std::set& node) { + static constexpr double SmallNodeThreshold = 0.6; + return static_cast(node.size()) / static_cast(largestNodeSize) + <= SmallNodeThreshold; + }; + + size_t numNotSmallNodes = 0; + for (auto&& cpus : nodes) + if (!is_node_small(cpus)) + numNotSmallNodes += 1; + + return (numThreads > largestNodeSize / 2 || numThreads >= numNotSmallNodes * 4) + && nodes.size() > 1; + } + + std::vector distribute_threads_among_numa_nodes(CpuIndex numThreads) const { + std::vector ns; + + if (nodes.size() == 1) + { + // Special case for when there's no NUMA nodes. This doesn't buy us + // much, but let's keep the default path simple. + ns.resize(numThreads, NumaIndex{0}); + } + else + { + std::vector occupation(nodes.size(), 0); + for (CpuIndex c = 0; c < numThreads; ++c) + { + NumaIndex bestNode{0}; + float bestNodeFill = std::numeric_limits::max(); + for (NumaIndex n = 0; n < nodes.size(); ++n) + { + float fill = + static_cast(occupation[n] + 1) / static_cast(nodes[n].size()); + // NOTE: Do we want to perhaps fill the first available node + // up to 50% first before considering other nodes? + // Probably not, because it would interfere with running + // multiple instances. We basically shouldn't favor any + // particular node. + if (fill < bestNodeFill) + { + bestNode = n; + bestNodeFill = fill; + } + } + ns.emplace_back(bestNode); + occupation[bestNode] += 1; + } + } + + return ns; + } + + NumaReplicatedAccessToken bind_current_thread_to_numa_node(NumaIndex n) const { + if (n >= nodes.size() || nodes[n].size() == 0) + std::exit(EXIT_FAILURE); + +#if defined(__linux__) && !defined(__ANDROID__) + + cpu_set_t* mask = CPU_ALLOC(highestCpuIndex + 1); + if (mask == nullptr) + std::exit(EXIT_FAILURE); + + const size_t masksize = CPU_ALLOC_SIZE(highestCpuIndex + 1); + + CPU_ZERO_S(masksize, mask); + + for (CpuIndex c : nodes[n]) + CPU_SET_S(c, masksize, mask); + + const int status = sched_setaffinity(0, masksize, mask); + + CPU_FREE(mask); + + if (status != 0) + std::exit(EXIT_FAILURE); + + // We yield this thread just to be sure it gets rescheduled. + // This is defensive, allowed because this code is not performance critical. + sched_yield(); + +#elif defined(_WIN64) + + // Requires Windows 11. No good way to set thread affinity spanning + // processor groups before that. + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t( + (void (*)()) GetProcAddress(k32, "SetThreadSelectedCpuSetMasks")); + + // We ALWAYS set affinity with the new API if available, because + // there's no downsides, and we forcibly keep it consistent with + // the old API should we need to use it. I.e. we always keep this + // as a superset of what we set with SetThreadGroupAffinity. + if (SetThreadSelectedCpuSetMasks_f != nullptr) + { + // Only available on Windows 11 and Windows Server 2022 onwards + const USHORT numProcGroups = USHORT( + ((highestCpuIndex + 1) + WIN_PROCESSOR_GROUP_SIZE - 1) / WIN_PROCESSOR_GROUP_SIZE); + auto groupAffinities = std::make_unique(numProcGroups); + std::memset(groupAffinities.get(), 0, sizeof(GROUP_AFFINITY) * numProcGroups); + for (WORD i = 0; i < numProcGroups; ++i) + groupAffinities[i].Group = i; + + for (CpuIndex c : nodes[n]) + { + const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE; + const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE; + groupAffinities[procGroupIndex].Mask |= KAFFINITY(1) << idxWithinProcGroup; + } + + HANDLE hThread = GetCurrentThread(); + + const BOOL status = + SetThreadSelectedCpuSetMasks_f(hThread, groupAffinities.get(), numProcGroups); + if (status == 0) + std::exit(EXIT_FAILURE); + + // We yield this thread just to be sure it gets rescheduled. + // This is defensive, allowed because this code is not performance critical. + SwitchToThread(); + } + + // Sometimes we need to force the old API, but do not use it unless necessary. + if (SetThreadSelectedCpuSetMasks_f == nullptr || STARTUP_USE_OLD_AFFINITY_API) + { + // On earlier windows version (since windows 7) we cannot run a single thread + // on multiple processor groups, so we need to restrict the group. + // We assume the group of the first processor listed for this node. + // Processors from outside this group will not be assigned for this thread. + // Normally this won't be an issue because windows used to assign NUMA nodes + // such that they cannot span processor groups. However, since Windows 10 + // Build 20348 the behaviour changed, so there's a small window of versions + // between this and Windows 11 that might exhibit problems with not all + // processors being utilized. + // + // We handle this in NumaConfig::from_system by manually splitting the + // nodes when we detect that there is no function to set affinity spanning + // processor nodes. This is required because otherwise our thread distribution + // code may produce suboptimal results. + // + // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support + GROUP_AFFINITY affinity; + std::memset(&affinity, 0, sizeof(GROUP_AFFINITY)); + // We use an ordered set to be sure to get the smallest cpu number here. + const size_t forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE; + affinity.Group = static_cast(forcedProcGroupIndex); + for (CpuIndex c : nodes[n]) + { + const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE; + const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE; + // We skip processors that are not in the same processor group. + // If everything was set up correctly this will never be an issue, + // but we have to account for bad NUMA node specification. + if (procGroupIndex != forcedProcGroupIndex) + continue; + + affinity.Mask |= KAFFINITY(1) << idxWithinProcGroup; + } + + HANDLE hThread = GetCurrentThread(); + + const BOOL status = SetThreadGroupAffinity(hThread, &affinity, nullptr); + if (status == 0) + std::exit(EXIT_FAILURE); + + // We yield this thread just to be sure it gets rescheduled. This is + // defensive, allowed because this code is not performance critical. + SwitchToThread(); + } + +#endif + + return NumaReplicatedAccessToken(n); + } + + template + void execute_on_numa_node(NumaIndex n, FuncT&& f) const { + std::thread th([this, &f, n]() { + bind_current_thread_to_numa_node(n); + std::forward(f)(); + }); + + th.join(); + } + + private: + std::vector> nodes; + std::map nodeByCpu; + CpuIndex highestCpuIndex; + + bool customAffinity; + + static NumaConfig empty() { return NumaConfig(EmptyNodeTag{}); } + + struct EmptyNodeTag {}; + + NumaConfig(EmptyNodeTag) : + highestCpuIndex(0), + customAffinity(false) {} + + void remove_empty_numa_nodes() { + std::vector> newNodes; + for (auto&& cpus : nodes) + if (!cpus.empty()) + newNodes.emplace_back(std::move(cpus)); + nodes = std::move(newNodes); + } + + // Returns true if successful + // Returns false if failed, i.e. when the cpu is already present + // strong guarantee, the structure remains unmodified + bool add_cpu_to_node(NumaIndex n, CpuIndex c) { + if (is_cpu_assigned(c)) + return false; + + while (nodes.size() <= n) + nodes.emplace_back(); + + nodes[n].insert(c); + nodeByCpu[c] = n; + + if (c > highestCpuIndex) + highestCpuIndex = c; + + return true; + } + + // Returns true if successful + // Returns false if failed, i.e. when any of the cpus is already present + // strong guarantee, the structure remains unmodified + bool add_cpu_range_to_node(NumaIndex n, CpuIndex cfirst, CpuIndex clast) { + for (CpuIndex c = cfirst; c <= clast; ++c) + if (is_cpu_assigned(c)) + return false; + + while (nodes.size() <= n) + nodes.emplace_back(); + + for (CpuIndex c = cfirst; c <= clast; ++c) + { + nodes[n].insert(c); + nodeByCpu[c] = n; + } + + if (clast > highestCpuIndex) + highestCpuIndex = clast; + + return true; + } + + static std::vector indices_from_shortened_string(const std::string& s) { + std::vector indices; + + if (s.empty()) + return indices; + + for (const auto& ss : split(s, ",")) + { + if (ss.empty()) + continue; + + auto parts = split(ss, "-"); + if (parts.size() == 1) + { + const CpuIndex c = CpuIndex{str_to_size_t(std::string(parts[0]))}; + indices.emplace_back(c); + } + else if (parts.size() == 2) + { + const CpuIndex cfirst = CpuIndex{str_to_size_t(std::string(parts[0]))}; + const CpuIndex clast = CpuIndex{str_to_size_t(std::string(parts[1]))}; + for (size_t c = cfirst; c <= clast; ++c) + { + indices.emplace_back(c); + } + } + } + + return indices; + } +}; + +class NumaReplicationContext; + +// Instances of this class are tracked by the NumaReplicationContext instance. +// NumaReplicationContext informs all tracked instances when NUMA configuration changes. +class NumaReplicatedBase { + public: + NumaReplicatedBase(NumaReplicationContext& ctx); + + NumaReplicatedBase(const NumaReplicatedBase&) = delete; + NumaReplicatedBase(NumaReplicatedBase&& other) noexcept; + + NumaReplicatedBase& operator=(const NumaReplicatedBase&) = delete; + NumaReplicatedBase& operator=(NumaReplicatedBase&& other) noexcept; + + virtual void on_numa_config_changed() = 0; + virtual ~NumaReplicatedBase(); + + const NumaConfig& get_numa_config() const; + + private: + NumaReplicationContext* context; +}; + +// We force boxing with a unique_ptr. If this becomes an issue due to added +// indirection we may need to add an option for a custom boxing type. When the +// NUMA config changes the value stored at the index 0 is replicated to other nodes. +template +class NumaReplicated: public NumaReplicatedBase { + public: + using ReplicatorFuncType = std::function; + + NumaReplicated(NumaReplicationContext& ctx) : + NumaReplicatedBase(ctx) { + replicate_from(T{}); + } + + NumaReplicated(NumaReplicationContext& ctx, T&& source) : + NumaReplicatedBase(ctx) { + replicate_from(std::move(source)); + } + + NumaReplicated(const NumaReplicated&) = delete; + NumaReplicated(NumaReplicated&& other) noexcept : + NumaReplicatedBase(std::move(other)), + instances(std::exchange(other.instances, {})) {} + + NumaReplicated& operator=(const NumaReplicated&) = delete; + NumaReplicated& operator=(NumaReplicated&& other) noexcept { + NumaReplicatedBase::operator=(*this, std::move(other)); + instances = std::exchange(other.instances, {}); + + return *this; + } + + NumaReplicated& operator=(T&& source) { + replicate_from(std::move(source)); + + return *this; + } + + ~NumaReplicated() override = default; + + const T& operator[](NumaReplicatedAccessToken token) const { + assert(token.get_numa_index() < instances.size()); + return *(instances[token.get_numa_index()]); + } + + const T& operator*() const { return *(instances[0]); } + + const T* operator->() const { return instances[0].get(); } + + template + void modify_and_replicate(FuncT&& f) { + auto source = std::move(instances[0]); + std::forward(f)(*source); + replicate_from(std::move(*source)); + } + + void on_numa_config_changed() override { + // Use the first one as the source. It doesn't matter which one we use, + // because they all must be identical, but the first one is guaranteed to exist. + auto source = std::move(instances[0]); + replicate_from(std::move(*source)); + } + + private: + std::vector> instances; + + void replicate_from(T&& source) { + instances.clear(); + + const NumaConfig& cfg = get_numa_config(); + if (cfg.requires_memory_replication()) + { + for (NumaIndex n = 0; n < cfg.num_numa_nodes(); ++n) + { + cfg.execute_on_numa_node( + n, [this, &source]() { instances.emplace_back(std::make_unique(source)); }); + } + } + else + { + assert(cfg.num_numa_nodes() == 1); + // We take advantage of the fact that replication is not required + // and reuse the source value, avoiding one copy operation. + instances.emplace_back(std::make_unique(std::move(source))); + } + } +}; + +// We force boxing with a unique_ptr. If this becomes an issue due to added +// indirection we may need to add an option for a custom boxing type. +template +class LazyNumaReplicated: public NumaReplicatedBase { + public: + using ReplicatorFuncType = std::function; + + LazyNumaReplicated(NumaReplicationContext& ctx) : + NumaReplicatedBase(ctx) { + prepare_replicate_from(T{}); + } + + LazyNumaReplicated(NumaReplicationContext& ctx, T&& source) : + NumaReplicatedBase(ctx) { + prepare_replicate_from(std::move(source)); + } + + LazyNumaReplicated(const LazyNumaReplicated&) = delete; + LazyNumaReplicated(LazyNumaReplicated&& other) noexcept : + NumaReplicatedBase(std::move(other)), + instances(std::exchange(other.instances, {})) {} + + LazyNumaReplicated& operator=(const LazyNumaReplicated&) = delete; + LazyNumaReplicated& operator=(LazyNumaReplicated&& other) noexcept { + NumaReplicatedBase::operator=(*this, std::move(other)); + instances = std::exchange(other.instances, {}); + + return *this; + } + + LazyNumaReplicated& operator=(T&& source) { + prepare_replicate_from(std::move(source)); + + return *this; + } + + ~LazyNumaReplicated() override = default; + + const T& operator[](NumaReplicatedAccessToken token) const { + assert(token.get_numa_index() < instances.size()); + ensure_present(token.get_numa_index()); + return *(instances[token.get_numa_index()]); + } + + const T& operator*() const { return *(instances[0]); } + + const T* operator->() const { return instances[0].get(); } + + template + void modify_and_replicate(FuncT&& f) { + auto source = std::move(instances[0]); + std::forward(f)(*source); + prepare_replicate_from(std::move(*source)); + } + + void on_numa_config_changed() override { + // Use the first one as the source. It doesn't matter which one we use, + // because they all must be identical, but the first one is guaranteed to exist. + auto source = std::move(instances[0]); + prepare_replicate_from(std::move(*source)); + } + + private: + mutable std::vector> instances; + mutable std::mutex mutex; + + void ensure_present(NumaIndex idx) const { + assert(idx < instances.size()); + + if (instances[idx] != nullptr) + return; + + assert(idx != 0); + + std::unique_lock lock(mutex); + // Check again for races. + if (instances[idx] != nullptr) + return; + + const NumaConfig& cfg = get_numa_config(); + cfg.execute_on_numa_node( + idx, [this, idx]() { instances[idx] = std::make_unique(*instances[0]); }); + } + + void prepare_replicate_from(T&& source) { + instances.clear(); + + const NumaConfig& cfg = get_numa_config(); + if (cfg.requires_memory_replication()) + { + assert(cfg.num_numa_nodes() > 0); + + // We just need to make sure the first instance is there. + // Note that we cannot move here as we need to reallocate the data + // on the correct NUMA node. + cfg.execute_on_numa_node( + 0, [this, &source]() { instances.emplace_back(std::make_unique(source)); }); + + // Prepare others for lazy init. + instances.resize(cfg.num_numa_nodes()); + } + else + { + assert(cfg.num_numa_nodes() == 1); + // We take advantage of the fact that replication is not required + // and reuse the source value, avoiding one copy operation. + instances.emplace_back(std::make_unique(std::move(source))); + } + } +}; + +class NumaReplicationContext { + public: + NumaReplicationContext(NumaConfig&& cfg) : + config(std::move(cfg)) {} + + NumaReplicationContext(const NumaReplicationContext&) = delete; + NumaReplicationContext(NumaReplicationContext&&) = delete; + + NumaReplicationContext& operator=(const NumaReplicationContext&) = delete; + NumaReplicationContext& operator=(NumaReplicationContext&&) = delete; + + ~NumaReplicationContext() { + // The context must outlive replicated objects + if (!trackedReplicatedObjects.empty()) + std::exit(EXIT_FAILURE); + } + + void attach(NumaReplicatedBase* obj) { + assert(trackedReplicatedObjects.count(obj) == 0); + trackedReplicatedObjects.insert(obj); + } + + void detach(NumaReplicatedBase* obj) { + assert(trackedReplicatedObjects.count(obj) == 1); + trackedReplicatedObjects.erase(obj); + } + + // oldObj may be invalid at this point + void move_attached([[maybe_unused]] NumaReplicatedBase* oldObj, NumaReplicatedBase* newObj) { + assert(trackedReplicatedObjects.count(oldObj) == 1); + assert(trackedReplicatedObjects.count(newObj) == 0); + trackedReplicatedObjects.erase(oldObj); + trackedReplicatedObjects.insert(newObj); + } + + void set_numa_config(NumaConfig&& cfg) { + config = std::move(cfg); + for (auto&& obj : trackedReplicatedObjects) + obj->on_numa_config_changed(); + } + + const NumaConfig& get_numa_config() const { return config; } + + private: + NumaConfig config; + + // std::set uses std::less by default, which is required for pointer comparison + std::set trackedReplicatedObjects; +}; + +inline NumaReplicatedBase::NumaReplicatedBase(NumaReplicationContext& ctx) : + context(&ctx) { + context->attach(this); +} + +inline NumaReplicatedBase::NumaReplicatedBase(NumaReplicatedBase&& other) noexcept : + context(std::exchange(other.context, nullptr)) { + context->move_attached(&other, this); +} + +inline NumaReplicatedBase& NumaReplicatedBase::operator=(NumaReplicatedBase&& other) noexcept { + context = std::exchange(other.context, nullptr); + + context->move_attached(&other, this); + + return *this; +} + +inline NumaReplicatedBase::~NumaReplicatedBase() { + if (context != nullptr) + context->detach(this); +} + +inline const NumaConfig& NumaReplicatedBase::get_numa_config() const { + return context->get_numa_config(); +} + +} // namespace Stockfish + + +#endif // #ifndef NUMA_H_INCLUDED diff --git a/stockfish/src/perft.h b/stockfish/src/perft.h new file mode 100644 index 0000000000000000000000000000000000000000..229debd4078121ef45721658c2b8a579ea5f1428 --- /dev/null +++ b/stockfish/src/perft.h @@ -0,0 +1,67 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PERFT_H_INCLUDED +#define PERFT_H_INCLUDED + +#include + +#include "movegen.h" +#include "position.h" +#include "types.h" +#include "uci.h" + +namespace Stockfish::Benchmark { + +// Utility to verify move generation. All the leaf nodes up +// to the given depth are generated and counted, and the sum is returned. +template +uint64_t perft(Position& pos, Depth depth) { + + StateInfo st; + + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2); + + for (const auto& m : MoveList(pos)) + { + if (Root && depth <= 1) + cnt = 1, nodes++; + else + { + pos.do_move(m, st); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); + nodes += cnt; + pos.undo_move(m); + } + if (Root) + sync_cout << UCIEngine::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + } + return nodes; +} + +inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) { + StateListPtr states(new std::deque(1)); + Position p; + p.set(fen, isChess960, &states->back()); + + return perft(p, depth); +} +} + +#endif // PERFT_H_INCLUDED diff --git a/stockfish/src/position.cpp b/stockfish/src/position.cpp new file mode 100644 index 0000000000000000000000000000000000000000..52e1004e8fce1e38d3ef8cb03ed9e8ab566799f5 --- /dev/null +++ b/stockfish/src/position.cpp @@ -0,0 +1,1318 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "position.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bitboard.h" +#include "misc.h" +#include "movegen.h" +#include "syzygy/tbprobe.h" +#include "tt.h" +#include "uci.h" + +using std::string; + +namespace Stockfish { + +namespace Zobrist { + +Key psq[PIECE_NB][SQUARE_NB]; +Key enpassant[FILE_NB]; +Key castling[CASTLING_RIGHT_NB]; +Key side, noPawns; +} + +namespace { + +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); + +constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; +} // namespace + + +// Returns an ASCII representation of the position +std::ostream& operator<<(std::ostream& os, const Position& pos) { + + os << "\n +---+---+---+---+---+---+---+---+\n"; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; + + os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; + } + + os << " a b c d e f g h\n" + << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase << std::setfill('0') + << std::setw(16) << pos.key() << std::setfill(' ') << std::dec << "\nCheckers: "; + + for (Bitboard b = pos.checkers(); b;) + os << UCIEngine::square(pop_lsb(b)) << " "; + + if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + StateInfo st; + + Position p; + p.set(pos.fen(), pos.is_chess960(), &st); + Tablebases::ProbeState s1, s2; + Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); + int dtz = Tablebases::probe_dtz(p, &s2); + os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" + << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; + } + + return os; +} + + +// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions +// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes +// to allow fast detection of recurring positions. For details see: +// http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf + +// First and second hash functions for indexing the cuckoo tables +inline int H1(Key h) { return h & 0x1fff; } +inline int H2(Key h) { return (h >> 16) & 0x1fff; } + +// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves +std::array cuckoo; +std::array cuckooMove; + +// Initializes at startup the various arrays used to compute hash keys +void Position::init() { + + PRNG rng(1070372); + + for (Piece pc : Pieces) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + Zobrist::psq[pc][s] = rng.rand(); + // pawns on these squares will promote + std::fill_n(Zobrist::psq[W_PAWN] + SQ_A8, 8, 0); + std::fill_n(Zobrist::psq[B_PAWN], 8, 0); + + for (File f = FILE_A; f <= FILE_H; ++f) + Zobrist::enpassant[f] = rng.rand(); + + for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) + Zobrist::castling[cr] = rng.rand(); + + Zobrist::side = rng.rand(); + Zobrist::noPawns = rng.rand(); + + // Prepare the cuckoo tables + cuckoo.fill(0); + cuckooMove.fill(Move::none()); + [[maybe_unused]] int count = 0; + for (Piece pc : Pieces) + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) + if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) + { + Move move = Move(s1, s2); + Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; + int i = H1(key); + while (true) + { + std::swap(cuckoo[i], key); + std::swap(cuckooMove[i], move); + if (move == Move::none()) // Arrived at empty slot? + break; + i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot + } + count++; + } + assert(count == 3668); +} + + +// Initializes the position object with the given FEN string. +// This function is not very robust - make sure that input FENs are correct, +// this is assumed to be the responsibility of the GUI. +Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { + /* + A FEN string defines a particular position using only the ASCII character set. + + A FEN string contains six fields separated by a space. The fields are: + + 1) Piece placement (from white's perspective). Each rank is described, starting + with rank 8 and ending with rank 1. Within each rank, the contents of each + square are described from file A through file H. Following the Standard + Algebraic Notation (SAN), each piece is identified by a single letter taken + from the standard English names. White pieces are designated using upper-case + letters ("PNBRQK") whilst Black uses lowercase ("pnbrqk"). Blank squares are + noted using digits 1 through 8 (the number of blank squares), and "/" + separates ranks. + + 2) Active color. "w" means white moves next, "b" means black. + + 3) Castling availability. If neither side can castle, this is "-". Otherwise, + this has one or more letters: "K" (White can castle kingside), "Q" (White + can castle queenside), "k" (Black can castle kingside), and/or "q" (Black + can castle queenside). + + 4) En passant target square (in algebraic notation). If there's no en passant + target square, this is "-". If a pawn has just made a 2-square move, this + is the position "behind" the pawn. Following X-FEN standard, this is recorded + only if there is a pawn in position to make an en passant capture, and if + there really is a pawn that might have advanced two squares. + + 5) Halfmove clock. This is the number of halfmoves since the last pawn advance + or capture. This is used to determine if a draw can be claimed under the + fifty-move rule. + + 6) Fullmove number. The number of the full move. It starts at 1, and is + incremented after Black's move. +*/ + + unsigned char col, row, token; + size_t idx; + Square sq = SQ_A8; + std::istringstream ss(fenStr); + + std::memset(this, 0, sizeof(Position)); + std::memset(si, 0, sizeof(StateInfo)); + st = si; + + ss >> std::noskipws; + + // 1. Piece placement + while ((ss >> token) && !isspace(token)) + { + if (isdigit(token)) + sq += (token - '0') * EAST; // Advance the given number of files + + else if (token == '/') + sq += 2 * SOUTH; + + else if ((idx = PieceToChar.find(token)) != string::npos) + { + put_piece(Piece(idx), sq); + ++sq; + } + } + + // 2. Active color + ss >> token; + sideToMove = (token == 'w' ? WHITE : BLACK); + ss >> token; + + // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, + // Shredder-FEN that uses the letters of the columns on which the rooks began + // the game instead of KQkq and also X-FEN standard that, in case of Chess960, + // if an inner rook is associated with the castling right, the castling tag is + // replaced by the file letter of the involved rook, as for the Shredder-FEN. + while ((ss >> token) && !isspace(token)) + { + Square rsq; + Color c = islower(token) ? BLACK : WHITE; + Piece rook = make_piece(c, ROOK); + + token = char(toupper(token)); + + if (token == 'K') + for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) + {} + + else if (token == 'Q') + for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) + {} + + else if (token >= 'A' && token <= 'H') + rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + + else + continue; + + set_castling_right(c, rsq); + } + + // 4. En passant square. + // Ignore if square is invalid or not on side to move relative rank 6. + bool enpassant = false; + + if (((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) + { + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + } + + if (!enpassant) + st->epSquare = SQ_NONE; + + // 5-6. Halfmove clock and fullmove number + ss >> std::skipws >> st->rule50 >> gamePly; + + // Convert from fullmove starting from 1 to gamePly starting from 0, + // handle also common incorrect FEN with fullmove = 0. + gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + + chess960 = isChess960; + set_state(); + + assert(pos_is_ok()); + + return *this; +} + + +// Helper function used to set castling +// rights given the corresponding color and the rook starting square. +void Position::set_castling_right(Color c, Square rfrom) { + + Square kfrom = square(c); + CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE); + + st->castlingRights |= cr; + castlingRightsMask[kfrom] |= cr; + castlingRightsMask[rfrom] |= cr; + castlingRookSquare[cr] = rfrom; + + Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); + Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); + + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom); +} + + +// Sets king attacks to detect if a move gives check +void Position::set_check_info() const { + + update_slider_blockers(WHITE); + update_slider_blockers(BLACK); + + Square ksq = square(~sideToMove); + + st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[KNIGHT] = attacks_bb(ksq); + st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); + st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); + st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; + st->checkSquares[KING] = 0; +} + + +// Computes the hash keys of the position, and other +// data that once computed is updated incrementally as moves are made. +// The function is only used when a new position is set up +void Position::set_state() const { + + st->key = st->materialKey = 0; + st->minorPieceKey = 0; + st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; + st->pawnKey = Zobrist::noPawns; + st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; + st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + + set_check_info(); + + for (Bitboard b = pieces(); b;) + { + Square s = pop_lsb(b); + Piece pc = piece_on(s); + st->key ^= Zobrist::psq[pc][s]; + + if (type_of(pc) == PAWN) + st->pawnKey ^= Zobrist::psq[pc][s]; + + else + { + st->nonPawnKey[color_of(pc)] ^= Zobrist::psq[pc][s]; + + if (type_of(pc) != KING) + { + st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; + + if (type_of(pc) <= BISHOP) + st->minorPieceKey ^= Zobrist::psq[pc][s]; + } + } + } + + if (st->epSquare != SQ_NONE) + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + + if (sideToMove == BLACK) + st->key ^= Zobrist::side; + + st->key ^= Zobrist::castling[st->castlingRights]; + + for (Piece pc : Pieces) + for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) + st->materialKey ^= Zobrist::psq[pc][8 + cnt]; +} + + +// Overload to initialize the position object with the given endgame code string +// like "KBPKN". It's mainly a helper to get the material key out of an endgame code. +Position& Position::set(const string& code, Color c, StateInfo* si) { + + assert(code[0] == 'K'); + + string sides[] = {code.substr(code.find('K', 1)), // Weak + code.substr(0, std::min(code.find('v'), code.find('K', 1)))}; // Strong + + assert(sides[0].length() > 0 && sides[0].length() < 8); + assert(sides[1].length() > 0 && sides[1].length() < 8); + + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + + string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1] + + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; + + return set(fenStr, false, si); +} + + +// Returns a FEN representation of the position. In case of +// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. +string Position::fen() const { + + int emptyCnt; + std::ostringstream ss; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + { + for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) + ++emptyCnt; + + if (emptyCnt) + ss << emptyCnt; + + if (f <= FILE_H) + ss << PieceToChar[piece_on(make_square(f, r))]; + } + + if (r > RANK_1) + ss << '/'; + } + + ss << (sideToMove == WHITE ? " w " : " b "); + + if (can_castle(WHITE_OO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO))) : 'K'); + + if (can_castle(WHITE_OOO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); + + if (can_castle(BLACK_OO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO))) : 'k'); + + if (can_castle(BLACK_OOO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); + + if (!can_castle(ANY_CASTLING)) + ss << '-'; + + ss << (ep_square() == SQ_NONE ? " - " : " " + UCIEngine::square(ep_square()) + " ") + << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + + return ss.str(); +} + +// Calculates st->blockersForKing[c] and st->pinners[~c], +// which store respectively the pieces preventing king of color c from being in check +// and the slider pieces of color ~c pinning pieces of color c to the king. +void Position::update_slider_blockers(Color c) const { + + Square ksq = square(c); + + st->blockersForKing[c] = 0; + st->pinners[~c] = 0; + + // Snipers are sliders that attack 's' when a piece and other snipers are removed + Bitboard snipers = ((attacks_bb(ksq) & pieces(QUEEN, ROOK)) + | (attacks_bb(ksq) & pieces(QUEEN, BISHOP))) + & pieces(~c); + Bitboard occupancy = pieces() ^ snipers; + + while (snipers) + { + Square sniperSq = pop_lsb(snipers); + Bitboard b = between_bb(ksq, sniperSq) & occupancy; + + if (b && !more_than_one(b)) + { + st->blockersForKing[c] |= b; + if (b & pieces(c)) + st->pinners[~c] |= sniperSq; + } + } +} + + +// Computes a bitboard of all pieces which attack a given square. +// Slider attacks use the occupied bitboard to indicate occupancy. +Bitboard Position::attackers_to(Square s, Bitboard occupied) const { + + return (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) + | (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) + | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb(s) & pieces(KING)); +} + +bool Position::attackers_to_exist(Square s, Bitboard occupied, Color c) const { + + return ((attacks_bb(s) & pieces(c, ROOK, QUEEN)) + && (attacks_bb(s, occupied) & pieces(c, ROOK, QUEEN))) + || ((attacks_bb(s) & pieces(c, BISHOP, QUEEN)) + && (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN))) + || (((pawn_attacks_bb(~c, s) & pieces(PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) + | (attacks_bb(s) & pieces(KING))) + & pieces(c)); +} + +// Tests whether a pseudo-legal move is legal +bool Position::legal(Move m) const { + + assert(m.is_ok()); + + Color us = sideToMove; + Square from = m.from_sq(); + Square to = m.to_sq(); + + assert(color_of(moved_piece(m)) == us); + assert(piece_on(square(us)) == make_piece(us, KING)); + + // En passant captures are a tricky special case. Because they are rather + // uncommon, we do it simply by testing whether the king is attacked after + // the move is made. + if (m.type_of() == EN_PASSANT) + { + Square ksq = square(us); + Square capsq = to - pawn_push(us); + Bitboard occupied = (pieces() ^ from ^ capsq) | to; + + assert(to == ep_square()); + assert(moved_piece(m) == make_piece(us, PAWN)); + assert(piece_on(capsq) == make_piece(~us, PAWN)); + assert(piece_on(to) == NO_PIECE); + + return !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, ROOK)) + && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); + } + + // Castling moves generation does not check if the castling path is clear of + // enemy attacks, it is delayed at a later time: now! + if (m.type_of() == CASTLING) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + to = relative_square(us, to > from ? SQ_G1 : SQ_C1); + Direction step = to > from ? WEST : EAST; + + for (Square s = to; s != from; s += step) + if (attackers_to_exist(s, pieces(), ~us)) + return false; + + // In case of Chess960, verify if the Rook blocks some checks. + // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. + return !chess960 || !(blockers_for_king(us) & m.to_sq()); + } + + // If the moving piece is a king, check whether the destination square is + // attacked by the opponent. + if (type_of(piece_on(from)) == KING) + return !(attackers_to_exist(to, pieces() ^ from, ~us)); + + // A non-king move is legal if and only if it is not pinned or it + // is moving along the ray towards or away from the king. + return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); +} + + +// Takes a random move and tests whether the move is +// pseudo-legal. It is used to validate moves from TT that can be corrupted +// due to SMP concurrent access or hash position key aliasing. +bool Position::pseudo_legal(const Move m) const { + + Color us = sideToMove; + Square from = m.from_sq(); + Square to = m.to_sq(); + Piece pc = moved_piece(m); + + // Use a slower but simpler function for uncommon cases + // yet we skip the legality check of MoveList(). + if (m.type_of() != NORMAL) + return checkers() ? MoveList(*this).contains(m) + : MoveList(*this).contains(m); + + // Is not a promotion, so the promotion piece must be empty + assert(m.promotion_type() - KNIGHT == NO_PIECE_TYPE); + + // If the 'from' square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (pc == NO_PIECE || color_of(pc) != us) + return false; + + // The destination square cannot be occupied by a friendly piece + if (pieces(us) & to) + return false; + + // Handle the special case of a pawn move + if (type_of(pc) == PAWN) + { + // We have already handled promotion moves, so destination cannot be on the 8th/1st rank + if ((Rank8BB | Rank1BB) & to) + return false; + + if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !((from + 2 * pawn_push(us) == to) // Not a double push + && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) + return false; + } + else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) + return false; + + // Evasions generator already takes care to avoid some kind of illegal moves + // and legal() relies on this. We therefore have to take care that the same + // kind of moves are filtered out here. + if (checkers()) + { + if (type_of(pc) != KING) + { + // Double check? In this case, a king move is required + if (more_than_one(checkers())) + return false; + + // Our move must be a blocking interposition or a capture of the checking piece + if (!(between_bb(square(us), lsb(checkers())) & to)) + return false; + } + // In case of king moves under check we have to remove the king so as to catch + // invalid moves like b1a1 when opposite queen is on c1. + else if (attackers_to_exist(to, pieces() ^ from, ~us)) + return false; + } + + return true; +} + + +// Tests whether a pseudo-legal move gives a check +bool Position::gives_check(Move m) const { + + assert(m.is_ok()); + assert(color_of(moved_piece(m)) == sideToMove); + + Square from = m.from_sq(); + Square to = m.to_sq(); + + // Is there a direct check? + if (check_squares(type_of(piece_on(from))) & to) + return true; + + // Is there a discovered check? + if (blockers_for_king(~sideToMove) & from) + return !(line_bb(from, to) & pieces(~sideToMove, KING)) || m.type_of() == CASTLING; + + switch (m.type_of()) + { + case NORMAL : + return false; + + case PROMOTION : + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); + + // En passant capture with check? We have already handled the case of direct + // checks and ordinary discovered check, so the only case we need to handle + // is the unusual case of a discovered check through the captured pawn. + case EN_PASSANT : { + Square capsq = make_square(file_of(to), rank_of(from)); + Bitboard b = (pieces() ^ from ^ capsq) | to; + + return (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) + | (attacks_bb(square(~sideToMove), b) + & pieces(sideToMove, QUEEN, BISHOP)); + } + default : //CASTLING + { + // Castling is encoded as 'king captures the rook' + Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); + + return check_squares(ROOK) & rto; + } + } +} + + +// Makes a move, and saves all information necessary +// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +// moves should be filtered out before this function is called. +// If a pointer to the TT table is passed, the entry for the new position +// will be prefetched +DirtyPiece Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + const TranspositionTable* tt = nullptr) { + + assert(m.is_ok()); + assert(&newSt != st); + + Key k = st->key ^ Zobrist::side; + + // Copy some fields of the old state to our new StateInfo object except the + // ones which are going to be recalculated from scratch anyway and then switch + // our state pointer to point to the new (ready to be updated) state. + std::memcpy(&newSt, st, offsetof(StateInfo, key)); + newSt.previous = st; + st->next = &newSt; + st = &newSt; + + // Increment ply counters. In particular, rule50 will be reset to zero later on + // in case of a capture or a pawn move. + ++gamePly; + ++st->rule50; + ++st->pliesFromNull; + + DirtyPiece dp; + dp.dirty_num = 1; + + Color us = sideToMove; + Color them = ~us; + Square from = m.from_sq(); + Square to = m.to_sq(); + Piece pc = piece_on(from); + Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + + assert(color_of(pc) == us); + assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us)); + assert(type_of(captured) != KING); + + if (m.type_of() == CASTLING) + { + assert(pc == make_piece(us, KING)); + assert(captured == make_piece(us, ROOK)); + + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto, &dp); + + k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; + st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; + captured = NO_PIECE; + } + + if (captured) + { + Square capsq = to; + + // If the captured piece is a pawn, update pawn hash key, otherwise + // update non-pawn material. + if (type_of(captured) == PAWN) + { + if (m.type_of() == EN_PASSANT) + { + capsq -= pawn_push(us); + + assert(pc == make_piece(us, PAWN)); + assert(to == st->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(to) == NO_PIECE); + assert(piece_on(capsq) == make_piece(them, PAWN)); + } + + st->pawnKey ^= Zobrist::psq[captured][capsq]; + } + else + { + st->nonPawnMaterial[them] -= PieceValue[captured]; + st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq]; + + if (type_of(captured) <= BISHOP) + st->minorPieceKey ^= Zobrist::psq[captured][capsq]; + } + + dp.dirty_num = 2; // 1 piece moved, 1 piece captured + dp.piece[1] = captured; + dp.from[1] = capsq; + dp.to[1] = SQ_NONE; + + // Update board and piece lists + remove_piece(capsq); + + k ^= Zobrist::psq[captured][capsq]; + st->materialKey ^= Zobrist::psq[captured][8 + pieceCount[captured]]; + + // Reset rule 50 counter + st->rule50 = 0; + } + + // Update hash key + k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + // Reset en passant square + if (st->epSquare != SQ_NONE) + { + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } + + // Update castling rights if needed + if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) + { + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + k ^= Zobrist::castling[st->castlingRights]; + } + + // Move the piece. The tricky Chess960 castling is handled earlier + if (m.type_of() != CASTLING) + { + dp.piece[0] = pc; + dp.from[0] = from; + dp.to[0] = to; + + move_piece(from, to); + } + + // If the moving piece is a pawn do some special extra work + if (type_of(pc) == PAWN) + { + // Set en passant square if the moved pawn can be captured + if ((int(to) ^ int(from)) == 16 + && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) + { + st->epSquare = to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + } + + else if (m.type_of() == PROMOTION) + { + Piece promotion = make_piece(us, m.promotion_type()); + PieceType promotionType = type_of(promotion); + + assert(relative_rank(us, to) == RANK_8); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); + + remove_piece(to); + put_piece(promotion, to); + + // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.piece[dp.dirty_num] = promotion; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = to; + dp.dirty_num++; + + // Update hash keys + // Zobrist::psq[pc][to] is zero, so we don't need to clear it + k ^= Zobrist::psq[promotion][to]; + st->materialKey ^= Zobrist::psq[promotion][8 + pieceCount[promotion] - 1] + ^ Zobrist::psq[pc][8 + pieceCount[pc]]; + + if (promotionType <= BISHOP) + st->minorPieceKey ^= Zobrist::psq[promotion][to]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[promotion]; + } + + // Update pawn hash key + st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + // Reset rule 50 draw counter + st->rule50 = 0; + } + + else + { + st->nonPawnKey[us] ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + if (type_of(pc) <= BISHOP) + st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + } + + // Update the key with the final value + st->key = k; + if (tt) + prefetch(tt->first_entry(key())); + + // Set capture piece + st->capturedPiece = captured; + + // Calculate checkers bitboard (if move gives check) + st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + + sideToMove = ~sideToMove; + + // Update king attacks used for fast check detection + set_check_info(); + + // Calculate the repetition info. It is the ply distance from the previous + // occurrence of the same position, negative in the 3-fold case, or zero + // if the position was not repeated. + st->repetition = 0; + int end = std::min(st->rule50, st->pliesFromNull); + if (end >= 4) + { + StateInfo* stp = st->previous->previous; + for (int i = 4; i <= end; i += 2) + { + stp = stp->previous->previous; + if (stp->key == st->key) + { + st->repetition = stp->repetition ? -i : i; + break; + } + } + } + + assert(pos_is_ok()); + + return dp; +} + + +// Unmakes a move. When it returns, the position should +// be restored to exactly the same state as before the move was made. +void Position::undo_move(Move m) { + + assert(m.is_ok()); + + sideToMove = ~sideToMove; + + Color us = sideToMove; + Square from = m.from_sq(); + Square to = m.to_sq(); + Piece pc = piece_on(to); + + assert(empty(from) || m.type_of() == CASTLING); + assert(type_of(st->capturedPiece) != KING); + + if (m.type_of() == PROMOTION) + { + assert(relative_rank(us, to) == RANK_8); + assert(type_of(pc) == m.promotion_type()); + assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); + + remove_piece(to); + pc = make_piece(us, PAWN); + put_piece(pc, to); + } + + if (m.type_of() == CASTLING) + { + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + } + else + { + move_piece(to, from); // Put the piece back at the source square + + if (st->capturedPiece) + { + Square capsq = to; + + if (m.type_of() == EN_PASSANT) + { + capsq -= pawn_push(us); + + assert(type_of(pc) == PAWN); + assert(to == st->previous->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(capsq) == NO_PIECE); + assert(st->capturedPiece == make_piece(~us, PAWN)); + } + + put_piece(st->capturedPiece, capsq); // Restore the captured piece + } + } + + // Finally point our state pointer back to the previous state + st = st->previous; + --gamePly; + + assert(pos_is_ok()); +} + + +// Helper used to do/undo a castling move. This is a bit +// tricky in Chess960 where from/to squares can overlap. +template +void Position::do_castling( + Color us, Square from, Square& to, Square& rfrom, Square& rto, DirtyPiece* const dp) { + + bool kingSide = to > from; + rfrom = to; // Castling is encoded as "king captures friendly rook" + rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); + to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + + assert(!Do || dp); + + if (Do) + { + dp->piece[0] = make_piece(us, KING); + dp->from[0] = from; + dp->to[0] = to; + dp->piece[1] = make_piece(us, ROOK); + dp->from[1] = rfrom; + dp->to[1] = rto; + dp->dirty_num = 2; + } + + // Remove both pieces first since squares could overlap in Chess960 + remove_piece(Do ? from : to); + remove_piece(Do ? rfrom : rto); + board[Do ? from : to] = board[Do ? rfrom : rto] = + NO_PIECE; // remove_piece does not do this for us + put_piece(make_piece(us, KING), Do ? to : from); + put_piece(make_piece(us, ROOK), Do ? rto : rfrom); +} + + +// Used to do a "null move": it flips +// the side to move without executing any move on the board. +void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { + + assert(!checkers()); + assert(&newSt != st); + + std::memcpy(&newSt, st, sizeof(StateInfo)); + + newSt.previous = st; + st->next = &newSt; + st = &newSt; + + if (st->epSquare != SQ_NONE) + { + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } + + st->key ^= Zobrist::side; + prefetch(tt.first_entry(key())); + + st->pliesFromNull = 0; + + sideToMove = ~sideToMove; + + set_check_info(); + + st->repetition = 0; + + assert(pos_is_ok()); +} + + +// Must be used to undo a "null move" +void Position::undo_null_move() { + + assert(!checkers()); + + st = st->previous; + sideToMove = ~sideToMove; +} + + +// Tests if the SEE (Static Exchange Evaluation) +// value of move is greater or equal to the given threshold. We'll use an +// algorithm similar to alpha-beta pruning with a null window. +bool Position::see_ge(Move m, int threshold) const { + + assert(m.is_ok()); + + // Only deal with normal moves, assume others pass a simple SEE + if (m.type_of() != NORMAL) + return VALUE_ZERO >= threshold; + + Square from = m.from_sq(), to = m.to_sq(); + + int swap = PieceValue[piece_on(to)] - threshold; + if (swap < 0) + return false; + + swap = PieceValue[piece_on(from)] - swap; + if (swap <= 0) + return true; + + assert(color_of(piece_on(from)) == sideToMove); + Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + Color stm = sideToMove; + Bitboard attackers = attackers_to(to, occupied); + Bitboard stmAttackers, bb; + int res = 1; + + while (true) + { + stm = ~stm; + attackers &= occupied; + + // If stm has no more attackers then give up: stm loses + if (!(stmAttackers = attackers & pieces(stm))) + break; + + // Don't allow pinned pieces to attack as long as there are + // pinners on their original square. + if (pinners(~stm) & occupied) + { + stmAttackers &= ~blockers_for_king(stm); + + if (!stmAttackers) + break; + } + + res ^= 1; + + // Locate and remove the next least valuable attacker, and add to + // the bitboard 'attackers' any X-ray attackers behind it. + if ((bb = stmAttackers & pieces(PAWN))) + { + if ((swap = PawnValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(KNIGHT))) + { + if ((swap = KnightValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + } + + else if ((bb = stmAttackers & pieces(BISHOP))) + { + if ((swap = BishopValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(ROOK))) + { + if ((swap = RookValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + + attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); + } + + else if ((bb = stmAttackers & pieces(QUEEN))) + { + swap = QueenValue - swap; + // implies that the previous recapture was done by a higher rated piece than a Queen (King is excluded) + assert(swap >= res); + occupied ^= least_significant_square_bb(bb); + + attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(to, occupied) & pieces(ROOK, QUEEN)); + } + + else // KING + // If we "capture" with the king but the opponent still has attackers, + // reverse the result. + return (attackers & ~pieces(stm)) ? res ^ 1 : res; + } + + return bool(res); +} + +// Tests whether the position is drawn by 50-move rule +// or by repetition. It does not detect stalemates. +bool Position::is_draw(int ply) const { + + if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) + return true; + + return is_repetition(ply); +} + +// Return a draw score if a position repeats once earlier but strictly +// after the root, or repeats twice before or at the root. +bool Position::is_repetition(int ply) const { return st->repetition && st->repetition < ply; } + +// Tests whether there has been at least one repetition +// of positions since the last capture or pawn move. +bool Position::has_repeated() const { + + StateInfo* stc = st; + int end = std::min(st->rule50, st->pliesFromNull); + while (end-- >= 4) + { + if (stc->repetition) + return true; + + stc = stc->previous; + } + return false; +} + + +// Tests if the position has a move which draws by repetition. +// This function accurately matches the outcome of is_draw() over all legal moves. +bool Position::upcoming_repetition(int ply) const { + + int j; + + int end = std::min(st->rule50, st->pliesFromNull); + + if (end < 3) + return false; + + Key originalKey = st->key; + StateInfo* stp = st->previous; + Key other = originalKey ^ stp->key ^ Zobrist::side; + + for (int i = 3; i <= end; i += 2) + { + stp = stp->previous; + other ^= stp->key ^ stp->previous->key ^ Zobrist::side; + stp = stp->previous; + + if (other != 0) + continue; + + Key moveKey = originalKey ^ stp->key; + if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) + { + Move move = cuckooMove[j]; + Square s1 = move.from_sq(); + Square s2 = move.to_sq(); + + if (!((between_bb(s1, s2) ^ s2) & pieces())) + { + if (ply > i) + return true; + + // For nodes before or at the root, check that the move is a + // repetition rather than a move to the current position. + if (stp->repetition) + return true; + } + } + } + return false; +} + + +// Flips position with the white and black sides reversed. This +// is only useful for debugging e.g. for finding evaluation symmetry bugs. +void Position::flip() { + + string f, token; + std::stringstream ss(fen()); + + for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement + { + std::getline(ss, token, r > RANK_1 ? '/' : ' '); + f.insert(0, token + (f.empty() ? " " : "/")); + } + + ss >> token; // Active color + f += (token == "w" ? "B " : "W "); // Will be lowercased later + + ss >> token; // Castling availability + f += token + " "; + + std::transform(f.begin(), f.end(), f.begin(), + [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); + + ss >> token; // En passant square + f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); + + std::getline(ss, token); // Half and full moves + f += token; + + set(f, is_chess960(), st); + + assert(pos_is_ok()); +} + + +// Performs some consistency checks for the position object +// and raise an assert if something wrong is detected. +// This is meant to be helpful when debugging. +bool Position::pos_is_ok() const { + + constexpr bool Fast = true; // Quick (default) or full check? + + if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square(WHITE)) != W_KING + || piece_on(square(BLACK)) != B_KING + || (ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6)) + assert(0 && "pos_is_ok: Default"); + + if (Fast) + return true; + + if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1 + || attackers_to_exist(square(~sideToMove), pieces(), sideToMove)) + assert(0 && "pos_is_ok: Kings"); + + if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8) + assert(0 && "pos_is_ok: Pawns"); + + if ((pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces() + || popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16) + assert(0 && "pos_is_ok: Bitboards"); + + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + assert(0 && "pos_is_ok: Bitboards"); + + + for (Piece pc : Pieces) + if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); + + for (Color c : {WHITE, BLACK}) + for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) + { + if (!can_castle(cr)) + continue; + + if (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[cr]] != cr + || (castlingRightsMask[square(c)] & cr) != cr) + assert(0 && "pos_is_ok: Castling"); + } + + return true; +} + +} // namespace Stockfish diff --git a/stockfish/src/position.h b/stockfish/src/position.h new file mode 100644 index 0000000000000000000000000000000000000000..75f22c7df58af41319b95c32befa218da7adfadb --- /dev/null +++ b/stockfish/src/position.h @@ -0,0 +1,374 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef POSITION_H_INCLUDED +#define POSITION_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "bitboard.h" +#include "types.h" + +namespace Stockfish { + +class TranspositionTable; + +// StateInfo struct stores information needed to restore a Position object to +// its previous state when we retract a move. Whenever a move is made on the +// board (by calling Position::do_move), a StateInfo object must be passed. + +struct StateInfo { + + // Copied when making a move + Key materialKey; + Key pawnKey; + Key minorPieceKey; + Key nonPawnKey[COLOR_NB]; + Value nonPawnMaterial[COLOR_NB]; + int castlingRights; + int rule50; + int pliesFromNull; + Square epSquare; + + // Not copied when making a move (will be recomputed anyhow) + Key key; + Bitboard checkersBB; + StateInfo* previous; + StateInfo* next; + Bitboard blockersForKing[COLOR_NB]; + Bitboard pinners[COLOR_NB]; + Bitboard checkSquares[PIECE_TYPE_NB]; + Piece capturedPiece; + int repetition; +}; + + +// A list to keep track of the position states along the setup moves (from the +// start position to the position just before the search starts). Needed by +// 'draw by repetition' detection. Use a std::deque because pointers to +// elements are not invalidated upon list resizing. +using StateListPtr = std::unique_ptr>; + + +// Position class stores information regarding the board representation as +// pieces, side to move, hash keys, castling info, etc. Important methods are +// do_move() and undo_move(), used by the search to update node info when +// traversing the search tree. +class Position { + public: + static void init(); + + Position() = default; + Position(const Position&) = delete; + Position& operator=(const Position&) = delete; + + // FEN string input/output + Position& set(const std::string& fenStr, bool isChess960, StateInfo* si); + Position& set(const std::string& code, Color c, StateInfo* si); + std::string fen() const; + + // Position representation + Bitboard pieces(PieceType pt = ALL_PIECES) const; + template + Bitboard pieces(PieceType pt, PieceTypes... pts) const; + Bitboard pieces(Color c) const; + template + Bitboard pieces(Color c, PieceTypes... pts) const; + Piece piece_on(Square s) const; + Square ep_square() const; + bool empty(Square s) const; + template + int count(Color c) const; + template + int count() const; + template + Square square(Color c) const; + + // Castling + CastlingRights castling_rights(Color c) const; + bool can_castle(CastlingRights cr) const; + bool castling_impeded(CastlingRights cr) const; + Square castling_rook_square(CastlingRights cr) const; + + // Checking + Bitboard checkers() const; + Bitboard blockers_for_king(Color c) const; + Bitboard check_squares(PieceType pt) const; + Bitboard pinners(Color c) const; + + // Attacks to/from a given square + Bitboard attackers_to(Square s) const; + Bitboard attackers_to(Square s, Bitboard occupied) const; + bool attackers_to_exist(Square s, Bitboard occupied, Color c) const; + void update_slider_blockers(Color c) const; + template + Bitboard attacks_by(Color c) const; + + // Properties of moves + bool legal(Move m) const; + bool pseudo_legal(const Move m) const; + bool capture(Move m) const; + bool capture_stage(Move m) const; + bool gives_check(Move m) const; + Piece moved_piece(Move m) const; + Piece captured_piece() const; + + // Doing and undoing moves + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + DirtyPiece do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); + void undo_null_move(); + + // Static Exchange Evaluation + bool see_ge(Move m, int threshold = 0) const; + + // Accessing hash keys + Key key() const; + Key material_key() const; + Key pawn_key() const; + Key minor_piece_key() const; + Key non_pawn_key(Color c) const; + + // Other properties of the position + Color side_to_move() const; + int game_ply() const; + bool is_chess960() const; + bool is_draw(int ply) const; + bool is_repetition(int ply) const; + bool upcoming_repetition(int ply) const; + bool has_repeated() const; + int rule50_count() const; + Value non_pawn_material(Color c) const; + Value non_pawn_material() const; + + // Position consistency check, for debugging + bool pos_is_ok() const; + void flip(); + + // Used by NNUE + StateInfo* state() const; + + void put_piece(Piece pc, Square s); + void remove_piece(Square s); + + private: + // Initialization helpers (used while setting up a position) + void set_castling_right(Color c, Square rfrom); + void set_state() const; + void set_check_info() const; + + // Other helpers + void move_piece(Square from, Square to); + template + void do_castling(Color us, + Square from, + Square& to, + Square& rfrom, + Square& rto, + DirtyPiece* const dp = nullptr); + template + Key adjust_key50(Key k) const; + + // Data members + Piece board[SQUARE_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + Bitboard byColorBB[COLOR_NB]; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + StateInfo* st; + int gamePly; + Color sideToMove; + bool chess960; +}; + +std::ostream& operator<<(std::ostream& os, const Position& pos); + +inline Color Position::side_to_move() const { return sideToMove; } + +inline Piece Position::piece_on(Square s) const { + assert(is_ok(s)); + return board[s]; +} + +inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } + +inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } + +inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } + +template +inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const { + return pieces(pt) | pieces(pts...); +} + +inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; } + +template +inline Bitboard Position::pieces(Color c, PieceTypes... pts) const { + return pieces(c) & pieces(pts...); +} + +template +inline int Position::count(Color c) const { + return pieceCount[make_piece(c, Pt)]; +} + +template +inline int Position::count() const { + return count(WHITE) + count(BLACK); +} + +template +inline Square Position::square(Color c) const { + assert(count(c) == 1); + return lsb(pieces(c, Pt)); +} + +inline Square Position::ep_square() const { return st->epSquare; } + +inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; } + +inline CastlingRights Position::castling_rights(Color c) const { + return c & CastlingRights(st->castlingRights); +} + +inline bool Position::castling_impeded(CastlingRights cr) const { + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + return pieces() & castlingPath[cr]; +} + +inline Square Position::castling_rook_square(CastlingRights cr) const { + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + return castlingRookSquare[cr]; +} + +inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); } + +template +inline Bitboard Position::attacks_by(Color c) const { + + if constexpr (Pt == PAWN) + return c == WHITE ? pawn_attacks_bb(pieces(WHITE, PAWN)) + : pawn_attacks_bb(pieces(BLACK, PAWN)); + else + { + Bitboard threats = 0; + Bitboard attackers = pieces(c, Pt); + while (attackers) + threats |= attacks_bb(pop_lsb(attackers), pieces()); + return threats; + } +} + +inline Bitboard Position::checkers() const { return st->checkersBB; } + +inline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; } + +inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; } + +inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } + +inline Key Position::key() const { return adjust_key50(st->key); } + +template +inline Key Position::adjust_key50(Key k) const { + return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); +} + +inline Key Position::pawn_key() const { return st->pawnKey; } + +inline Key Position::material_key() const { return st->materialKey; } + +inline Key Position::minor_piece_key() const { return st->minorPieceKey; } + +inline Key Position::non_pawn_key(Color c) const { return st->nonPawnKey[c]; } + +inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } + +inline Value Position::non_pawn_material() const { + return non_pawn_material(WHITE) + non_pawn_material(BLACK); +} + +inline int Position::game_ply() const { return gamePly; } + +inline int Position::rule50_count() const { return st->rule50; } + +inline bool Position::is_chess960() const { return chess960; } + +inline bool Position::capture(Move m) const { + assert(m.is_ok()); + return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT; +} + +// Returns true if a move is generated from the capture stage, having also +// queen promotions covered, i.e. consistency with the capture stage move +// generation is needed to avoid the generation of duplicate moves. +inline bool Position::capture_stage(Move m) const { + assert(m.is_ok()); + return capture(m) || m.promotion_type() == QUEEN; +} + +inline Piece Position::captured_piece() const { return st->capturedPiece; } + +inline void Position::put_piece(Piece pc, Square s) { + + board[s] = pc; + byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; + byColorBB[color_of(pc)] |= s; + pieceCount[pc]++; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; +} + +inline void Position::remove_piece(Square s) { + + Piece pc = board[s]; + byTypeBB[ALL_PIECES] ^= s; + byTypeBB[type_of(pc)] ^= s; + byColorBB[color_of(pc)] ^= s; + board[s] = NO_PIECE; + pieceCount[pc]--; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; +} + +inline void Position::move_piece(Square from, Square to) { + + Piece pc = board[from]; + Bitboard fromTo = from | to; + byTypeBB[ALL_PIECES] ^= fromTo; + byTypeBB[type_of(pc)] ^= fromTo; + byColorBB[color_of(pc)] ^= fromTo; + board[from] = NO_PIECE; + board[to] = pc; +} + +inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { + do_move(m, newSt, gives_check(m), tt); +} + +inline StateInfo* Position::state() const { return st; } + +} // namespace Stockfish + +#endif // #ifndef POSITION_H_INCLUDED diff --git a/stockfish/src/score.cpp b/stockfish/src/score.cpp new file mode 100644 index 0000000000000000000000000000000000000000..561bc23c48e1bcab248043b165e64e339a8fad9f --- /dev/null +++ b/stockfish/src/score.cpp @@ -0,0 +1,48 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "score.h" + +#include +#include +#include + +#include "uci.h" + +namespace Stockfish { + +Score::Score(Value v, const Position& pos) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + + if (!is_decisive(v)) + { + score = InternalUnits{UCIEngine::to_cp(v, pos)}; + } + else if (std::abs(v) <= VALUE_TB) + { + auto distance = VALUE_TB - std::abs(v); + score = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false}; + } + else + { + auto distance = VALUE_MATE - std::abs(v); + score = (v > 0) ? Mate{distance} : Mate{-distance}; + } +} + +} \ No newline at end of file diff --git a/stockfish/src/score.h b/stockfish/src/score.h new file mode 100644 index 0000000000000000000000000000000000000000..eda90af35c034b431d582f3976dde162c22c4c61 --- /dev/null +++ b/stockfish/src/score.h @@ -0,0 +1,70 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SCORE_H_INCLUDED +#define SCORE_H_INCLUDED + +#include +#include + +#include "types.h" + +namespace Stockfish { + +class Position; + +class Score { + public: + struct Mate { + int plies; + }; + + struct Tablebase { + int plies; + bool win; + }; + + struct InternalUnits { + int value; + }; + + Score() = default; + Score(Value v, const Position& pos); + + template + bool is() const { + return std::holds_alternative(score); + } + + template + T get() const { + return std::get(score); + } + + template + decltype(auto) visit(F&& f) const { + return std::visit(std::forward(f), score); + } + + private: + std::variant score; +}; + +} + +#endif // #ifndef SCORE_H_INCLUDED diff --git a/stockfish/src/search.cpp b/stockfish/src/search.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0c543c30895b12f514a4375c83fdc1452336cee3 --- /dev/null +++ b/stockfish/src/search.cpp @@ -0,0 +1,2220 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "search.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "evaluate.h" +#include "history.h" +#include "misc.h" +#include "movegen.h" +#include "movepick.h" +#include "nnue/network.h" +#include "nnue/nnue_accumulator.h" +#include "position.h" +#include "syzygy/tbprobe.h" +#include "thread.h" +#include "timeman.h" +#include "tt.h" +#include "uci.h" +#include "ucioption.h" + +namespace Stockfish { + +namespace TB = Tablebases; + +void syzygy_extend_pv(const OptionsMap& options, + const Search::LimitsType& limits, + Stockfish::Position& pos, + Stockfish::Search::RootMove& rootMove, + Value& v); + +using namespace Search; + +namespace { + +// (*Scalers): +// The values with Scaler asterisks have proven non-linear scaling. +// They are optimized to time controls of 180 + 1.8 and longer, +// so changing them or adding conditions that are similar requires +// tests at these types of time controls. + +// Futility margin +Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { + Value futilityMult = 110 - 25 * noTtCutNode; + Value improvingDeduction = improving * futilityMult * 2; + Value worseningDeduction = oppWorsening * futilityMult / 3; + + return futilityMult * d - improvingDeduction - worseningDeduction; +} + +constexpr int futility_move_count(bool improving, Depth depth) { + return (3 + depth * depth) / (2 - improving); +} + +int correction_value(const Worker& w, const Position& pos, const Stack* const ss) { + const Color us = pos.side_to_move(); + const auto m = (ss - 1)->currentMove; + const auto pcv = w.pawnCorrectionHistory[pawn_structure_index(pos)][us]; + const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us]; + const auto wnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us]; + const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; + const auto cntcv = + m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] + : 0; + + return 7685 * pcv + 7495 * micv + 9144 * (wnpcv + bnpcv) + 6469 * cntcv; +} + +int risk_tolerance(const Position& pos, Value v) { + // Returns (some constant of) second derivative of sigmoid. + static constexpr auto sigmoid_d2 = [](int x, int y) { + return 644800 * x / ((x * x + 3 * y * y) * y); + }; + + int m = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() + + 337 * pos.count() + 553 * pos.count()) + / 64; + + // a and b are the crude approximation of the wdl model. + // The win rate is: 1/(1+exp((a-v)/b)) + // The loss rate is 1/(1+exp((v+a)/b)) + int a = 356; + int b = ((65 * m - 3172) * m + 240578) / 2048; + + // guard against overflow + assert(abs(v) + a <= std::numeric_limits::max() / 644800); + + // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) + // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) + int winning_risk = sigmoid_d2(v - a, b); + int losing_risk = sigmoid_d2(v + a, b); + + return -(winning_risk + losing_risk) * 32; +} + +// Add correctionHistory value to raw staticEval and guarantee evaluation +// does not hit the tablebase range. +Value to_corrected_static_eval(const Value v, const int cv) { + return std::clamp(v + cv / 131072, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); +} + +void update_correction_history(const Position& pos, + Stack* const ss, + Search::Worker& workerThread, + const int bonus) { + const Move m = (ss - 1)->currentMove; + const Color us = pos.side_to_move(); + + static constexpr int nonPawnWeight = 162; + + workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] + << bonus * 111 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; + workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] + << bonus * nonPawnWeight / 128; + workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] + << bonus * nonPawnWeight / 128; + + if (m.is_ok()) + (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] + << bonus * 143 / 128; +} + +// Add a small random component to draw evaluations to avoid 3-fold blindness +Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } +Value value_to_tt(Value v, int ply); +Value value_from_tt(Value v, int ply, int r50c); +void update_pv(Move* pv, Move move, const Move* childPv); +void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); +void update_quiet_histories( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Square prevSq, + ValueList& quietsSearched, + ValueList& capturesSearched, + Depth depth, + bool isTTMove, + int moveCount); + +} // namespace + +Search::Worker::Worker(SharedState& sharedState, + std::unique_ptr sm, + size_t threadId, + NumaReplicatedAccessToken token) : + // Unpack the SharedState struct into member variables + threadIdx(threadId), + numaAccessToken(token), + manager(std::move(sm)), + options(sharedState.options), + threads(sharedState.threads), + tt(sharedState.tt), + networks(sharedState.networks), + refreshTable(networks[token]) { + clear(); +} + +void Search::Worker::ensure_network_replicated() { + // Access once to force lazy initialization. + // We do this because we want to avoid initialization during search. + (void) (networks[numaAccessToken]); +} + +void Search::Worker::start_searching() { + + accumulatorStack.reset(rootPos, networks[numaAccessToken], refreshTable); + + // Non-main threads go directly to iterative_deepening() + if (!is_mainthread()) + { + iterative_deepening(); + return; + } + + main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options, + main_manager()->originalTimeAdjust); + tt.new_search(); + + if (rootMoves.empty()) + { + rootMoves.emplace_back(Move::none()); + main_manager()->updates.onUpdateNoMoves( + {0, {rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos}}); + } + else + { + threads.start_searching(); // start non-main threads + iterative_deepening(); // main thread start searching + } + + // When we reach the maximum depth, we can arrive here without a raise of + // threads.stop. However, if we are pondering or in an infinite search, + // the UCI protocol states that we shouldn't print the best move before the + // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here + // until the GUI sends one of those commands. + while (!threads.stop && (main_manager()->ponder || limits.infinite)) + {} // Busy wait for a stop or a ponder reset + + // Stop the threads if not already stopped (also raise the stop if + // "ponderhit" just reset threads.ponder) + threads.stop = true; + + // Wait until all threads have finished + threads.wait_for_search_finished(); + + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (limits.npmsec) + main_manager()->tm.advance_nodes_time(threads.nodes_searched() + - limits.inc[rootPos.side_to_move()]); + + Worker* bestThread = this; + Skill skill = + Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); + + if (int(options["MultiPV"]) == 1 && !limits.depth && !limits.mate && !skill.enabled() + && rootMoves[0].pv[0] != Move::none()) + bestThread = threads.get_best_thread()->worker.get(); + + main_manager()->bestPreviousScore = bestThread->rootMoves[0].score; + main_manager()->bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; + + // Send again PV info if we have a new best thread + if (bestThread != this) + main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth); + + std::string ponder; + + if (bestThread->rootMoves[0].pv.size() > 1 + || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) + ponder = UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + + auto bestmove = UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + main_manager()->updates.onBestmove(bestmove, ponder); +} + +// Main iterative deepening loop. It calls search() +// repeatedly with increasing depth until the allocated thinking time has been +// consumed, the user stops the search, or the maximum search depth is reached. +void Search::Worker::iterative_deepening() { + + SearchManager* mainThread = (is_mainthread() ? main_manager() : nullptr); + + Move pv[MAX_PLY + 1]; + + Depth lastBestMoveDepth = 0; + Value lastBestScore = -VALUE_INFINITE; + auto lastBestPV = std::vector{Move::none()}; + + Value alpha, beta; + Value bestValue = -VALUE_INFINITE; + Color us = rootPos.side_to_move(); + double timeReduction = 1, totBestMoveChanges = 0; + int delta, iterIdx = 0; + + // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): + // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), + // (ss + 2) is needed for initialization of cutOffCnt. + Stack stack[MAX_PLY + 10] = {}; + Stack* ss = stack + 7; + + for (int i = 7; i > 0; --i) + { + (ss - i)->continuationHistory = + &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; + (ss - i)->staticEval = VALUE_NONE; + (ss - i)->reduction = 0; + } + + for (int i = 0; i <= MAX_PLY + 2; ++i) + { + (ss + i)->ply = i; + (ss + i)->reduction = 0; + } + + ss->pv = pv; + + if (mainThread) + { + if (mainThread->bestPreviousScore == VALUE_INFINITE) + mainThread->iterValue.fill(VALUE_ZERO); + else + mainThread->iterValue.fill(mainThread->bestPreviousScore); + } + + size_t multiPV = size_t(options["MultiPV"]); + Skill skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); + + // When playing with strength handicap enable MultiPV search that we will + // use behind-the-scenes to retrieve a set of possible moves. + if (skill.enabled()) + multiPV = std::max(multiPV, size_t(4)); + + multiPV = std::min(multiPV, rootMoves.size()); + + int searchAgainCounter = 0; + + lowPlyHistory.fill(92); + + // Iterative deepening loop until requested to stop or the target depth is reached + while (++rootDepth < MAX_PLY && !threads.stop + && !(limits.depth && mainThread && rootDepth > limits.depth)) + { + // Age out PV variability metric + if (mainThread) + totBestMoveChanges /= 2; + + // Save the last iteration's scores before the first PV line is searched and + // all the move scores except the (new) PV are set to -VALUE_INFINITE. + for (RootMove& rm : rootMoves) + rm.previousScore = rm.score; + + size_t pvFirst = 0; + pvLast = 0; + + if (!threads.increaseDepth) + searchAgainCounter++; + + // MultiPV loop. We perform a full root search for each PV line + for (pvIdx = 0; pvIdx < multiPV; ++pvIdx) + { + if (pvIdx == pvLast) + { + pvFirst = pvLast; + for (pvLast++; pvLast < rootMoves.size(); pvLast++) + if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) + break; + } + + // Reset UCI info selDepth for each depth and each PV line + selDepth = 0; + + // Reset aspiration window starting size + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11834; + Value avg = rootMoves[pvIdx].averageScore; + alpha = std::max(avg - delta, -VALUE_INFINITE); + beta = std::min(avg + delta, VALUE_INFINITE); + + // Adjust optimism based on root move's averageScore + optimism[us] = 138 * avg / (std::abs(avg) + 84); + optimism[~us] = -optimism[us]; + + // Start with a small aspiration window and, in the case of a fail + // high/low, re-search with a bigger window until we don't fail + // high/low anymore. + int failedHighCnt = 0; + while (true) + { + // Adjust the effective depth searched, but ensure at least one + // effective increment for every four searchAgain steps (see issue #2717). + Depth adjustedDepth = + std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); + rootDelta = beta - alpha; + bestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); + + // Bring the best move to the front. It is critical that sorting + // is done with a stable algorithm because all the values but the + // first and eventually the new best one is set to -VALUE_INFINITE + // and we want to keep the same order for all the moves except the + // new PV that goes to the front. Note that in the case of MultiPV + // search the already searched PV lines are preserved. + std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); + + // If search has been stopped, we break immediately. Sorting is + // safe because RootMoves is still valid, although it refers to + // the previous iteration. + if (threads.stop) + break; + + // When failing high/low give some update before a re-search. To avoid + // excessive output that could hang GUIs like Fritz 19, only start + // at nodes > 10M (rather than depth N, which can be reached quickly) + if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) + && nodes > 10000000) + main_manager()->pv(*this, threads, tt, rootDepth); + + // In case of failing low/high increase aspiration window and re-search, + // otherwise exit the loop. + if (bestValue <= alpha) + { + beta = (alpha + beta) / 2; + alpha = std::max(bestValue - delta, -VALUE_INFINITE); + + failedHighCnt = 0; + if (mainThread) + mainThread->stopOnPonderhit = false; + } + else if (bestValue >= beta) + { + beta = std::min(bestValue + delta, VALUE_INFINITE); + ++failedHighCnt; + } + else + break; + + delta += delta / 3; + + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } + + // Sort the PV lines searched so far and update the GUI + std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); + + if (mainThread + && (threads.stop || pvIdx + 1 == multiPV || nodes > 10000000) + // A thread that aborted search can have mated-in/TB-loss PV and + // score that cannot be trusted, i.e. it can be delayed or refuted + // if we would have had time to fully search other root-moves. Thus + // we suppress this output and below pick a proven score/PV for this + // thread (from the previous iteration). + && !(threads.abortedSearch && is_loss(rootMoves[0].uciScore))) + main_manager()->pv(*this, threads, tt, rootDepth); + + if (threads.stop) + break; + } + + if (!threads.stop) + completedDepth = rootDepth; + + // We make sure not to pick an unproven mated-in score, + // in case this thread prematurely stopped search (aborted-search). + if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE + && is_loss(rootMoves[0].score)) + { + // Bring the last best move to the front for best thread selection. + Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)]( + const auto& rm) { return rm == lastBestPV[0]; }); + rootMoves[0].pv = lastBestPV; + rootMoves[0].score = rootMoves[0].uciScore = lastBestScore; + } + else if (rootMoves[0].pv[0] != lastBestPV[0]) + { + lastBestPV = rootMoves[0].pv; + lastBestScore = rootMoves[0].score; + lastBestMoveDepth = rootDepth; + } + + if (!mainThread) + continue; + + // Have we found a "mate in x"? + if (limits.mate && rootMoves[0].score == rootMoves[0].uciScore + && ((rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - rootMoves[0].score <= 2 * limits.mate) + || (rootMoves[0].score != -VALUE_INFINITE + && rootMoves[0].score <= VALUE_MATED_IN_MAX_PLY + && VALUE_MATE + rootMoves[0].score <= 2 * limits.mate))) + threads.stop = true; + + // If the skill level is enabled and time is up, pick a sub-optimal best move + if (skill.enabled() && skill.time_to_pick(rootDepth)) + skill.pick_best(rootMoves, multiPV); + + // Use part of the gained time from a previous stable move for the current move + for (auto&& th : threads) + { + totBestMoveChanges += th->worker->bestMoveChanges; + th->worker->bestMoveChanges = 0; + } + + // Do we have time for the next iteration? Can we stop searching now? + if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) + { + int nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); + + double fallingEval = + (11.396 + 2.035 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.968 * (mainThread->iterValue[iterIdx] - bestValue)) + / 100.0; + fallingEval = std::clamp(fallingEval, 0.5786, 1.6752); + + // If the bestMove is stable over several iterations, reduce time accordingly + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.4857 : 0.7046; + double reduction = + (1.4540 + mainThread->previousTimeReduction) / (2.1593 * timeReduction); + double bestMoveInstability = 0.9929 + 1.8519 * totBestMoveChanges / threads.size(); + + double totalTime = + mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; + + // Cap used time in case of a single legal move for a better viewer experience + if (rootMoves.size() == 1) + totalTime = std::min(500.0, totalTime); + + auto elapsedTime = elapsed(); + + if (completedDepth >= 10 && nodesEffort >= 97056 && elapsedTime > totalTime * 0.6540 + && !mainThread->ponder) + threads.stop = true; + + // Stop the search if we have exceeded the totalTime or maximum + if (elapsedTime > std::min(totalTime, double(mainThread->tm.maximum()))) + { + // If we are allowed to ponder do not stop the search now but + // keep pondering until the GUI sends "ponderhit" or "stop". + if (mainThread->ponder) + mainThread->stopOnPonderhit = true; + else + threads.stop = true; + } + else + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.5138; + } + + mainThread->iterValue[iterIdx] = bestValue; + iterIdx = (iterIdx + 1) & 3; + } + + if (!mainThread) + return; + + mainThread->previousTimeReduction = timeReduction; + + // If the skill level is enabled, swap the best PV line with the sub-optimal one + if (skill.enabled()) + std::swap(rootMoves[0], + *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(rootMoves, multiPV))); +} + + +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st) { + do_move(pos, move, st, pos.gives_check(move)); +} + +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck) { + DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); + accumulatorStack.push(dp); +} + +void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } + +void Search::Worker::undo_move(Position& pos, const Move move) { + pos.undo_move(move); + accumulatorStack.pop(); +} + +void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } + + +// Reset histories, usually before a new game +void Search::Worker::clear() { + mainHistory.fill(66); + lowPlyHistory.fill(105); + captureHistory.fill(-646); + pawnHistory.fill(-1262); + pawnCorrectionHistory.fill(6); + minorPieceCorrectionHistory.fill(0); + nonPawnCorrectionHistory.fill(0); + + for (auto& to : continuationCorrectionHistory) + for (auto& h : to) + h.fill(5); + + for (bool inCheck : {false, true}) + for (StatsType c : {NoCaptures, Captures}) + for (auto& to : continuationHistory[inCheck][c]) + for (auto& h : to) + h.fill(-468); + + for (size_t i = 1; i < reductions.size(); ++i) + reductions[i] = int(2954 / 128.0 * std::log(i)); + + refreshTable.clear(networks[numaAccessToken]); +} + + +// Main search function for both PV and non-PV nodes +template +Value Search::Worker::search( + Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { + + constexpr bool PvNode = nodeType != NonPV; + constexpr bool rootNode = nodeType == Root; + const bool allNode = !(PvNode || cutNode); + + // Dive into quiescence search when the depth reaches zero + if (depth <= 0) + { + constexpr auto nt = PvNode ? PV : NonPV; + return qsearch(pos, ss, alpha, beta); + } + + // Limit the depth if extensions made it too large + depth = std::min(depth, MAX_PLY - 1); + + // Check if we have an upcoming move that draws by repetition + if (!rootNode && alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) + { + alpha = value_draw(this->nodes); + if (alpha >= beta) + return alpha; + } + + assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(0 < depth && depth < MAX_PLY); + assert(!(PvNode && cutNode)); + + Move pv[MAX_PLY + 1]; + StateInfo st; + + Key posKey; + Move move, excludedMove, bestMove; + Depth extension, newDepth; + Value bestValue, value, eval, maxValue, probCutBeta; + bool givesCheck, improving, priorCapture, opponentWorsening; + bool capture, ttCapture; + int priorReduction = (ss - 1)->reduction; + (ss - 1)->reduction = 0; + Piece movedPiece; + + ValueList capturesSearched; + ValueList quietsSearched; + + // Step 1. Initialize node + Worker* thisThread = this; + ss->inCheck = pos.checkers(); + priorCapture = pos.captured_piece(); + Color us = pos.side_to_move(); + ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; + + // Check for the available remaining time + if (is_mainthread()) + main_manager()->check_time(*thisThread); + + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; + + if (!rootNode) + { + // Step 2. Check for aborted search and immediate draw + if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) + || ss->ply >= MAX_PLY) + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) + : value_draw(thisThread->nodes); + + // Step 3. Mate distance pruning. Even if we mate at the next move our score + // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because + // a shorter mate was found upward in the tree then there is no need to search + // because we will never beat the current alpha. Same logic but with reversed + // signs apply also in the opposite condition of being mated instead of giving + // mate. In this case, return a fail-high score. + alpha = std::max(mated_in(ss->ply), alpha); + beta = std::min(mate_in(ss->ply + 1), beta); + if (alpha >= beta) + return alpha; + } + + assert(0 <= ss->ply && ss->ply < MAX_PLY); + + bestMove = Move::none(); + (ss + 2)->cutoffCnt = 0; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; + ss->statScore = 0; + + // Step 4. Transposition table lookup + excludedMove = ss->excludedMove; + posKey = pos.key(); + auto [ttHit, ttData, ttWriter] = tt.probe(posKey); + // Need further processing of the saved data + ss->ttHit = ttHit; + ttData.move = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] + : ttHit ? ttData.move + : Move::none(); + ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE; + ss->ttPv = excludedMove ? ss->ttPv : PvNode || (ttHit && ttData.is_pv); + ttCapture = ttData.move && pos.capture_stage(ttData.move); + + // At this point, if excluded, skip straight to step 6, static eval. However, + // to save indentation, we list the condition in all code between here and there. + + // At non-PV nodes we check for an early TT cutoff + if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) + && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() + && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) + && (cutNode == (ttData.value >= beta) || depth > 5)) + { + // If ttMove is quiet, update move sorting heuristics on TT hit + if (ttData.move && ttData.value >= beta) + { + // Bonus for a quiet ttMove that fails high + if (!ttCapture) + update_quiet_histories(pos, ss, *this, ttData.move, + std::min(120 * depth - 75, 1241)); + + // Extra penalty for early quiet moves of the previous ply + if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + -std::min(809 * (depth + 1) - 249, 3052)); + } + + // Partial workaround for the graph history interaction problem + // For high rule50 counts don't produce transposition table cutoffs. + if (pos.rule50_count() < 90) + return ttData.value; + } + + // Step 5. Tablebases probe + if (!rootNode && !excludedMove && tbConfig.cardinality) + { + int piecesCount = pos.count(); + + if (piecesCount <= tbConfig.cardinality + && (piecesCount < tbConfig.cardinality || depth >= tbConfig.probeDepth) + && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING)) + { + TB::ProbeState err; + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); + + // Force check of time on the next occasion + if (is_mainthread()) + main_manager()->callsCnt = 0; + + if (err != TB::ProbeState::FAIL) + { + thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); + + int drawScore = tbConfig.useRule50 ? 1 : 0; + + Value tbValue = VALUE_TB - ss->ply; + + // Use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score + value = wdl < -drawScore ? -tbValue + : wdl > drawScore ? tbValue + : VALUE_DRAW + 2 * wdl * drawScore; + + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER + : BOUND_EXACT; + + if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + { + ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, + std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE, + tt.generation()); + + return value; + } + + if (PvNode) + { + if (b == BOUND_LOWER) + bestValue = value, alpha = std::max(alpha, bestValue); + else + maxValue = value; + } + } + } + } + + // Step 6. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; + const auto correctionValue = correction_value(*thisThread, pos, ss); + if (ss->inCheck) + { + // Skip early pruning when in check + ss->staticEval = eval = (ss - 2)->staticEval; + improving = false; + goto moves_loop; + } + else if (excludedMove) + { + // Providing the hint that this node's accumulator will be used often + unadjustedStaticEval = eval = ss->staticEval; + } + else if (ss->ttHit) + { + // Never assume anything about values stored in TT + unadjustedStaticEval = ttData.eval; + if (!is_valid(unadjustedStaticEval)) + unadjustedStaticEval = evaluate(pos); + + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); + + // ttValue can be used as a better position evaluation + if (is_valid(ttData.value) + && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER))) + eval = ttData.value; + } + else + { + unadjustedStaticEval = evaluate(pos); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); + + // Static evaluation is saved as it was before adjustment by correction history + ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), + unadjustedStaticEval, tt.generation()); + } + + // Use static evaluation difference to improve quiet move ordering + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) + { + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1950, 1416) + 655; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1124 / 1024; + if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) + thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] + << bonus * 1196 / 1024; + } + + // Set up the improving flag, which is true if current static evaluation is + // bigger than the previous static evaluation at our turn (if we were in + // check at our previous move we go back until we weren't in check) and is + // false otherwise. The improving flag is used in various pruning heuristics. + improving = ss->staticEval > (ss - 2)->staticEval; + + opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; + + if (priorReduction >= 3 && !opponentWorsening) + depth++; + if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 188) + depth--; + + // Step 7. Razoring + // If eval is really low, skip search entirely and return the qsearch value. + // For PvNodes, we must have a guard against mates being returned. + if (!PvNode && eval < alpha - 461 - 315 * depth * depth) + return qsearch(pos, ss, alpha, beta); + + // Step 8. Futility pruning: child node + // The depth condition is important for mate finding. + if (!ss->ttPv && depth < 14 + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) + - (ss - 1)->statScore / 301 + 37 - std::abs(correctionValue) / 139878 + >= beta + && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) + return beta + (eval - beta) / 3; + + // Step 9. Null move search with verification search + if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta + && ss->staticEval >= beta - 19 * depth + 418 && !excludedMove && pos.non_pawn_material(us) + && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) + { + assert(eval - beta >= 0); + + // Null move dynamic reduction based on depth and eval + Depth R = std::min(int(eval - beta) / 232, 6) + depth / 3 + 5; + + ss->currentMove = Move::null(); + ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; + ss->continuationCorrectionHistory = &thisThread->continuationCorrectionHistory[NO_PIECE][0]; + + do_null_move(pos, st); + + Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); + + undo_null_move(pos); + + // Do not return unproven mate or TB scores + if (nullValue >= beta && !is_win(nullValue)) + { + if (thisThread->nmpMinPly || depth < 16) + return nullValue; + + assert(!thisThread->nmpMinPly); // Recursive verification is not allowed + + // Do verification search at high depths, with null move pruning disabled + // until ply exceeds nmpMinPly. + thisThread->nmpMinPly = ss->ply + 3 * (depth - R) / 4; + + Value v = search(pos, ss, beta - 1, beta, depth - R, false); + + thisThread->nmpMinPly = 0; + + if (v >= beta) + return nullValue; + } + } + + improving |= ss->staticEval >= beta + 94; + + // Step 10. Internal iterative reductions + // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. + // (* Scaler) Especially if they make IIR more aggressive. + if (((PvNode || cutNode) && depth >= 7 - 3 * PvNode) && !ttData.move) + depth--; + + // Step 11. ProbCut + // If we have a good enough capture (or queen promotion) and a reduced search + // returns a value much above beta, we can (almost) safely prune the previous move. + probCutBeta = beta + 185 - 58 * improving; + if (depth >= 3 + && !is_decisive(beta) + // If value from transposition table is lower than probCutBeta, don't attempt + // probCut there and in further interactions with transposition table cutoff + // depth is set to depth - 3 because probCut search has depth set to depth - 4 + // but we also do a move before it. So effective depth is equal to depth - 3. + && !(is_valid(ttData.value) && ttData.value < probCutBeta)) + { + assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); + + MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); + Depth probCutDepth = std::max(depth - 4, 0); + + while ((move = mp.next_move()) != Move::none()) + { + assert(move.is_ok()); + + if (move == excludedMove) + continue; + + if (!pos.legal(move)) + continue; + + assert(pos.capture_stage(move)); + + movedPiece = pos.moved_piece(move); + + do_move(pos, move, st); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + + ss->currentMove = move; + ss->isTTMove = (move == ttData.move); + ss->continuationHistory = + &this->continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; + ss->continuationCorrectionHistory = + &this->continuationCorrectionHistory[movedPiece][move.to_sq()]; + + // Perform a preliminary qsearch to verify that the move holds + value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); + + // If the qsearch held, perform the regular search + if (value >= probCutBeta && probCutDepth > 0) + value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, probCutDepth, + !cutNode); + + undo_move(pos, move); + + if (value >= probCutBeta) + { + // Save ProbCut data into transposition table + ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, + probCutDepth + 1, move, unadjustedStaticEval, tt.generation()); + + if (!is_decisive(value)) + return value - (probCutBeta - beta); + } + } + } + +moves_loop: // When in check, search starts here + + // Step 12. A small Probcut idea + probCutBeta = beta + 415; + if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta + && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) + return probCutBeta; + + const PieceToHistory* contHist[] = { + (ss - 1)->continuationHistory, (ss - 2)->continuationHistory, (ss - 3)->continuationHistory, + (ss - 4)->continuationHistory, (ss - 5)->continuationHistory, (ss - 6)->continuationHistory}; + + + MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->lowPlyHistory, + &thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply); + + value = bestValue; + + int moveCount = 0; + + // Step 13. Loop through all pseudo-legal moves until no moves remain + // or a beta cutoff occurs. + while ((move = mp.next_move()) != Move::none()) + { + assert(move.is_ok()); + + if (move == excludedMove) + continue; + + // Check for legality + if (!pos.legal(move)) + continue; + + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. In MultiPV mode we also skip PV moves that have been already + // searched and those of lower "TB rank" if we are in a TB root position. + if (rootNode + && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, + thisThread->rootMoves.begin() + thisThread->pvLast, move)) + continue; + + ss->moveCount = ++moveCount; + + if (rootNode && is_mainthread() && nodes > 10000000) + { + main_manager()->updates.onIter( + {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); + } + if (PvNode) + (ss + 1)->pv = nullptr; + + extension = 0; + capture = pos.capture_stage(move); + movedPiece = pos.moved_piece(move); + givesCheck = pos.gives_check(move); + + // Calculate new depth for this move + newDepth = depth - 1; + + int delta = beta - alpha; + + Depth r = reduction(improving, depth, moveCount, delta); + + r -= 32 * moveCount; + + // Increase reduction for ttPv nodes (*Scaler) + // Smaller or even negative value is better for short time controls + // Bigger value is better for long time controls + if (ss->ttPv) + r += 979; + + // Step 14. Pruning at shallow depth. + // Depth conditions are important for mate finding. + if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) + { + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold + if (moveCount >= futility_move_count(improving, depth)) + mp.skip_quiet_moves(); + + // Reduced depth of the next LMR search + int lmrDepth = newDepth - r / 1024; + + if (capture || givesCheck) + { + Piece capturedPiece = pos.piece_on(move.to_sq()); + int captHist = + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; + + // Futility pruning for captures + if (!givesCheck && lmrDepth < 7 && !ss->inCheck) + { + Value futilityValue = ss->staticEval + 242 + 230 * lmrDepth + + PieceValue[capturedPiece] + 133 * captHist / 1024; + if (futilityValue <= alpha) + continue; + } + + // SEE based pruning for captures and checks + int seeHist = std::clamp(captHist / 32, -138 * depth, 135 * depth); + if (!pos.see_ge(move, -154 * depth - seeHist)) + continue; + } + else + { + int history = + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; + + // Continuation history based pruning + if (history < -4348 * depth) + continue; + + history += 68 * thisThread->mainHistory[us][move.from_to()] / 32; + + lmrDepth += history / 3593; + + Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth + + 103 * (bestValue < ss->staticEval - 128); + + // Futility pruning: parent node + // (*Scaler): Generally, more frequent futility pruning + // scales well with respect to time and threads + if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) + { + if (bestValue <= futilityValue && !is_decisive(bestValue) + && !is_win(futilityValue)) + bestValue = futilityValue; + continue; + } + + lmrDepth = std::max(lmrDepth, 0); + + // Prune moves with negative SEE + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + continue; + } + } + + // Step 15. Extensions + // We take care to not overdo to avoid search getting stuck. + if (ss->ply < thisThread->rootDepth * 2) + { + // Singular extension search. If all moves but one + // fail low on a search of (alpha-s, beta-s), and just one fails high on + // (alpha, beta), then that move is singular and should be extended. To + // verify this we do a reduced search on the position excluding the ttMove + // and if the result is lower than ttValue minus a margin, then we will + // extend the ttMove. Recursive singular search is avoided. + + // (* Scaler) Generally, higher singularBeta (i.e closer to ttValue) + // and lower extension margins scale well. + + if (!rootNode && move == ttData.move && !excludedMove + && depth >= 6 - (thisThread->completedDepth > 29) + ss->ttPv + && is_valid(ttData.value) && !is_decisive(ttData.value) + && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) + { + Value singularBeta = ttData.value - (59 + 77 * (ss->ttPv && !PvNode)) * depth / 54; + Depth singularDepth = newDepth / 2; + + ss->excludedMove = move; + value = + search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + ss->excludedMove = Move::none(); + + if (value < singularBeta) + { + int corrValAdj1 = std::abs(correctionValue) / 248873; + int corrValAdj2 = std::abs(correctionValue) / 255331; + int doubleMargin = 262 * PvNode - 188 * !ttCapture - corrValAdj1; + int tripleMargin = + 88 + 265 * PvNode - 256 * !ttCapture + 93 * ss->ttPv - corrValAdj2; + + extension = 1 + (value < singularBeta - doubleMargin) + + (value < singularBeta - tripleMargin); + + depth++; + } + + // Multi-cut pruning + // Our ttMove is assumed to fail high based on the bound of the TT entry, + // and if after excluding the ttMove with a reduced search we fail high + // over the original beta, we assume this expected cut-node is not + // singular (multiple moves fail high), and we can prune the whole + // subtree by returning a softbound. + else if (value >= beta && !is_decisive(value)) + return value; + + // Negative extensions + // If other moves failed high over (ttValue - margin) without the + // ttMove on a reduced search, but we cannot do multi-cut because + // (ttValue - margin) is lower than the original beta, we do not know + // if the ttMove is singular or can do a multi-cut, so we reduce the + // ttMove in favor of other moves based on some conditions: + + // If the ttMove is assumed to fail high over current beta + else if (ttData.value >= beta) + extension = -3; + + // If we are on a cutNode but the ttMove is not assumed to fail high + // over current beta + else if (cutNode) + extension = -2; + } + } + + // Step 16. Make the move + do_move(pos, move, st, givesCheck); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + + // Add extension to new depth + newDepth += extension; + + // Update the current move (this must be done after singular extension search) + ss->currentMove = move; + ss->isTTMove = (move == ttData.move); + ss->continuationHistory = + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; + ss->continuationCorrectionHistory = + &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; + uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; + + // Decrease reduction for PvNodes (*Scaler) + if (ss->ttPv) + r -= 2381 + PvNode * 1008 + (ttData.value > alpha) * 880 + + (ttData.depth >= depth) * (1022 + cutNode * 1140); + + // These reduction adjustments have no proven non-linear scaling + + r += 306 - moveCount * 34; + + r -= std::abs(correctionValue) / 29696; + + if (PvNode && std::abs(bestValue) <= 2000) + r -= risk_tolerance(pos, bestValue); + + // Increase reduction for cut nodes + if (cutNode) + r += 2784 + 1038 * !ttData.move; + + // Increase reduction if ttMove is a capture but the current move is not a capture + if (ttCapture && !capture) + r += 1171 + (depth < 8) * 985; + + // Increase reduction if next ply has a lot of fail high + if ((ss + 1)->cutoffCnt > 3) + r += 1042 + allNode * 864; + + // For first picked move (ttMove) reduce reduction + else if (move == ttData.move) + r -= 1937; + + if (capture) + ss->statScore = + 846 * int(PieceValue[pos.captured_piece()]) / 128 + + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] + - 4822; + else if (ss->inCheck) + ss->statScore = thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] - 2771; + else + ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] - 3271; + + // Decrease/increase reduction for moves with a good/bad history + r -= ss->statScore * 1582 / 16384; + + // Step 17. Late moves reduction / extension (LMR) + if (depth >= 2 && moveCount > 1) + { + // In general we want to cap the LMR depth search at newDepth, but when + // reduction is negative, we allow this move a limited search extension + // beyond the first move depth. + // To prevent problems when the max value is less than the min value, + // std::clamp has been replaced by a more robust implementation. + + + Depth d = std::max( + 1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); + + ss->reduction = newDepth - d; + + value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); + ss->reduction = 0; + + + // Do a full-depth search when reduced LMR search fails high + if (value > alpha && d < newDepth) + { + // Adjust full-depth search based on LMR results - if the result was + // good enough search deeper, if it was bad enough search shallower. + const bool doDeeperSearch = value > (bestValue + 43 + 2 * newDepth); + const bool doShallowerSearch = value < bestValue + 9; + + newDepth += doDeeperSearch - doShallowerSearch; + + if (newDepth > d) + value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); + + // Post LMR continuation history updates + int bonus = 1600; + update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); + } + else if (value > alpha && value < bestValue + 9) + newDepth--; + } + + // Step 18. Full-depth search when LMR is skipped + else if (!PvNode || moveCount > 1) + { + // Increase reduction if ttMove is not present + if (!ttData.move) + r += 1156; + + // Note that if expected reduction is high, we reduce search depth here + value = -search(pos, ss + 1, -(alpha + 1), -alpha, + newDepth - (r > 3495) - (r > 5510 && newDepth > 2), !cutNode); + } + + // For PV nodes only, do a full PV search on the first move or after a fail high, + // otherwise let the parent node fail low with value <= alpha and try another move. + if (PvNode && (moveCount == 1 || value > alpha)) + { + (ss + 1)->pv = pv; + (ss + 1)->pv[0] = Move::none(); + + // Extend move from transposition table if we are about to dive into qsearch. + if (move == ttData.move && thisThread->rootDepth > 8) + newDepth = std::max(newDepth, 1); + + value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); + } + + // Step 19. Undo move + undo_move(pos, move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Step 20. Check for a new best move + // Finished searching the move. If a stop occurred, the return value of + // the search cannot be trusted, and we return immediately without updating + // best move, principal variation nor transposition table. + if (threads.stop.load(std::memory_order_relaxed)) + return VALUE_ZERO; + + if (rootNode) + { + RootMove& rm = + *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); + + rm.effort += nodes - nodeCount; + + rm.averageScore = + rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value; + + rm.meanSquaredScore = rm.meanSquaredScore != -VALUE_INFINITE * VALUE_INFINITE + ? (value * std::abs(value) + rm.meanSquaredScore) / 2 + : value * std::abs(value); + + // PV move or new best move? + if (moveCount == 1 || value > alpha) + { + rm.score = rm.uciScore = value; + rm.selDepth = thisThread->selDepth; + rm.scoreLowerbound = rm.scoreUpperbound = false; + + if (value >= beta) + { + rm.scoreLowerbound = true; + rm.uciScore = beta; + } + else if (value <= alpha) + { + rm.scoreUpperbound = true; + rm.uciScore = alpha; + } + + rm.pv.resize(1); + + assert((ss + 1)->pv); + + for (Move* m = (ss + 1)->pv; *m != Move::none(); ++m) + rm.pv.push_back(*m); + + // We record how often the best move has been changed in each iteration. + // This information is used for time management. In MultiPV mode, + // we must take care to only do this for the first PV line. + if (moveCount > 1 && !thisThread->pvIdx) + ++thisThread->bestMoveChanges; + } + else + // All other moves but the PV, are set to the lowest value: this + // is not a problem when sorting because the sort is stable and the + // move position in the list is preserved - just the PV is pushed up. + rm.score = -VALUE_INFINITE; + } + + // In case we have an alternative move equal in eval to the current bestmove, + // promote it to bestmove by pretending it just exceeds alpha (but not beta). + int inc = (value == bestValue && ss->ply + 2 >= thisThread->rootDepth + && (int(nodes) & 15) == 0 && !is_win(std::abs(value) + 1)); + + if (value + inc > bestValue) + { + bestValue = value; + + if (value + inc > alpha) + { + bestMove = move; + + if (PvNode && !rootNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss + 1)->pv); + + if (value >= beta) + { + // (* Scaler) Especially if they make cutoffCnt increment more often. + ss->cutoffCnt += (extension < 2) || PvNode; + assert(value >= beta); // Fail high + break; + } + else + { + // Reduce other moves if we have found at least one score improvement + if (depth > 2 && depth < 16 && !is_decisive(value)) + depth -= 2; + + assert(depth > 0); + alpha = value; // Update alpha! Always alpha < beta + } + } + } + + // If the move is worse than some previously searched move, + // remember it, to update its stats later. + if (move != bestMove && moveCount <= 32) + { + if (capture) + capturesSearched.push_back(move); + else + quietsSearched.push_back(move); + } + } + + // Step 21. Check for mate and stalemate + // All legal moves have been searched and if there are no legal moves, it + // must be a mate or a stalemate. If we are in a singular extension search then + // return a fail low score. + + assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); + + // Adjust best value for fail high cases + if (bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) && !is_decisive(alpha)) + bestValue = (bestValue * depth + beta) / (depth + 1); + + if (!moveCount) + bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; + + // If there is a move that produces search value greater than alpha, + // we update the stats of searched moves. + else if (bestMove) + update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, + bestMove == ttData.move, moveCount); + + // Bonus for prior quiet countermove that caused the fail low + else if (!priorCapture && prevSq != SQ_NONE) + { + int bonusScale = (std::clamp(160 * (depth - 4) / 2, 0, 200) + 34 * !allNode + + 164 * ((ss - 1)->moveCount > 8) + + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 112, 303)); + + bonusScale = std::max(bonusScale, 0); + + const int scaledBonus = std::min(160 * depth - 99, 1492) * bonusScale; + + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + scaledBonus * 388 / 32768); + + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] + << scaledBonus * 212 / 32768; + + if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) + thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] + << scaledBonus * 1055 / 32768; + } + + // Bonus for prior capture countermove that caused the fail low + else if (priorCapture && prevSq != SQ_NONE) + { + Piece capturedPiece = pos.captured_piece(); + assert(capturedPiece != NO_PIECE); + thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1100; + } + + if (PvNode) + bestValue = std::min(bestValue, maxValue); + + // If no good move is found and the previous position was ttPv, then the previous + // opponent move is probably good and the new position is added to the search tree. + if (bestValue <= alpha) + ss->ttPv = ss->ttPv || (ss - 1)->ttPv; + + // Write gathered information in transposition table. Note that the + // static evaluation is saved as it was before correction history. + if (!excludedMove && !(rootNode && thisThread->pvIdx)) + ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, + bestValue >= beta ? BOUND_LOWER + : PvNode && bestMove ? BOUND_EXACT + : BOUND_UPPER, + depth, bestMove, unadjustedStaticEval, tt.generation()); + + // Adjust correction history + if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) + && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high + || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low + { + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + update_correction_history(pos, ss, *thisThread, bonus); + } + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; +} + + +// Quiescence search function, which is called by the main search function with +// depth zero, or recursively with further decreasing depth. With depth <= 0, we +// "should" be using static eval only, but tactical moves may confuse the static eval. +// To fight this horizon effect, we implement this qsearch of tactical moves. +// See https://www.chessprogramming.org/Horizon_Effect +// and https://www.chessprogramming.org/Quiescence_Search +template +Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { + + static_assert(nodeType != Root); + constexpr bool PvNode = nodeType == PV; + + assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + + // Check if we have an upcoming move that draws by repetition + if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) + { + alpha = value_draw(this->nodes); + if (alpha >= beta) + return alpha; + } + + Move pv[MAX_PLY + 1]; + StateInfo st; + + Key posKey; + Move move, bestMove; + Value bestValue, value, futilityBase; + bool pvHit, givesCheck, capture; + int moveCount; + + // Step 1. Initialize node + if (PvNode) + { + (ss + 1)->pv = pv; + ss->pv[0] = Move::none(); + } + + Worker* thisThread = this; + bestMove = Move::none(); + ss->inCheck = pos.checkers(); + moveCount = 0; + + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; + + // Step 2. Check for an immediate draw or maximum ply reached + if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; + + assert(0 <= ss->ply && ss->ply < MAX_PLY); + + // Step 3. Transposition table lookup + posKey = pos.key(); + auto [ttHit, ttData, ttWriter] = tt.probe(posKey); + // Need further processing of the saved data + ss->ttHit = ttHit; + ttData.move = ttHit ? ttData.move : Move::none(); + ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE; + pvHit = ttHit && ttData.is_pv; + + // At non-PV nodes we check for an early TT cutoff + if (!PvNode && ttData.depth >= DEPTH_QS + && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() + && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))) + return ttData.value; + + // Step 4. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; + const auto correctionValue = correction_value(*thisThread, pos, ss); + if (ss->inCheck) + bestValue = futilityBase = -VALUE_INFINITE; + else + { + if (ss->ttHit) + { + // Never assume anything about values stored in TT + unadjustedStaticEval = ttData.eval; + if (!is_valid(unadjustedStaticEval)) + unadjustedStaticEval = evaluate(pos); + ss->staticEval = bestValue = + to_corrected_static_eval(unadjustedStaticEval, correctionValue); + + // ttValue can be used as a better position evaluation + if (is_valid(ttData.value) && !is_decisive(ttData.value) + && (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER))) + bestValue = ttData.value; + } + else + { + // In case of null move search, use previous static eval with opposite sign + unadjustedStaticEval = + (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; + ss->staticEval = bestValue = + to_corrected_static_eval(unadjustedStaticEval, correctionValue); + } + + // Stand pat. Return immediately if static value is at least beta + if (bestValue >= beta) + { + if (!is_decisive(bestValue)) + bestValue = (bestValue + beta) / 2; + if (!ss->ttHit) + ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, + DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, + tt.generation()); + return bestValue; + } + + if (bestValue > alpha) + alpha = bestValue; + + futilityBase = ss->staticEval + 359; + } + + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, + (ss - 2)->continuationHistory}; + + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; + + // Initialize a MovePicker object for the current position, and prepare to search + // the moves. We presently use two stages of move generator in quiescence search: + // captures, or evasions only when in check. + MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->lowPlyHistory, + &thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply); + + // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta + // cutoff occurs. + while ((move = mp.next_move()) != Move::none()) + { + assert(move.is_ok()); + + if (!pos.legal(move)) + continue; + + givesCheck = pos.gives_check(move); + capture = pos.capture_stage(move); + + moveCount++; + + // Step 6. Pruning + if (!is_loss(bestValue)) + { + // Futility pruning and moveCount pruning + if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase) + && move.type_of() != PROMOTION) + { + if (moveCount > 2) + continue; + + Value futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())]; + + // If static eval + value of piece we are going to capture is + // much lower than alpha, we can prune this move. + if (futilityValue <= alpha) + { + bestValue = std::max(bestValue, futilityValue); + continue; + } + + // If static exchange evaluation is low enough + // we can prune this move. + if (!pos.see_ge(move, alpha - futilityBase)) + { + bestValue = std::min(alpha, futilityBase); + continue; + } + } + + // Continuation history based pruning + if (!capture + && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] + [move.to_sq()] + <= 6290) + continue; + + // Do not search moves with bad enough SEE values + if (!pos.see_ge(move, -75)) + continue; + } + + // Step 7. Make and search the move + Piece movedPiece = pos.moved_piece(move); + + do_move(pos, move, st, givesCheck); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + + // Update the current move + ss->currentMove = move; + ss->continuationHistory = + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; + ss->continuationCorrectionHistory = + &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; + + value = -qsearch(pos, ss + 1, -beta, -alpha); + undo_move(pos, move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Step 8. Check for a new best move + if (value > bestValue) + { + bestValue = value; + + if (value > alpha) + { + bestMove = move; + + if (PvNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss + 1)->pv); + + if (value < beta) // Update alpha here! + alpha = value; + else + break; // Fail high + } + } + } + + // Step 9. Check for mate + // All legal moves have been searched. A special case: if we are + // in check and no legal moves were found, it is checkmate. + if (ss->inCheck && bestValue == -VALUE_INFINITE) + { + assert(!MoveList(pos).size()); + return mated_in(ss->ply); // Plies to mate from the root + } + + if (!is_decisive(bestValue) && bestValue > beta) + bestValue = (bestValue + beta) / 2; + + // Save gathered info in transposition table. The static evaluation + // is saved as it was before adjustment by correction history. + ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit, + bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, DEPTH_QS, bestMove, + unadjustedStaticEval, tt.generation()); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; +} + +Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { + int reductionScale = reductions[d] * reductions[mn]; + return reductionScale - delta * 764 / rootDelta + !i * reductionScale * 191 / 512 + 1087; +} + +// elapsed() returns the time elapsed since the search started. If the +// 'nodestime' option is enabled, it will return the count of nodes searched +// instead. This function is called to check whether the search should be +// stopped based on predefined thresholds like time limits or nodes searched. +// +// elapsed_time() returns the actual time elapsed since the start of the search. +// This function is intended for use only when printing PV outputs, and not used +// for making decisions within the search algorithm itself. +TimePoint Search::Worker::elapsed() const { + return main_manager()->tm.elapsed([this]() { return threads.nodes_searched(); }); +} + +TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); } + +Value Search::Worker::evaluate(const Position& pos) { + return Eval::evaluate(networks[numaAccessToken], pos, accumulatorStack, refreshTable, + optimism[pos.side_to_move()]); +} + +namespace { +// Adjusts a mate or TB score from "plies to mate from the root" to +// "plies to mate from the current position". Standard scores are unchanged. +// The function is called before storing a value in the transposition table. +Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; } + + +// Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition +// table (which refers to the plies to mate/be mated from current position) to +// "plies to mate/be mated (TB win/loss) from the root". However, to avoid +// potentially false mate or TB scores related to the 50 moves rule and the +// graph history interaction, we return the highest non-TB score instead. +Value value_from_tt(Value v, int ply, int r50c) { + + if (!is_valid(v)) + return VALUE_NONE; + + // handle TB win or better + if (is_win(v)) + { + // Downgrade a potentially false mate score + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + + return v - ply; + } + + // handle TB loss or worse + if (is_loss(v)) + { + // Downgrade a potentially false mate score. + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + + return v + ply; + } + + return v; +} + + +// Adds current move and appends child pv[] +void update_pv(Move* pv, Move move, const Move* childPv) { + + for (*pv++ = move; childPv && *childPv != Move::none();) + *pv++ = *childPv++; + *pv = Move::none(); +} + + +// Updates stats at the end of search() when a bestMove is found +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Square prevSq, + ValueList& quietsSearched, + ValueList& capturesSearched, + Depth depth, + bool isTTMove, + int moveCount) { + + CapturePieceToHistory& captureHistory = workerThread.captureHistory; + Piece moved_piece = pos.moved_piece(bestMove); + PieceType captured; + + int bonus = std::min(141 * depth - 89, 1613) + 311 * isTTMove; + int malus = std::min(695 * depth - 215, 2808) - 31 * (moveCount - 1); + + if (!pos.capture_stage(bestMove)) + { + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1129 / 1024); + + // Decrease stats for all non-best quiet moves + for (Move move : quietsSearched) + update_quiet_histories(pos, ss, workerThread, move, -malus * 1246 / 1024); + } + else + { + // Increase stats for the best move in case it was a capture move + captured = type_of(pos.piece_on(bestMove.to_sq())); + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1187 / 1024; + } + + // Extra penalty for a quiet early move that was not a TT move in + // previous ply when it gets refuted. + if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 987 / 1024); + + // Decrease stats for all non-best capture moves + for (Move move : capturesSearched) + { + moved_piece = pos.moved_piece(move); + captured = type_of(pos.piece_on(move.to_sq())); + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1377 / 1024; + } +} + + +// Updates histories of the move pairs formed by moves +// at ply -1, -2, -3, -4, and -6 with current move. +void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { + static constexpr std::array conthist_bonuses = { + {{1, 1103}, {2, 659}, {3, 323}, {4, 533}, {5, 121}, {6, 474}}}; + + for (const auto [i, weight] : conthist_bonuses) + { + // Only update the first 2 continuation histories if we are in check + if (ss->inCheck && i > 2) + break; + if (((ss - i)->currentMove).is_ok()) + (*(ss - i)->continuationHistory)[pc][to] << bonus * weight / 1024; + } +} + +// Updates move sorting heuristics + +void update_quiet_histories( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { + + Color us = pos.side_to_move(); + workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort + + if (ss->ply < LOW_PLY_HISTORY_SIZE) + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 829 / 1024; + + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 1004 / 1024); + + int pIndex = pawn_structure_index(pos); + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 587 / 1024; +} + +} + +// When playing with strength handicap, choose the best move among a set of +// RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. +Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { + static PRNG rng(now()); // PRNG sequence should be non-deterministic + + // RootMoves are already sorted by score in descending order + Value topScore = rootMoves[0].score; + int delta = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue)); + int maxScore = -VALUE_INFINITE; + double weakness = 120 - 2 * level; + + // Choose best move. For each move score we add two terms, both dependent on + // weakness. One is deterministic and bigger for weaker levels, and one is + // random. Then we choose the move with the resulting highest score. + for (size_t i = 0; i < multiPV; ++i) + { + // This is our magic formula + int push = (weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) + / 128; + + if (rootMoves[i].score + push >= maxScore) + { + maxScore = rootMoves[i].score + push; + best = rootMoves[i].pv[0]; + } + } + + return best; +} + + +// Used to print debug info and, more importantly, to detect +// when we are out of available time and thus stop the search. +void SearchManager::check_time(Search::Worker& worker) { + if (--callsCnt > 0) + return; + + // When using nodes, ensure checking rate is not lower than 0.1% of nodes + callsCnt = worker.limits.nodes ? std::min(512, int(worker.limits.nodes / 1024)) : 512; + + static TimePoint lastInfoTime = now(); + + TimePoint elapsed = tm.elapsed([&worker]() { return worker.threads.nodes_searched(); }); + TimePoint tick = worker.limits.startTime + elapsed; + + if (tick - lastInfoTime >= 1000) + { + lastInfoTime = tick; + dbg_print(); + } + + // We should not stop pondering until told so by the GUI + if (ponder) + return; + + if ( + // Later we rely on the fact that we can at least use the mainthread previous + // root-search score and PV in a multithreaded environment to prove mated-in scores. + worker.completedDepth >= 1 + && ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) + || (worker.limits.movetime && elapsed >= worker.limits.movetime) + || (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes))) + worker.threads.stop = worker.threads.abortedSearch = true; +} + +// Used to correct and extend PVs for moves that have a TB (but not a mate) score. +// Keeps the search based PV for as long as it is verified to maintain the game +// outcome, truncates afterwards. Finally, extends to mate the PV, providing a +// possible continuation (but not a proven mating line). +void syzygy_extend_pv(const OptionsMap& options, + const Search::LimitsType& limits, + Position& pos, + RootMove& rootMove, + Value& v) { + + auto t_start = std::chrono::steady_clock::now(); + int moveOverhead = int(options["Move Overhead"]); + bool rule50 = bool(options["Syzygy50MoveRule"]); + + // Do not use more than moveOverhead / 2 time, if time management is active + auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool { + auto t_end = std::chrono::steady_clock::now(); + return limits.use_time_management() + && 2 * std::chrono::duration(t_end - t_start).count() + > moveOverhead; + }; + + std::list sts; + + // Step 0, do the rootMove, no correction allowed, as needed for MultiPV in TB. + auto& stRoot = sts.emplace_back(); + pos.do_move(rootMove.pv[0], stRoot); + int ply = 1; + + // Step 1, walk the PV to the last position in TB with correct decisive score + while (size_t(ply) < rootMove.pv.size()) + { + Move& pvMove = rootMove.pv[ply]; + + RootMoves legalMoves; + for (const auto& m : MoveList(pos)) + legalMoves.emplace_back(m); + + Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves); + RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); + + if (legalMoves[0].tbRank != rm.tbRank) + break; + + ply++; + + auto& st = sts.emplace_back(); + pos.do_move(pvMove, st); + + // Do not allow for repetitions or drawing moves along the PV in TB regime + if (config.rootInTB && ((rule50 && pos.is_draw(ply)) || pos.is_repetition(ply))) + { + pos.undo_move(pvMove); + ply--; + break; + } + + // Full PV shown will thus be validated and end in TB. + // If we cannot validate the full PV in time, we do not show it. + if (config.rootInTB && time_abort()) + break; + } + + // Resize the PV to the correct part + rootMove.pv.resize(ply); + + // Step 2, now extend the PV to mate, as if the user explored syzygy-tables.info + // using top ranked moves (minimal DTZ), which gives optimal mates only for simple + // endgames e.g. KRvK. + while (!(rule50 && pos.is_draw(0))) + { + if (time_abort()) + break; + + RootMoves legalMoves; + for (const auto& m : MoveList(pos)) + { + auto& rm = legalMoves.emplace_back(m); + StateInfo tmpSI; + pos.do_move(m, tmpSI); + // Give a score of each move to break DTZ ties restricting opponent mobility, + // but not giving the opponent a capture. + for (const auto& mOpp : MoveList(pos)) + rm.tbRank -= pos.capture(mOpp) ? 100 : 1; + pos.undo_move(m); + } + + // Mate found + if (legalMoves.size() == 0) + break; + + // Sort moves according to their above assigned rank. + // This will break ties for moves with equal DTZ in rank_root_moves. + std::stable_sort( + legalMoves.begin(), legalMoves.end(), + [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); + + // The winning side tries to minimize DTZ, the losing side maximizes it + Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true); + + // If DTZ is not available we might not find a mate, so we bail out + if (!config.rootInTB || config.cardinality > 0) + break; + + ply++; + + Move& pvMove = legalMoves[0].pv[0]; + rootMove.pv.push_back(pvMove); + auto& st = sts.emplace_back(); + pos.do_move(pvMove, st); + } + + // Finding a draw in this function is an exceptional case, that cannot happen when rule50 is false or + // during engine game play, since we have a winning score, and play correctly + // with TB support. However, it can be that a position is draw due to the 50 move + // rule if it has been been reached on the board with a non-optimal 50 move counter + // (e.g. 8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106 ) which TB with dtz counter rounding + // cannot always correctly rank. See also + // https://github.com/official-stockfish/Stockfish/issues/5175#issuecomment-2058893495 + // We adjust the score to match the found PV. Note that a TB loss score can be + // displayed if the engine did not find a drawing move yet, but eventually search + // will figure it out (e.g. 1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1 ) + if (pos.is_draw(0)) + v = VALUE_DRAW; + + // Undo the PV moves + for (auto it = rootMove.pv.rbegin(); it != rootMove.pv.rend(); ++it) + pos.undo_move(*it); + + // Inform if we couldn't get a full extension in time + if (time_abort()) + sync_cout + << "info string Syzygy based PV extension requires more time, increase Move Overhead as needed." + << sync_endl; +} + +void SearchManager::pv(Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) { + + const auto nodes = threads.nodes_searched(); + auto& rootMoves = worker.rootMoves; + auto& pos = worker.rootPos; + size_t pvIdx = worker.pvIdx; + size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); + uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); + + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; + + if (depth == 1 && !updated && i > 0) + continue; + + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; + + bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB; + v = tb ? rootMoves[i].tbScore : v; + + bool isExact = i != pvIdx || tb || !updated; // tablebase- and previous-scores are exact + + // Potentially correct and extend the PV, and in exceptional cases v + if (is_decisive(v) && std::abs(v) < VALUE_MATE_IN_MAX_PLY + && ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isExact)) + syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v); + + std::string pv; + for (Move m : rootMoves[i].pv) + pv += UCIEngine::move(m, pos.is_chess960()) + " "; + + // Remove last whitespace + if (!pv.empty()) + pv.pop_back(); + + auto wdl = worker.options["UCI_ShowWDL"] ? UCIEngine::wdl(v, pos) : ""; + auto bound = rootMoves[i].scoreLowerbound + ? "lowerbound" + : (rootMoves[i].scoreUpperbound ? "upperbound" : ""); + + InfoFull info; + + info.depth = d; + info.selDepth = rootMoves[i].selDepth; + info.multiPV = i + 1; + info.score = {v, pos}; + info.wdl = wdl; + + if (!isExact) + info.bound = bound; + + TimePoint time = std::max(TimePoint(1), tm.elapsed_time()); + info.timeMs = time; + info.nodes = nodes; + info.nps = nodes * 1000 / time; + info.tbHits = tbHits; + info.pv = pv; + info.hashfull = tt.hashfull(); + + updates.onUpdateFull(info); + } +} + +// Called in case we have no ponder move before exiting the search, +// for instance, in case we stop the search during a fail high at root. +// We try hard to have a ponder move to return to the GUI, +// otherwise in case of 'ponder on' we have nothing to think about. +bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) { + + StateInfo st; + + assert(pv.size() == 1); + if (pv[0] == Move::none()) + return false; + + pos.do_move(pv[0], st, &tt); + + auto [ttHit, ttData, ttWriter] = tt.probe(pos.key()); + if (ttHit) + { + if (MoveList(pos).contains(ttData.move)) + pv.push_back(ttData.move); + } + + pos.undo_move(pv[0]); + return pv.size() > 1; +} + + +} // namespace Stockfish diff --git a/stockfish/src/search.h b/stockfish/src/search.h new file mode 100644 index 0000000000000000000000000000000000000000..071773f81b6f0074e9d89481d084e0dc439cc774 --- /dev/null +++ b/stockfish/src/search.h @@ -0,0 +1,373 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SEARCH_H_INCLUDED +#define SEARCH_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "history.h" +#include "misc.h" +#include "nnue/network.h" +#include "nnue/nnue_accumulator.h" +#include "numa.h" +#include "position.h" +#include "score.h" +#include "syzygy/tbprobe.h" +#include "timeman.h" +#include "types.h" + +namespace Stockfish { + +// Different node types, used as a template parameter +enum NodeType { + NonPV, + PV, + Root +}; + +class TranspositionTable; +class ThreadPool; +class OptionsMap; + +namespace Search { + +// Stack struct keeps track of the information we need to remember from nodes +// shallower and deeper in the tree during the search. Each search thread has +// its own array of Stack objects, indexed by the current ply. +struct Stack { + Move* pv; + PieceToHistory* continuationHistory; + CorrectionHistory* continuationCorrectionHistory; + int ply; + Move currentMove; + Move excludedMove; + Value staticEval; + int statScore; + int moveCount; + bool inCheck; + bool ttPv; + bool ttHit; + int cutoffCnt; + int reduction; + bool isTTMove; +}; + + +// RootMove struct is used for moves at the root of the tree. For each root move +// we store a score and a PV (really a refutation in the case of moves which +// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. +struct RootMove { + + explicit RootMove(Move m) : + pv(1, m) {} + bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos); + bool operator==(const Move& m) const { return pv[0] == m; } + // Sort in descending order + bool operator<(const RootMove& m) const { + return m.score != score ? m.score < score : m.previousScore < previousScore; + } + + uint64_t effort = 0; + Value score = -VALUE_INFINITE; + Value previousScore = -VALUE_INFINITE; + Value averageScore = -VALUE_INFINITE; + Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE; + Value uciScore = -VALUE_INFINITE; + bool scoreLowerbound = false; + bool scoreUpperbound = false; + int selDepth = 0; + int tbRank = 0; + Value tbScore; + std::vector pv; +}; + +using RootMoves = std::vector; + + +// LimitsType struct stores information sent by the caller about the analysis required. +struct LimitsType { + + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { + time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); + movestogo = depth = mate = perft = infinite = 0; + nodes = 0; + ponderMode = false; + } + + bool use_time_management() const { return time[WHITE] || time[BLACK]; } + + std::vector searchmoves; + TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; + bool ponderMode; +}; + + +// The UCI stores the uci options, thread pool, and transposition table. +// This struct is used to easily forward data to the Search::Worker class. +struct SharedState { + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + const LazyNumaReplicated& nets) : + options(optionsMap), + threads(threadPool), + tt(transpositionTable), + networks(nets) {} + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const LazyNumaReplicated& networks; +}; + +class Worker; + +// Null Object Pattern, implement a common interface for the SearchManagers. +// A Null Object will be given to non-mainthread workers. +class ISearchManager { + public: + virtual ~ISearchManager() {} + virtual void check_time(Search::Worker&) = 0; +}; + +struct InfoShort { + int depth; + Score score; +}; + +struct InfoFull: InfoShort { + int selDepth; + size_t multiPV; + std::string_view wdl; + std::string_view bound; + size_t timeMs; + size_t nodes; + size_t nps; + size_t tbHits; + std::string_view pv; + int hashfull; +}; + +struct InfoIteration { + int depth; + std::string_view currmove; + size_t currmovenumber; +}; + +// Skill structure is used to implement strength limit. If we have a UCI_Elo, +// we convert it to an appropriate skill level, anchored to the Stash engine. +// This method is based on a fit of the Elo results for games played between +// Stockfish at various skill levels and various versions of the Stash engine. +// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately +// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 +struct Skill { + // Lowest and highest Elo ratings used in the skill level calculation + constexpr static int LowestElo = 1320; + constexpr static int HighestElo = 3190; + + Skill(int skill_level, int uci_elo) { + if (uci_elo) + { + double e = double(uci_elo - LowestElo) / (HighestElo - LowestElo); + level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0); + } + else + level = double(skill_level); + } + bool enabled() const { return level < 20.0; } + bool time_to_pick(Depth depth) const { return depth == 1 + int(level); } + Move pick_best(const RootMoves&, size_t multiPV); + + double level; + Move best = Move::none(); +}; + +// SearchManager manages the search from the main thread. It is responsible for +// keeping track of the time, and storing data strictly related to the main thread. +class SearchManager: public ISearchManager { + public: + using UpdateShort = std::function; + using UpdateFull = std::function; + using UpdateIter = std::function; + using UpdateBestmove = std::function; + + struct UpdateContext { + UpdateShort onUpdateNoMoves; + UpdateFull onUpdateFull; + UpdateIter onIter; + UpdateBestmove onBestmove; + }; + + + SearchManager(const UpdateContext& updateContext) : + updates(updateContext) {} + + void check_time(Search::Worker& worker) override; + + void pv(Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth); + + Stockfish::TimeManagement tm; + double originalTimeAdjust; + int callsCnt; + std::atomic_bool ponder; + + std::array iterValue; + double previousTimeReduction; + Value bestPreviousScore; + Value bestPreviousAverageScore; + bool stopOnPonderhit; + + size_t id; + + const UpdateContext& updates; +}; + +class NullSearchManager: public ISearchManager { + public: + void check_time(Search::Worker&) override {} +}; + + +// Search::Worker is the class that does the actual search. +// It is instantiated once per thread, and it is responsible for keeping track +// of the search history, and storing data required for the search. +class Worker { + public: + Worker(SharedState&, std::unique_ptr, size_t, NumaReplicatedAccessToken); + + // Called at instantiation to initialize reductions tables. + // Reset histories, usually before a new game. + void clear(); + + // Called when the program receives the UCI 'go' command. + // It searches from the root position and outputs the "bestmove". + void start_searching(); + + bool is_mainthread() const { return threadIdx == 0; } + + void ensure_network_replicated(); + + // Public because they need to be updatable by the stats + ButterflyHistory mainHistory; + LowPlyHistory lowPlyHistory; + + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; + + CorrectionHistory pawnCorrectionHistory; + CorrectionHistory minorPieceCorrectionHistory; + CorrectionHistory nonPawnCorrectionHistory; + CorrectionHistory continuationCorrectionHistory; + + private: + void iterative_deepening(); + + void do_move(Position& pos, const Move move, StateInfo& st); + void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck); + void do_null_move(Position& pos, StateInfo& st); + void undo_move(Position& pos, const Move move); + void undo_null_move(Position& pos); + + // This is the main search function, for both PV and non-PV nodes + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + + // Quiescence search function, which is called by the main search + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta); + + Depth reduction(bool i, Depth d, int mn, int delta) const; + + // Pointer to the search manager, only allowed to be called by the main thread + SearchManager* main_manager() const { + assert(threadIdx == 0); + return static_cast(manager.get()); + } + + TimePoint elapsed() const; + TimePoint elapsed_time() const; + + Value evaluate(const Position&); + + LimitsType limits; + + size_t pvIdx, pvLast; + std::atomic nodes, tbHits, bestMoveChanges; + int selDepth, nmpMinPly; + + Value optimism[COLOR_NB]; + + Position rootPos; + StateInfo rootState; + RootMoves rootMoves; + Depth rootDepth, completedDepth; + Value rootDelta; + + size_t threadIdx; + NumaReplicatedAccessToken numaAccessToken; + + // Reductions lookup table initialized at startup + std::array reductions; // [depth or moveNumber] + + // The main thread has a SearchManager, the others have a NullSearchManager + std::unique_ptr manager; + + Tablebases::Config tbConfig; + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const LazyNumaReplicated& networks; + + // Used by NNUE + Eval::NNUE::AccumulatorStack accumulatorStack; + Eval::NNUE::AccumulatorCaches refreshTable; + + friend class Stockfish::ThreadPool; + friend class SearchManager; +}; + +struct ConthistBonus { + int index; + int weight; +}; + + +} // namespace Search + +} // namespace Stockfish + +#endif // #ifndef SEARCH_H_INCLUDED diff --git a/stockfish/src/syzygy/tbprobe.cpp b/stockfish/src/syzygy/tbprobe.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cbf8dce5ea2313c860498c6148547e1bc429731d --- /dev/null +++ b/stockfish/src/syzygy/tbprobe.cpp @@ -0,0 +1,1765 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "tbprobe.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../bitboard.h" +#include "../misc.h" +#include "../movegen.h" +#include "../position.h" +#include "../search.h" +#include "../types.h" +#include "../ucioption.h" + +#ifndef _WIN32 + #include + #include + #include +#else + #define WIN32_LEAN_AND_MEAN + #ifndef NOMINMAX + #define NOMINMAX // Disable macros min() and max() + #endif + #include +#endif + +using namespace Stockfish::Tablebases; + +int Stockfish::Tablebases::MaxCardinality; + +namespace Stockfish { + +namespace { + +constexpr int TBPIECES = 7; // Max number of supported pieces +constexpr int MAX_DTZ = + 1 << 18; // Max DTZ supported times 2, large enough to deal with the syzygy TB limit. + +enum { + BigEndian, + LittleEndian +}; +enum TBType { + WDL, + DTZ +}; // Used as template parameter + +// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables +enum TBFlag { + STM = 1, + Mapped = 2, + WinPlies = 4, + LossPlies = 8, + Wide = 16, + SingleValue = 128 +}; + +inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } +inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } + +constexpr std::string_view PieceToChar = " PNBRQK pnbrqk"; + +int MapPawns[SQUARE_NB]; +int MapB1H1H7[SQUARE_NB]; +int MapA1D1D4[SQUARE_NB]; +int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] + +int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements +int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] +int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] + +// Comparison function to sort leading pawns in ascending MapPawns[] order +bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } +int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } + +constexpr Value WDL_to_value[] = {-VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW, + VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1}; + +template +inline void swap_endian(T& x) { + static_assert(std::is_unsigned_v, "Argument of swap_endian not unsigned"); + + uint8_t tmp, *c = (uint8_t*) &x; + for (int i = 0; i < Half; ++i) + tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; +} +template<> +inline void swap_endian(uint8_t&) {} + +template +T number(void* addr) { + T v; + + if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare) + std::memcpy(&v, addr, sizeof(T)); + else + v = *((T*) addr); + + if (LE != IsLittleEndian) + swap_endian(v); + return v; +} + +// DTZ tables don't store valid scores for moves that reset the rule50 counter +// like captures and pawn moves but we can easily recover the correct dtz of the +// previous move if we know the position's WDL score. +int dtz_before_zeroing(WDLScore wdl) { + return wdl == WDLWin ? 1 + : wdl == WDLCursedWin ? 101 + : wdl == WDLBlessedLoss ? -101 + : wdl == WDLLoss ? -1 + : 0; +} + +// Return the sign of a number (-1, 0, 1) +template +int sign_of(T val) { + return (T(0) < val) - (val < T(0)); +} + +// Numbers in little-endian used by sparseIndex[] to point into blockLength[] +struct SparseEntry { + char block[4]; // Number of block + char offset[2]; // Offset within the block +}; + +static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); + +using Sym = uint16_t; // Huffman symbol + +struct LR { + enum Side { + Left, + Right + }; + + uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 + // bits is the right-hand symbol. If the symbol has length 1, + // then the left-hand symbol is the stored value. + template + Sym get() { + return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] + : S == Right ? (lr[2] << 4) | (lr[1] >> 4) + : (assert(false), Sym(-1)); + } +}; + +static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes"); + +// Tablebases data layout is structured as following: +// +// TBFile: memory maps/unmaps the physical .rtbw and .rtbz files +// TBTable: one object for each file with corresponding indexing information +// TBTables: has ownership of TBTable objects, keeping a list and a hash + +// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are +// memory mapped for best performance. Files are mapped at first access: at init +// time only existence of the file is checked. +class TBFile: public std::ifstream { + + std::string fname; + + public: + // Look for and open the file among the Paths directories where the .rtbw + // and .rtbz files can be found. Multiple directories are separated by ";" + // on Windows and by ":" on Unix-based operating systems. + // + // Example: + // C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6 + static std::string Paths; + + TBFile(const std::string& f) { + +#ifndef _WIN32 + constexpr char SepChar = ':'; +#else + constexpr char SepChar = ';'; +#endif + std::stringstream ss(Paths); + std::string path; + + while (std::getline(ss, path, SepChar)) + { + fname = path + "/" + f; + std::ifstream::open(fname); + if (is_open()) + return; + } + } + + // Memory map the file and check it. + uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) { + if (is_open()) + close(); // Need to re-open to get native file descriptor + +#ifndef _WIN32 + struct stat statbuf; + int fd = ::open(fname.c_str(), O_RDONLY); + + if (fd == -1) + return *baseAddress = nullptr, nullptr; + + fstat(fd, &statbuf); + + if (statbuf.st_size % 64 != 16) + { + std::cerr << "Corrupt tablebase file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + *mapping = statbuf.st_size; + *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); + #if defined(MADV_RANDOM) + madvise(*baseAddress, statbuf.st_size, MADV_RANDOM); + #endif + ::close(fd); + + if (*baseAddress == MAP_FAILED) + { + std::cerr << "Could not mmap() " << fname << std::endl; + exit(EXIT_FAILURE); + } +#else + // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. + HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); + + if (fd == INVALID_HANDLE_VALUE) + return *baseAddress = nullptr, nullptr; + + DWORD size_high; + DWORD size_low = GetFileSize(fd, &size_high); + + if (size_low % 64 != 16) + { + std::cerr << "Corrupt tablebase file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr); + CloseHandle(fd); + + if (!mmap) + { + std::cerr << "CreateFileMapping() failed" << std::endl; + exit(EXIT_FAILURE); + } + + *mapping = uint64_t(mmap); + *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); + + if (!*baseAddress) + { + std::cerr << "MapViewOfFile() failed, name = " << fname + << ", error = " << GetLastError() << std::endl; + exit(EXIT_FAILURE); + } +#endif + uint8_t* data = (uint8_t*) *baseAddress; + + constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}}; + + if (memcmp(data, Magics[type == WDL], 4)) + { + std::cerr << "Corrupted table in file " << fname << std::endl; + unmap(*baseAddress, *mapping); + return *baseAddress = nullptr, nullptr; + } + + return data + 4; // Skip Magics's header + } + + static void unmap(void* baseAddress, uint64_t mapping) { + +#ifndef _WIN32 + munmap(baseAddress, mapping); +#else + UnmapViewOfFile(baseAddress); + CloseHandle((HANDLE) mapping); +#endif + } +}; + +std::string TBFile::Paths; + +// struct PairsData contains low-level indexing information to access TB data. +// There are 8, 4, or 2 PairsData records for each TBTable, according to the type +// of table and if positions have pawns or not. It is populated at first access. +struct PairsData { + uint8_t flags; // Table flags, see enum TBFlag + uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols + uint8_t minSymLen; // Minimum length in bits of the Huffman symbols + uint32_t blocksNum; // Number of blocks in the TB file + size_t sizeofBlock; // Block size in bytes + size_t span; // About every span values there is a SparseIndex[] entry + Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value + LR* btree; // btree[sym] stores the left and right symbols that expand sym + uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 + uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum + SparseEntry* sparseIndex; // Partial indices into blockLength[] + size_t sparseIndexSize; // Size of SparseIndex[] table + uint8_t* data; // Start of Huffman compressed data + std::vector + base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l + std::vector + symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 + Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups + uint64_t groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces + int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1) + uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) +}; + +// struct TBTable contains indexing information to access the corresponding TBFile. +// There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable +// is populated at init time but the nested PairsData records are populated at +// first access, when the corresponding file is memory mapped. +template +struct TBTable { + using Ret = std::conditional_t; + + static constexpr int Sides = Type == WDL ? 2 : 1; + + std::atomic_bool ready; + void* baseAddress; + uint8_t* map; + uint64_t mapping; + Key key; + Key key2; + int pieceCount; + bool hasPawns; + bool hasUniquePieces; + uint8_t pawnCount[2]; // [Lead color / other color] + PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] + + PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; } + + TBTable() : + ready(false), + baseAddress(nullptr) {} + explicit TBTable(const std::string& code); + explicit TBTable(const TBTable& wdl); + + ~TBTable() { + if (baseAddress) + TBFile::unmap(baseAddress, mapping); + } +}; + +template<> +TBTable::TBTable(const std::string& code) : + TBTable() { + + StateInfo st; + Position pos; + + key = pos.set(code, WHITE, &st).material_key(); + pieceCount = pos.count(); + hasPawns = pos.pieces(PAWN); + + hasUniquePieces = false; + for (Color c : {WHITE, BLACK}) + for (PieceType pt = PAWN; pt < KING; ++pt) + if (popcount(pos.pieces(c, pt)) == 1) + hasUniquePieces = true; + + // Set the leading color. In case both sides have pawns the leading color + // is the side with fewer pawns because this leads to better compression. + bool c = !pos.count(BLACK) + || (pos.count(WHITE) && pos.count(BLACK) >= pos.count(WHITE)); + + pawnCount[0] = pos.count(c ? WHITE : BLACK); + pawnCount[1] = pos.count(c ? BLACK : WHITE); + + key2 = pos.set(code, BLACK, &st).material_key(); +} + +template<> +TBTable::TBTable(const TBTable& wdl) : + TBTable() { + + // Use the corresponding WDL table to avoid recalculating all from scratch + key = wdl.key; + key2 = wdl.key2; + pieceCount = wdl.pieceCount; + hasPawns = wdl.hasPawns; + hasUniquePieces = wdl.hasUniquePieces; + pawnCount[0] = wdl.pawnCount[0]; + pawnCount[1] = wdl.pawnCount[1]; +} + +// class TBTables creates and keeps ownership of the TBTable objects, one for +// each TB file found. It supports a fast, hash-based, table lookup. Populated +// at init time, accessed at probe time. +class TBTables { + + struct Entry { + Key key; + TBTable* wdl; + TBTable* dtz; + + template + TBTable* get() const { + return (TBTable*) (Type == WDL ? (void*) wdl : (void*) dtz); + } + }; + + static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb + static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket + + Entry hashTable[Size + Overflow]; + + std::deque> wdlTable; + std::deque> dtzTable; + size_t foundDTZFiles = 0; + size_t foundWDLFiles = 0; + + void insert(Key key, TBTable* wdl, TBTable* dtz) { + uint32_t homeBucket = uint32_t(key) & (Size - 1); + Entry entry{key, wdl, dtz}; + + // Ensure last element is empty to avoid overflow when looking up + for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) + { + Key otherKey = hashTable[bucket].key; + if (otherKey == key || !hashTable[bucket].get()) + { + hashTable[bucket] = entry; + return; + } + + // Robin Hood hashing: If we've probed for longer than this element, + // insert here and search for a new spot for the other element instead. + uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1); + if (otherHomeBucket > homeBucket) + { + std::swap(entry, hashTable[bucket]); + key = otherKey; + homeBucket = otherHomeBucket; + } + } + std::cerr << "TB hash table size too low!" << std::endl; + exit(EXIT_FAILURE); + } + + public: + template + TBTable* get(Key key) { + for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry) + { + if (entry->key == key || !entry->get()) + return entry->get(); + } + } + + void clear() { + memset(hashTable, 0, sizeof(hashTable)); + wdlTable.clear(); + dtzTable.clear(); + foundDTZFiles = 0; + foundWDLFiles = 0; + } + + void info() const { + sync_cout << "info string Found " << foundWDLFiles << " WDL and " << foundDTZFiles + << " DTZ tablebase files (up to " << MaxCardinality << "-man)." << sync_endl; + } + + void add(const std::vector& pieces); +}; + +TBTables TBTables; + +// If the corresponding file exists two new objects TBTable and TBTable +// are created and added to the lists and hash table. Called at init time. +void TBTables::add(const std::vector& pieces) { + + std::string code; + + for (PieceType pt : pieces) + code += PieceToChar[pt]; + code.insert(code.find('K', 1), "v"); + + TBFile file_dtz(code + ".rtbz"); // KRK -> KRvK + if (file_dtz.is_open()) + { + file_dtz.close(); + foundDTZFiles++; + } + + TBFile file(code + ".rtbw"); // KRK -> KRvK + + if (!file.is_open()) // Only WDL file is checked + return; + + file.close(); + foundWDLFiles++; + + MaxCardinality = std::max(int(pieces.size()), MaxCardinality); + + wdlTable.emplace_back(code); + dtzTable.emplace_back(wdlTable.back()); + + // Insert into the hash keys for both colors: KRvK with KR white and black + insert(wdlTable.back().key, &wdlTable.back(), &dtzTable.back()); + insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); +} + +// TB tables are compressed with canonical Huffman code. The compressed data is divided into +// blocks of size d->sizeofBlock, and each block stores a variable number of symbols. +// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols +// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536 +// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after +// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most +// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly +// of draws or mostly of wins, but such tables are actually quite common. In principle, the +// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for +// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so +// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. +// The generator picks the size that leads to the smallest table. The "book" of symbols and +// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file +// will have one table for wtm and one for btm, a TB file with pawns will have tables per +// file a,b,c,d also, in this case, one set for wtm and one for btm. +int decompress_pairs(PairsData* d, uint64_t idx) { + + // Special case where all table positions store the same value + if (d->flags & TBFlag::SingleValue) + return d->minSymLen; + + // First we need to locate the right block that stores the value at index "idx". + // Because each block n stores blockLength[n] + 1 values, the index i of the block + // that contains the value at position idx is: + // + // for (i = -1, sum = 0; sum <= idx; i++) + // sum += blockLength[i + 1] + 1; + // + // This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that + // point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry + // that stores the blockLength[] index and the offset within that block of the value + // with index I(k), where: + // + // I(k) = k * d->span + d->span / 2 (1) + + // First step is to get the 'k' of the I(k) nearest to our idx, using definition (1) + uint32_t k = uint32_t(idx / d->span); + + // Then we read the corresponding SparseIndex[] entry + uint32_t block = number(&d->sparseIndex[k].block); + int offset = number(&d->sparseIndex[k].offset); + + // Now compute the difference idx - I(k). From the definition of k, we know that + // + // idx = k * d->span + idx % d->span (2) + // + // So from (1) and (2) we can compute idx - I(K): + int diff = idx % d->span - d->span / 2; + + // Sum the above to offset to find the offset corresponding to our idx + offset += diff; + + // Move to the previous/next block, until we reach the correct block that contains idx, + // that is when 0 <= offset <= d->blockLength[block] + while (offset < 0) + offset += d->blockLength[--block] + 1; + + while (offset > d->blockLength[block]) + offset -= d->blockLength[block++] + 1; + + // Finally, we find the start address of our block of canonical Huffman symbols + uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock)); + + // Read the first 64 bits in our block, this is a (truncated) sequence of + // unknown number of symbols of unknown length but we know the first one + // is at the beginning of this 64-bit sequence. + uint64_t buf64 = number(ptr); + ptr += 2; + int buf64Size = 64; + Sym sym; + + while (true) + { + int len = 0; // This is the symbol length - d->min_sym_len + + // Now get the symbol length. For any symbol s64 of length l right-padded + // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we + // can find the symbol length iterating through base64[]. + while (buf64 < d->base64[len]) + ++len; + + // All the symbols of a given length are consecutive integers (numerical + // sequence property), so we can compute the offset of our symbol of + // length len, stored at the beginning of buf64. + sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen)); + + // Now add the value of the lowest symbol of length len to get our symbol + sym += number(&d->lowestSym[len]); + + // If our offset is within the number of values represented by symbol sym, + // we are done. + if (offset < d->symlen[sym] + 1) + break; + + // ...otherwise update the offset and continue to iterate + offset -= d->symlen[sym] + 1; + len += d->minSymLen; // Get the real length + buf64 <<= len; // Consume the just processed symbol + buf64Size -= len; + + if (buf64Size <= 32) + { // Refill the buffer + buf64Size += 32; + buf64 |= uint64_t(number(ptr++)) << (64 - buf64Size); + } + } + + // Now we have our symbol that expands into d->symlen[sym] + 1 symbols. + // We binary-search for our value recursively expanding into the left and + // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 + // that will store the value we need. + while (d->symlen[sym]) + { + Sym left = d->btree[sym].get(); + + // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and + // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then + // we know that, for instance, the tenth value (offset = 10) will be on + // the left side because in Recursive Pairing child symbols are adjacent. + if (offset < d->symlen[left] + 1) + sym = left; + else + { + offset -= d->symlen[left] + 1; + sym = d->btree[sym].get(); + } + } + + return d->btree[sym].get(); +} + +bool check_dtz_stm(TBTable*, int, File) { return true; } + +bool check_dtz_stm(TBTable* entry, int stm, File f) { + + auto flags = entry->get(stm, f)->flags; + return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns); +} + +// DTZ scores are sorted by frequency of occurrence and then assigned the +// values 0, 1, 2, ... in order of decreasing frequency. This is done for each +// of the four WDLScore values. The mapping information necessary to reconstruct +// the original values are stored in the TB file and read during map[] init. +WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(value - 2); } + +int map_score(TBTable* entry, File f, int value, WDLScore wdl) { + + constexpr int WDLMap[] = {1, 3, 0, 2, 0}; + + auto flags = entry->get(0, f)->flags; + + uint8_t* map = entry->map; + uint16_t* idx = entry->get(0, f)->map_idx; + if (flags & TBFlag::Mapped) + { + if (flags & TBFlag::Wide) + value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value]; + else + value = map[idx[WDLMap[wdl + 2]] + value]; + } + + // DTZ tables store distance to zero in number of moves or plies. We + // want to return plies, so we have to convert to plies when needed. + if ((wdl == WDLWin && !(flags & TBFlag::WinPlies)) + || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin + || wdl == WDLBlessedLoss) + value *= 2; + + return value + 1; +} + +// A temporary fix for the compiler bug with AVX-512. (#4450) +#ifdef USE_AVX512 + #if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15 + #define CLANG_AVX512_BUG_FIX __attribute__((optnone)) + #endif +#endif + +#ifndef CLANG_AVX512_BUG_FIX + #define CLANG_AVX512_BUG_FIX +#endif + +// Compute a unique index out of a position and use it to probe the TB file. To +// encode k pieces of the same type and color, first sort the pieces by square in +// ascending order s1 <= s2 <= ... <= sk then compute the unique index as: +// +// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] +// +template +CLANG_AVX512_BUG_FIX Ret +do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { + + Square squares[TBPIECES]; + Piece pieces[TBPIECES]; + uint64_t idx; + int next = 0, size = 0, leadPawnsCnt = 0; + PairsData* d; + Bitboard b, leadPawns = 0; + File tbFile = FILE_A; + + // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. + // If both sides have the same pieces keys are equal. In this case TB tables + // only stores the 'white to move' case, so if the position to lookup has black + // to move, we need to switch the color and flip the squares before to lookup. + bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); + + // TB files are calculated for white as the stronger side. For instance, we + // have KRvK, not KvKR. A position where the stronger side is white will have + // its material key == entry->key, otherwise we have to switch the color and + // flip the squares before to lookup. + bool blackStronger = (pos.material_key() != entry->key); + + int flipColor = (symmetricBlackToMove || blackStronger) * 8; + int flipSquares = (symmetricBlackToMove || blackStronger) * 56; + int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move(); + + // For pawns, TB files store 4 separate tables according if leading pawn is on + // file a, b, c or d after reordering. The leading pawn is the one with maximum + // MapPawns[] value, that is the one most toward the edges and with lowest rank. + if (entry->hasPawns) + { + + // In all the 4 tables, pawns are at the beginning of the piece sequence and + // their color is the reference one. So we just pick the first one. + Piece pc = Piece(entry->get(0, 0)->pieces[0] ^ flipColor); + + assert(type_of(pc) == PAWN); + + leadPawns = b = pos.pieces(color_of(pc), PAWN); + do + squares[size++] = pop_lsb(b) ^ flipSquares; + while (b); + + leadPawnsCnt = size; + + std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp)); + + tbFile = File(edge_distance(file_of(squares[0]))); + } + + // DTZ tables are one-sided, i.e. they store positions only for white to + // move or only for black to move, so check for side to move to be stm, + // early exit otherwise. + if (!check_dtz_stm(entry, stm, tbFile)) + return *result = CHANGE_STM, Ret(); + + // Now we are ready to get all the position pieces (but the lead pawns) and + // directly map them to the correct color and square. + b = pos.pieces() ^ leadPawns; + do + { + Square s = pop_lsb(b); + squares[size] = s ^ flipSquares; + pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); + } while (b); + + assert(size >= 2); + + d = entry->get(stm, tbFile); + + // Then we reorder the pieces to have the same sequence as the one stored + // in pieces[i]: the sequence that ensures the best compression. + for (int i = leadPawnsCnt; i < size - 1; ++i) + for (int j = i + 1; j < size; ++j) + if (d->pieces[i] == pieces[j]) + { + std::swap(pieces[i], pieces[j]); + std::swap(squares[i], squares[j]); + break; + } + + // Now we map again the squares so that the square of the lead piece is in + // the triangle A1-D1-D4. + if (file_of(squares[0]) > FILE_D) + for (int i = 0; i < size; ++i) + squares[i] = flip_file(squares[i]); + + // Encode leading pawns starting with the one with minimum MapPawns[] and + // proceeding in ascending order. + if (entry->hasPawns) + { + idx = LeadPawnIdx[leadPawnsCnt][squares[0]]; + + std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp); + + for (int i = 1; i < leadPawnsCnt; ++i) + idx += Binomial[i][MapPawns[squares[i]]]; + + goto encode_remaining; // With pawns we have finished special treatments + } + + // In positions without pawns, we further flip the squares to ensure leading + // piece is below RANK_5. + if (rank_of(squares[0]) > RANK_4) + for (int i = 0; i < size; ++i) + squares[i] = flip_rank(squares[i]); + + // Look for the first piece of the leading group not on the A1-D4 diagonal + // and ensure it is mapped below the diagonal. + for (int i = 0; i < d->groupLen[0]; ++i) + { + if (!off_A1H8(squares[i])) + continue; + + if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 + for (int j = i; j < size; ++j) + squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); + break; + } + + // Encode the leading group. + // + // Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR + // and bK (each 0...63). The simplest way to map this position to an index + // is like this: + // + // index = wK * 64 * 64 + wR * 64 + bK; + // + // But this way the TB is going to have 64*64*64 = 262144 positions, with + // lots of positions being equivalent (because they are mirrors of each + // other) and lots of positions being invalid (two pieces on one square, + // adjacent kings, etc.). + // Usually the first step is to take the wK and bK together. There are just + // 462 ways legal and not-mirrored ways to place the wK and bK on the board. + // Once we have placed the wK and bK, there are 62 squares left for the wR + // Mapping its square from 0..63 to available squares 0..61 can be done like: + // + // wR -= (wR > wK) + (wR > bK); + // + // In words: if wR "comes later" than wK, we deduct 1, and the same if wR + // "comes later" than bK. In case of two same pieces like KRRvK we want to + // place the two Rs "together". If we have 62 squares left, we can place two + // Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be + // swapped and still get the same position.) + // + // In case we have at least 3 unique pieces (including kings) we encode them + // together. + if (entry->hasUniquePieces) + { + + int adjust1 = squares[1] > squares[0]; + int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); + + // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 + // triangle to 0...5. There are 63 squares for second piece and 62 + // (mapped to 0...61) for the third. + if (off_A1H8(squares[0])) + idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2; + + // First piece is on a1-h8 diagonal, second below: map this occurrence to + // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal + // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27. + else if (off_A1H8(squares[1])) + idx = (6 * 63 + rank_of(squares[0]) * 28 + MapB1H1H7[squares[1]]) * 62 + squares[2] + - adjust2; + + // First two pieces are on a1-h8 diagonal, third below + else if (off_A1H8(squares[2])) + idx = 6 * 63 * 62 + 4 * 28 * 62 + rank_of(squares[0]) * 7 * 28 + + (rank_of(squares[1]) - adjust1) * 28 + MapB1H1H7[squares[2]]; + + // All 3 pieces on the diagonal a1-h8 + else + idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + rank_of(squares[0]) * 7 * 6 + + (rank_of(squares[1]) - adjust1) * 6 + (rank_of(squares[2]) - adjust2); + } + else + // We don't have at least 3 unique pieces, like in KRRvKBB, just map + // the kings. + idx = MapKK[MapA1D1D4[squares[0]]][squares[1]]; + +encode_remaining: + idx *= d->groupIdx[0]; + Square* groupSq = squares + d->groupLen[0]; + + // Encode remaining pawns and then pieces according to square, in ascending order + bool remainingPawns = entry->hasPawns && entry->pawnCount[1]; + + while (d->groupLen[++next]) + { + std::stable_sort(groupSq, groupSq + d->groupLen[next]); + uint64_t n = 0; + + // Map down a square if "comes later" than a square in the previous + // groups (similar to what was done earlier for leading group pieces). + for (int i = 0; i < d->groupLen[next]; ++i) + { + auto f = [&](Square s) { return groupSq[i] > s; }; + auto adjust = std::count_if(squares, groupSq, f); + n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns]; + } + + remainingPawns = false; + idx += n * d->groupIdx[next]; + groupSq += d->groupLen[next]; + } + + // Now that we have the index, decompress the pair and get the score + return map_score(entry, tbFile, decompress_pairs(d, idx), wdl); +} + +// Group together pieces that will be encoded together. The general rule is that +// a group contains pieces of the same type and color. The exception is the leading +// group that, in case of positions without pawns, can be formed by 3 different +// pieces (default) or by the king pair when there is not a unique piece apart +// from the kings. When there are pawns, pawns are always first in pieces[]. +// +// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K +// +// The actual grouping depends on the TB generator and can be inferred from the +// sequence of pieces in piece[] array. +template +void set_groups(T& e, PairsData* d, int order[], File f) { + + int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2; + d->groupLen[n] = 1; + + // Number of pieces per group is stored in groupLen[], for instance in KRKN + // the encoder will default on '111', so groupLen[] will be (3, 1). + for (int i = 1; i < e.pieceCount; ++i) + if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1]) + d->groupLen[n]++; + else + d->groupLen[++n] = 1; + + d->groupLen[++n] = 0; // Zero-terminated + + // The sequence in pieces[] defines the groups, but not the order in which + // they are encoded. If the pieces in a group g can be combined on the board + // in N(g) different ways, then the position encoding will be of the form: + // + // g1 * N(g2) * N(g3) + g2 * N(g3) + g3 + // + // This ensures unique encoding for the whole position. The order of the + // groups is a per-table parameter and could not follow the canonical leading + // pawns/pieces -> remaining pawns -> remaining pieces. In particular the + // first group is at order[0] position and the remaining pawns, when present, + // are at order[1] position. + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + int next = pp ? 2 : 1; + int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); + uint64_t idx = 1; + + for (int k = 0; next < n || k == order[0] || k == order[1]; ++k) + if (k == order[0]) // Leading pawns or pieces + { + d->groupIdx[0] = idx; + idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462; + } + else if (k == order[1]) // Remaining pawns + { + d->groupIdx[1] = idx; + idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]]; + } + else // Remaining pieces + { + d->groupIdx[next] = idx; + idx *= Binomial[d->groupLen[next]][freeSquares]; + freeSquares -= d->groupLen[next++]; + } + + d->groupIdx[n] = idx; +} + +// In Recursive Pairing each symbol represents a pair of children symbols. So +// read d->btree[] symbols data and expand each one in his left and right child +// symbol until reaching the leaves that represent the symbol value. +uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { + + visited[s] = true; // We can set it now because tree is acyclic + Sym sr = d->btree[s].get(); + + if (sr == 0xFFF) + return 0; + + Sym sl = d->btree[s].get(); + + if (!visited[sl]) + d->symlen[sl] = set_symlen(d, sl, visited); + + if (!visited[sr]) + d->symlen[sr] = set_symlen(d, sr, visited); + + return d->symlen[sl] + d->symlen[sr] + 1; +} + +uint8_t* set_sizes(PairsData* d, uint8_t* data) { + + d->flags = *data++; + + if (d->flags & TBFlag::SingleValue) + { + d->blocksNum = d->blockLengthSize = 0; + d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init + d->minSymLen = *data++; // Here we store the single value + return data; + } + + // groupLen[] is a zero-terminated list of group lengths, the last groupIdx[] + // element stores the biggest index that is the tb size. + uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; + + d->sizeofBlock = 1ULL << *data++; + d->span = 1ULL << *data++; + d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up + auto padding = number(data++); + d->blocksNum = number(data); + data += sizeof(uint32_t); + d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] + // does not point out of range. + d->maxSymLen = *data++; + d->minSymLen = *data++; + d->lowestSym = (Sym*) data; + d->base64.resize(d->maxSymLen - d->minSymLen + 1); + + // See https://en.wikipedia.org/wiki/Huffman_coding + // The canonical code is ordered such that longer symbols (in terms of + // the number of bits of their Huffman code) have a lower numeric value, + // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). + // Starting from this we compute a base64[] table indexed by symbol length + // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. + + // Implementation note: we first cast the unsigned size_t "base64.size()" + // to a signed int "base64_size" variable and then we are able to subtract 2, + // avoiding unsigned overflow warnings. + + int base64_size = static_cast(d->base64.size()); + for (int i = base64_size - 2; i >= 0; --i) + { + d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) + - number(&d->lowestSym[i + 1])) + / 2; + + assert(d->base64[i] * 2 >= d->base64[i + 1]); + } + + // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more + // than d->base64[i+1] and given the above assert condition, we ensure that + // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i + // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. + for (int i = 0; i < base64_size; ++i) + d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits + + data += base64_size * sizeof(Sym); + d->symlen.resize(number(data)); + data += sizeof(uint16_t); + d->btree = (LR*) data; + + // The compression scheme used is "Recursive Pairing", that replaces the most + // frequent adjacent pair of symbols in the source message by a new symbol, + // reevaluating the frequencies of all of the symbol pairs with respect to + // the extended alphabet, and then repeating the process. + // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf + std::vector visited(d->symlen.size()); + + for (std::size_t sym = 0; sym < d->symlen.size(); ++sym) + if (!visited[sym]) + d->symlen[sym] = set_symlen(d, sym, visited); + + return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1); +} + +uint8_t* set_dtz_map(TBTable&, uint8_t* data, File) { return data; } + +uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { + + e.map = data; + + for (File f = FILE_A; f <= maxFile; ++f) + { + auto flags = e.get(0, f)->flags; + if (flags & TBFlag::Mapped) + { + if (flags & TBFlag::Wide) + { + data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table + for (int i = 0; i < 4; ++i) + { // Sequence like 3,x,x,x,1,x,0,2,x,x + e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1); + data += 2 * number(data) + 2; + } + } + else + { + for (int i = 0; i < 4; ++i) + { + e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1); + data += *data + 1; + } + } + } + } + + return data += uintptr_t(data) & 1; // Word alignment +} + +// Populate entry's PairsData records with data from the just memory-mapped file. +// Called at first access. +template +void set(T& e, uint8_t* data) { + + PairsData* d; + + enum { + Split = 1, + HasPawns = 2 + }; + + assert(e.hasPawns == bool(*data & HasPawns)); + assert((e.key != e.key2) == bool(*data & Split)); + + data++; // First byte stores flags + + const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; + const File maxFile = e.hasPawns ? FILE_D : FILE_A; + + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + + assert(!pp || e.pawnCount[0]); + + for (File f = FILE_A; f <= maxFile; ++f) + { + + for (int i = 0; i < sides; i++) + *e.get(i, f) = PairsData(); + + int order[][2] = {{*data & 0xF, pp ? *(data + 1) & 0xF : 0xF}, + {*data >> 4, pp ? *(data + 1) >> 4 : 0xF}}; + data += 1 + pp; + + for (int k = 0; k < e.pieceCount; ++k, ++data) + for (int i = 0; i < sides; i++) + e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); + + for (int i = 0; i < sides; ++i) + set_groups(e, e.get(i, f), order[i], f); + } + + data += uintptr_t(data) & 1; // Word alignment + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) + data = set_sizes(e.get(i, f), data); + + data = set_dtz_map(e, data, maxFile); + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) + { + (d = e.get(i, f))->sparseIndex = (SparseEntry*) data; + data += d->sparseIndexSize * sizeof(SparseEntry); + } + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) + { + (d = e.get(i, f))->blockLength = (uint16_t*) data; + data += d->blockLengthSize * sizeof(uint16_t); + } + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) + { + data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment + (d = e.get(i, f))->data = data; + data += d->blocksNum * d->sizeofBlock; + } +} + +// If the TB file corresponding to the given position is already memory-mapped +// then return its base address, otherwise, try to memory map and init it. Called +// at every probe, memory map, and init only at first access. Function is thread +// safe and can be called concurrently. +template +void* mapped(TBTable& e, const Position& pos) { + + static std::mutex mutex; + + // Use 'acquire' to avoid a thread reading 'ready' == true while + // another is still working. (compiler reordering may cause this). + if (e.ready.load(std::memory_order_acquire)) + return e.baseAddress; // Could be nullptr if file does not exist + + std::scoped_lock lk(mutex); + + if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock + return e.baseAddress; + + // Pieces strings in decreasing order for each color, like ("KPP","KR") + std::string fname, w, b; + for (PieceType pt = KING; pt >= PAWN; --pt) + { + w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); + b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); + } + + fname = + (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz"); + + uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type); + + if (data) + set(e, data); + + e.ready.store(true, std::memory_order_release); + return e.baseAddress; +} + +template::Ret> +Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { + + if (pos.count() == 2) // KvK + return Ret(WDLDraw); + + TBTable* entry = TBTables.get(pos.material_key()); + + if (!entry || !mapped(*entry, pos)) + return *result = FAIL, Ret(); + + return do_probe_table(pos, entry, wdl, result); +} + +// For a position where the side to move has a winning capture it is not necessary +// to store a winning value so the generator treats such positions as "don't care" +// and tries to assign to it a value that improves the compression ratio. Similarly, +// if the side to move has a drawing capture, then the position is at least drawn. +// If the position is won, then the TB needs to store a win value. But if the +// position is drawn, the TB may store a loss value if that is better for compression. +// All of this means that during probing, the engine must look at captures and probe +// their results and must probe the position itself. The "best" result of these +// probes is the correct result for the position. +// DTZ tables do not store values when a following move is a zeroing winning move +// (winning capture or winning pawn move). Also, DTZ store wrong values for positions +// where the best move is an ep-move (even if losing). So in all these cases set +// the state to ZEROING_BEST_MOVE. +template +WDLScore search(Position& pos, ProbeState* result) { + + WDLScore value, bestValue = WDLLoss; + StateInfo st; + + auto moveList = MoveList(pos); + size_t totalCount = moveList.size(), moveCount = 0; + + for (const Move move : moveList) + { + if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) + continue; + + moveCount++; + + pos.do_move(move, st); + value = -search(pos, result); + pos.undo_move(move); + + if (*result == FAIL) + return WDLDraw; + + if (value > bestValue) + { + bestValue = value; + + if (value >= WDLWin) + { + *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move + return value; + } + } + } + + // In case we have already searched all the legal moves we don't have to probe + // the TB because the stored score could be wrong. For instance TB tables + // do not contain information on position with ep rights, so in this case + // the result of probe_wdl_table is wrong. Also in case of only capture + // moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to + // return with ZEROING_BEST_MOVE set. + bool noMoreMoves = (moveCount && moveCount == totalCount); + + if (noMoreMoves) + value = bestValue; + else + { + value = probe_table(pos, result); + + if (*result == FAIL) + return WDLDraw; + } + + // DTZ stores a "don't care" value if bestValue is a win + if (bestValue >= value) + return *result = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; + + return *result = OK, value; +} + +} // namespace + + +// Called at startup and after every change to +// "SyzygyPath" UCI option to (re)create the various tables. It is not thread +// safe, nor it needs to be. +void Tablebases::init(const std::string& paths) { + + TBTables.clear(); + MaxCardinality = 0; + TBFile::Paths = paths; + + if (paths.empty()) + return; + + // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27 + int code = 0; + for (Square s = SQ_A1; s <= SQ_H8; ++s) + if (off_A1H8(s) < 0) + MapB1H1H7[s] = code++; + + // MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9 + std::vector diagonal; + code = 0; + for (Square s = SQ_A1; s <= SQ_D4; ++s) + if (off_A1H8(s) < 0 && file_of(s) <= FILE_D) + MapA1D1D4[s] = code++; + + else if (!off_A1H8(s) && file_of(s) <= FILE_D) + diagonal.push_back(s); + + // Diagonal squares are encoded as last ones + for (auto s : diagonal) + MapA1D1D4[s] = code++; + + // MapKK[] encodes all the 462 possible legal positions of two kings where + // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 + // diagonal, the other one shall not be above the a1-h8 diagonal. + std::vector> bothOnDiagonal; + code = 0; + for (int idx = 0; idx < 10; idx++) + for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1) + if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 + { + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + if ((PseudoAttacks[KING][s1] | s1) & s2) + continue; // Illegal position + + else if (!off_A1H8(s1) && off_A1H8(s2) > 0) + continue; // First on diagonal, second above + + else if (!off_A1H8(s1) && !off_A1H8(s2)) + bothOnDiagonal.emplace_back(idx, s2); + + else + MapKK[idx][s2] = code++; + } + + // Legal positions with both kings on a diagonal are encoded as last ones + for (auto p : bothOnDiagonal) + MapKK[p.first][p.second] = code++; + + // Binomial[] stores the Binomial Coefficients using Pascal rule. There + // are Binomial[k][n] ways to choose k elements from a set of n elements. + Binomial[0][0] = 1; + + for (int n = 1; n < 64; n++) // Squares + for (int k = 0; k < 6 && k <= n; ++k) // Pieces + Binomial[k][n] = + (k > 0 ? Binomial[k - 1][n - 1] : 0) + (k < n ? Binomial[k][n - 1] : 0); + + // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible + // available squares when the leading one is in 's'. Moreover the pawn with + // highest MapPawns[] is the leading pawn, the one nearest the edge, and + // among pawns with the same file, the one with the lowest rank. + int availableSquares = 47; // Available squares when lead pawn is in a2 + + // Init the tables for the encoding of leading pawns group: with 7-men TB we + // can have up to 5 leading pawns (KPPPPPK). + for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt) + for (File f = FILE_A; f <= FILE_D; ++f) + { + // Restart the index at every file because TB table is split + // by file, so we can reuse the same index for different files. + int idx = 0; + + // Sum all possible combinations for a given file, starting with + // the leading pawn on rank 2 and increasing the rank. + for (Rank r = RANK_2; r <= RANK_7; ++r) + { + Square sq = make_square(f, r); + + // Compute MapPawns[] at first pass. + // If sq is the leading pawn square, any other pawn cannot be + // below or more toward the edge of sq. There are 47 available + // squares when sq = a2 and reduced by 2 for any rank increase + // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45 + if (leadPawnsCnt == 1) + { + MapPawns[sq] = availableSquares--; + MapPawns[flip_file(sq)] = availableSquares--; + } + LeadPawnIdx[leadPawnsCnt][sq] = idx; + idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]]; + } + // After a file is traversed, store the cumulated per-file index + LeadPawnsSize[leadPawnsCnt][f] = idx; + } + + // Add entries in TB tables if the corresponding ".rtbw" file exists + for (PieceType p1 = PAWN; p1 < KING; ++p1) + { + TBTables.add({KING, p1, KING}); + + for (PieceType p2 = PAWN; p2 <= p1; ++p2) + { + TBTables.add({KING, p1, p2, KING}); + TBTables.add({KING, p1, KING, p2}); + + for (PieceType p3 = PAWN; p3 < KING; ++p3) + TBTables.add({KING, p1, p2, KING, p3}); + + for (PieceType p3 = PAWN; p3 <= p2; ++p3) + { + TBTables.add({KING, p1, p2, p3, KING}); + + for (PieceType p4 = PAWN; p4 <= p3; ++p4) + { + TBTables.add({KING, p1, p2, p3, p4, KING}); + + for (PieceType p5 = PAWN; p5 <= p4; ++p5) + TBTables.add({KING, p1, p2, p3, p4, p5, KING}); + + for (PieceType p5 = PAWN; p5 < KING; ++p5) + TBTables.add({KING, p1, p2, p3, p4, KING, p5}); + } + + for (PieceType p4 = PAWN; p4 < KING; ++p4) + { + TBTables.add({KING, p1, p2, p3, KING, p4}); + + for (PieceType p5 = PAWN; p5 <= p4; ++p5) + TBTables.add({KING, p1, p2, p3, KING, p4, p5}); + } + } + + for (PieceType p3 = PAWN; p3 <= p1; ++p3) + for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4) + TBTables.add({KING, p1, p2, KING, p3, p4}); + } + } + + TBTables.info(); +} + +// Probe the WDL table for a particular position. +// If *result != FAIL, the probe was successful. +// The return value is from the point of view of the side to move: +// -2 : loss +// -1 : loss, but draw under 50-move rule +// 0 : draw +// 1 : win, but draw under 50-move rule +// 2 : win +WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { + + *result = OK; + return search(pos, result); +} + +// Probe the DTZ table for a particular position. +// If *result != FAIL, the probe was successful. +// The return value is from the point of view of the side to move: +// n < -100 : loss, but draw under 50-move rule +// -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0) +// -1 : loss, the side to move is mated +// 0 : draw +// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0) +// 100 < n : win, but draw under 50-move rule +// +// The return value n can be off by 1: a return value -n can mean a loss +// in n+1 ply and a return value +n can mean a win in n+1 ply. This +// cannot happen for tables with positions exactly on the "edge" of +// the 50-move rule. +// +// This implies that if dtz > 0 is returned, the position is certainly +// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine +// picks moves that preserve dtz + 50-move-counter <= 99. +// +// If n = 100 immediately after a capture or pawn move, then the position +// is also certainly a win, and during the whole phase until the next +// capture or pawn move, the inequality to be preserved is +// dtz + 50-move-counter <= 100. +// +// In short, if a move is available resulting in dtz + 50-move-counter <= 99, +// then do not accept moves leading to dtz + 50-move-counter == 100. +int Tablebases::probe_dtz(Position& pos, ProbeState* result) { + + *result = OK; + WDLScore wdl = search(pos, result); + + if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws + return 0; + + // DTZ stores a 'don't care value in this case, or even a plain wrong + // one as in case the best move is a losing ep, so it cannot be probed. + if (*result == ZEROING_BEST_MOVE) + return dtz_before_zeroing(wdl); + + int dtz = probe_table(pos, result, wdl); + + if (*result == FAIL) + return 0; + + if (*result != CHANGE_STM) + return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl); + + // DTZ stores results for the other side, so we need to do a 1-ply search and + // find the winning move that minimizes DTZ. + StateInfo st; + int minDTZ = 0xFFFF; + + for (const Move move : MoveList(pos)) + { + bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; + + pos.do_move(move, st); + + // For zeroing moves we want the dtz of the move _before_ doing it, + // otherwise we will get the dtz of the next move sequence. Search the + // position after the move to get the score sign (because even in a + // winning position we could make a losing capture or go for a draw). + dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) : -probe_dtz(pos, result); + + // If the move mates, force minDTZ to 1 + if (dtz == 1 && pos.checkers() && MoveList(pos).size() == 0) + minDTZ = 1; + + // Convert result from 1-ply search. Zeroing moves are already accounted + // by dtz_before_zeroing() that returns the DTZ of the previous move. + if (!zeroing) + dtz += sign_of(dtz); + + // Skip the draws and if we are winning only pick positive dtz + if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl)) + minDTZ = dtz; + + pos.undo_move(move); + + if (*result == FAIL) + return 0; + } + + // When there are no legal moves, the position is mate: we return -1 + return minDTZ == 0xFFFF ? -1 : minDTZ; +} + + +// Use the DTZ tables to rank root moves. +// +// A return value false indicates that not all probes were successful. +bool Tablebases::root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ) { + + ProbeState result = OK; + StateInfo st; + + // Obtain 50-move counter for the root position + int cnt50 = pos.rule50_count(); + + // Check whether a position was repeated since the last zeroing move. + bool rep = pos.has_repeated(); + + int dtz, bound = rule50 ? (MAX_DTZ / 2 - 100) : 1; + + // Probe and rank each move + for (auto& m : rootMoves) + { + pos.do_move(m.pv[0], st); + + // Calculate dtz for the current move counting from the root position + if (pos.rule50_count() == 0) + { + // In case of a zeroing move, dtz is one of -101/-1/0/1/101 + WDLScore wdl = -probe_wdl(pos, &result); + dtz = dtz_before_zeroing(wdl); + } + else if ((rule50 && pos.is_draw(1)) || pos.is_repetition(1)) + { + // In case a root move leads to a draw by repetition or 50-move rule, + // we set dtz to zero. Note: since we are only 1 ply from the root, + // this must be a true 3-fold repetition inside the game history. + dtz = 0; + } + else + { + // Otherwise, take dtz for the new position and correct by 1 ply + dtz = -probe_dtz(pos, &result); + dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz; + } + + // Make sure that a mating move is assigned a dtz value of 1 + if (pos.checkers() && dtz == 2 && MoveList(pos).size() == 0) + dtz = 1; + + pos.undo_move(m.pv[0]); + + if (result == FAIL) + return false; + + // Better moves are ranked higher. Certain wins are ranked equally. + // Losing moves are ranked equally unless a 50-move draw is in sight. + int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ - (rankDTZ ? dtz : 0) + : MAX_DTZ / 2 - (dtz + cnt50)) + : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ - (rankDTZ ? dtz : 0) + : -MAX_DTZ / 2 + (-dtz + cnt50)) + : 0; + m.tbRank = r; + + // Determine the score to be displayed for this move. Assign at least + // 1 cp to cursed wins and let it grow to 49 cp as the positions gets + // closer to a real win. + m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 + : r > 0 ? Value((std::max(3, r - (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200) + : r == 0 ? VALUE_DRAW + : r > -bound + ? Value((std::min(-3, r + (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200) + : -VALUE_MATE + MAX_PLY + 1; + } + + return true; +} + + +// Use the WDL tables to rank root moves. +// This is a fallback for the case that some or all DTZ tables are missing. +// +// A return value false indicates that not all probes were successful. +bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) { + + static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ}; + + ProbeState result = OK; + StateInfo st; + WDLScore wdl; + + + // Probe and rank each move + for (auto& m : rootMoves) + { + pos.do_move(m.pv[0], st); + + if (pos.is_draw(1)) + wdl = WDLDraw; + else + wdl = -probe_wdl(pos, &result); + + pos.undo_move(m.pv[0]); + + if (result == FAIL) + return false; + + m.tbRank = WDL_to_rank[wdl + 2]; + + if (!rule50) + wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw; + m.tbScore = WDL_to_value[wdl + 2]; + } + + return true; +} + +Config Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ) { + Config config; + + if (rootMoves.empty()) + return config; + + config.rootInTB = false; + config.useRule50 = bool(options["Syzygy50MoveRule"]); + config.probeDepth = int(options["SyzygyProbeDepth"]); + config.cardinality = int(options["SyzygyProbeLimit"]); + + bool dtz_available = true; + + // Tables with fewer pieces than SyzygyProbeLimit are searched with + // probeDepth == DEPTH_ZERO + if (config.cardinality > MaxCardinality) + { + config.cardinality = MaxCardinality; + config.probeDepth = 0; + } + + if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + // Rank moves using DTZ tables + config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ); + + if (!config.rootInTB) + { + // DTZ tables are missing; try to rank moves using WDL tables + dtz_available = false; + config.rootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]); + } + } + + if (config.rootInTB) + { + // Sort moves according to TB rank + std::stable_sort( + rootMoves.begin(), rootMoves.end(), + [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); + + // Probe during search only if DTZ is not available and we are winning + if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) + config.cardinality = 0; + } + else + { + // Clean up if root_probe() and root_probe_wdl() have failed + for (auto& m : rootMoves) + m.tbRank = 0; + } + + return config; +} +} // namespace Stockfish diff --git a/stockfish/src/syzygy/tbprobe.h b/stockfish/src/syzygy/tbprobe.h new file mode 100644 index 0000000000000000000000000000000000000000..c34338fe3c932f0474c6a1690158cedd79c756fe --- /dev/null +++ b/stockfish/src/syzygy/tbprobe.h @@ -0,0 +1,78 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TBPROBE_H +#define TBPROBE_H + +#include +#include + + +namespace Stockfish { +class Position; +class OptionsMap; + +using Depth = int; + +namespace Search { +struct RootMove; +using RootMoves = std::vector; +} +} + +namespace Stockfish::Tablebases { + +struct Config { + int cardinality = 0; + bool rootInTB = false; + bool useRule50 = false; + Depth probeDepth = 0; +}; + +enum WDLScore { + WDLLoss = -2, // Loss + WDLBlessedLoss = -1, // Loss, but draw under 50-move rule + WDLDraw = 0, // Draw + WDLCursedWin = 1, // Win, but draw under 50-move rule + WDLWin = 2, // Win +}; + +// Possible states after a probing operation +enum ProbeState { + FAIL = 0, // Probe failed (missing file table) + OK = 1, // Probe successful + CHANGE_STM = -1, // DTZ should check the other side + ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) +}; + +extern int MaxCardinality; + + +void init(const std::string& paths); +WDLScore probe_wdl(Position& pos, ProbeState* result); +int probe_dtz(Position& pos, ProbeState* result); +bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50, bool rankDTZ); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); +Config rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ = false); + +} // namespace Stockfish::Tablebases + +#endif diff --git a/stockfish/src/thread.cpp b/stockfish/src/thread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..43ba7d9c08dd20af7caa9de2d54bc191ede922e0 --- /dev/null +++ b/stockfish/src/thread.cpp @@ -0,0 +1,410 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "thread.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "movegen.h" +#include "search.h" +#include "syzygy/tbprobe.h" +#include "timeman.h" +#include "types.h" +#include "uci.h" +#include "ucioption.h" + +namespace Stockfish { + +// Constructor launches the thread and waits until it goes to sleep +// in idle_loop(). Note that 'searching' and 'exit' should be already set. +Thread::Thread(Search::SharedState& sharedState, + std::unique_ptr sm, + size_t n, + OptionalThreadToNumaNodeBinder binder) : + idx(n), + nthreads(sharedState.options["Threads"]), + stdThread(&Thread::idle_loop, this) { + + wait_for_search_finished(); + + run_custom_job([this, &binder, &sharedState, &sm, n]() { + // Use the binder to [maybe] bind the threads to a NUMA node before doing + // the Worker allocation. Ideally we would also allocate the SearchManager + // here, but that's minor. + this->numaAccessToken = binder(); + this->worker = + std::make_unique(sharedState, std::move(sm), n, this->numaAccessToken); + }); + + wait_for_search_finished(); +} + + +// Destructor wakes up the thread in idle_loop() and waits +// for its termination. Thread should be already waiting. +Thread::~Thread() { + + assert(!searching); + + exit = true; + start_searching(); + stdThread.join(); +} + +// Wakes up the thread that will start the search +void Thread::start_searching() { + assert(worker != nullptr); + run_custom_job([this]() { worker->start_searching(); }); +} + +// Clears the histories for the thread worker (usually before a new game) +void Thread::clear_worker() { + assert(worker != nullptr); + run_custom_job([this]() { worker->clear(); }); +} + +// Blocks on the condition variable until the thread has finished searching +void Thread::wait_for_search_finished() { + + std::unique_lock lk(mutex); + cv.wait(lk, [&] { return !searching; }); +} + +// Launching a function in the thread +void Thread::run_custom_job(std::function f) { + { + std::unique_lock lk(mutex); + cv.wait(lk, [&] { return !searching; }); + jobFunc = std::move(f); + searching = true; + } + cv.notify_one(); +} + +void Thread::ensure_network_replicated() { worker->ensure_network_replicated(); } + +// Thread gets parked here, blocked on the condition variable +// when the thread has no work to do. + +void Thread::idle_loop() { + while (true) + { + std::unique_lock lk(mutex); + searching = false; + cv.notify_one(); // Wake up anyone waiting for search finished + cv.wait(lk, [&] { return searching; }); + + if (exit) + return; + + std::function job = std::move(jobFunc); + jobFunc = nullptr; + + lk.unlock(); + + if (job) + job(); + } +} + +Search::SearchManager* ThreadPool::main_manager() { return main_thread()->worker->main_manager(); } + +uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); } +uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); } + +// Creates/destroys threads to match the requested number. +// Created and launched threads will immediately go to sleep in idle_loop. +// Upon resizing, threads are recreated to allow for binding if necessary. +void ThreadPool::set(const NumaConfig& numaConfig, + Search::SharedState sharedState, + const Search::SearchManager::UpdateContext& updateContext) { + + if (threads.size() > 0) // destroy any existing thread(s) + { + main_thread()->wait_for_search_finished(); + + threads.clear(); + + boundThreadToNumaNode.clear(); + } + + const size_t requested = sharedState.options["Threads"]; + + if (requested > 0) // create new thread(s) + { + // Binding threads may be problematic when there's multiple NUMA nodes and + // multiple Stockfish instances running. In particular, if each instance + // runs a single thread then they would all be mapped to the first NUMA node. + // This is undesirable, and so the default behaviour (i.e. when the user does not + // change the NumaConfig UCI setting) is to not bind the threads to processors + // unless we know for sure that we span NUMA nodes and replication is required. + const std::string numaPolicy(sharedState.options["NumaPolicy"]); + const bool doBindThreads = [&]() { + if (numaPolicy == "none") + return false; + + if (numaPolicy == "auto") + return numaConfig.suggests_binding_threads(requested); + + // numaPolicy == "system", or explicitly set by the user + return true; + }(); + + boundThreadToNumaNode = doBindThreads + ? numaConfig.distribute_threads_among_numa_nodes(requested) + : std::vector{}; + + while (threads.size() < requested) + { + const size_t threadId = threads.size(); + const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0; + auto manager = threadId == 0 ? std::unique_ptr( + std::make_unique(updateContext)) + : std::make_unique(); + + // When not binding threads we want to force all access to happen + // from the same NUMA node, because in case of NUMA replicated memory + // accesses we don't want to trash cache in case the threads get scheduled + // on the same NUMA node. + auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId) + : OptionalThreadToNumaNodeBinder(numaId); + + threads.emplace_back( + std::make_unique(sharedState, std::move(manager), threadId, binder)); + } + + clear(); + + main_thread()->wait_for_search_finished(); + } +} + + +// Sets threadPool data to initial values +void ThreadPool::clear() { + if (threads.size() == 0) + return; + + for (auto&& th : threads) + th->clear_worker(); + + for (auto&& th : threads) + th->wait_for_search_finished(); + + // These two affect the time taken on the first move of a game: + main_manager()->bestPreviousAverageScore = VALUE_INFINITE; + main_manager()->previousTimeReduction = 0.85; + + main_manager()->callsCnt = 0; + main_manager()->bestPreviousScore = VALUE_INFINITE; + main_manager()->originalTimeAdjust = -1; + main_manager()->tm.clear(); +} + +void ThreadPool::run_on_thread(size_t threadId, std::function f) { + assert(threads.size() > threadId); + threads[threadId]->run_custom_job(std::move(f)); +} + +void ThreadPool::wait_on_thread(size_t threadId) { + assert(threads.size() > threadId); + threads[threadId]->wait_for_search_finished(); +} + +size_t ThreadPool::num_threads() const { return threads.size(); } + + +// Wakes up main thread waiting in idle_loop() and returns immediately. +// Main thread will wake up other threads and start the search. +void ThreadPool::start_thinking(const OptionsMap& options, + Position& pos, + StateListPtr& states, + Search::LimitsType limits) { + + main_thread()->wait_for_search_finished(); + + main_manager()->stopOnPonderhit = stop = abortedSearch = false; + main_manager()->ponder = limits.ponderMode; + + increaseDepth = true; + + Search::RootMoves rootMoves; + const auto legalmoves = MoveList(pos); + + for (const auto& uciMove : limits.searchmoves) + { + auto move = UCIEngine::to_move(pos, uciMove); + + if (std::find(legalmoves.begin(), legalmoves.end(), move) != legalmoves.end()) + rootMoves.emplace_back(move); + } + + if (rootMoves.empty()) + for (const auto& m : legalmoves) + rootMoves.emplace_back(m); + + Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves); + + // After ownership transfer 'states' becomes empty, so if we stop the search + // and call 'go' again without setting a new position states.get() == nullptr. + assert(states.get() || setupStates.get()); + + if (states.get()) + setupStates = std::move(states); // Ownership transfer, states is now empty + + // We use Position::set() to set root position across threads. But there are + // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot + // be deduced from a fen string, so set() clears them and they are set from + // setupStates->back() later. The rootState is per thread, earlier states are + // shared since they are read-only. + for (auto&& th : threads) + { + th->run_custom_job([&]() { + th->worker->limits = limits; + th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly = + th->worker->bestMoveChanges = 0; + th->worker->rootDepth = th->worker->completedDepth = 0; + th->worker->rootMoves = rootMoves; + th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); + th->worker->rootState = setupStates->back(); + th->worker->tbConfig = tbConfig; + }); + } + + for (auto&& th : threads) + th->wait_for_search_finished(); + + main_thread()->start_searching(); +} + +Thread* ThreadPool::get_best_thread() const { + + Thread* bestThread = threads.front().get(); + Value minScore = VALUE_NONE; + + std::unordered_map votes( + 2 * std::min(size(), bestThread->worker->rootMoves.size())); + + // Find the minimum score of all threads + for (auto&& th : threads) + minScore = std::min(minScore, th->worker->rootMoves[0].score); + + // Vote according to score and depth, and select the best thread + auto thread_voting_value = [minScore](Thread* th) { + return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth); + }; + + for (auto&& th : threads) + votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th.get()); + + for (auto&& th : threads) + { + const auto bestThreadScore = bestThread->worker->rootMoves[0].score; + const auto newThreadScore = th->worker->rootMoves[0].score; + + const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv; + const auto& newThreadPV = th->worker->rootMoves[0].pv; + + const auto bestThreadMoveVote = votes[bestThreadPV[0]]; + const auto newThreadMoveVote = votes[newThreadPV[0]]; + + const bool bestThreadInProvenWin = is_win(bestThreadScore); + const bool newThreadInProvenWin = is_win(newThreadScore); + + const bool bestThreadInProvenLoss = + bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore); + const bool newThreadInProvenLoss = + newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore); + + // We make sure not to pick a thread with truncated principal variation + const bool betterVotingValue = + thread_voting_value(th.get()) * int(newThreadPV.size() > 2) + > thread_voting_value(bestThread) * int(bestThreadPV.size() > 2); + + if (bestThreadInProvenWin) + { + // Make sure we pick the shortest mate / TB conversion + if (newThreadScore > bestThreadScore) + bestThread = th.get(); + } + else if (bestThreadInProvenLoss) + { + // Make sure we pick the shortest mated / TB conversion + if (newThreadInProvenLoss && newThreadScore < bestThreadScore) + bestThread = th.get(); + } + else if (newThreadInProvenWin || newThreadInProvenLoss + || (!is_loss(newThreadScore) + && (newThreadMoveVote > bestThreadMoveVote + || (newThreadMoveVote == bestThreadMoveVote && betterVotingValue)))) + bestThread = th.get(); + } + + return bestThread; +} + + +// Start non-main threads. +// Will be invoked by main thread after it has started searching. +void ThreadPool::start_searching() { + + for (auto&& th : threads) + if (th != threads.front()) + th->start_searching(); +} + + +// Wait for non-main threads +void ThreadPool::wait_for_search_finished() const { + + for (auto&& th : threads) + if (th != threads.front()) + th->wait_for_search_finished(); +} + +std::vector ThreadPool::get_bound_thread_count_by_numa_node() const { + std::vector counts; + + if (!boundThreadToNumaNode.empty()) + { + NumaIndex highestNumaNode = 0; + for (NumaIndex n : boundThreadToNumaNode) + if (n > highestNumaNode) + highestNumaNode = n; + + counts.resize(highestNumaNode + 1, 0); + + for (NumaIndex n : boundThreadToNumaNode) + counts[n] += 1; + } + + return counts; +} + +void ThreadPool::ensure_network_replicated() { + for (auto&& th : threads) + th->ensure_network_replicated(); +} + +} // namespace Stockfish diff --git a/stockfish/src/thread.h b/stockfish/src/thread.h new file mode 100644 index 0000000000000000000000000000000000000000..912d4433528f2d6a5eaa2345ef558875b3290f34 --- /dev/null +++ b/stockfish/src/thread.h @@ -0,0 +1,178 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef THREAD_H_INCLUDED +#define THREAD_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "numa.h" +#include "position.h" +#include "search.h" +#include "thread_win32_osx.h" + +namespace Stockfish { + + +class OptionsMap; +using Value = int; + +// Sometimes we don't want to actually bind the threads, but the recipient still +// needs to think it runs on *some* NUMA node, such that it can access structures +// that rely on NUMA node knowledge. This class encapsulates this optional process +// such that the recipient does not need to know whether the binding happened or not. +class OptionalThreadToNumaNodeBinder { + public: + OptionalThreadToNumaNodeBinder(NumaIndex n) : + numaConfig(nullptr), + numaId(n) {} + + OptionalThreadToNumaNodeBinder(const NumaConfig& cfg, NumaIndex n) : + numaConfig(&cfg), + numaId(n) {} + + NumaReplicatedAccessToken operator()() const { + if (numaConfig != nullptr) + return numaConfig->bind_current_thread_to_numa_node(numaId); + else + return NumaReplicatedAccessToken(numaId); + } + + private: + const NumaConfig* numaConfig; + NumaIndex numaId; +}; + +// Abstraction of a thread. It contains a pointer to the worker and a native thread. +// After construction, the native thread is started with idle_loop() +// waiting for a signal to start searching. +// When the signal is received, the thread starts searching and when +// the search is finished, it goes back to idle_loop() waiting for a new signal. +class Thread { + public: + Thread(Search::SharedState&, + std::unique_ptr, + size_t, + OptionalThreadToNumaNodeBinder); + virtual ~Thread(); + + void idle_loop(); + void start_searching(); + void clear_worker(); + void run_custom_job(std::function f); + + void ensure_network_replicated(); + + // Thread has been slightly altered to allow running custom jobs, so + // this name is no longer correct. However, this class (and ThreadPool) + // require further work to make them properly generic while maintaining + // appropriate specificity regarding search, from the point of view of an + // outside user, so renaming of this function is left for whenever that happens. + void wait_for_search_finished(); + size_t id() const { return idx; } + + std::unique_ptr worker; + std::function jobFunc; + + private: + std::mutex mutex; + std::condition_variable cv; + size_t idx, nthreads; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; + NumaReplicatedAccessToken numaAccessToken; +}; + + +// ThreadPool struct handles all the threads-related stuff like init, starting, +// parking and, most importantly, launching a thread. All the access to threads +// is done through this class. +class ThreadPool { + public: + ThreadPool() {} + + ~ThreadPool() { + // destroy any existing thread(s) + if (threads.size() > 0) + { + main_thread()->wait_for_search_finished(); + + threads.clear(); + } + } + + ThreadPool(const ThreadPool&) = delete; + ThreadPool(ThreadPool&&) = delete; + + ThreadPool& operator=(const ThreadPool&) = delete; + ThreadPool& operator=(ThreadPool&&) = delete; + + void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType); + void run_on_thread(size_t threadId, std::function f); + void wait_on_thread(size_t threadId); + size_t num_threads() const; + void clear(); + void set(const NumaConfig& numaConfig, + Search::SharedState, + const Search::SearchManager::UpdateContext&); + + Search::SearchManager* main_manager(); + Thread* main_thread() const { return threads.front().get(); } + uint64_t nodes_searched() const; + uint64_t tb_hits() const; + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; + + std::vector get_bound_thread_count_by_numa_node() const; + + void ensure_network_replicated(); + + std::atomic_bool stop, abortedSearch, increaseDepth; + + auto cbegin() const noexcept { return threads.cbegin(); } + auto begin() noexcept { return threads.begin(); } + auto end() noexcept { return threads.end(); } + auto cend() const noexcept { return threads.cend(); } + auto size() const noexcept { return threads.size(); } + auto empty() const noexcept { return threads.empty(); } + + private: + StateListPtr setupStates; + std::vector> threads; + std::vector boundThreadToNumaNode; + + uint64_t accumulate(std::atomic Search::Worker::*member) const { + + uint64_t sum = 0; + for (auto&& th : threads) + sum += (th->worker.get()->*member).load(std::memory_order_relaxed); + return sum; + } +}; + +} // namespace Stockfish + +#endif // #ifndef THREAD_H_INCLUDED diff --git a/stockfish/src/thread_win32_osx.h b/stockfish/src/thread_win32_osx.h new file mode 100644 index 0000000000000000000000000000000000000000..fb4b2ec9718c5b5fe62a1b39ce278fe5a1cbef13 --- /dev/null +++ b/stockfish/src/thread_win32_osx.h @@ -0,0 +1,78 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef THREAD_WIN32_OSX_H_INCLUDED +#define THREAD_WIN32_OSX_H_INCLUDED + +#include + +// On OSX threads other than the main thread are created with a reduced stack +// size of 512KB by default, this is too low for deep searches, which require +// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. +// The implementation calls pthread_create() with the stack size parameter +// equal to the Linux 8MB default, on platforms that support it. + +#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) + + #include + #include + +namespace Stockfish { + +class NativeThread { + pthread_t thread; + + static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024; + + public: + template + explicit NativeThread(Function&& fun, Args&&... args) { + auto func = new std::function( + std::bind(std::forward(fun), std::forward(args)...)); + + pthread_attr_t attr_storage, *attr = &attr_storage; + pthread_attr_init(attr); + pthread_attr_setstacksize(attr, TH_STACK_SIZE); + + auto start_routine = [](void* ptr) -> void* { + auto f = reinterpret_cast*>(ptr); + // Call the function + (*f)(); + delete f; + return nullptr; + }; + + pthread_create(&thread, attr, start_routine, func); + } + + void join() { pthread_join(thread, nullptr); } +}; + +} // namespace Stockfish + +#else // Default case: use STL classes + +namespace Stockfish { + +using NativeThread = std::thread; + +} // namespace Stockfish + +#endif + +#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED diff --git a/stockfish/src/timeman.cpp b/stockfish/src/timeman.cpp new file mode 100644 index 0000000000000000000000000000000000000000..29ebffcaa29aac8ff1ef55bcec7acba48d1bd8b0 --- /dev/null +++ b/stockfish/src/timeman.cpp @@ -0,0 +1,143 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "timeman.h" + +#include +#include +#include +#include + +#include "search.h" +#include "ucioption.h" + +namespace Stockfish { + +TimePoint TimeManagement::optimum() const { return optimumTime; } +TimePoint TimeManagement::maximum() const { return maximumTime; } + +void TimeManagement::clear() { + availableNodes = -1; // When in 'nodes as time' mode +} + +void TimeManagement::advance_nodes_time(std::int64_t nodes) { + assert(useNodesTime); + availableNodes = std::max(int64_t(0), availableNodes - nodes); +} + +// Called at the beginning of the search and calculates +// the bounds of time allowed for the current game ply. We currently support: +// 1) x basetime (+ z increment) +// 2) x moves in y seconds (+ z increment) +void TimeManagement::init(Search::LimitsType& limits, + Color us, + int ply, + const OptionsMap& options, + double& originalTimeAdjust) { + TimePoint npmsec = TimePoint(options["nodestime"]); + + // If we have no time, we don't need to fully initialize TM. + // startTime is used by movetime and useNodesTime is used in elapsed calls. + startTime = limits.startTime; + useNodesTime = npmsec != 0; + + if (limits.time[us] == 0) + return; + + TimePoint moveOverhead = TimePoint(options["Move Overhead"]); + + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; + + // If we have to play in 'nodes as time' mode, then convert from time + // to nodes, and use resulting values in time management formulas. + // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) + // must be much lower than the real engine speed. + if (useNodesTime) + { + if (availableNodes == -1) // Only once at game start + availableNodes = npmsec * limits.time[us]; // Time is in msec + + // Convert from milliseconds to nodes + limits.time[us] = TimePoint(availableNodes); + limits.inc[us] *= npmsec; + limits.npmsec = npmsec; + moveOverhead *= npmsec; + } + + // These numbers are used where multiplications, divisions or comparisons + // with constants are involved. + const int64_t scaleFactor = useNodesTime ? npmsec : 1; + const TimePoint scaledTime = limits.time[us] / scaleFactor; + const TimePoint scaledInc = limits.inc[us] / scaleFactor; + + // Maximum move horizon + int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; + + // If less than one second, gradually reduce mtg + if (scaledTime < 1000 && double(centiMTG) / scaledInc > 5.051) + { + centiMTG = scaledTime * 5.051; + } + + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = + std::max(TimePoint(1), + limits.time[us] + + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100); + + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed the actual available + // game time for the current move, so also cap to a percentage of available game time. + if (limits.movestogo == 0) + { + // Extra time according to timeLeft + if (originalTimeAdjust < 0) + originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; + + // Calculate time constants based on current time left. + double logTimeInSec = std::log10(scaledTime / 1000.0); + double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); + double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); + + optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, + 0.213035 * limits.time[us] / timeLeft) + * originalTimeAdjust; + + maxScale = std::min(6.67704, maxConstant + ply / 11.9847); + } + + // x moves in y seconds (+ z increment) + else + { + optScale = + std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft); + maxScale = 1.3 + 0.11 * (centiMTG / 100.0); + } + + // Limit the maximum possible time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = + TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + + if (options["Ponder"]) + optimumTime += optimumTime / 4; +} + +} // namespace Stockfish diff --git a/stockfish/src/timeman.h b/stockfish/src/timeman.h new file mode 100644 index 0000000000000000000000000000000000000000..e8602bb7ce801edd4d01ef449694080418458109 --- /dev/null +++ b/stockfish/src/timeman.h @@ -0,0 +1,67 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TIMEMAN_H_INCLUDED +#define TIMEMAN_H_INCLUDED + +#include + +#include "misc.h" +#include "types.h" + +namespace Stockfish { + +class OptionsMap; + +namespace Search { +struct LimitsType; +} + +// The TimeManagement class computes the optimal time to think depending on +// the maximum available time, the game move number, and other parameters. +class TimeManagement { + public: + void init(Search::LimitsType& limits, + Color us, + int ply, + const OptionsMap& options, + double& originalTimeAdjust); + + TimePoint optimum() const; + TimePoint maximum() const; + template + TimePoint elapsed(FUNC nodes) const { + return useNodesTime ? TimePoint(nodes()) : elapsed_time(); + } + TimePoint elapsed_time() const { return now() - startTime; }; + + void clear(); + void advance_nodes_time(std::int64_t nodes); + + private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; + + std::int64_t availableNodes = -1; // When in 'nodes as time' mode + bool useNodesTime = false; // True if we are in 'nodes as time' mode +}; + +} // namespace Stockfish + +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/stockfish/src/tt.cpp b/stockfish/src/tt.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5d8457611dded9c7ead36a6748e51d004c4b7066 --- /dev/null +++ b/stockfish/src/tt.cpp @@ -0,0 +1,251 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "tt.h" + +#include +#include +#include +#include +#include + +#include "memory.h" +#include "misc.h" +#include "syzygy/tbprobe.h" +#include "thread.h" + +namespace Stockfish { + + +// TTEntry struct is the 10 bytes transposition table entry, defined as below: +// +// key 16 bit +// depth 8 bit +// generation 5 bit +// pv node 1 bit +// bound type 2 bit +// move 16 bit +// value 16 bit +// evaluation 16 bit +// +// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially. +// Equally, the store order in save() matches this order. + +struct TTEntry { + + // Convert internal bitfields to external types + TTData read() const { + return TTData{Move(move16), Value(value16), + Value(eval16), Depth(depth8 + DEPTH_ENTRY_OFFSET), + Bound(genBound8 & 0x3), bool(genBound8 & 0x4)}; + } + + bool is_occupied() const; + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); + // The returned age is a multiple of TranspositionTable::GENERATION_DELTA + uint8_t relative_age(const uint8_t generation8) const; + + private: + friend class TranspositionTable; + + uint16_t key16; + uint8_t depth8; + uint8_t genBound8; + Move move16; + int16_t value16; + int16_t eval16; +}; + +// `genBound8` is where most of the details are. We use the following constants to manipulate 5 leading generation bits +// and 3 trailing miscellaneous bits. + +// These bits are reserved for other things. +static constexpr unsigned GENERATION_BITS = 3; +// increment for generation field +static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); +// cycle length +static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA; +// mask to pull out generation number +static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; + +// DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but +// 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits": +// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted in `save`.) +bool TTEntry::is_occupied() const { return bool(depth8); } + +// Populates the TTEntry with a new node's data, possibly +// overwriting an old position. The update is not atomic and can be racy. +void TTEntry::save( + Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) { + + // Preserve the old ttmove if we don't have a new one + if (m || uint16_t(k) != key16) + move16 = m; + + // Overwrite less valuable entries (cheapest checks first) + if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_ENTRY_OFFSET + 2 * pv > depth8 - 4 + || relative_age(generation8)) + { + assert(d > DEPTH_ENTRY_OFFSET); + assert(d < 256 + DEPTH_ENTRY_OFFSET); + + key16 = uint16_t(k); + depth8 = uint8_t(d - DEPTH_ENTRY_OFFSET); + genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b); + value16 = int16_t(v); + eval16 = int16_t(ev); + } +} + + +uint8_t TTEntry::relative_age(const uint8_t generation8) const { + // Due to our packed storage format for generation and its cyclic + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + return (GENERATION_CYCLE + generation8 - genBound8) & GENERATION_MASK; +} + + +// TTWriter is but a very thin wrapper around the pointer +TTWriter::TTWriter(TTEntry* tte) : + entry(tte) {} + +void TTWriter::write( + Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) { + entry->save(k, v, pv, b, d, m, ev, generation8); +} + + +// A TranspositionTable is an array of Cluster, of size clusterCount. Each cluster consists of ClusterSize number +// of TTEntry. Each non-empty TTEntry contains information on exactly one position. The size of a Cluster should +// divide the size of a cache line for best performance, as the cacheline is prefetched when possible. + +static constexpr int ClusterSize = 3; + +struct Cluster { + TTEntry entry[ClusterSize]; + char padding[2]; // Pad to 32 bytes +}; + +static_assert(sizeof(Cluster) == 32, "Suboptimal Cluster size"); + + +// Sets the size of the transposition table, +// measured in megabytes. Transposition table consists +// of clusters and each cluster consists of ClusterSize number of TTEntry. +void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) { + aligned_large_pages_free(table); + + clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); + + table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); + + if (!table) + { + std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } + + clear(threads); +} + + +// Initializes the entire transposition table to zero, +// in a multi-threaded way. +void TranspositionTable::clear(ThreadPool& threads) { + generation8 = 0; + const size_t threadCount = threads.num_threads(); + + for (size_t i = 0; i < threadCount; ++i) + { + threads.run_on_thread(i, [this, i, threadCount]() { + // Each thread will zero its part of the hash table + const size_t stride = clusterCount / threadCount; + const size_t start = stride * i; + const size_t len = i + 1 != threadCount ? stride : clusterCount - start; + + std::memset(&table[start], 0, len * sizeof(Cluster)); + }); + } + + for (size_t i = 0; i < threadCount; ++i) + threads.wait_on_thread(i); +} + + +// Returns an approximation of the hashtable +// occupation during a search. The hash is x permill full, as per UCI protocol. +// Only counts entries which match the current generation. +int TranspositionTable::hashfull(int maxAge) const { + int maxAgeInternal = maxAge << GENERATION_BITS; + int cnt = 0; + for (int i = 0; i < 1000; ++i) + for (int j = 0; j < ClusterSize; ++j) + cnt += table[i].entry[j].is_occupied() + && table[i].entry[j].relative_age(generation8) <= maxAgeInternal; + + return cnt / ClusterSize; +} + + +void TranspositionTable::new_search() { + // increment by delta to keep lower bits as is + generation8 += GENERATION_DELTA; +} + + +uint8_t TranspositionTable::generation() const { return generation8; } + + +// Looks up the current position in the transposition +// table. It returns true if the position is found. +// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry +// to be replaced later. The replace value of an entry is calculated as its depth +// minus 8 times its relative age. TTEntry t1 is considered more valuable than +// TTEntry t2 if its replace value is greater than that of t2. +std::tuple TranspositionTable::probe(const Key key) const { + + TTEntry* const tte = first_entry(key); + const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster + + for (int i = 0; i < ClusterSize; ++i) + if (tte[i].key16 == key16) + // This gap is the main place for read races. + // After `read()` completes that copy is final, but may be self-inconsistent. + return {tte[i].is_occupied(), tte[i].read(), TTWriter(&tte[i])}; + + // Find an entry to be replaced according to the replacement strategy + TTEntry* replace = tte; + for (int i = 1; i < ClusterSize; ++i) + if (replace->depth8 - replace->relative_age(generation8) * 2 + > tte[i].depth8 - tte[i].relative_age(generation8) * 2) + replace = &tte[i]; + + return {false, + TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_ENTRY_OFFSET, BOUND_NONE, false}, + TTWriter(replace)}; +} + + +TTEntry* TranspositionTable::first_entry(const Key key) const { + return &table[mul_hi64(key, clusterCount)].entry[0]; +} + +} // namespace Stockfish diff --git a/stockfish/src/tt.h b/stockfish/src/tt.h new file mode 100644 index 0000000000000000000000000000000000000000..26b63c1adb4de4af17aada357cc6ed434b4ce361 --- /dev/null +++ b/stockfish/src/tt.h @@ -0,0 +1,110 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TT_H_INCLUDED +#define TT_H_INCLUDED + +#include +#include +#include + +#include "memory.h" +#include "types.h" + +namespace Stockfish { + +class ThreadPool; +struct TTEntry; +struct Cluster; + +// There is only one global hash table for the engine and all its threads. For chess in particular, we even allow racy +// updates between threads to and from the TT, as taking the time to synchronize access would cost thinking time and +// thus elo. As a hash table, collisions are possible and may cause chess playing issues (bizarre blunders, faulty mate +// reports, etc). Fixing these also loses elo; however such risk decreases quickly with larger TT size. +// +// `probe` is the primary method: given a board position, we lookup its entry in the table, and return a tuple of: +// 1) whether the entry already has this position +// 2) a copy of the prior data (if any) (may be inconsistent due to read races) +// 3) a writer object to this entry +// The copied data and the writer are separated to maintain clear boundaries between local vs global objects. + + +// A copy of the data already in the entry (possibly collided). `probe` may be racy, resulting in inconsistent data. +struct TTData { + Move move; + Value value, eval; + Depth depth; + Bound bound; + bool is_pv; + + TTData() = delete; + + // clang-format off + TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) : + move(m), + value(v), + eval(ev), + depth(d), + bound(b), + is_pv(pv) {}; + // clang-format on +}; + + +// This is used to make racy writes to the global TT. +struct TTWriter { + public: + void write(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); + + private: + friend class TranspositionTable; + TTEntry* entry; + TTWriter(TTEntry* tte); +}; + + +class TranspositionTable { + + public: + ~TranspositionTable() { aligned_large_pages_free(table); } + + void resize(size_t mbSize, ThreadPool& threads); // Set TT size + void clear(ThreadPool& threads); // Re-initialize memory, multithreaded + int hashfull(int maxAge = 0) + const; // Approximate what fraction of entries (permille) have been written to during this root search + + void + new_search(); // This must be called at the beginning of each root search to track entry aging + uint8_t generation() const; // The current age, used when writing new data to the TT + std::tuple + probe(const Key key) const; // The main method, whose retvals separate local vs global objects + TTEntry* first_entry(const Key key) + const; // This is the hash function; its only external use is memory prefetching. + + private: + friend struct TTEntry; + + size_t clusterCount; + Cluster* table = nullptr; + + uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 +}; + +} // namespace Stockfish + +#endif // #ifndef TT_H_INCLUDED diff --git a/stockfish/src/tune.cpp b/stockfish/src/tune.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f53a0eb52d6a7d23ae18879dca60c42a3c01e9f9 --- /dev/null +++ b/stockfish/src/tune.cpp @@ -0,0 +1,126 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "tune.h" + +#include +#include +#include +#include +#include +#include + +#include "ucioption.h" + +using std::string; + +namespace Stockfish { + +bool Tune::update_on_last; +const Option* LastOption = nullptr; +OptionsMap* Tune::options; +namespace { +std::map TuneResults; + +std::optional on_tune(const Option& o) { + + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); + + return std::nullopt; +} +} + +void Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange& r) { + + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; + + if (TuneResults.count(n)) + v = TuneResults[n]; + + opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); + LastOption = &((*opts)[n]); + + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," // + << v << "," // + << r(v).first << "," // + << r(v).second << "," // + << (r(v).second - r(v).first) / 20.0 << "," // + << "0.0020" << std::endl; +} + +string Tune::next(string& names, bool pop) { + + string name; + + do + { + string token = names.substr(0, names.find(',')); + + if (pop) + names.erase(0, token.size() + 1); + + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace + + } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); + + return name; +} + + +template<> +void Tune::Entry::init_option() { + make_option(options, name, value, range); +} + +template<> +void Tune::Entry::read_option() { + if (options->count(name)) + value = int((*options)[name]); +} + +// Instead of a variable here we have a PostUpdate function: just call it +template<> +void Tune::Entry::init_option() {} +template<> +void Tune::Entry::read_option() { + value(); +} + +} // namespace Stockfish + + +// Init options with tuning session results instead of default values. Useful to +// get correct bench signature after a tuning session or to test tuned values. +// Just copy fishtest tuning results in a result.txt file and extract the +// values with: +// +// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/ TuneResults["\1"] = int(round(\2));/' +// +// Then paste the output below, as the function body + + +namespace Stockfish { + +void Tune::read_results() { /* ...insert your values here... */ } + +} // namespace Stockfish diff --git a/stockfish/src/tune.h b/stockfish/src/tune.h new file mode 100644 index 0000000000000000000000000000000000000000..4dab6bf457e9f05b6973560a611872692ef14b38 --- /dev/null +++ b/stockfish/src/tune.h @@ -0,0 +1,183 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TUNE_H_INCLUDED +#define TUNE_H_INCLUDED + +#include +#include +#include +#include // IWYU pragma: keep +#include +#include + +namespace Stockfish { + +class OptionsMap; + +using Range = std::pair; // Option's min-max values +using RangeFun = Range(int); + +// Default Range function, to calculate Option's min-max values +inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } + +struct SetRange { + explicit SetRange(RangeFun f) : + fun(f) {} + SetRange(int min, int max) : + fun(nullptr), + range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } + + RangeFun* fun; + Range range; +}; + +#define SetDefaultRange SetRange(default_range) + + +// Tune class implements the 'magic' code that makes the setup of a fishtest tuning +// session as easy as it can be. Mainly you have just to remove const qualifiers +// from the variables you want to tune and flag them for tuning, so if you have: +// +// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; +// +// If you have a my_post_update() function to run after values have been updated, +// and a my_range() function to set custom Option's min-max values, then you just +// remove the 'const' qualifiers and write somewhere below in the file: +// +// TUNE(SetRange(my_range), myValue, my_post_update); +// +// You can also set the range directly, and restore the default at the end +// +// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); +// +// In case update function is slow and you have many parameters, you can add: +// +// UPDATE_ON_LAST(); +// +// And the values update, including post update function call, will be done only +// once, after the engine receives the last UCI option, that is the one defined +// and created as the last one, so the GUI should send the options in the same +// order in which have been defined. + +class Tune { + + using PostUpdate = void(); // Post-update function + + Tune() { read_results(); } + Tune(const Tune&) = delete; + void operator=(const Tune&) = delete; + void read_results(); + + static Tune& instance() { + static Tune t; + return t; + } // Singleton + + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template + struct Entry: public EntryBase { + + static_assert(!std::is_const_v, "Parameter cannot be const!"); + + static_assert(std::is_same_v || std::is_same_v, + "Parameter type not supported!"); + + Entry(const std::string& n, T& v, const SetRange& r) : + name(n), + value(v), + range(r) {} + void operator=(const Entry&) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T& value; + SetRange range; + }; + + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string& names, bool pop = true); + + int add(const SetRange&, std::string&&) { return 0; } + + template + int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional arrays + template + int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template + int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { + return add(value, (next(names), std::move(names)), args...); + } + + static void make_option(OptionsMap* options, const std::string& n, int v, const SetRange& r); + + std::vector> list; + + public: + template + static int add(const std::string& names, Args&&... args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init(OptionsMap& o) { + options = &o; + for (auto& e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCIEngine::Options access + static void read_options() { + for (auto& e : instance().list) + e->read_option(); + } + + static bool update_on_last; + static OptionsMap* options; +}; + +// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() +#define STRINGIFY(x) #x +#define UNIQUE2(x, y) x##y +#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ +#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__) + +#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true + +} // namespace Stockfish + +#endif // #ifndef TUNE_H_INCLUDED diff --git a/stockfish/src/types.h b/stockfish/src/types.h new file mode 100644 index 0000000000000000000000000000000000000000..6465dfd6b4f8551e9d861de5d1f64df775849474 --- /dev/null +++ b/stockfish/src/types.h @@ -0,0 +1,436 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TYPES_H_INCLUDED + #define TYPES_H_INCLUDED + +// When compiling with provided Makefile (e.g. for Linux and OSX), configuration +// is done automatically. To get started type 'make help'. +// +// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches +// need to be set manually: +// +// -DNDEBUG | Disable debugging mode. Always use this for release. +// +// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to +// | run on some very old machines. +// +// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works +// | only in 64-bit mode and requires hardware with popcnt support. +// +// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works +// | only in 64-bit mode and requires hardware with pext support. + + #include + #include + + #if defined(_MSC_VER) + // Disable some silly and noisy warnings from MSVC compiler + #pragma warning(disable: 4127) // Conditional expression is constant + #pragma warning(disable: 4146) // Unary minus operator applied to unsigned type + #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' + #endif + +// Predefined macros hell: +// +// __GNUC__ Compiler is GCC, Clang or ICX +// __clang__ Compiler is Clang or ICX +// __INTEL_LLVM_COMPILER Compiler is ICX +// _MSC_VER Compiler is MSVC +// _WIN32 Building on Windows (any) +// _WIN64 Building on Windows 64 bit + + #if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \ + && defined(_WIN32) && !defined(__clang__) + #define ALIGNAS_ON_STACK_VARIABLES_BROKEN + #endif + + #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + + #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used + #include // Microsoft header for _BitScanForward64() + #define IS_64BIT + #endif + + #if defined(USE_POPCNT) && defined(_MSC_VER) + #include // Microsoft header for _mm_popcnt_u64() + #endif + + #if !defined(NO_PREFETCH) && defined(_MSC_VER) + #include // Microsoft header for _mm_prefetch() + #endif + + #if defined(USE_PEXT) + #include // Header for _pext_u64() intrinsic + #define pext(b, m) _pext_u64(b, m) + #else + #define pext(b, m) 0 + #endif + +namespace Stockfish { + + #ifdef USE_POPCNT +constexpr bool HasPopCnt = true; + #else +constexpr bool HasPopCnt = false; + #endif + + #ifdef USE_PEXT +constexpr bool HasPext = true; + #else +constexpr bool HasPext = false; + #endif + + #ifdef IS_64BIT +constexpr bool Is64Bit = true; + #else +constexpr bool Is64Bit = false; + #endif + +using Key = uint64_t; +using Bitboard = uint64_t; + +constexpr int MAX_MOVES = 256; +constexpr int MAX_PLY = 246; + +enum Color { + WHITE, + BLACK, + COLOR_NB = 2 +}; + +enum CastlingRights { + NO_CASTLING, + WHITE_OO, + WHITE_OOO = WHITE_OO << 1, + BLACK_OO = WHITE_OO << 2, + BLACK_OOO = WHITE_OO << 3, + + KING_SIDE = WHITE_OO | BLACK_OO, + QUEEN_SIDE = WHITE_OOO | BLACK_OOO, + WHITE_CASTLING = WHITE_OO | WHITE_OOO, + BLACK_CASTLING = BLACK_OO | BLACK_OOO, + ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, + + CASTLING_RIGHT_NB = 16 +}; + +enum Bound { + BOUND_NONE, + BOUND_UPPER, + BOUND_LOWER, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER +}; + +// Value is used as an alias for int, this is done to differentiate between a search +// value and any other integer value. The values used in search are always supposed +// to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range. +using Value = int; + +constexpr Value VALUE_ZERO = 0; +constexpr Value VALUE_DRAW = 0; +constexpr Value VALUE_NONE = 32002; +constexpr Value VALUE_INFINITE = 32001; + +constexpr Value VALUE_MATE = 32000; +constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY; +constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY; + +constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; +constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; +constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; + + +constexpr bool is_valid(Value value) { return value != VALUE_NONE; } + +constexpr bool is_win(Value value) { + assert(is_valid(value)); + return value >= VALUE_TB_WIN_IN_MAX_PLY; +} + +constexpr bool is_loss(Value value) { + assert(is_valid(value)); + return value <= VALUE_TB_LOSS_IN_MAX_PLY; +} + +constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } + +// In the code, we make the assumption that these values +// are such that non_pawn_material() can be used to uniquely +// identify the material on the board. +constexpr Value PawnValue = 208; +constexpr Value KnightValue = 781; +constexpr Value BishopValue = 825; +constexpr Value RookValue = 1276; +constexpr Value QueenValue = 2538; + + +// clang-format off +enum PieceType { + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, + ALL_PIECES = 0, + PIECE_TYPE_NB = 8 +}; + +enum Piece { + NO_PIECE, + W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + PIECE_NB = 16 +}; +// clang-format on + +constexpr Value PieceValue[PIECE_NB] = { + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO}; + +using Depth = int; + +enum : int { + // The following DEPTH_ constants are used for transposition table entries + // and quiescence search move generation stages. In regular search, the + // depth stored in the transposition table is literal: the search depth + // (effort) used to make the corresponding transposition table value. In + // quiescence search, however, the transposition table entries only store + // the current quiescence move generation stage (which should thus compare + // lower than any regular search depth). + DEPTH_QS = 0, + // For transposition table entries where no searching at all was done + // (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus + // compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET + // is used only for the transposition table entry occupancy check (see tt.cpp), + // and should thus be lower than DEPTH_UNSEARCHED. + DEPTH_UNSEARCHED = -2, + DEPTH_ENTRY_OFFSET = -3 +}; + +// clang-format off +enum Square : int { + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, + SQ_NONE, + + SQUARE_ZERO = 0, + SQUARE_NB = 64 +}; +// clang-format on + +enum Direction : int { + NORTH = 8, + EAST = 1, + SOUTH = -NORTH, + WEST = -EAST, + + NORTH_EAST = NORTH + EAST, + SOUTH_EAST = SOUTH + EAST, + SOUTH_WEST = SOUTH + WEST, + NORTH_WEST = NORTH + WEST +}; + +enum File : int { + FILE_A, + FILE_B, + FILE_C, + FILE_D, + FILE_E, + FILE_F, + FILE_G, + FILE_H, + FILE_NB +}; + +enum Rank : int { + RANK_1, + RANK_2, + RANK_3, + RANK_4, + RANK_5, + RANK_6, + RANK_7, + RANK_8, + RANK_NB +}; + +// Keep track of what a move changes on the board (used by NNUE) +struct DirtyPiece { + + // Number of changed pieces + int dirty_num; + + // Max 3 pieces can change in one move. A promotion with capture moves + // both the pawn and the captured piece to SQ_NONE and the piece promoted + // to from SQ_NONE to the capture square. + Piece piece[3]; + + // From and to squares, which may be SQ_NONE + Square from[3]; + Square to[3]; +}; + + #define ENABLE_INCR_OPERATORS_ON(T) \ + inline T& operator++(T& d) { return d = T(int(d) + 1); } \ + inline T& operator--(T& d) { return d = T(int(d) - 1); } + +ENABLE_INCR_OPERATORS_ON(PieceType) +ENABLE_INCR_OPERATORS_ON(Square) +ENABLE_INCR_OPERATORS_ON(File) +ENABLE_INCR_OPERATORS_ON(Rank) + + #undef ENABLE_INCR_OPERATORS_ON + +constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int(d1) + int(d2)); } +constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); } + +// Additional operators to add a Direction to a Square +constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +inline Square& operator+=(Square& s, Direction d) { return s = s + d; } +inline Square& operator-=(Square& s, Direction d) { return s = s - d; } + +// Toggle color +constexpr Color operator~(Color c) { return Color(c ^ BLACK); } + +// Swap A1 <-> A8 +constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); } + +// Swap A1 <-> H1 +constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); } + +// Swap color of piece B_KNIGHT <-> W_KNIGHT +constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); } + +constexpr CastlingRights operator&(Color c, CastlingRights cr) { + return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); +} + +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } + +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } + +constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); } + +constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); } + +constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } + +inline Color color_of(Piece pc) { + assert(pc != NO_PIECE); + return Color(pc >> 3); +} + +constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } + +constexpr File file_of(Square s) { return File(s & 7); } + +constexpr Rank rank_of(Square s) { return Rank(s >> 3); } + +constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } + +constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); } + +constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); } + +constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } + + +// Based on a congruential pseudo-random number generator +constexpr Key make_key(uint64_t seed) { + return seed * 6364136223846793005ULL + 1442695040888963407ULL; +} + + +enum MoveType { + NORMAL, + PROMOTION = 1 << 14, + EN_PASSANT = 2 << 14, + CASTLING = 3 << 14 +}; + +// A move needs 16 bits to be stored +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +// NOTE: en passant bit is set only when a pawn can be captured +// +// Special cases are Move::none() and Move::null(). We can sneak these in because +// in any normal move the destination square and origin square are always different, +// but Move::none() and Move::null() have the same origin and destination square. + +class Move { + public: + Move() = default; + constexpr explicit Move(std::uint16_t d) : + data(d) {} + + constexpr Move(Square from, Square to) : + data((from << 6) + to) {} + + template + static constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); + } + + constexpr Square from_sq() const { + assert(is_ok()); + return Square((data >> 6) & 0x3F); + } + + constexpr Square to_sq() const { + assert(is_ok()); + return Square(data & 0x3F); + } + + constexpr int from_to() const { return data & 0xFFF; } + + constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } + + constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } + + constexpr bool is_ok() const { return none().data != data && null().data != data; } + + static constexpr Move null() { return Move(65); } + static constexpr Move none() { return Move(0); } + + constexpr bool operator==(const Move& m) const { return data == m.data; } + constexpr bool operator!=(const Move& m) const { return data != m.data; } + + constexpr explicit operator bool() const { return data != 0; } + + constexpr std::uint16_t raw() const { return data; } + + struct MoveHash { + std::size_t operator()(const Move& m) const { return make_key(m.data); } + }; + + protected: + std::uint16_t data; +}; + +} // namespace Stockfish + +#endif // #ifndef TYPES_H_INCLUDED + +#include "tune.h" // Global visibility to tuning setup diff --git a/stockfish/src/uci.cpp b/stockfish/src/uci.cpp new file mode 100644 index 0000000000000000000000000000000000000000..500e881843c55c821394b5ddfecd580eb80f1310 --- /dev/null +++ b/stockfish/src/uci.cpp @@ -0,0 +1,667 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "uci.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark.h" +#include "engine.h" +#include "memory.h" +#include "movegen.h" +#include "position.h" +#include "score.h" +#include "search.h" +#include "types.h" +#include "ucioption.h" + +namespace Stockfish { + +constexpr auto BenchmarkCommand = "speedtest"; + +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +template +struct overload: Ts... { + using Ts::operator()...; +}; + +template +overload(Ts...) -> overload; + +void UCIEngine::print_info_string(std::string_view str) { + sync_cout_start(); + for (auto& line : split(str, "\n")) + { + if (!is_whitespace(line)) + { + std::cout << "info string " << line << '\n'; + } + } + sync_cout_end(); +} + +UCIEngine::UCIEngine(int argc, char** argv) : + engine(argv[0]), + cli(argc, argv) { + + engine.get_options().add_info_listener([](const std::optional& str) { + if (str.has_value()) + print_info_string(*str); + }); + + init_search_update_listeners(); +} + +void UCIEngine::init_search_update_listeners() { + engine.set_on_iter([](const auto& i) { on_iter(i); }); + engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); }); + engine.set_on_update_full( + [this](const auto& i) { on_update_full(i, engine.get_options()["UCI_ShowWDL"]); }); + engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); }); + engine.set_on_verify_networks([](const auto& s) { print_info_string(s); }); +} + +void UCIEngine::loop() { + std::string token, cmd; + + for (int i = 1; i < cli.argc; ++i) + cmd += std::string(cli.argv[i]) + " "; + + do + { + if (cli.argc == 1 + && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + cmd = "quit"; + + std::istringstream is(cmd); + + token.clear(); // Avoid a stale if getline() returns nothing or a blank line + is >> std::skipws >> token; + + if (token == "quit" || token == "stop") + engine.stop(); + + // The GUI sends 'ponderhit' to tell that the user has played the expected move. + // So, 'ponderhit' is sent if pondering was done on the same move that the user + // has played. The search should continue, but should also switch from pondering + // to the normal search. + else if (token == "ponderhit") + engine.set_ponderhit(false); + + else if (token == "uci") + { + sync_cout << "id name " << engine_info(true) << "\n" + << engine.get_options() << sync_endl; + + sync_cout << "uciok" << sync_endl; + } + + else if (token == "setoption") + setoption(is); + else if (token == "go") + { + // send info strings after the go command is sent for old GUIs and python-chess + print_info_string(engine.numa_config_information_as_string()); + print_info_string(engine.thread_allocation_information_as_string()); + go(is); + } + else if (token == "position") + position(is); + else if (token == "ucinewgame") + engine.search_clear(); + else if (token == "isready") + sync_cout << "readyok" << sync_endl; + + // Add custom non-UCI commands, mainly for debugging purposes. + // These commands must not be used during a search! + else if (token == "flip") + engine.flip(); + else if (token == "bench") + bench(is); + else if (token == BenchmarkCommand) + benchmark(is); + else if (token == "d") + sync_cout << engine.visualize() << sync_endl; + else if (token == "eval") + engine.trace_eval(); + else if (token == "compiler") + sync_cout << compiler_info() << sync_endl; + else if (token == "export_net") + { + std::pair, std::string> files[2]; + + if (is >> std::skipws >> files[0].second) + files[0].first = files[0].second; + + if (is >> std::skipws >> files[1].second) + files[1].first = files[1].second; + + engine.save_network(files); + } + else if (token == "--help" || token == "help" || token == "--license" || token == "license") + sync_cout + << "\nStockfish is a powerful chess engine for playing and analyzing." + "\nIt is released as free software licensed under the GNU GPLv3 License." + "\nStockfish is normally used with a graphical user interface (GUI) and implements" + "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." + "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" + "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" + << sync_endl; + else if (!token.empty() && token[0] != '#') + sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." + << sync_endl; + + } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot +} + +Search::LimitsType UCIEngine::parse_limits(std::istream& is) { + Search::LimitsType limits; + std::string token; + + limits.startTime = now(); // The search starts as early as possible + + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) + limits.searchmoves.push_back(to_lower(token)); + + else if (token == "wtime") + is >> limits.time[WHITE]; + else if (token == "btime") + is >> limits.time[BLACK]; + else if (token == "winc") + is >> limits.inc[WHITE]; + else if (token == "binc") + is >> limits.inc[BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder") + limits.ponderMode = true; + + return limits; +} + +void UCIEngine::go(std::istringstream& is) { + + Search::LimitsType limits = parse_limits(is); + + if (limits.perft) + perft(limits); + else + engine.go(limits); +} + +void UCIEngine::bench(std::istream& args) { + std::string token; + uint64_t num, nodes = 0, cnt = 1; + uint64_t nodesSearched = 0; + const auto& options = engine.get_options(); + + engine.set_on_update_full([&](const auto& i) { + nodesSearched = i.nodes; + on_update_full(i, options["UCI_ShowWDL"]); + }); + + std::vector list = Benchmark::setup_bench(engine.fen(), args); + + num = count_if(list.begin(), list.end(), + [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + + TimePoint elapsed = now(); + + for (const auto& cmd : list) + { + std::istringstream is(cmd); + is >> std::skipws >> token; + + if (token == "go" || token == "eval") + { + std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << engine.fen() << ")" + << std::endl; + if (token == "go") + { + Search::LimitsType limits = parse_limits(is); + + if (limits.perft) + nodesSearched = perft(limits); + else + { + engine.go(limits); + engine.wait_for_search_finished(); + } + + nodes += nodesSearched; + nodesSearched = 0; + } + else + engine.trace_eval(); + } + else if (token == "setoption") + setoption(is); + else if (token == "position") + position(is); + else if (token == "ucinewgame") + { + engine.search_clear(); // search_clear may take a while + elapsed = now(); + } + } + + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); + + std::cerr << "\n===========================" // + << "\nTotal time (ms) : " << elapsed // + << "\nNodes searched : " << nodes // + << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; + + // reset callback, to not capture a dangling reference to nodesSearched + engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); +} + +void UCIEngine::benchmark(std::istream& args) { + // Probably not very important for a test this long, but include for completeness and sanity. + static constexpr int NUM_WARMUP_POSITIONS = 3; + + std::string token; + uint64_t nodes = 0, cnt = 1; + uint64_t nodesSearched = 0; + + engine.set_on_update_full([&](const Engine::InfoFull& i) { nodesSearched = i.nodes; }); + + engine.set_on_iter([](const auto&) {}); + engine.set_on_update_no_moves([](const auto&) {}); + engine.set_on_bestmove([](const auto&, const auto&) {}); + engine.set_on_verify_networks([](const auto&) {}); + + Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args); + + const int numGoCommands = count_if(setup.commands.begin(), setup.commands.end(), + [](const std::string& s) { return s.find("go ") == 0; }); + + TimePoint totalTime = 0; + + // Set options once at the start. + auto ss = std::istringstream("name Threads value " + std::to_string(setup.threads)); + setoption(ss); + ss = std::istringstream("name Hash value " + std::to_string(setup.ttSize)); + setoption(ss); + ss = std::istringstream("name UCI_Chess960 value false"); + setoption(ss); + + // Warmup + for (const auto& cmd : setup.commands) + { + std::istringstream is(cmd); + is >> std::skipws >> token; + + if (token == "go") + { + // One new line is produced by the search, so omit it here + std::cerr << "\rWarmup position " << cnt++ << '/' << NUM_WARMUP_POSITIONS; + + Search::LimitsType limits = parse_limits(is); + + TimePoint elapsed = now(); + + // Run with silenced network verification + engine.go(limits); + engine.wait_for_search_finished(); + + totalTime += now() - elapsed; + + nodes += nodesSearched; + nodesSearched = 0; + } + else if (token == "position") + position(is); + else if (token == "ucinewgame") + { + engine.search_clear(); // search_clear may take a while + } + + if (cnt > NUM_WARMUP_POSITIONS) + break; + } + + std::cerr << "\n"; + + cnt = 1; + nodes = 0; + + int numHashfullReadings = 0; + constexpr int hashfullAges[] = {0, 999}; // Only normal hashfull and touched hash. + int totalHashfull[std::size(hashfullAges)] = {0}; + int maxHashfull[std::size(hashfullAges)] = {0}; + + auto updateHashfullReadings = [&]() { + numHashfullReadings += 1; + + for (int i = 0; i < static_cast(std::size(hashfullAges)); ++i) + { + const int hashfull = engine.get_hashfull(hashfullAges[i]); + maxHashfull[i] = std::max(maxHashfull[i], hashfull); + totalHashfull[i] += hashfull; + } + }; + + engine.search_clear(); // search_clear may take a while + + for (const auto& cmd : setup.commands) + { + std::istringstream is(cmd); + is >> std::skipws >> token; + + if (token == "go") + { + // One new line is produced by the search, so omit it here + std::cerr << "\rPosition " << cnt++ << '/' << numGoCommands; + + Search::LimitsType limits = parse_limits(is); + + TimePoint elapsed = now(); + + // Run with silenced network verification + engine.go(limits); + engine.wait_for_search_finished(); + + totalTime += now() - elapsed; + + updateHashfullReadings(); + + nodes += nodesSearched; + nodesSearched = 0; + } + else if (token == "position") + position(is); + else if (token == "ucinewgame") + { + engine.search_clear(); // search_clear may take a while + } + } + + totalTime = std::max(totalTime, 1); // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); + + std::cerr << "\n"; + + static_assert( + std::size(hashfullAges) == 2 && hashfullAges[0] == 0 && hashfullAges[1] == 999, + "Hardcoded for display. Would complicate the code needlessly in the current state."); + + std::string threadBinding = engine.thread_binding_information_as_string(); + if (threadBinding.empty()) + threadBinding = "none"; + + // clang-format off + + std::cerr << "===========================" + << "\nVersion : " + << engine_version_info() + // "\nCompiled by : " + << compiler_info() + << "Large pages : " << (has_large_pages() ? "yes" : "no") + << "\nUser invocation : " << BenchmarkCommand << " " + << setup.originalInvocation << "\nFilled invocation : " << BenchmarkCommand + << " " << setup.filledInvocation + << "\nAvailable processors : " << engine.get_numa_config_as_string() + << "\nThread count : " << setup.threads + << "\nThread binding : " << threadBinding + << "\nTT size [MiB] : " << setup.ttSize + << "\nHash max, avg [per mille] : " + << "\n single search : " << maxHashfull[0] << ", " + << totalHashfull[0] / numHashfullReadings + << "\n single game : " << maxHashfull[1] << ", " + << totalHashfull[1] / numHashfullReadings + << "\nTotal nodes searched : " << nodes + << "\nTotal search time [s] : " << totalTime / 1000.0 + << "\nNodes/second : " << 1000 * nodes / totalTime << std::endl; + + // clang-format on + + init_search_update_listeners(); +} + +void UCIEngine::setoption(std::istringstream& is) { + engine.wait_for_search_finished(); + engine.get_options().setoption(is); +} + +std::uint64_t UCIEngine::perft(const Search::LimitsType& limits) { + auto nodes = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return nodes; +} + +void UCIEngine::position(std::istringstream& is) { + std::string token, fen; + + is >> token; + + if (token == "startpos") + { + fen = StartFEN; + is >> token; // Consume the "moves" token, if any + } + else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + + std::vector moves; + + while (is >> token) + { + moves.push_back(token); + } + + engine.set_position(fen, moves); +} + +namespace { + +struct WinRateParams { + double a; + double b; +}; + +WinRateParams win_rate_params(const Position& pos) { + + int material = pos.count() + 3 * pos.count() + 3 * pos.count() + + 5 * pos.count() + 9 * pos.count(); + + // The fitted model only uses data for material counts in [17, 78], and is anchored at count 58. + double m = std::clamp(material, 17, 78) / 58.0; + + // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model + constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; + constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; + + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + + return {a, b}; +} + +// The win rate model is 1 / (1 + exp((a - eval) / b)), where a = p_a(material) and b = p_b(material). +// It fits the LTC fishtest statistics rather accurately. +int win_rate_model(Value v, const Position& pos) { + + auto [a, b] = win_rate_params(pos); + + // Return the win rate in per mille units, rounded to the nearest integer. + return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); +} +} + +std::string UCIEngine::format_score(const Score& s) { + constexpr int TB_CP = 20000; + const auto format = + overload{[](Score::Mate mate) -> std::string { + auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; + return std::string("mate ") + std::to_string(m); + }, + [](Score::Tablebase tb) -> std::string { + return std::string("cp ") + + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); + }, + [](Score::InternalUnits units) -> std::string { + return std::string("cp ") + std::to_string(units.value); + }}; + + return s.visit(format); +} + +// Turns a Value to an integer centipawn number, +// without treatment of mate and similar special scores. +int UCIEngine::to_cp(Value v, const Position& pos) { + + // In general, the score can be defined via the WDL as + // (log(1/L - 1) - log(1/W - 1)) / (log(1/L - 1) + log(1/W - 1)). + // Based on our win_rate_model, this simply yields v / a. + + auto [a, b] = win_rate_params(pos); + + return std::round(100 * int(v) / a); +} + +std::string UCIEngine::wdl(Value v, const Position& pos) { + std::stringstream ss; + + int wdl_w = win_rate_model(v, pos); + int wdl_l = win_rate_model(-v, pos); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} + +std::string UCIEngine::square(Square s) { + return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; +} + +std::string UCIEngine::move(Move m, bool chess960) { + if (m == Move::none()) + return "(none)"; + + if (m == Move::null()) + return "0000"; + + Square from = m.from_sq(); + Square to = m.to_sq(); + + if (m.type_of() == CASTLING && !chess960) + to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); + + std::string move = square(from) + square(to); + + if (m.type_of() == PROMOTION) + move += " pnbrqk"[m.promotion_type()]; + + return move; +} + + +std::string UCIEngine::to_lower(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return std::tolower(c); }); + + return str; +} + +Move UCIEngine::to_move(const Position& pos, std::string str) { + str = to_lower(str); + + for (const auto& m : MoveList(pos)) + if (str == move(m, pos.is_chess960())) + return m; + + return Move::none(); +} + +void UCIEngine::on_update_no_moves(const Engine::InfoShort& info) { + sync_cout << "info depth " << info.depth << " score " << format_score(info.score) << sync_endl; +} + +void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) { + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " seldepth " << info.selDepth // + << " multipv " << info.multiPV // + << " score " << format_score(info.score); // + + if (showWDL) + ss << " wdl " << info.wdl; + + if (!info.bound.empty()) + ss << " " << info.bound; + + ss << " nodes " << info.nodes // + << " nps " << info.nps // + << " hashfull " << info.hashfull // + << " tbhits " << info.tbHits // + << " time " << info.timeMs // + << " pv " << info.pv; // + + sync_cout << ss.str() << sync_endl; +} + +void UCIEngine::on_iter(const Engine::InfoIter& info) { + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " currmove " << info.currmove // + << " currmovenumber " << info.currmovenumber; // + + sync_cout << ss.str() << sync_endl; +} + +void UCIEngine::on_bestmove(std::string_view bestmove, std::string_view ponder) { + sync_cout << "bestmove " << bestmove; + if (!ponder.empty()) + std::cout << " ponder " << ponder; + std::cout << sync_endl; +} + +} // namespace Stockfish diff --git a/stockfish/src/uci.h b/stockfish/src/uci.h new file mode 100644 index 0000000000000000000000000000000000000000..5c1c07f7b1880bd7a3de2a54d1e274756a63df12 --- /dev/null +++ b/stockfish/src/uci.h @@ -0,0 +1,80 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UCI_H_INCLUDED +#define UCI_H_INCLUDED + +#include +#include +#include +#include + +#include "engine.h" +#include "misc.h" +#include "search.h" + +namespace Stockfish { + +class Position; +class Move; +class Score; +enum Square : int; +using Value = int; + +class UCIEngine { + public: + UCIEngine(int argc, char** argv); + + void loop(); + + static int to_cp(Value v, const Position& pos); + static std::string format_score(const Score& s); + static std::string square(Square s); + static std::string move(Move m, bool chess960); + static std::string wdl(Value v, const Position& pos); + static std::string to_lower(std::string str); + static Move to_move(const Position& pos, std::string str); + + static Search::LimitsType parse_limits(std::istream& is); + + auto& engine_options() { return engine.get_options(); } + + private: + Engine engine; + CommandLine cli; + + static void print_info_string(std::string_view str); + + void go(std::istringstream& is); + void bench(std::istream& args); + void benchmark(std::istream& args); + void position(std::istringstream& is); + void setoption(std::istringstream& is); + std::uint64_t perft(const Search::LimitsType&); + + static void on_update_no_moves(const Engine::InfoShort& info); + static void on_update_full(const Engine::InfoFull& info, bool showWDL); + static void on_iter(const Engine::InfoIter& info); + static void on_bestmove(std::string_view bestmove, std::string_view ponder); + + void init_search_update_listeners(); +}; + +} // namespace Stockfish + +#endif // #ifndef UCI_H_INCLUDED diff --git a/stockfish/src/ucioption.cpp b/stockfish/src/ucioption.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a76bd3ace96b40e247a513110ab98500d67cbdc1 --- /dev/null +++ b/stockfish/src/ucioption.cpp @@ -0,0 +1,213 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "ucioption.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" + +namespace Stockfish { + +bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const { + + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); +} + +void OptionsMap::add_info_listener(InfoListener&& message_func) { info = std::move(message_func); } + +void OptionsMap::setoption(std::istringstream& is) { + std::string token, name, value; + + is >> token; // Consume the "name" token + + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; + + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; + + if (options_map.count(name)) + options_map[name] = value; + else + sync_cout << "No such option: " << name << sync_endl; +} + +const Option& OptionsMap::operator[](const std::string& name) const { + auto it = options_map.find(name); + assert(it != options_map.end()); + return it->second; +} + +// Inits options and assigns idx in the correct printing order +void OptionsMap::add(const std::string& name, const Option& option) { + if (!options_map.count(name)) + { + static size_t insert_order = 0; + + options_map[name] = option; + + options_map[name].parent = this; + options_map[name].idx = insert_order++; + } + else + { + std::cerr << "Option \"" << name << "\" was already added!" << std::endl; + std::exit(EXIT_FAILURE); + } +} + + +std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } + +Option::Option(const OptionsMap* map) : + parent(map) {} + +Option::Option(const char* v, OnChange f) : + type("string"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = currentValue = v; +} + +Option::Option(bool v, OnChange f) : + type("check"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = currentValue = (v ? "true" : "false"); +} + +Option::Option(OnChange f) : + type("button"), + min(0), + max(0), + on_change(std::move(f)) {} + +Option::Option(double v, int minv, int maxv, OnChange f) : + type("spin"), + min(minv), + max(maxv), + on_change(std::move(f)) { + defaultValue = currentValue = std::to_string(v); +} + +Option::Option(const char* v, const char* cur, OnChange f) : + type("combo"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = v; + currentValue = cur; +} + +Option::operator int() const { + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); +} + +Option::operator std::string() const { + assert(type == "string"); + return currentValue; +} + +bool Option::operator==(const char* s) const { + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); +} + +bool Option::operator!=(const char* s) const { return !(*this == s); } + + +// Updates currentValue and triggers on_change() action. It's up to +// the GUI to check for option's limits, but we could receive the new value +// from the user by console window, so let's check the bounds anyway. +Option& Option::operator=(const std::string& v) { + + assert(!type.empty()); + + if ((type != "button" && type != "string" && v.empty()) + || (type == "check" && v != "true" && v != "false") + || (type == "spin" && (std::stof(v) < min || std::stof(v) > max))) + return *this; + + if (type == "combo") + { + OptionsMap comboMap; // To have case insensitive compare + std::string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap.add(token, Option()); + if (!comboMap.count(v) || v == "var") + return *this; + } + + if (type == "string") + currentValue = v == "" ? "" : v; + else if (type != "button") + currentValue = v; + + if (on_change) + { + const auto ret = on_change(*this); + + if (ret && parent != nullptr && parent->info != nullptr) + parent->info(ret); + } + + return *this; +} + +std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto& it : om.options_map) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; + + if (o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; + + else if (o.type == "string") + { + std::string defaultValue = o.defaultValue.empty() ? "" : o.defaultValue; + os << " default " << defaultValue; + } + + else if (o.type == "spin") + os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " + << o.max; + + break; + } + + return os; +} +} diff --git a/stockfish/src/ucioption.h b/stockfish/src/ucioption.h new file mode 100644 index 0000000000000000000000000000000000000000..3d7386c30a49992b993a8395c326099fbbea5f8b --- /dev/null +++ b/stockfish/src/ucioption.h @@ -0,0 +1,106 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UCIOPTION_H_INCLUDED +#define UCIOPTION_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace Stockfish { +// Define a custom comparator, because the UCI options should be case-insensitive +struct CaseInsensitiveLess { + bool operator()(const std::string&, const std::string&) const; +}; + +class OptionsMap; + +// The Option class implements each option as specified by the UCI protocol +class Option { + public: + using OnChange = std::function(const Option&)>; + + Option(const OptionsMap*); + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(double v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char* cur, OnChange = nullptr); + + Option& operator=(const std::string&); + operator int() const; + operator std::string() const; + bool operator==(const char*) const; + bool operator!=(const char*) const; + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + int operator<<(const Option&) = delete; + + private: + friend class OptionsMap; + friend class Engine; + friend class Tune; + + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; + const OptionsMap* parent = nullptr; +}; + +class OptionsMap { + public: + using InfoListener = std::function)>; + + OptionsMap() = default; + OptionsMap(const OptionsMap&) = delete; + OptionsMap(OptionsMap&&) = delete; + OptionsMap& operator=(const OptionsMap&) = delete; + OptionsMap& operator=(OptionsMap&&) = delete; + + void add_info_listener(InfoListener&&); + + void setoption(std::istringstream&); + + const Option& operator[](const std::string&) const; + + void add(const std::string&, const Option& option); + + std::size_t count(const std::string&) const; + + private: + friend class Engine; + friend class Option; + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + // The options container is defined as a std::map + using OptionsStore = std::map; + + OptionsStore options_map; + InfoListener info; +}; + +} +#endif // #ifndef UCIOPTION_H_INCLUDED diff --git a/stockfish/stockfish-ubuntu-x86-64-avx2 b/stockfish/stockfish-ubuntu-x86-64-avx2 new file mode 100644 index 0000000000000000000000000000000000000000..a07c8810c6106c703c821efe37c75f208bcd76f9 --- /dev/null +++ b/stockfish/stockfish-ubuntu-x86-64-avx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fecbc0b26454b62be5e3b237b58dc5666401b56e520aeb1b0bf8f53fa8f2ef3 +size 78768416 diff --git a/stockfish/wiki/Advanced-topics.md b/stockfish/wiki/Advanced-topics.md new file mode 100644 index 0000000000000000000000000000000000000000..27f56aa87f886728dc175345ca75560f45ad8d03 --- /dev/null +++ b/stockfish/wiki/Advanced-topics.md @@ -0,0 +1,198 @@ +## Syzygy tablebases + +If the engine is searching a position that is not in the tablebases (e.g. a position with 8 pieces), it will access the tablebases (TB) during the search. If the engine reports a very large score (near 200.00), it means that it has found a winning line into a tablebase position. For general play, tablebases bring only a limited increase in strength (see [our Elo measurements](Useful-data#elo-gain-using-syzygy)). + +When the engine is given a position to search that is in the tablebases, it will use the tablebases at the beginning of the search to preselect all good moves, i.e. all moves that preserve the win or preserve the draw while taking into account the 50-move rule. It will then perform a search only on those moves. The engine **will not move immediately**, unless there is only a single good move. The engine **will search for the shortest possible mate**, even though the position is known to be won. + +It is therefore clear that this behavior is not identical to what one might be used to with Nalimov tablebases. There are technical reasons for this difference, the main technical reason being that Nalimov tablebases use the DTM metric (distance-to-mate), while the Syzygy tablebases use a variation of the DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move counter). This special metric is one of the reasons that the Syzygy tablebases are more compact than Nalimov tablebases, while still storing all the information needed for optimal play and in addition being able to take into account the 50-move rule. + +The use of 7-piece tablebases is possible with Stockfish and requires around 17TB of storage. Furthermore, the system must allow a single process to open all 1511 files, which sometimes requires increasing the default limit (e.g. `ulimit -n`). + +--- + +## Large Pages + +Stockfish supports large pages on Linux and Windows. Large pages make the hash access more efficient, improving the engine speed, especially on large hash sizes. +The support is automatic, Stockfish attempts to use large pages when available and will fall back to regular memory allocation when this is not the case. +Typical increases are 5-10% in terms of nodes per second, but speed increases up to 30% have been measured. + +### Linux + +Large page support on Linux is obtained by the Linux kernel transparent huge pages functionality. Typically, transparent huge pages are already enabled, and no configuration is needed. + +### Windows + +The use of large pages requires "Lock Pages in Memory" privilege. See [Enable the Lock Pages in Memory Option (Windows)](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows) on how to enable this privilege, then run [RAMMap](https://docs.microsoft.com/en-us/sysinternals/downloads/rammap) to double-check that large pages are used. +We suggest that you reboot your computer after you have enabled large pages, because long Windows sessions suffer from memory fragmentation, which may prevent Stockfish from getting large pages: a fresh session is better in this regard. + +--- + +## Measure the speed of Stockfish + +The "speed of Stockfish" is the number of nodes (positions) Stockfish can search per second. +Nodes per second (nps) is a useful benchmark number as the same version of Stockfish playing will play stronger with larger nps. +Different versions of Stockfish will play at different nps, for example, if the NNUE network architecture changes, but in this case the nps difference is not related to the strength difference. + +> [!NOTE] +> * Stop all other applications when measuring the speedup of Stockfish +> * Run at least 20 default benches (depth 13) for each build of Stockfish to have accurate measures +> * A speedup of 0.3% could be meaningless (i.e. within the measurement noise) + +To measure the speed of several builds of Stockfish, use one of these applications: +* All OS: + * [pyshbench](https://github.com/hazzl/pyshbench): Latest release [pyshbench](https://github.com/hazzl/pyshbench/archive/master.zip) + * bash script `bench_parallel.sh` (run `bash bench_parallel.sh` for the help) + it might be that you have to install gawk as well on your system to not get syntax errors. + +
Click to view + + ```bash + #!/bin/bash + _bench () { + ${1} << EOF > /dev/null 2>> ${2} + bench 16 1 ${depth} default depth + EOF + } + # _bench function customization example + # setoption name SyzygyPath value C:\table_bases\wdl345;C:\table_bases\dtz345 + # bench 128 4 ${depth} default depth + + if [[ ${#} -ne 4 ]]; then + cat << EOF + usage: ${0} ./stockfish_base ./stockfish_test depth n_runs + fast bench: + ${0} ./stockfish_base ./stockfish_test 13 10 + slow bench: + ${0} ./stockfish_base ./stockfish_test 20 10 + EOF + exit 1 + fi + + sf_base=${1} + sf_test=${2} + depth=${3} + n_runs=${4} + + # preload of CPU/cache/memory + printf "preload CPU" + (_bench ${sf_base} sf_base0.txt)& + (_bench ${sf_test} sf_test0.txt)& + wait + + # temporary files initialization + : > sf_base0.txt + : > sf_test0.txt + : > sf_temp0.txt + + # bench loop: SMP bench with background subshells + for ((k=1; k<=${n_runs}; k++)); do + printf "\rrun %3d /%3d" ${k} ${n_runs} + + # swap the execution order to avoid bias + if [ $((k%2)) -eq 0 ]; then + (_bench ${sf_base} sf_base0.txt)& + (_bench ${sf_test} sf_test0.txt)& + wait + else + (_bench ${sf_test} sf_test0.txt)& + (_bench ${sf_base} sf_base0.txt)& + wait + fi + done + + # text processing to extract nps values + cat sf_base0.txt | grep second | grep -Eo '[0-9]{1,}' > sf_base1.txt + cat sf_test0.txt | grep second | grep -Eo '[0-9]{1,}' > sf_test1.txt + + for ((k=1; k<=${n_runs}; k++)); do + echo ${k} >> sf_temp0.txt + done + + printf "\rrun sf_base sf_test diff\n" + paste sf_temp0.txt sf_base1.txt sf_test1.txt | awk '{printf "%3d %8d %8d %8+d\n", $1, $2, $3, $3-$2}' + #paste sf_temp0.txt sf_base1.txt sf_test1.txt | awk '{printf "%3d\t%8d\t%8d\t%7+d\n", $1, $2, $3, $3-$2}' + paste sf_base1.txt sf_test1.txt | awk '{printf "%d\t%d\t%d\n", $1, $2, $2-$1}' > sf_temp0.txt + + # compute: sample mean, 1.96 * std of sample mean (95% of samples), speedup + # std of sample mean = sqrt(NR/(NR-1)) * (std population) / sqrt(NR) + cat sf_temp0.txt | awk '{sum1 += $1 ; sumq1 += $1**2 ;sum2 += $2 ; sumq2 += $2**2 ;sum3 += $3 ; sumq3 += $3**2 } END {printf "\nsf_base = %8d +/- %6d (95%)\nsf_test = %8d +/- %6d (95%)\ndiff = %8d +/- %6d (95%)\nspeedup = %.5f% +/- %.3f% (95%)\n\n", sum1/NR , 1.96 * sqrt(sumq1/NR - (sum1/NR)**2)/sqrt(NR-1) , sum2/NR , 1.96 * sqrt(sumq2/NR - (sum2/NR)**2)/sqrt(NR-1) , sum3/NR , 1.96 * sqrt(sumq3/NR - (sum3/NR)**2)/sqrt(NR-1) , 100*(sum2 - sum1)/sum1 , 100 * (1.96 * sqrt(sumq3/NR - (sum3/NR)**2)/sqrt(NR-1)) / (sum1/NR) }' + + # remove temporary files + rm -f sf_base0.txt sf_test0.txt sf_temp0.txt sf_base1.txt sf_test1.txt + ``` +
+ +
Bench two git branches with bench_parallel + + ```bash + #!/bin/bash + + if [ "$#" -ne 5 ]; then + echo "Usage: $0 branch1 branch2 depth runs compile_flags" + exit 1 + fi + + BRANCH1=$1 + BRANCH2=$2 + DEPTH=$3 + RUNS=$4 + COMPILE_FLAGS=$5 + + set -e + + echo "Switching to $BRANCH1 and building..." + git switch $BRANCH1 + make clean + make -j profile-build EXE=stockfish-$BRANCH1 $COMPILE_FLAGS + + echo "Switching to $BRANCH2 and building..." + git switch $BRANCH2 + make clean + make -j profile-build EXE=stockfish-$BRANCH2 $COMPILE_FLAGS + + echo "Running bench_parallel.sh with stockfish-$BRANCH1 and stockfish-$BRANCH2..." + ./bench_parallel.sh ./stockfish-$BRANCH1 ./stockfish-$BRANCH2 $DEPTH $RUNS + ``` +
+ * [`speedtest`](UCI-&-Commands#speedtest) command + +* Windows only: + * [FishBench](https://github.com/zardav/FishBench): Latest release [Fishbench v6.0](https://github.com/zardav/FishBench/releases/download/v6.0/FishBench.zip) + * [Buildtester](http://software.farseer.org/): Latest release [Buildtester 1.4.7.0](http://software.farseer.org/Software/BuildTester.7z) + +--- + +## Cluster version + +There is a branch developed with a MPI cluster implementation of Stockfish, allowing stockfish to run on clusters of compute nodes connected with a high-speed network. See https://github.com/official-stockfish/Stockfish/pull/1571 for some discussion of the initial implementation and https://github.com/official-stockfish/Stockfish/pull/1931 for some early performance results. + +Feedback on this branch is welcome! Here are some git commands for people interested to test this MPI/Cluster idea: + +1. If you don't have the cluster branch yet on your local git repository, you can download the latest state of the [`official-stockfish/cluster`](https://github.com/official-stockfish/Stockfish/tree/cluster) branch, then switch to it with the following commands: + ```bash + git fetch official cluster:cluster + git checkout -f cluster + ``` + +2. After switching to the cluster branch as above, see the README.md for detailed instructions on how to compile and run the branch. TL;DR: + ```bash + make clean + make -j ARCH=x86-64-avx2 COMPCXX=mpic++ build + mpirun -np 4 ./stockfish bench + ``` + +--- + +## Classical versus NNUE evaluation + +Current versions of Stockfish use a strong neural network (NNUE) to evaluate positions. The venerable "Classical" evaluation, which was about 250 Elo points weaker, was removed from the codebase in August 2023. + +Both approaches assign a value to a position that is used in alpha-beta (PVS) search to find the best move. The classical evaluation computed this value as a function of various chess concepts, handcrafted by experts, tested and tuned using fishtest. The NNUE evaluation computes this value with a neural network based on basic inputs (e.g. piece positions only). The network is optimized and trained on the evaluations of millions of positions at moderate search depth. + +The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward. It can be evaluated efficiently on CPUs, and exploits the fact that only parts of the neural network need to be updated after a typical chess move. [The nodchip repository](https://github.com/nodchip/Stockfish) provided the first version of the needed tools to train and develop the NNUE networks. Today, more advanced training tools are available in [the nnue-pytorch repository](https://github.com/official-stockfish/nnue-pytorch), while data generation tools are available in [a dedicated branch](https://github.com/official-stockfish/Stockfish/tree/tools). + +On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation results in much stronger playing strength, even if the nodes per second computed by the engine is somewhat lower (roughly 50% of nps is typical). + +> [!NOTE] +> 1. The NNUE evaluation depends on the Stockfish binary and the network parameter file (see the `EvalFile` UCI option). Not every parameter file is compatible with a given Stockfish binary, but the default value of the EvalFile UCI option is the name of a network that is guaranteed to be compatible with that binary. +> 2. To use the NNUE evaluation, the additional data file with neural network parameters needs to be available. Normally, this file is already embedded in the binary or it can be downloaded. The filename for the default (recommended) net can be found as the default value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue` (for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from `https://tests.stockfishchess.org/api/nn/[filename]` replacing `[filename]` as needed. diff --git a/stockfish/wiki/Compiling-from-source.md b/stockfish/wiki/Compiling-from-source.md new file mode 100644 index 0000000000000000000000000000000000000000..0521e6eecb963b6100e91f589137d2b288fe9087 --- /dev/null +++ b/stockfish/wiki/Compiling-from-source.md @@ -0,0 +1,919 @@ +## General + +`make target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]` + +### Targets + +``` +help > Display architecture details +profile-build > standard build with profile-guided optimization +build > skip profile-guided optimization +net > Download the default nnue nets +strip > Strip executable +install > Install executable +clean > Clean up +``` + +### Archs + +``` +native > select the best architecture for the host processor (default) +x86-64-vnni512 > x86 64-bit with vnni 512bit support +x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide +x86-64-avx512 > x86 64-bit with avx512 support +x86-64-avxvnni > x86 64-bit with vnni 256bit support +x86-64-bmi2 > x86 64-bit with bmi2 support +x86-64-avx2 > x86 64-bit with avx2 support +x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support +x86-64-modern > deprecated, currently x86-64-sse41-popcnt +x86-64-ssse3 > x86 64-bit with ssse3 support +x86-64-sse3-popcnt > x86 64-bit with sse3 compile and popcnt support +x86-64 > x86 64-bit generic (with sse2 support) +x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support +x86-32-sse2 > x86 32-bit with sse2 support +x86-32 > x86 32-bit generic (with mmx compile support) +ppc-64 > PPC 64-bit +ppc-64-altivec > PPC 64-bit with altivec support +ppc-64-vsx > PPC 64-bit with vsx support +ppc-32 > PPC 32-bit +armv7 > ARMv7 32-bit +armv7-neon > ARMv7 32-bit with popcnt and neon +armv8 > ARMv8 64-bit with popcnt and neon +armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support +e2k > Elbrus 2000 +apple-silicon > Apple silicon ARM64 +general-64 > unspecified 64-bit +general-32 > unspecified 32-bit +riscv64 > RISC-V 64-bit +loongarch64 > LoongArch 64-bit +loongarch64-lsx > LoongArch 64-bit with SIMD eXtension +loongarch64-lasx > LoongArch 64-bit with Advanced SIMD eXtension +``` + +### Compilers + +``` +gcc > GNU compiler (default) +mingw > GNU compiler with MinGW under Windows +clang > LLVM Clang compiler +icx > Intel oneAPI DPC++/C++ Compiler +ndk > Google NDK to cross-compile for Android +``` + +### Simple examples + +If you don't know what to do, you likely want to run: + +Fast compile for most common modern CPUs +```bash +make -j build +``` +Slow compile for 64-bit systems +```bash +make -j build ARCH=x86-64 +``` +Slow compile for 32-bit systems +```bash +make -j build ARCH=x86-32 +``` + +### Advanced examples + +For experienced users looking for performance: + +```bash +# Providing no ARCH so it will try to find the best ARCH for you +make -j profile-build +``` +```bash +make -j profile-build ARCH=x86-64-bmi2 +``` +```bash +make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0 +``` +```bash +make -j build ARCH=x86-64-ssse3 COMP=clang +``` + +_See also: [How to lower compilation time](#lower-compilation-time) and [How to optimize for your CPU](#optimize-for-your-cpu)._ + +--- + +## Linux + +On Unix-like systems, it should be easy to compile Stockfish directly from the source code with the included Makefile in the folder `src`. + +In general it is recommended to run `make help` to see a list of make targets with corresponding descriptions. + +```bash +cd src +make help +make -j profile-build ARCH=x86-64-avx2 +``` + +--- + +## Windows + +### About MSYS2 & MinGW-w64 + +MSYS2 is a software distribution and building platform for Windows. It provides a Unix-like environment, a command line interface, and a software repository, making it easy to install software on Windows or build software on Windows with either the GCC compiler or the Clang/LLVM compiler and using the Microsoft Visual C++ Runtime (mvscrt, shipped with all Windows versions) or the newer Microsoft Universal C Runtime (ucrt, shipped by default starting with Windows 10). + +MSYS2 consists of several subsystems, `msys2`, `mingw32`, and `mingw64`: +* The `mingw32` and `mingw64` subsystems are native Windows applications that use either the mvscrt or the ucrt. +* The `msys2` subsystem provides an emulated mostly-POSIX-compliant environment based on Cygwin. + +Each subsystem has an associated "terminal/shell", which is essentially a set of environment variables that allows the subsystems to co-operate properly: +* `MSYS2 MinGW x64`, to build Windows-native 64-bit applications with GCC compiler using mvscrt. +* `MSYS2 MinGW x86`, to build Windows-native 32-bit applications using GCC compiler using mvscrt. +* `MSYS2 MSYS`, to build POSIX applications using the Cygwin compatibility layer. +* `MSYS2 MinGW UCRT x64`, to build Windows-native 64-bit applications with GCC compiler using ucrt. +* `MSYS2 MinGW Clang x64`, to build Windows-native 64-bit applications with Clang/LLVM compiler using ucrt. + +Refer to the [MSYS2 homepage](https://www.msys2.org/) for more detailed information on the MSYS2 subsystems and terminals/shells. + +### Installing MSYS2 + +### Install MSYS2 with WinGet +[WinGet](https://learn.microsoft.com/en-us/windows/package-manager/winget/) is the Microsoft command line tool enabling users to discover, install, upgrade, remove and configure applications on Windows 10, Windows 11, and Windows Server 2025 computers. This tool is the client interface to the Windows Package Manager service. +1. Open a powershell and run: +```cmd +winget install MSYS2.MSYS2 +``` + +### Install MSYS2 with Chocolatey (for Windows 8) +[Chocolatey](https://chocolatey.org/) is a third-party command line package manager for Windows 8, Windows 10 and Windows 11, always run Chocolatey commands in a powershell/cmd with administrator rights (right click on `Start` menu, select `Windows Powershell (Admin)` or `Command Prompt (Admin)`): +1. Open a powershell (admin) (not a cmd) and copy the official [Chocolatey install command](https://chocolatey.org/install) to install Chocolatey +2. In a powershell/cmd (admin) execute the command: +```cmd +choco install msys2 -y +``` + +As alternative write this text file `install_choco_msys2.cmd`, right click and select `Run as administrator`: +
Click to view + +```cmd +@echo off +::https://chocolatey.org/install +::https://chocolatey.org/courses/installation/installing?method=installing-chocolatey?quiz=true + +::download and run install.ps1 +"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" +::install msys2 +choco install msys2 -y +``` +
+ +### Install MSYS2 with the official installer +1. Download and start the [one-click installer for MSYS2](https://www.msys2.org/). MSYS2 no longer support an installer for Windows 32-bit, the [latest provided](https://github.com/msys2/msys2-installer/releases/tag/2020-05-17) is not able to install packages. +2. The installer runs a `MSYS2 MSYS` shell as a last step. Update the core packages by typing and executing `pacman -Syuu`. When finished, close the `MSYS2 MSYS` shell. + +With MSYS2 installed to `C:\msys64` your home directory will be `C:\msys64\home\`. Note that within the MSYS2 shell, paths are written in Unix-like way: + +* Windows path: `C:\msys64` +* Unix-like path: `/c/msys64` +* Windows path: `C:\msys64\home` +* Unix-like path: `/home` or `/c/msys64/home` + +> [!TIP] +> You can also use `ls` to list the files and folders in a directory, similar to how you would use `dir` in Windows. + +### GCC +This works with all the Windows versions. + +1. Using your favorite text editor, copy and paste the following bash script, calling it `makefish.sh`: + +
64-bit Windows + +```bash +#!/bin/bash +# makefish.sh + +# install packages if not already installed +pacman -S --noconfirm --needed unzip make mingw-w64-x86_64-gcc + +branch='master' +github_user='official-stockfish' + +# download the Stockfish source code +wget -O ${branch}.zip https://github.com/${github_user}/Stockfish/archive/refs/heads/${branch}.zip +unzip -o ${branch}.zip +cd Stockfish-${branch}/src +file_nnue=$(grep 'define.*EvalFileDefaultName' evaluate.h | grep -Ewo 'nn-[a-z0-9]{12}.nnue') +ls *.nnue | grep -v ${file_nnue} | xargs -d '\n' -r rm -- + +# check all given flags +check_flags () { + for flag; do + printf '%s\n' "$flags" | grep -q -w "$flag" || return 1 + done +} + +# find the CPU architecture +output=$(g++ -Q -march=native --help=target) +flags=$(printf '%s\n' "$output" | awk '/\[enabled\]/ {print substr($1, 3)}' | tr '\n' ' ') +arch=$(printf '%s\n' "$output" | awk '/march/ {print $NF; exit}' | tr -d '[:space:]') + +if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then + arch_cpu='x86-64-vnni256' +elif check_flags 'avx512f' 'avx512bw'; then + arch_cpu='x86-64-avx512' +elif check_flags 'bmi2' && [ $arch != 'znver1' ] && [ $arch != 'znver2' ]; then + arch_cpu='x86-64-bmi2' +elif check_flags 'avx2'; then + arch_cpu='x86-64-avx2' +elif check_flags 'sse4.1' 'popcnt'; then + arch_cpu='x86-64-sse41-popcnt' +elif check_flags 'ssse3'; then + arch_cpu='x86-64-ssse3' +elif check_flags 'sse3' 'popcnt'; then + arch_cpu='x86-64-sse3-popcnt' +else + arch_cpu='x86-64' +fi + +# build the fastest Stockfish executable +make -j profile-build ARCH=${arch_cpu} COMP=mingw +make strip +mv stockfish.exe ../../stockfish_${arch_cpu}.exe +make clean +cd +``` +
+ +
32-bit Windows + +```bash +#!/bin/bash +# makefish.sh + +# install packages if not already installed +pacman -S --noconfirm --needed unzip make mingw-w64-i686-gcc + +branch='master' +github_user='official-stockfish' + +# download the Stockfish source code +wget -O ${branch}.zip https://github.com/${github_user}/Stockfish/archive/refs/heads/${branch}.zip +unzip -o ${branch}.zip +cd Stockfish-${branch}/src +file_nnue=$(grep 'define.*EvalFileDefaultName' evaluate.h | grep -Ewo 'nn-[a-z0-9]{12}.nnue') +ls *.nnue | grep -v ${file_nnue} | xargs -d '\n' -r rm -- + +# find the CPU architecture +gcc_enabled=$(g++ -Q -march=native --help=target | grep "\[enabled\]") +gcc_arch=$(g++ -Q -march=native --help=target | grep "march") + +if [[ "${gcc_enabled}" =~ "-mpopcnt " && "${gcc_enabled}" =~ "-msse4.1 " ]] ; then + arch_cpu="x86-32-sse41-popcnt" +elif [[ "${gcc_enabled}" =~ "--msse2 " ]] ; then + arch_cpu="x86-32-sse2" +else + arch_cpu="x86-32" +fi + +# build the fastest Stockfish executable +make -j profile-build ARCH=${arch_cpu} COMP=mingw +make strip +mv stockfish.exe ../../stockfish_${arch_cpu}.exe +make clean +cd +``` +
+ +2. Start a `MSYS2 MinGW x64` shell (not a `MSYS2 MSYS` one), `C:\msys64\mingw64.exe`, or start a `MSYS2 MinGW x86` shell, `C:\msys64\mingw32.exe`, to build a 32 bit application. +3. Navigate to wherever you saved the script (e.g. type and execute `cd '/d/Program Files/Stockfish'` to navigate to `D:\Program Files\Stockfish`). +4. Run the script by typing and executing `bash makefish.sh`. + +### Clang/LLVM +With Windows version older than Windows 10 you could need to install the Microsoft Windows Universal C Runtime. + +1. Using your favorite text editor, copy and paste the following bash script, calling it `makefish.sh`: + +
64-bit Windows + +```bash +#!/bin/bash +# makefish.sh + +# install packages if not already installed +pacman -S --noconfirm --needed unzip make mingw-w64-clang-x86_64-clang + +branch='master' +github_user='official-stockfish' + +# download the Stockfish source code +wget -O ${branch}.zip https://github.com/${github_user}/Stockfish/archive/refs/heads/${branch}.zip +unzip -o ${branch}.zip +cd Stockfish-${branch}/src +file_nnue=$(grep 'define.*EvalFileDefaultName' evaluate.h | grep -Ewo 'nn-[a-z0-9]{12}.nnue') +ls *.nnue | grep -v ${file_nnue} | xargs -d '\n' -r rm -- + +# check all given flags +check_flags () { + for flag; do + printf '%s\n' "$flags" | grep -q -w "$flag" || return 1 + done +} + +# find the CPU architecture +output=$(clang++ -E - -march=native -### 2>&1) +flags=$(printf '%s\n' "$output" | grep -o '"-target-feature" "[^"]*"' | cut -d '"' -f 4 | grep '^\+' | cut -c 2- | tr '\n' ' ') +arch=$(printf '%s\n' "$output" | grep -o '"-target-cpu" "[^"]*"' | cut -d '"' -f 4) + +if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then + arch_cpu='x86-64-vnni256' +elif check_flags 'avx512f' 'avx512bw'; then + arch_cpu='x86-64-avx512' +elif check_flags 'bmi2' && [ $arch != 'znver1' ] && [ $arch != 'znver2' ]; then + arch_cpu='x86-64-bmi2' +elif check_flags 'avx2'; then + arch_cpu='x86-64-avx2' +elif check_flags 'sse4.1' 'popcnt'; then + arch_cpu='x86-64-sse41-popcnt' +elif check_flags 'ssse3'; then + arch_cpu='x86-64-ssse3' +elif check_flags 'sse3' 'popcnt'; then + arch_cpu='x86-64-sse3-popcnt' +else + arch_cpu='x86-64' +fi + +# build the fastest Stockfish executable +make -j profile-build ARCH=${arch_cpu} COMP=clang +make strip COMP=clang +mv stockfish.exe ../../stockfish_${arch_cpu}.exe +make clean COMP=clang +cd +``` +
+ +2. Start a `MSYS2 MinGW Clang x64` shell, `C:\msys64\clang64.exe`. +3. Navigate to wherever you saved the script (e.g. type and execute `cd '/d/Program Files/Stockfish'` to navigate to `D:\Program Files\Stockfish`). +4. Run the script by typing and executing `bash makefish.sh`. + +### Microsoft Visual Studio + +> [!CAUTION] +> Building Stockfish with Visual Studio is **not officially supported**. + +It is required to explicitly set the stack reserve to avoid crashes. See point 5. below. + +If you want to use MSVC to get a "optimized" build, you can change these settings in the IDE: + +1. Add "NDEBUG;USE_POPCNT;USE_PEXT" to preprocessor definitions. Optionally, depending on your processor's support, add one of the following definitions: USE_AVX512 / USE_AVX2 / USE_SSSE3 / USE_SSE2 / USE_MMX. Also, if your processor supports VNNI instructions, add the USE_AVXVNNI definition. For a 64-bit target, if your CPU supports AVX or later extensions set one of the following: /arch:AVX or /arch:AVX2 or /arch:AVX512. +2. Optimization flags: /O2, /Oi, /Ot, /Oy, /GL +3. Static link with runtime: /MT. +4. Disable stack cookies: /GS-. +5. Set stack reserve to 8388608 in under Linker -> System or use the linker option /STACK:reserve=8388608. +6. Disable debugging information in compiler/linker. +7. (VS 2017 only): Make a PGO instrument build(set under General), it should depend on "pgort140.dll" and it probably won't start. +8. (VS 2017 only): Copy pgort140.dll from "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64" to the output folder. +9. Run bench with the instrument build(very slow) and quit, it should generate "Stockfish.pgd" and "Stockfish!1.pgc". +10. Make a PGO optimized build(set under General), should show something like: + +``` +1>0 of 0 ( 0.0%) original invalid call sites were matched. +1>0 new call sites were added. +1>54 of 4076 ( 1.32%) profiled functions will be compiled for speed, and the rest of the functions will be compiled for size +1>18615 of 46620 inline instances were from dead/cold paths +1>4076 of 4076 functions (100.0%) were optimized using profile data +1>14499840744 of 14499840744 instructions (100.0%) were optimized using profile data +``` + +11. Enjoy, local tests show comparable speed to GCC builds. + +### Troubleshooting + +If this tutorial will not work on your pc, you may try to change the `Windows Security` settings in via `Windows Security` >> `App & Browser Control` >> `Exploit Protection Settings`: + 1. Try to turn off _"Force randomization for images (Mandatory ASLR)"_, if this not solve the problem then, + 2. Try to turn off also _"Randomize memory allocations (Bottom-up ASLR)"_ . + +### Using other MinGW-w64 with MSYS2 + +To use with MSYS2 a MinGW-w64 built by other projects, simply follow these instructions (Windows 64 bit): +1. Download another version of MinGW-w64, e.g. [MinGW-w64 (64-bit) GCC 8.1.0](https://www.msys2.org/), extract the *mingw64* folder renaming it to *mingw64-810*, copy the folder into *C:\msys64*, check to have the directory *C:\msys64\mingw64-810\bin* + +2. Build Stockfish writing and executing this bash script +
Click to view + +```bash +#!/bin/bash +# makefish.sh + +# set PATH to use GCC 8.1.0 +if [ -d "/mingw64-810/bin" ] ; then + PATH="/mingw64-810/bin:${PATH}" +else + echo "folder error" + exit 1 +fi + +branch='master' +github_user='official-stockfish' + +# download the Stockfish source code +wget -O ${branch}.zip https://github.com/${github_user}/Stockfish/archive/refs/heads/${branch}.zip +unzip ${branch}.zip +cd Stockfish-${branch}/src + +# find the CPU architecture +# CPU without popcnt and bmi2 instructions (e.g. older than Intel Sandy Bridge) +arch_cpu=x86-64 +# CPU with bmi2 instruction (e.g. Intel Haswell or newer) +if [ "$(g++ -Q -march=native --help=target | grep mbmi2 | grep enabled)" ] ; then + # CPU AMD zen + if [ "$(g++ -Q -march=native --help=target | grep march | grep 'znver[12]')" ] ; then + arch_cpu=x86-64-avx2 + else + arch_cpu=x86-64-bmi2 + fi +# CPU with popcnt instruction (e.g. Intel Sandy Bridge) +elif [ "$(g++ -Q -march=native --help=target | grep mpopcnt | grep enabled)" ] ; then + arch_cpu=x86-64-sse41-popcnt +fi + +# build the Stockfish executable +make profile-build ARCH=${arch_cpu} COMP=mingw +make strip +mv stockfish.exe ../../stockfish_${arch_cpu}.exe +make clean +cd +``` +
+ +To use the compiler in the CLI write and run the script `use_gcc810.sh` in the user home folder +```bash +# set PATH to use GCC 8.1.0 +# use this command: source use_gcc810.sh +if [ -d "/mingw64-810/bin" ] ; then + PATH="/mingw64-810/bin:${PATH}" +else + echo "folder error" +fi +``` + +--- + +## macOS + +On macOS 10.14 or higher, it is possible to use the Clang compiler provided by Apple +to compile Stockfish out of the box, and this is the method used by default +in our Makefile (the Makefile sets the macosx-version-min=10.14 flag to select +the right libc++ library for the Clang compiler with recent c++17 support). + +But it is quite possible to compile and run Stockfish on older versions of macOS! Below +we describe a method to install a recent GNU compiler on these Macs, to get +the c++17 support. We have tested the following procedure to install gcc10 on +machines running macOS 10.7, macOS 10.9 and macOS 10.13. + +1) Install Xcode for your machine. + +2) Install Apple command-line developer tools for Xcode, by typing the following + command in a Terminal: + + ```bash + sudo xcode-select --install + ``` + +3) Go to the Stockfish "src" directory, then try a default build and run Stockfish: + + ```bash + make clean + make build + make net + ./stockfish + ``` + +4) If step 3 worked, congrats! You have a compiler recent enough on your Mac +to compile Stockfish. If not, continue with step 5 to install GNU gcc10 :-) + +5) Install the MacPorts package manager (https://www.macports.org/install.php), +for instance using the fast method in the "macOS Package (.pkg) Installer" +section of the page. + +6) Use the "port" command to install the gcc10 package of MacPorts by typing the +following command: + + ```bash + sudo port install gcc10 + ``` + +With this step, MacPorts will install the gcc10 compiler under the name "g++-mp-10" +in the /opt/local/bin directory: + +``` +which g++-mp-10 + +/opt/local/bin/g++-mp-10 <--- answer +``` + +7) You can now go back to the "src" directory of Stockfish, and try to build +Stockfish by pointing at the right compiler: + + ```bash + make clean + make build COMP=gcc COMPCXX=/opt/local/bin/g++-mp-10 + make net + ./stockfish + ``` + +8) Enjoy Stockfish on macOS! + +See [this pull request](https://github.com/official-stockfish/Stockfish/pull/3049) for further discussion. + +--- + +## For Android + +You can build Stockfish for your ARM CPU based mobile Android device, +using the Stockfish supplied Makefile. + +Supported architectures are: +- **armv7**: 32 bit ARM CPUs without Neon extension +- **armv7-neon**: 32 bit ARM CPUs with Neon SIMD Instruction Set Extension +- **armv8**: 64 bit ARM CPUs, with Neon extension + +As most modern Android smartphones and tablets nowadays use armv8 64 bit CPUs, +we will cover these in this example. Before you try to build Stockfish, make sure +that you know what kind of CPU architecture your device uses. You will have to specify +one of the three mentioned architectures later on in the MAKE command by giving the +`"ARCH=..."` variable on the command line. + +Furthermore you should be aware of the fact that Stockfish is just the chess engine. +You cannot use it stand-alone, rather you need a host program, i.e. a Chess GUI that +displays the chess board, takes your moves etc. on the one hand, and on the other hand +"talks" to the Stockfish chess engine so that it analyses the chess position in a game +and calculates its moves or variations, and gives that back to the GUI for display. + +The Chess GUI which is probably used most often on Android is Droidfish, so we will +cover this here. You can get it from the [F-Droid](https://f-droid.org/repository/browse/?fdid=org.petero.droidfish) alternative app store. + + +To build Stockfish, you need: + +1. The Android Native Development Kit (NDK), which contains a C/C++ compiler toolchain + and all required headers and libraries to build native software for Android. + +2. POSIX compatible build enviroment with GNU `coreutils`, `fileutils`and the `make`, `git`, + `expect` (if you want to run the testsuite) and either `wget` or `curl` utilities. + + On Linux, install those tools using the package manager of your Linux distribution. + + On Windows, you can use MSYS2. We will assume further down in this example that + you installed it to "C:\msys64". Adapt the instructions accordingly, if you installed + it elsewhere. + + Please see [how to compile on Windows](#windows) + for more details about MSYS2, but _**please note**_ that you ONLY need to install the basic + MSYS environment and the package groups `base-devel`, `expect` and `git` with this command: + ```bash + pacman -S --noconfirm --needed base-devel expect git wget curl + ``` + You do NOT need any of the MINGW64, MINGW32 nor the CLANG64 compiler toolchain for an Android build. + We will use the Android NDK compiler toolchain instead. + + +To get the Android NDK, you have two options: + +a) If you already have Android Studio installed, use its built-in SDK manager to download + and install the NDK, as follows: + + - On the "Welcome to Android Studio" start page of the IDE, click "Customize" on the left pane, + then click "All Settings..." on the bottom. This should open the "Android SDK" maintenance dialog. + - Click on the "SDK Tools" tab on top of the list window. + - Click and select the "NDK (Side by Side)" option and then click on "OK" + This will download and install the latest NDK version under the directory + of the "Android SDK Location" directory. + + In this example, for the Windows environment we will assume that Android Studio is installed + in "C:\Android\Android Studio", and that the Android SDK is installed in "C:\Android\Sdk". + The NDK will then have been installed in "C:\Android\Sdk\ndk" by the above installation process. + +b) If you do NOT have Android Studio installed, don't worry! You don't need it. + To download just the Android NDK, go to https://developer.android.com/ndk/downloads + and pick the latest version for your platform. In this example we will stick to either Windows + or Linux, but the Mac version should be not much different to use. + +> [!NOTE] +> The latest LTS version is r23b (23.1.7779620). This will work fine. The minimum version you need is r21e (21.4.7075529), so if you already have that, you are all set. + +If you downloaded it directly, unzip it to "C:\Android\Sdk\ndk\", or if you are on Linux, inside your home +directory to /home/(your user name)/Android/Sdk/ndk + +When the installation is finished, locate the NDK compiler toolchain directory. + +On Windows, this should be something like: +"C:\Android\Sdk\ndk\23.1.7779620\toolchains\llvm\prebuilt\windows-x86_64" + +On Linux, the path might look like: +/home/johndoe/Android/Sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/linux-x86_64 + +On Linux, we are now almost ready to go. On Windows however, to simplify its use with MSYS2, we will +now create a symbolic link to that directory inside the MSYS2 installation directory. +To do that, open a CMD.EXE command prompt with Administrator privileges, and use the Windows command MKLINK as follows: +```cmd +mklink /D "C:\msys64\Android" "C:\Android\Sdk\ndk\23.1.7779620\toolchains\llvm\prebuilt\windows-x86_64" +``` +Now the Android NDK compiler toolchain is available from inside an MSYS environment terminal session +in the same way as the MINGW64 toolchain would be, and we don't have to type such long path names anymore. + +On Windows, you should now start the MSYS environment launcher. This will give you a terminal session +with the Bash (1) shell. The PATH environment variable DOES NOT include a compiler toolchain yet. Verify this by entering: + +```bash +$ echo $PATH +/usr/local/bin:/usr/bin:/bin:/opt/bin:/c/Windows/System32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0/:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl +``` + +On Linux, you can just open a plain terminal session. + +Now we prepend the NDK compiler toolchain bin/ subdirectory to our PATH: + +On Windows: +```bash +$ export PATH=/Android/bin:$PATH +``` + +On Linux: +```bash +$ export PATH=/home/johndoe/Android/Sdk/ndk/23.1.7779620/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH +``` + +> [!NOTE] +> These PATH settings are in effect only for your current session. + +Now you should be able to call the compiler we will use from the command line. +Let's check which version we have, this example output is from Windows, the Linux output should be quite similar. + +```bash +$ aarch64-linux-android21-clang++ --version +Android (7019983 based on r365631c3) clang version 9.0.9 (https://android.googlesource.com/toolchain/llvm-project a2a1e703c0edb03ba29944e529ccbf457742737b) (based on LLVM 9.0.9svn) +Target: aarch64-unknown-linux-android21 +Thread model: posix +InstalledDir: C:\msys64\Android\bin +$ +``` + +If you get an error message that the command "aarch64-linux-android21-clang++" could not be found, please go back +and check that you have got all the path names correct in the above steps. + +Now we will checkout the Stockfish source code with git, and start the build. The steps for Windows in the MSYS environment +session and for Linux are now basically the same. In your home directory, make subdirectories for the git checkout: + +```bash +$ mkdir -p ~/repos/official-stockfish +$ cd ~/repos/official-stockfish +$ git clone https://github.com/official-stockfish/Stockfish.git +. +. +. +$ cd Stockfish/src +``` + +Now lets start the build. First we can display a help page with all the supported make targets (and ignore them :-) + +```bash +$ make help +``` + +Next we download the NNUE neural network that powers Stockfish's evaluation. By default, it will be embedded into the +compiled `stockfish` executable. + +```bash +$ make net +``` + +This should download `.nnue` file. If you get an error message that neither `curl` +nor `wget` are installed, then please install one of these tools and repeat. + +Now we are ready to build. You now need to know your architecture (see the start of this documentation). +We will use armv8 as an example here. Issue the command: + +```bash +$ make -j build ARCH=armv8 COMP=ndk +``` + +After a short amount of time (or a minute, or two, depending on the speed of your machine) the compilation and +linking should complete with messages like this: + +```bash +. +. +aarch64-linux-android21-clang++ -o stockfish benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o search.o thread.o timeman.o tt.o uci.o ucioption.o tune.o tbprobe.o evaluate_nnue.o half_ka_v2_hm.o -static-libstdc++ -pie -lm -latomic -Wall -Wcast-qual -fno-exceptions -std=c++17 -stdlib=libc++ -fPIE -DUSE_PTHREADS -DNDEBUG -O3 -fexperimental-new-pass-manager -DIS_64BIT -DUSE_POPCNT -DUSE_NEON=8 -flto +make[1]: Leaving directory '/home/johndoe/repos/official-stockfish/Stockfish/src' +``` + +You will now have a binary file called `stockfish` in your current directory. Check with the file command that +it is indeed an ARM binary for Android: + +```bash +$ file stockfish +stockfish: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, with debug_info, not stripped +``` + +To make it smaller and run faster, we should strip the symbol table from the executable; this is not needed for running it, only for debugging. +Issue the command: + +```bash +$ make strip ARCH=armv8 COMP=ndk +aarch64-linux-android-strip stockfish +``` + +Issuing the file command again shows that it has been stripped: + +```bash +$ file stockfish +stockfish: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, stripped +``` + +It is a good idea to rename the binary: + +```bash +$ mv stockfish stockfish_DEV_armv8 +``` + +So in Droidfish you will be able to see that you are running your self compiled DEVelopment version of Stockfish, and not the built-in +version, which is substantially older. + +Voilà: you have your Android build of Stockfish. What is now left to do is to copy it over to your smartphone or tablet, +to the "uci" subdirectory of your Droidfish installation. You can find the Droidfish documentation here: +https://github.com/peterosterlund2/droidfish/tree/master/doc + +To do this, the easiest possibility is to have your build machine and your Android device on the same network, +create a Windows network share, copy the Stockfish binary into that share directory, and then on your Android +device use the File Manager app to connect to that network share and copy the Stockfish binary over to the +Droidfish uci (engines) directory. + +In Droidfish, open the left menu, find the Engines management submenu, pick the `stockfish_DEV_armv8` binary, +and in the configuration menu adjust its UCI parameter settings. Depending on the CPU power of your device +and the available memory, you should probably give it more than the default one Thread, and more than the default +16 MB of Hash memory. A good start would be to try 2 Threads, and 512 MB for the Hash tables, and see if you can beat it ;-) + +> [!WARNING] +> As Stockfish is a very computation-intense program, you should probably not give it as many threads as your device CPU has processor cores. Especially in Analysis mode, when Stockfish is thinking permanently, and for extended amounts of time, this might suck your device battery empty quite quickly. + +Enjoy! + +--- + +## Cross compilation + +### For Windows in Ubuntu + +The script works with Ubuntu 18.04, Ubuntu 21.10 and Ubuntu 22.04, other versions could still have a packaging bug. + +
Click to view + +```bash +#!/bin/bash +# functions to build Stockfish +_build_sf () { +make build ARCH=x86-64$1 COMP=mingw -j +make strip COMP=mingw +mv stockfish.exe ../../stockfish-x64${1}.exe +make clean COMP=mingw +} + +_build_sf_pgo () { +make profile-build ARCH=x86-64$1 COMP=mingw PGOBENCH="wine ./stockfish.exe bench" -j +make strip COMP=mingw +mv stockfish.exe ../../stockfish-x64${1}-pgo.exe +make clean COMP=mingw +} + +# full-upgrade and install required packages +sudo apt update && sudo apt full-upgrade -y && sudo apt autoremove -y && sudo apt clean +sudo apt install -y \ + make \ + mingw-w64 \ + git \ + wine64 \ + binutils + +# clone Stockfish source code +git clone --single-branch --branch master https://github.com/official-stockfish/Stockfish.git +cd Stockfish/src + +# build Stockfish executables +# to speedup the building process you can keep only the section fitting your CPU architecture + +# build the binary for CPUs without popcnt and bmi2 instructions (e.g. older than Intel Sandy Bridge) +_build_sf_pgo + +# build the binary for CPU with popcnt instruction (e.g. Intel Sandy Bridge) +if [ "$(x86_64-w64-mingw32-c++-posix -Q -march=native --help=target | grep mpopcnt | grep enabled)" ] ; then + _build_sf_pgo -sse41-popcnt +else + _build_sf -sse41-popcnt +fi + +# build the binary for CPU with bmi2 instruction (e.g. Intel Haswell or newer) +if [ "$(x86_64-w64-mingw32-c++-posix -Q -march=native --help=target | grep mbmi2 | grep enabled)" ] ; then + _build_sf_pgo -bmi2 +else + _build_sf -bmi2 +fi +``` +
+ +### For all platforms (host/target) using Zig + +[Zig](https://ziglang.org/) is a programming language in early development stage that is binary compatible with C. +The Zig toolchain, based on LLVM, ships the source code of all the required libraries to easily cross compile Zig/C/C++ code for several CPU Architecture and OS combinations. All the work required is to set as target the proper supported [triple \](https://github.com/ziglang/zig-bootstrap#supported-triples) (eg `x86_64-windows-gnu`, `aarch64-linux-musl`). + +You can use Zig: +* installing Zig with a [package manager](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager) for your OS, or +* unzipping the [Zig archive](https://ziglang.org/download/) (~70 Mbi) and setting the PATH for the shell with `export PATH=/home/username/zig:$PATH` + +Here is a script to cross compile from a clean Ubuntu a static build of Stockfish targeting an armv8 or armv7 CPU running on Linux or Android: + +
Click to view + +```bash +# Use a clean Ubuntu to cross compile +# a static build for armv8 and armv7 on Linux/Android + +# one time configuration +sudo apt update && sudo apt install -y make git +sudo snap install zig --classic --edge +sudo apt install -y qemu-user + +# armv8 static build with musl libc +git clone https://github.com/official-stockfish/Stockfish.git +cd Stockfish/src +make -j build ARCH=armv8 COMP=clang CXX="zig c++ -target aarch64-linux-musl" + +# test: qemu's magic at work +qemu-aarch64 stockfish compiler +qemu-aarch64 stockfish bench + +# armv7 static build with musl libc +make clean +make -j build ARCH=armv7 COMP=clang CXX="zig c++ -target arm-linux-musleabihf" + +# test: qemu's magic at work +qemu-arm stockfish compiler +qemu-arm stockfish bench + +``` +
+ +Here is a script to cross compile from a msys2 msys/mingw-w64 shell a static build of Stockfish targeting an armv8 or armv7 CPU running on Linux or Android: + +
Click to view + +```bash +# Use msys2 to cross compile +# a static build for armv8 and armv7 on Linux/Android + +# one time configuration +pacman -S --noconfirm --needed git make unzip +wget https://ziglang.org/builds/zig-windows-x86_64-0.14.0-dev.2546+0ff0bdb4a.zip + +unzip zig-windows-x86_64-0.14.0-dev.2546+0ff0bdb4a.zip +export PATH="$(pwd)/zig-windows-x86_64-0.14.0-dev.2546+0ff0bdb4a:$PATH" + +# armv8 static build with musl libc +git clone https://github.com/official-stockfish/Stockfish.git +cd Stockfish/src +make -j build ARCH=armv8 COMP=clang CXX="zig c++ -target aarch64-linux-musl" +mv stockfish.exe stockfish_armv8 +``` +
+ +--- + +## Lower compilation time + +It is possible to lower the compile time on cpu multi core using make with the flag *-j \*, where \ is the number of jobs (commands) to run simultaneously. The flag *-j* enables one job for each logical CPU core. + +```bash +make -j profile-build ARCH=x86-64-avx2 COMP=mingw +``` + +--- + +## Optimize for your CPU + +To get the max speedup for your CPU (1.5% on Ivy Bridge) simply prepend the shell variable `CXXFLAGS='-march=native'` to the `make` command. At example, for a CPU Sandy/Ivy Bridge use this command: + +```bash +CXXFLAGS='-march=native' make -j profile-build ARCH=x86-64-avx2 COMP=gcc +``` + +To view the compiler flags for your CPU: + +``` +# for gcc +gcc -Q -march=native --help=target | grep -v "\[disabled\]" + +# for clang +clang -E - -march=native -### +``` + +*-march=native* implies *-mtune=native*, below a high level explanation of the compiler flags *-march* and *-mtune*, view the [gcc manual](https://gcc.gnu.org/onlinedocs/gcc-5.3.0/gcc/x86-Options.html#x86-Options) for more technically sound details: + + * *-march*: determines what instruction sets are used in the binary. An instruction set is the list of commands implemented by the cpu. **The generated code may not run at all on processors other than the one indicated.** + + * *-mtune*: determines the cost model that is used when generating code. The cost model describes how long it takes the cpu to do operations. This information is used by the scheduler to decide what operations to use and in what order. diff --git a/stockfish/wiki/Developers.md b/stockfish/wiki/Developers.md new file mode 100644 index 0000000000000000000000000000000000000000..c41af0208b3313ce58051b0a4e89a7c2833d5019 --- /dev/null +++ b/stockfish/wiki/Developers.md @@ -0,0 +1,221 @@ +## Stockfish Development Setup + +### Windows + +
+Show more + +#### Installing a compiler +1. https://www.msys2.org/ +2. Download the installer + +In the MSYS2 Installer change the installation folder to: +`C:\tools\msys64` + +In the `URTC64` Shell run: +`pacman -S --needed base-devel mingw-w64-ucrt-x86_64-toolchain` + +#### clang-format + +Download [LLVM 18](https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/LLVM-18.1.8-win64.exe) + +Run the executable and in the installer choose: +`Add LLVM to the system PATH for current user` + +#### Video Setup + +_There's a much higher quality version of this available on our [Discord](https://discord.com/channels/435943710472011776/1032922913499783169/1191837643256901732)._ + +https://github.com/official-stockfish/Stockfish/assets/45608332/d0323339-21f1-4d1d-aa86-183a7e10ed06 + +_More in depth information about various compilers can be found [here](Compiling-from-source#windows)._ + +
+ +### Ubuntu + +
+Show more + +#### Installing a compiler + +On Unix-like systems you will most likely have all the tools installed, +which are required to build Stockfish. Expect `clang-format` which we use to format our codebase. + +```bash +sudo apt install build-essential git +``` + +#### clang-format + +```bash +sudo apt install clang-format-18 +``` + +
+ +### MacOS + +
+Show more + +#### Installing a compiler + +On MacOS you will need to install the Xcode Command Line Tools. +It is enough to run the following command in your terminal, instead of installing the full Xcode. + +```bash +sudo xcode-select --install +``` + +#### clang-format + +```bash +brew install clang-format@17 +``` + +
+ +## Participating in the project + +Stockfish's improvement over the last decade has been a great community effort. +Nowadays most development talk takes place on [Discord](https://discord.gg/GWDRS3kU6R). + +There are many ways to contribute to Stockfish: + +- [Stockfish](#stockfish) (C++) +- [Fishtest](#fishtest) (Python) +- [nnue-pytorch](#nnue-pytorch) (C++ and Python) +- [Donating hardware](#donating-hardware) + +### Stockfish + +If you want to contribute to Stockfish directly, you can do so in a couple of ways. +Follow the steps described in our [Fishtest wiki](https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test) to create your first test. +It is advised to first follow the [development setup](#stockfish-development-setup) steps for your platform. + +New commits to stockfish can mostly be categorised in 2 categories: + +#### Non functional changes + +These are changes that don't change the search behaviour and can be directly +submitted as pull requests. + +#### Functional changes + +These change the search behaviour and lead to a different search tree. +Every functional patch (commit) has to be verified by +[Fishtest](https://tests.stockfishchess.org/tests), our testing framework. + +### NNUE Pytorch + +NNUE Pytorch is the trainer for Stockfish's neural network. +Usually changes here are tested by training a new network and testing it against the current network via Fishtest. + +### Donating hardware + +Improving Stockfish requires a massive amount of testing. +You can donate your hardware resources by installing the [Fishtest Worker](https://github.com/official-stockfish/fishtest/wiki/Running-the-worker) and view the current tests on [Fishtest](https://tests.stockfishchess.org/tests). + +## Using Stockfish in your own project + +First of all, you should **read our [Terms of Use](#terms-of-use)** and follow them carefully. + +Stockfish is a UCI chess engine, but what does that mean? It means that Stockfish follows the UCI protocol, which you find explained [here](https://backscattering.de/chess/uci/) in great detail. This is the usual way of communicating with Stockfish, so you do not need to write any C++! + +Your next step is probably gonna be researching how you can open an executable in your programming language. You will need to write to `stdin` and listen to `stdout`, that is where Stockfish's output will end up. + +### Examples + +- Python: https://python-chess.readthedocs.io/en/latest/engine.html +- NodeJS: You can follow [this guide](https://blog.logrocket.com/using-stdout-stdin-stderr-node-js/) on how to communicate with a program. +- C++: [Boost.Process](https://www.boost.org/doc/libs/1_64_0/doc/html/process.html) can be used for easy process communication. +- Rust: Examine the documentation on [how to spawn a Command](https://doc.rust-lang.org/std/process/struct.Command.html). + +### Limitations + +> I want Stockfish to comment on the move it made, what do I need to do? + +That is not possible. You will have to write your own logic to create such a feature. + +> I want to get an evaluation of the current position. + +While Stockfish has an [`eval`](UCI-&-Commands#eval) command, it only statically evaluates positions without performing any search. A more precise evaluation is available after you use the [`go`](UCI-&-Commands#go) command together with a specified limit. + +### Other resources + +- [Commands](UCI-&-Commands) +- [Advanced topics](Advanced-topics) +- [Useful data](Useful-data) + +### Terms of use + +Stockfish is free and distributed under the [**GNU General Public License version 3**](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt) (GPL v3). Essentially, this means you are **free to do almost exactly what you want** with the program, including **distributing it** among your friends, **making it available for download** from your website, **selling it** (either by itself or as part of some bigger software package), or **using it as the starting point** for a software project of your own. This also means that you can distribute Stockfish [alongside your proprietary system](https://www.gnu.org/licenses/gpl-faq.html#GPLInProprietarySystem), but to do this validly, you must make sure that Stockfish and your program communicate at arm's length, that they are not combined in a way that would make them effectively a single program. + +The only real limitation is that whenever you distribute Stockfish in some way, **you MUST always include the license and the full source code** (or a pointer to where the source code can be found) to generate the exact binary you are distributing. If you make any changes to the source code, these changes must also be made available under GPL v3. + +## Git Hooks + +
+ +Show more + +Place the following file into `.git/hooks/pre-push` and make it executable. +`chmod +x .git/hooks/pre-push`. This will prevent you from pushing commits that +do not contain a Bench or 'No functional change' in the commit message. + +Only really useful for maintainers. + +```bash +#!/bin/bash + +if ! which clang-format-18 >/dev/null; then + CLANG_FORMAT=clang-format +else + CLANG_FORMAT=clang-format-18 +fi + +# Extracted from the Makefile +SRCS=$(awk '/^SRCS = /{flag=1; sub(/^SRCS = /,""); print} /^$/{flag=0} flag && !/^SRCS = /{print}' ./src/Makefile | tr -d '\\' | xargs echo | tr ' ' '\n' | sed 's|^|./src/|') +HEADERS=$(awk '/^HEADERS = /{flag=1; sub(/^HEADERS = /,""); print} /^$/{flag=0} flag && !/^HEADERS = /{print}' ./src/Makefile | tr -d '\\' | xargs echo | tr ' ' '\n' | sed 's|^|./src/|') + +while read local_ref local_sha remote_ref remote_sha; do + if [[ "$remote_ref" == "refs/heads/master" ]]; then + # Check open diffs + if [[ -n $(git status --porcelain) ]]; then + echo "Please commit or stash your changes before pushing." + exit 1 + fi + + # Check formatting + if ! $CLANG_FORMAT --dry-run -Werror -style=file $SRCS $HEADERS; then + echo "Please run 'make format' to fix formatting issues and rebase the last commit." + exit 1 + fi + + # Iterate through commits + for commit in $(git rev-list --no-merges $remote_sha..$local_sha); do + commit_msg=$(git log --format=%B -n 1 $commit) + + # bench regex as defined in ci + # check for the existence of a bench in the commit message + bench_regex='\b[Bb]ench[ :]+[1-9][0-9]{5,7}\b' + if echo "$commit_msg" | grep -m 1 -o -x -E "$bench_regex" >/dev/null; then + continue + fi + + # check for the existence of "No functional change" in the commit message + no_functional_change_regex='\b[Nn]o[[:space:]][Ff]unctional[[:space:]][Cc]hange\b' + if echo "$commit_msg" | grep -o -x -E "$no_functional_change_regex" >/dev/null; then + continue + fi + + echo "Commit $commit does not contain a Bench or 'No functional change'." + exit 1 + done + fi +done + +exit 0 +``` +
diff --git a/stockfish/wiki/Download-and-usage.md b/stockfish/wiki/Download-and-usage.md new file mode 100644 index 0000000000000000000000000000000000000000..4411ade3876a4d1afb3bd0805b926a67a1c4b5cf --- /dev/null +++ b/stockfish/wiki/Download-and-usage.md @@ -0,0 +1,232 @@ +## Download Stockfish + +> [!NOTE] +> Stockfish is a [**command line program**](Stockfish-FAQ#executing-stockfish-opens-a-cmd-window). You may want to use it in your own UCI-compatible [chess GUI](#download-a-chess-gui). +> Developers should communicate with Stockfish via the [UCI protocol](UCI-&-Commands#standard-commands). + +### Get started + +1. First [download](#official-downloads) Stockfish. Stockfish itself is *completely free* with all its options. +2. Next, [download a GUI](#download-a-chess-gui) (Graphical User Interface) as it is needed to conveniently use Stockfish. There are multiple free and commercial GUIs available. Different GUI's have more or less advanced features, for example, an opening explorer or automatic game analysis. +3. Now Stockfish must be made available to the GUI. [Install in a Chess GUI](#install-in-a-chess-gui) explains how this can be done for some of them. If a different GUI is used, please read the GUI's manual. +4. Ultimately, change the default [settings](#change-settings) of Stockfish to get the [best possible analysis](Stockfish-FAQ#optimal-settings). + +--- +### Official downloads + +#### Latest release + +- https://stockfishchess.org/download/ +- https://github.com/official-stockfish/Stockfish/releases/latest + +#### Latest development build + +- https://github.com/official-stockfish/Stockfish/releases?q=prerelease%3Atrue + +> [!NOTE] +> We **only** recommend downloading from the [official GitHub releases](https://github.com/official-stockfish/Stockfish/releases?q=prerelease%3Atrue). +> Websites such as Abrok are third parties, so we cannot guarantee the safety, reliability, and availability of those binaries because we are not responsible for them. + +### Choose a binary + +In order of preference: +1. x86-64-vnni512 +2. x86-64-vnni256 +3. x86-64-avx512 + - AMD Zen 4+ +4. x86-64-bmi2 + - Intel (2013+) and AMD Zen 3+ +5. x86-64-avx2 + - Intel (2013+) and AMD (2015+) +6. x86-64-sse41-popcnt + - Intel (2008+) and AMD (2011+) +7. x86-64 + +--- + +## Download a Chess GUI + +A chess graphical user interface allows you to interact with the engine in a user-friendly way. Popular GUIs are: + +### Free + +#### Computer + +| **[En Croissant](https://www.encroissant.org/)** ([source code](https://github.com/franciscoBSalgueiro/en-croissant))
[How to install Stockfish](#en-croissant)
[Change settings](#en-croissant-1) | **[Nibbler](https://github.com/rooklift/nibbler/releases/latest)** ([source code](https://github.com/rooklift/nibbler))
[How to install Stockfish](#nibbler)
[Change settings](#nibbler-1) | +|:---|:---| +| [![][encroissant]][encroissant] | [![][nibbler]][nibbler] | +| **[Arena](http://www.playwitharena.de/)**
[How to install Stockfish](#arena)
[Change settings](#arena-1) | **[Lichess Local Engine](https://github.com/fitztrev/lichess-tauri/releases/latest)** ([source code](https://github.com/fitztrev/lichess-tauri)) (**WIP**)
[How to install Stockfish](#lichess-local-engine)
[Change settings](#lichess) | +| [![][arena]][arena] | [![][lichesslocalengine]][lichesslocalengine] | +| **[BanksiaGUI](https://banksiagui.com/download/)** | **[Cutechess](https://github.com/cutechess/cutechess/releases/latest)** ([source code](https://github.com/cutechess/cutechess)) | +| [![][banksia]][banksia] | [![][cutechess]][cutechess] | +| **[ChessX](https://chessx.sourceforge.io)** ([source code](https://sourceforge.net/projects/chessx/)) | **[LiGround](https://github.com/ml-research/liground/releases/latest)** ([source code](https://github.com/ml-research/liground)) | +| [![][chessx]][chessx] | [![][liground]][liground] | +| **[Lucas Chess](https://lucaschess.pythonanywhere.com/downloads)** ([source code](https://github.com/lukasmonk/lucaschessR2)) | **[Scid vs. PC](https://scidvspc.sourceforge.net/#toc3)** ([source code](https://sourceforge.net/projects/scidvspc/)) | +| [![][lucaschess]][lucaschess] | [![][scidvspc]][scidvspc] | +| **[XBoard](https://www.gnu.org/software/xboard/#download)** ([source code](https://ftp.gnu.org/gnu/xboard/)) | **[jose](https://bitbucket.org/hrimfaxi/jose)** ([source code](https://bitbucket.org/hrimfaxi/jose/src/main))
[How to install Stockfish](#jose) | +| [![][xboard]][xboard] | ![jose](https://hrimfaxi.bitbucket.io/jose/images/shots/shot01.png) | + + + +#### Mobile + +| **[DroidFish](https://f-droid.org/packages/org.petero.droidfish/)** ([source code](https://github.com/peterosterlund2/droidfish)) | **[SmallFish](https://apps.apple.com/us/app/smallfish-chess-for-stockfish/id675049147)** | +|:---|:---| +| [![][droidfish]][droidfish] | [![][smallfish]][smallfish] | +| **[Chessis](https://play.google.com/store/apps/details?id=com.chessimprovement.chessis)** | | +| [![][chessis]][chessis] | | + +### Paid + +| **[Chessbase](https://shop.chessbase.com/en/categories/chessprogramms)** | **[Hiarcs](https://www.hiarcs.com/chess-explorer.html)** | +|:---|:---| +| [![][chessbase]][chessbase] | [![][hiarcs]][hiarcs] | +| **[Shredder](https://www.shredderchess.com/)** | | +| [![][shredder]][shredder] | | + +### Online + +> [!NOTE] +> If you don't want to download a GUI, you can also use some of the available online interfaces. Keep in mind that you might not get the latest version of Stockfish, settings might be limited and speed will be slower. + +| **[Lichess](https://lichess.org/analysis)**
[Change settings](#lichess) | **[Chess.com](https://www.chess.com/analysis)**
[Change settings](#chesscom) | +|:---|:---| +| [![][lichess]][lichess] | [![][chesscom]][chesscom] | +| **[ChessMonitor](https://www.chessmonitor.com/explorer)** | **[Chessify](https://chessify.me/analysis)** | +| [![][chessmonitor]][chessmonitor] | [![][chessify]][chessify] | +| **[DecodeChess](https://app.decodechess.com/)** | | +| [![][decodechess]][decodechess] | | + +--- + +## Install in a Chess GUI + +### En Croissant + +Engines tab > Add new > Install Stockfish + +![encroissant_install](https://github.com/user-attachments/assets/4bf61e37-5a69-4059-ba3e-f7c2f5020aee) + +### Arena + +1. Engines > Install New Engine... + + ![arena_install_1](https://user-images.githubusercontent.com/63931154/206901675-33341f5f-03c7-4ca1-aaa5-185a2a7f5b83.png) + +2. Select and open the Stockfish executable + + ![arena_install_2](https://user-images.githubusercontent.com/63931154/206901703-a6538e9f-352b-4a6e-9c89-be804d57f010.png) + +### Nibbler + +1. Engine > Choose engine... + + ![nibbler_install_1](https://user-images.githubusercontent.com/63931154/206902163-8a92d15c-0793-4b1a-9f9c-c5d8a9dd294e.png) + +2. Select and open the Stockfish executable + + ![nibbler_install_2](https://user-images.githubusercontent.com/63931154/206902197-0062badd-3d12-45dd-b19f-918edfbb22ca.png) + +### Lichess Local Engine + +1. Log in with Lichess + + ![lichesslocalengine_install_1](https://user-images.githubusercontent.com/63931154/232722746-b85d345f-e455-4d62-ad33-98d29756d51c.png) + + ![lichesslocalengine_install_2](https://user-images.githubusercontent.com/63931154/232723150-5e51029a-b345-4789-b12d-beef91c7e835.png) + +2. Click the Install Stockfish button + + ![lichesslocalengine_install_3](https://user-images.githubusercontent.com/63931154/232723405-8c15861d-578d-432b-a009-362d63bd69d0.png) + +3. Go to the Lichess analysis page + + https://lichess.org/analysis + +4. Select the engine from the engine list + + ![lichesslocalengine_install_4](https://github.com/user-attachments/assets/9bcaccfb-9eb1-43a6-9379-c118f2ac77bf) + +### jose + +Stockfish is already bundled with jose. To enable it for play and analysis do: + +1. Edit > Options (F9) +2. select the 'Engine' tab +3. select Stockfish in the list of engines +4. below, you can edit all the engine parameters + +--- + +## Change settings + +> [!NOTE] +> Please check our [FAQ guide](Stockfish-FAQ#optimal-settings) to set the optimal settings. + +### Arena + +> [!NOTE] +> First uncheck these two settings +> +> ![arena_settings_note](https://github.com/user-attachments/assets/c33d0e80-611a-4044-8f3f-04b18268e098) + +Right click in the engine name > Configure + +![arena_settings_1](https://user-images.githubusercontent.com/63931154/206901924-aad83991-dfde-4083-a29c-a565effca034.png) +![arena_settings_2](https://user-images.githubusercontent.com/63931154/206913983-82b8cf42-2a03-4896-9511-3472b1185a7e.png) +![arena_settings_3](https://github.com/user-attachments/assets/aa0ae3fa-1848-4c54-953c-090a860471e8) + +### Nibbler + +Open the Engine menu + +![nibbler_settings_1](https://user-images.githubusercontent.com/63931154/206902419-4a2a5580-2d66-4ea1-97f2-93bc2ff846bd.png) + +### En Croissant + +Select Stockfish in the engines tab + +![encroissant_settings_1](https://github.com/user-attachments/assets/e6d8dec3-9a1e-4171-8b6e-ffac9bd51cea) + +or open the engine settings in the Analysis board + +![encroissant_settings_2](https://github.com/user-attachments/assets/e20112a3-d627-46d1-8423-93c39b17f4b5) + +### Lichess + +Open the engine settings + +![lichess_settings_1](https://github.com/user-attachments/assets/d2f48f41-1c68-4e14-902b-cb1f270618e6) + +### Chess.com + +Click the settings button in the [analysis page](https://www.chess.com/analysis?tab=analysis) + +![chesscom_settings_1](https://github.com/user-attachments/assets/c2be2d17-9be4-4e32-a17c-9bdb8e456944) +![chesscom_settings_2](https://github.com/user-attachments/assets/3ee3ce21-fb11-4443-84fc-20c2d7e41166) + +[encroissant]: https://github.com/official-stockfish/Stockfish/assets/63931154/e7b46c8a-6d96-49c7-b3a3-885a7a450037 +[nibbler]: https://github.com/official-stockfish/Stockfish/assets/63931154/06d67bf8-4ed8-466f-a79d-c185c6103d51 +[arena]: https://github.com/official-stockfish/Stockfish/assets/63931154/c166fda2-2fd2-45e2-9239-d24222e5fb71 +[lichesslocalengine]: https://github.com/official-stockfish/Stockfish/assets/63931154/c5737058-befc-442f-8d65-75f151232269 +[banksia]: https://github.com/official-stockfish/Stockfish/assets/63931154/8aae852c-31f7-4e47-998f-4086fb19681c +[cutechess]: https://github.com/official-stockfish/Stockfish/assets/63931154/67b6a236-3c50-4808-ad41-51a6c6299453 +[chessx]: https://github.com/official-stockfish/Stockfish/assets/63931154/e0b3df75-ad90-4edf-a70e-b7781db7eca7 +[lucaschess]: https://github.com/official-stockfish/Stockfish/assets/63931154/f4cf7eed-b74f-4e04-b962-fa44a3f2cba5 +[liground]: https://github.com/official-stockfish/Stockfish/assets/63931154/75692235-227a-415f-8e39-1d8f21c36d92 +[scidvspc]: https://github.com/official-stockfish/Stockfish/assets/63931154/d3d9ad5d-29f7-4675-be68-306195e53ca3 +[xboard]: https://github.com/official-stockfish/Stockfish/assets/63931154/e336adf5-b5d7-47b4-81d2-5c276d174648 + +[droidfish]: https://github.com/official-stockfish/Stockfish/assets/63931154/f575a217-2153-45e3-be1d-223d4344fd44 +[smallfish]: https://github.com/official-stockfish/Stockfish/assets/63931154/0ec44c5b-82de-4fb4-a662-63615a4a971a +[chessis]: https://github.com/official-stockfish/Stockfish/assets/63931154/fdcc0c02-5fe7-4b67-8fdf-ab3be4e7b4cd + +[chessbase]: https://github.com/official-stockfish/Stockfish/assets/63931154/3fd2f64d-bb04-4b8e-b193-3aa53033d897 +[hiarcs]: https://github.com/official-stockfish/Stockfish/assets/63931154/a1e7a951-a743-4e04-9029-c97f2550a773 +[shredder]: https://github.com/official-stockfish/Stockfish/assets/63931154/66d0186c-9286-466e-95b5-8f88cbeb9214 + +[lichess]: https://github.com/official-stockfish/Stockfish/assets/63931154/cc6ea148-2a1a-4b61-a4fa-6af3b076e408 +[chesscom]: https://github.com/official-stockfish/Stockfish/assets/63931154/f5b31849-0429-45d0-8dbc-758959352f9b +[chessmonitor]: https://github.com/official-stockfish/Stockfish/assets/63931154/d4f6d61b-3492-4c1f-998d-99d82252fd89 +[chessify]: https://github.com/official-stockfish/Stockfish/assets/63931154/36cee80d-f63c-4ff9-97e9-5d51539589a8 +[decodechess]: https://github.com/official-stockfish/Stockfish/assets/63931154/20042d29-b50b-4d37-b8f7-e6fb65c18e6a diff --git a/stockfish/wiki/Governance-and-responsibilities.md b/stockfish/wiki/Governance-and-responsibilities.md new file mode 100644 index 0000000000000000000000000000000000000000..ac0adc1729fe7249afee6ed83547ffe9dc73082e --- /dev/null +++ b/stockfish/wiki/Governance-and-responsibilities.md @@ -0,0 +1,129 @@ +## Stockfish + +### Stockfish organization + +**Link**: https://github.com/official-stockfish +**Type**: Organization, GitHub Team for Open Source +**Owners**: [glinscott] [mcostalba] [zamar] [snicolet] [vondele] [disservin] + +The Owners of the organization have Admin access to all repositories and can add new owners and maintainers. +Maintainers have "Admin" permission level on the repo. +Collaborators have "Write" permission level on the repo. + +#### Stockfish repository + +**Link**: https://github.com/official-stockfish/Stockfish +**Active Maintainers**: [disservin] [snicolet] [vondele] +**Inactive Maintainers**: [glinscott] [mcostalba] [zamar] [locutus2] + +#### Fishtest repository + +**Link**: https://github.com/official-stockfish/fishtest +**Maintainers**: [ppigazzini] +**Collaborators**: [zungur] +**Inactive Maintainers**: [tomtor] [stefano80] [glinscott] + +#### NN trainer repository + +**Link**: https://github.com/official-stockfish/nnue-pytorch +**Maintainers**: [Sopel97] [vondele] [glinscott] + +#### Website repository + +**Link**: https://github.com/official-stockfish/stockfish-web +**Maintainer**: [daylen] [dav1312] + +#### Books repository + +**Link**: https://github.com/official-stockfish/books +**Maintainers**: [vondele] [snicolet] [disservin] + +--- + +## Fishtest + +### VPS + +**Link**: https://tests.stockfishchess.org +**Owner**: [glinscott] +**Maintainers**: [ppigazzini] [zungur] + +### Networks Server repository + +**Link**: https://github.com/ppigazzini/net-server +**Maintainers**: [ppigazzini] + +### Networks + +**Server**: data.stockfishchess.org see also https://tests.stockfishchess.org/nns +**Owner**: [glinscott] +**Maintainers**: [ppigazzini] [zungur] + +### S3 Mongodb & Nets backups + +**Owner**: [glinscott] + +### History of past tests + +**Link**: https://groups.google.com/g/fishcooking-results +**Owners**: [ppigazzini] [vondele] [zungur] +**Link**: https://groups.google.com/g/fishcooking_results +**Owner**: [mcostalba] + +--- + +## Website + +### Hosting + +**Link**: https://stockfishchess.org/ +**Domain**: [daylen] (stockfishchess.org) +**CDN**: [daylen] + +--- + +## Social + +### Twitter + +**Link**: https://twitter.com/stockfishchess +**Owner**: [daylen] +**Maintainer**: [daylen] + +### Facebook + +**Link**: https://www.facebook.com/stockfishchess +**Owner**: [daylen] +**Maintainer**: [daylen] + +### Discord server + +**Link**: https://discord.gg/GWDRS3kU6R +**Owner**: [disservin] +**Admins**: [Sopel97] [vondele] + +### Forum (deprecated, use discord) + +**Link**: https://groups.google.com/g/fishcooking +**Owner**: [mcostalba] + +### Abrok (unofficial development versions) + +**Link**: http://abrok.eu/stockfish/ +**Owner**: Roman Korba ([roman.korba@t-online.de](mailto:roman.korba@t-online.de), [korba.roman@gmail.com](mailto:korba.roman@gmail.com)) + +[glinscott]: https://github.com/glinscott +[mcostalba]: https://github.com/mcostalba +[zamar]: https://github.com/zamar +[snicolet]: https://github.com/snicolet +[vondele]: https://github.com/vondele +[Disservin]: https://github.com/Disservin +[ppigazzini]: https://github.com/ppigazzini +[tomtor]: https://github.com/tomtor +[stefano80]: https://github.com/stefano80 +[Sopel97]: https://github.com/Sopel97 +[zungur]: https://github.com/zungur +[daylen]: https://github.com/daylen +[locutus2]: https://github.com/locutus2 +[noobpwnftw]: https://github.com/noobpwnftw +[dav1312]: https://github.com/dav1312 diff --git a/stockfish/wiki/Home.md b/stockfish/wiki/Home.md new file mode 100644 index 0000000000000000000000000000000000000000..fdb03df792ad3d23d770ac166cae6070055a3d3c --- /dev/null +++ b/stockfish/wiki/Home.md @@ -0,0 +1,38 @@ +[Stockfish](https://stockfishchess.org/) is a **free, open source, and strong UCI chess engine** derived from Glaurung 2.1 that analyzes chess positions and computes the optimal moves. + +Stockfish **does not include a graphical user interface** (GUI), which is needed to display a chessboard and to make it easy to input moves. Several [free GUIs](Download-and-usage#free) have been developed independently of Stockfish and are available online. +**Read the documentation for the GUI** of your choice to find out how to use it with Stockfish. + +## Guides + +* For users + * [Download Stockfish](Download-and-usage#download-stockfish) + * [Download GUI](Download-and-usage#download-a-chess-gui) + * [Install in GUI](Download-and-usage#install-in-a-chess-gui) + * [Commands and options](UCI-&-Commands) + * [Frequently asked questions (FAQ)](Stockfish-FAQ) + +* [For Developers](Developers) + * [Compiling from sources](Compiling-from-source) + * [Using Stockfish in your project](Developers#using-stockfish-in-your-own-project) + * [Contributing](Developers#participating-in-the-project) + * [Copying](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt) + +## Support + +If you're looking for any kind of assistance with Stockfish. + +* [Discord](https://discord.gg/GWDRS3kU6R) +* [Github discussions](https://github.com/official-stockfish/Stockfish/discussions/categories/q-a) + +## Related repositories + +* Trainer: [official-stockfish/nnue-pytorch](https://github.com/official-stockfish/nnue-pytorch) +* Fishtest: [official-stockfish/fishtest](https://github.com/official-stockfish/fishtest) +* Website: [official-stockfish/stockfish-web](https://github.com/official-stockfish/stockfish-web) +* WDL model: [official-stockfish/WDL_model](https://github.com/official-stockfish/WDL_model) +* Fishtest worker setup: [ppigazzini/fishtest-worker-setup](https://github.com/ppigazzini/fishtest-worker-setup) +* Matetrack: [vondele/matetrack](https://github.com/vondele/matetrack) +* Mac app: [daylen/stockfish-mac](https://github.com/daylen/stockfish-mac) + +See also our overview of [Governance and Responsibilities](Governance-and-responsibilities). diff --git a/stockfish/wiki/Regression-Tests.md b/stockfish/wiki/Regression-Tests.md new file mode 100644 index 0000000000000000000000000000000000000000..5250ed74681d6c45173661c457e4dda58c9d1401 --- /dev/null +++ b/stockfish/wiki/Regression-Tests.md @@ -0,0 +1,2038 @@ +All of the information below has been generated from the results of tests performed on the [Fishtest] framework. + +Current Testing Criteria + +* `1 Thread` 60 seconds + 0.6 seconds for 60,000 games `(2019-11-21 - current)` +* `8 Threads` 60 seconds + 0.6 seconds for 60,000 games `(2023-06-29 - current)` +* [UHO_4060_v3.epd][book-uho4060v3] opening book `(2023-09-10 - current)` + +
+ Previous Testing Criteria
+ +* `1 Thread` 60 seconds + 0.6 seconds for 40,000 games `(2016-01-02 - 2019-11-21)` +* `1 Thread` 60 seconds + 0.5 seconds for 40,000 games `(2013-10-13 - 2016-01-02)` +* `1 Thread` 60 seconds + 0.5 seconds for 20,000 games `(2013-03-04 - 2013-10-13)` +* `8 Threads` 30 seconds + 0.3 seconds for 40,000 games `(2018-12-13 - 2023-06-29)` +* 8moves_GM.pgn opening book `(2013-04-10 - 2013-11-01)` +* varied.bin opening book `(2013-03-04 - 2013-04-07)` +* [8moves_v3.pgn][book-8mv3] opening book `(2013-11-09 - 2023-06-29)` +* [UHO_XXL_+0.90_+1.19.epd][book-uho21epd] opening book `(2022-04-17 - 2023-06-29)` +* [UHO_4060_v2.epd][book-uho4060v2] opening book `(2023-06-29 - 2023-09-10)` + +
+ +--- + +## Current Development + +
+ +[![][graph-current]][graph-current] + +| `Date` | `Version` | `1 Thread` | `8 Threads` | +|:---:|:---:|:---:|:---:| +| 2024‑10‑12 | [master][121024-master] vs [Stockfish 17]
`Bench: 1283457`
Make low ply history size fixed
[\[differences\]][121024-dif] `40`
| Elo: [8.99][121024-elo1] ±1.3
Ptnml: 48, 5604, 17142, 7159, 47
nElo: 19.27 ±2.8
PairsRatio: 1.27
[\[raw statistics\]][121024-raw1]
| Elo: [4.85][121024-elo8] ±1.1
Ptnml: 4, 4647, 19867, 5471, 11
nElo: 11.80 ±2.8
PairsRatio: 1.18
[\[raw statistics\]][121024-raw8]
+| 2024‑11‑13 | [master][131124-master] vs [Stockfish 17]
`Bench: 840721`
Adjust statscore for captures
[\[differences\]][131124-dif] `63`
| Elo: [11.88][131124-elo1] ±1.3
Ptnml: 34, 5232, 17427, 7263, 44
nElo: 25.85 ±2.8
PairsRatio: 1.39
[\[raw statistics\]][131124-raw1]
| Elo: [4.81][131124-elo8] ±1.1
Ptnml: 3, 4639, 19887, 5466, 5
nElo: 11.72 ±2.8
PairsRatio: 1.18
[\[raw statistics\]][131124-raw8]
+| 2024‑12‑08 | [master][081224-master] vs [Stockfish 17]
`Bench: 934447`
Small Major/Minor piece key simplification/optimization.
[\[differences\]][081224-dif] `81`
| Elo: [13.02][081224-elo1] ±1.3
Ptnml: 36, 5189, 17325, 7392, 58
nElo: 28.18 ±2.8
PairsRatio: 1.43
[\[raw statistics\]][081224-raw1]
| Elo: [7.59][081224-elo8] ±1.1
Ptnml: 9, 4389, 19890, 5707, 5
nElo: 18.49 ±2.8
PairsRatio: 1.30
[\[raw statistics\]][081224-raw8]
+| 2025‑01‑06 | [master][060125-master] vs [Stockfish 17]
`Bench: 999324`
Remove non-functional std::min()
[\[differences\]][060125-dif] `102`
| Elo: [15.05][060125-elo1] ±1.3
Ptnml: 33, 5044, 17264, 7611, 48
nElo: 32.62 ±2.8
PairsRatio: 1.51
[\[raw statistics\]][060125-raw1]
| Elo: [4.81][060125-elo8] ±1.1
Ptnml: 9, 4603, 19941, 5443, 4
nElo: 11.73 ±2.8
PairsRatio: 1.18
[\[raw statistics\]][060125-raw8]
+| 2025‑01‑12 | [master][120125-master] vs [Stockfish 17]
`Bench: 1379150`
Increase the depth margin
[\[differences\]][120125-dif] `118`
| Elo: [10.87][120125-elo1] ±1.3
Ptnml: 52, 5424, 17161, 7321, 42
nElo: 23.35 ±2.8
PairsRatio: 1.34
[\[raw statistics\]][120125-raw1]
| Elo: [12.23][120125-elo8] ±1.1
Ptnml: 8, 4024, 19823, 6138, 7
nElo: 29.85 ±2.8
PairsRatio: 1.52
[\[raw statistics\]][120125-raw8]
+| 2025‑01‑25 | [master][250125-master] vs [Stockfish 17]
`Bench: 1438043`
Simplify futility margin in lmr for quiets.
[\[differences\]][250125-dif] `150`
| Elo: [16.96][250125-elo1] ±1.3
Ptnml: 50, 4861, 17260, 7770, 59
nElo: 36.72 ±2.8
PairsRatio: 1.59
[\[raw statistics\]][250125-raw1]
| Elo: [14.54][250125-elo8] ±1.1
Ptnml: 3, 3796, 19893, 6304, 4
nElo: 35.75 ±2.8
PairsRatio: 1.66
[\[raw statistics\]][250125-raw8]
+| 2025‑02‑04 | [master][040225-master] vs [Stockfish 17]
`Bench: 2887850`
Reduce less for positions without tt move
[\[differences\]][040225-dif] `177`
| Elo: [7.14][040225-elo1] ±1.3
Ptnml: 68, 5808, 17016, 7039, 69
nElo: 15.14 ±2.8
PairsRatio: 1.21
[\[raw statistics\]][040225-raw1]
| Elo: [14.61][040225-elo8] ±1.1
Ptnml: 5, 3923, 19625, 6440, 7
nElo: 35.41 ±2.8
PairsRatio: 1.64
[\[raw statistics\]][040225-raw8]
+| 2025‑02‑05 | [master][050225-master] vs [Stockfish 17]
`Bench: 3197798`
Increase PCM bonus when cutOffCnt is low
[\[differences\]][050225-dif] `181`
| Elo: [10.76][050225-elo1] ±1.3
Ptnml: 43, 5526, 17030, 7333, 68
nElo: 22.94 ±2.8
PairsRatio: 1.33
[\[raw statistics\]][050225-raw1]
| Elo: [15.31][050225-elo8] ±1.1
Ptnml: 4, 3845, 19662, 6482, 7
nElo: 37.23 ±2.8
PairsRatio: 1.69
[\[raw statistics\]][050225-raw8]
+| 2025‑02‑24 | [master][240225-master] vs [Stockfish 17]
`Bench: 2146010`
Simplify bestvalue update formula
[\[differences\]][240225-dif] `200`
| Elo: [19.43][240225-elo1] ±1.3
Ptnml: 21, 4662, 17306, 7966, 45
nElo: 42.50 ±2.8
PairsRatio: 1.71
[\[raw statistics\]][240225-raw1]
| Elo: [12.09][240225-elo8] ±1.2
Ptnml: 7, 4157, 19584, 6246, 6
nElo: 29.15 ±2.8
PairsRatio: 1.50
[\[raw statistics\]][240225-raw8]
+| 2025‑03‑21 | [master][210325-master] vs [Stockfish 17]
`Bench: 2030154`
Change layout of CorrectionHistory
[\[differences\]][210325-dif] `221`
| Elo: [19.80][210325-elo1] ±1.3
Ptnml: 30, 4563, 17419, 7938, 50
nElo: 43.44 ±2.8
PairsRatio: 1.74
[\[raw statistics\]][210325-raw1]
| Elo: [18.76][210325-elo8] ±1.1
Ptnml: 6, 3604, 19549, 6830, 11
nElo: 45.55 ±2.8
PairsRatio: 1.90
[\[raw statistics\]][210325-raw8]
+
+ +--- + +## Version Comparison + +### Elo Progression + +| [![][graph-elo1]][graph-elo1] | [![][graph-elo8]][graph-elo8] | +|:---------------------------------:|:---------------------------------:| + +### Normalized Elo Progression + +| [![][graph-nelo1]][graph-nelo1] | [![][graph-nelo8]][graph-nelo8] | +|:---------------------------------:|:---------------------------------:| + +### Game Pair Ratio Progression + +| [![][graph-gpr1]][graph-gpr1] | [![][graph-gpr8]][graph-gpr8] | +|:---------------------------------:|:---------------------------------:| + +### 30 Day Average + +| [![][graph-thirty1]][graph-thirty1] | [![][graph-thirty8]][graph-thirty8] | +|:-----------------------------------:|:-----------------------------------:| + +### Draw Percentage vs Elo + +| [![][graph-dve1]][graph-dve1] | [![][graph-dve8]][graph-dve8] | +|:-----------------------------:|:-----------------------------:| + +--- + +## Historical Information + +
+ Stockfish 3 Development (2013-03-01 - 2013-04-30)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2013‑03‑04 | [master][040313-master] vs [Stockfish 2.3.1]
`Bench: 4968764`
Increase see prune depth
[\[differences\]][040313-dif] `226`
| Elo: [15.00][040313-elo1] ±2.8
WDL: 2906, 13325, 3769
nElo: 26.02 ±4.8
[\[raw statistics\]][040313-raw1]
| +| 2013‑03‑11 | [master][110313A-master] vs [Stockfish 2.3.1]
`Bench: 4968764`
Be more aggressive on trying to finish iterations
[\[differences\]][110313A-dif] `227`
| Elo: [15.49][110313A-elo1] ±2.8
WDL: 3016, 13077, 3907
nElo: 26.38 ±4.8
[\[raw statistics\]][110313A-raw1]
| +| 2013‑03‑11 | [master][110313B-master] vs [Stockfish 2.3.1]
`Bench: 4968764`
Check for easy move just once
[\[differences\]][110313B-dif] `228`
| Elo: [13.42][110313B-elo1] ±2.8
WDL: 2974, 13280, 3746
nElo: 23.19 ±4.8
[\[raw statistics\]][110313B-raw1]
| +| 2013‑03‑16 | [master][160313-master] vs [Stockfish 2.3.1]
`Bench: 5442365`
Further increase SEE prune depth
[\[differences\]][160313-dif] `232`
| Elo: [17.77][160313-elo1] ±2.8
WDL: 2897, 13184, 3919
nElo: 30.53 ±4.8
[\[raw statistics\]][160313-raw1]
| +| 2013‑03‑24 | [master][240313-master] vs [Stockfish 2.3.1]
`Bench: 4985829`
Update bestValue when futility pruning (2)
[\[differences\]][240313-dif] `237`
| Elo: [16.71][240313-elo1] ±2.8
WDL: 2874, 13291, 3835
nElo: 28.92 ±4.8
[\[raw statistics\]][240313-raw1]
| +| 2013‑03‑30 | [master][300313-master] vs [Stockfish 2.3.1]
`Bench: 4781239`
Set IID half way between d/2 and d-4
[\[differences\]][300313-dif] `241`
| Elo: [18.76][300313-elo1] ±2.8
WDL: 2824, 13273, 3903
nElo: 32.46 ±4.8
[\[raw statistics\]][300313-raw1]
| +| 2013‑04‑03 | [master][030413-master] vs [Stockfish 2.3.1]
`Bench: 4705335`
Double Impact of Gain tables
[\[differences\]][030413-dif] `242`
| Elo: [15.44][030413-elo1] ±2.8
WDL: 3040, 13032, 3928
nElo: 26.21 ±4.8
[\[raw statistics\]][030413-raw1]
| +| 2013‑04‑06 | [master][060413-master] vs [Stockfish 2.3.1]
`Bench: 4361224`
Increase null verification threshold to 12 plies
[\[differences\]][060413-dif] `249`
| Elo: [17.11][060413-elo1] ±2.8
WDL: 2774, 12861, 3727
nElo: 29.62 ±4.9
[\[raw statistics\]][060413-raw1]
| +| 2013‑04‑07 | [master][070413-master] vs [Stockfish 2.3.1]
`Bench: 5473339`
Rescale UCI parameters to 100
[\[differences\]][070413-dif] `252`
| Elo: [19.02][070413-elo1] ±2.8
WDL: 2948, 13010, 4042
nElo: 32.29 ±4.8
[\[raw statistics\]][070413-raw1]
| +| 2013‑04‑10 | [master][100413-master] vs [Stockfish 2.3.1]
`Bench: 5157061`
De-templetize Position::is_draw()
[\[differences\]][100413-dif] `257`
| Elo: [24.13][100413-elo1] ±2.8
WDL: 2791, 13031, 4178
nElo: 41.10 ±4.8
[\[raw statistics\]][100413-raw1]
| +| 2013‑04‑19 | [master][190413-master] vs [Stockfish 2.3.1]
`Bench: 5274705`
Skip a couple of popcount in previous patch
[\[differences\]][190413-dif] `262`
| Elo: [28.27][190413-elo1] ±2.9
WDL: 2754, 12868, 4378
nElo: 47.69 ±4.9
[\[raw statistics\]][190413-raw1]
| +| 2013‑04‑26 | [master][260413-master] vs [Stockfish 2.3.1]
`Bench: 4311634`
Fix a crash introduced few days ago
[\[differences\]][260413-dif] `270`
| Elo: [33.67][260413-elo1] ±2.9
WDL: 2642, 12784, 4574
nElo: 56.61 ±4.9
[\[raw statistics\]][260413-raw1]
| +| 2013‑04‑28 | [master][280413-master] vs [Stockfish 2.3.1]
`Bench: 4176431`
Temporary revert "Expose EvalInfo struct to search"
[\[differences\]][280413-dif] `273`
| Elo: [30.86][280413-elo1] ±2.9
WDL: 2721, 12786, 4493
nElo: 51.82 ±4.9
[\[raw statistics\]][280413-raw1]
| +| 2013‑04‑30 | [Stockfish 3] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF3RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF3DP]
`Bench: 4176431`
[\[differences\]][300413-dif] `275`
| + +
+ +
+ Stockfish 4 Development (2013-04-30 - 2013-08-20)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2013‑05‑16 | [master][160513-master] vs [Stockfish 3]
`Bench: 4327405`
Use two counter moves instead of one
[\[differences\]][160513-dif] `28`
| Elo: [22.11][160513-elo1] ±3.0
WDL: 3212, 12305, 4483
nElo: 35.78 ±4.8
[\[raw statistics\]][160513-raw1]
| +| 2013‑05‑23 | [master][230513-master] vs [Stockfish 3]
`Bench: 4821467`
Bunch of 3 small patches
[\[differences\]][230513-dif] `33`
| Elo: [26.70][230513-elo1] ±2.1
WDL: 6435, 24062, 9503
nElo: 42.53 ±3.4
[\[raw statistics\]][230513-raw1]
| +| 2013‑05‑31 | [master][310513-master] vs [Stockfish 3]
`Bench: 4931544`
Passed pawn tuning
[\[differences\]][310513-dif] `38`
| Elo: [29.50][310513-elo1] ±2.2
WDL: 6588, 23436, 9976
nElo: 46.13 ±3.4
[\[raw statistics\]][310513-raw1]
| +| 2013‑06‑23 | [master][230613-master] vs [Stockfish 3]
`Bench: 4609948`
Fix some stale comments
[\[differences\]][230613-dif] `72`
| Elo: [35.47][230613-elo1] ±2.2
WDL: 6196, 23539, 10265
nElo: 55.80 ±3.4
[\[raw statistics\]][230613-raw1]
| +| 2013‑07‑03 | [master][030713-master] vs [Stockfish 3]
`Bench: 4507288`
Simplify aspiration window code
[\[differences\]][030713-dif] `88`
| Elo: [37.36][030713-elo1] ±2.2
WDL: 6223, 23269, 10508
nElo: 58.35 ±3.4
[\[raw statistics\]][030713-raw1]
| +| 2013‑07‑13 | [master][130713-master] vs [Stockfish 3]
`Bench: 4558173`
Fully qualify memset and memcpy
[\[differences\]][130713-dif] `100`
| Elo: [39.27][130713-elo1] ±3.1
WDL: 3052, 11645, 5303
nElo: 61.44 ±4.9
[\[raw statistics\]][130713-raw1]
| +| 2013‑07‑19 | [master][190713-master] vs [Stockfish 3]
`Bench: 4769737`
Halve king eval margin
[\[differences\]][190713-dif] `110`
| Elo: [39.83][190713-elo1] ±3.1
WDL: 3067, 11583, 5350
nElo: 62.10 ±4.9
[\[raw statistics\]][190713-raw1]
| +| 2013‑07‑25 | [master][250713-master] vs [Stockfish 3]
`Bench: 4727133`
Rewrite pawn shield and storm code
[\[differences\]][250713-dif] `133`
| Elo: [48.84][250713-elo1] ±3.3
WDL: 3203, 10801, 5996
nElo: 73.11 ±4.9
[\[raw statistics\]][250713-raw1]
| +| 2013‑08‑03 | [master][030813-master] vs [Stockfish 3]
`Bench: 4424151`
Streamline time computation
[\[differences\]][030813-dif] `147`
| Elo: [50.95][030813-elo1] ±3.2
WDL: 3056, 10976, 5968
nElo: 77.14 ±4.9
[\[raw statistics\]][030813-raw1]
| +| 2013‑08‑18 | [master][180813-master] vs [Stockfish 3]
`Bench: 4132374`
Further tweak movecount pruning
[\[differences\]][180813-dif] `162`
| Elo: [56.66][180813-elo1] ±3.3
WDL: 2988, 10791, 6221
nElo: 85.22 ±5.0
[\[raw statistics\]][180813-raw1]
| +| 2013‑08‑20 | [Stockfish 4] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF4RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF4DP]
`Bench: 4132374`
[\[differences\]][200813-dif] `165`
| + +
+ +
+ Stockfish DD Development (2013-08-20 - 2013-11-29)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2013‑08‑29 | [master][290813-master] vs [Stockfish 4]
`Bench: 4620975`
Enable LMR for dangerous moves
[\[differences\]][290813-dif] `12`
| Elo: [16.18][290813-elo1] ±2.9
WDL: 3221, 12627, 4152
nElo: 26.72 ±4.8
[\[raw statistics\]][290813-raw1]
| +| 2013‑09‑01 | [master][010913-master] vs [Stockfish 4]
`Bench: 3453945`
Don't use lpthread for Android
[\[differences\]][010913-dif] `18`
| Elo: [19.30][010913-elo1] ±2.9
WDL: 3083, 12724, 4193
nElo: 32.11 ±4.8
[\[raw statistics\]][010913-raw1]
| +| 2013‑09‑05 | [master][050913-master] vs [Stockfish 4]
`Bench: 4633330`
Do not prune useless checks in QS
[\[differences\]][050913-dif] `23`
| Elo: [23.80][050913-elo1] ±2.9
WDL: 2932, 12768, 4300
nElo: 39.78 ±4.8
[\[raw statistics\]][050913-raw1]
| +| 2013‑09‑07 | [master][070913-master] vs [Stockfish 4]
`Bench: 3864419`
Remove unreachable values in mobility table
[\[differences\]][070913-dif] `27`
| Elo: [27.66][070913-elo1] ±2.9
WDL: 2766, 12879, 4355
nElo: 46.68 ±4.9
[\[raw statistics\]][070913-raw1]
| +| 2013‑09‑11 | [master][110913-master] vs [Stockfish 4]
`Bench: 4554576`
Extend checks more when below alpha
[\[differences\]][110913-dif] `35`
| Elo: [24.87][110913-elo1] ±2.9
WDL: 2824, 12923, 4253
nElo: 42.04 ±4.8
[\[raw statistics\]][110913-raw1]
| +| 2013‑09‑12 | [master][120913-master] vs [Stockfish 4]
`Bench: 4554579`
Revert "Move draw by material check"
[\[differences\]][120913-dif] `37`
| Elo: [24.34][120913-elo1] ±2.9
WDL: 2825, 12951, 4224
nElo: 41.22 ±4.8
[\[raw statistics\]][120913-raw1]
| +| 2013‑09‑13 | [master][130913-master] vs [Stockfish 4]
`Bench: 3846852`
Increase passed bonus for having more pieces
[\[differences\]][130913-dif] `38`
| Elo: [26.21][130913-elo1] ±2.9
WDL: 2871, 12752, 4377
nElo: 43.80 ±4.8
[\[raw statistics\]][130913-raw1]
| +| 2013‑09‑16 | [master][160913-master] vs [Stockfish 4]
`Bench: 3884003`
Fix time parameters for blitz games
[\[differences\]][160913-dif] `47`
| Elo: [31.56][160913-elo1] ±2.1
WDL: 5588, 25200, 9212
nElo: 52.33 ±3.4
[\[raw statistics\]][160913-raw1]
| +| 2013‑09‑23 | [master][230913-master] vs [Stockfish 4]
`Bench: 3529630`
Update disabled warnings for Intel compiler
[\[differences\]][230913-dif] `54`
| Elo: [34.03][230913-elo1] ±2.1
WDL: 5449, 25197, 9354
nElo: 56.49 ±3.4
[\[raw statistics\]][230913-raw1]
| +| 2013‑09‑28 | [master][280913-master] vs [Stockfish 4]
`Bench: 3172206`
Drop 'is' prefix from query functions
[\[differences\]][280913-dif] `62`
| Elo: [33.49][280913-elo1] ±2.9
WDL: 2571, 12936, 4493
nElo: 56.93 ±4.9
[\[raw statistics\]][280913-raw1]
| +| 2013‑09‑29 | [master][290913-master] vs [Stockfish 4]
`Bench: 8336338`
Add more depth/positions to bench
[\[differences\]][290913-dif] `63`
| Elo: [33.90][290913-elo1] ±2.0
WDL: 5292, 25525, 9183
nElo: 56.93 ±3.4
[\[raw statistics\]][290913-raw1]
| +| 2013‑10‑08 | [master][081013A-master] vs [Stockfish 4]
`Bench: 8340585`
Use TT refined value to stand pat
[\[differences\]][081013A-dif] `66`
| Elo: [36.58][081013A-elo1] ±2.9
WDL: 2623, 12656, 4721
nElo: 61.07 ±4.9
[\[raw statistics\]][081013A-raw1]
| +| 2013‑10‑08 | [master][081013B-master] vs [Stockfish 4]
`Bench: 8340585`
Increase slowmover and reduce instability
[\[differences\]][081013B-dif] `67`
| Elo: [38.91][081013B-elo1] ±2.1
WDL: 5102, 25335, 9563
nElo: 65.11 ±3.5
[\[raw statistics\]][081013B-raw1]
| +| 2013‑10‑09 | [master][091013-master] vs [Stockfish 4]
`Bench: 8279065`
Smoother transition for LMR
[\[differences\]][091013-dif] `68`
| Elo: [39.29][091013-elo1] ±2.1
WDL: 5071, 25354, 9575
nElo: 65.80 ±3.4
[\[raw statistics\]][091013-raw1]
| +| 2013‑10‑14 | [master][141013-master] vs [Stockfish 4]
`Bench: 7700683`
Double king safety weights
[\[differences\]][141013-dif] `75`
| Elo: [43.64][141013-elo1] ±2.9
WDL: 2432, 12637, 4931
nElo: 73.12 ±4.9
[\[raw statistics\]][141013-raw1]
| +| 2013‑10‑18 | [master][181013-master] vs [Stockfish 4]
`Bench: 8440524`
Score chain pawn also by rank
[\[differences\]][181013-dif] `78`
| Elo: [49.51][181013-elo1] ±2.1
WDL: 4821, 24696, 10483
nElo: 81.68 ±3.5
[\[raw statistics\]][181013-raw1]
| +| 2013‑10‑19 | [master][191013-master] vs [Stockfish 4]
`Bench: 9160831`
Further increase safe checks bonus
[\[differences\]][191013-dif] `80`
| Elo: [50.11][191013-elo1] ±2.1
WDL: 4817, 24636, 10547
nElo: 82.54 ±3.5
[\[raw statistics\]][191013-raw1]
| +| 2013‑10‑20 | [master][201013-master] vs [Stockfish 4]
`Bench: 9294116`
Further improve chain pawn evaluation
[\[differences\]][201013-dif] `84`
| Elo: [49.72][201013-elo1] ±3.1
WDL: 2677, 11803, 5520
nElo: 79.12 ±4.9
[\[raw statistics\]][201013-raw1]
| +| 2013‑10‑22 | [master][221013-master] vs [Stockfish 4]
`Bench: 8455956`
Tweak again chain pawn bonus
[\[differences\]][221013-dif] `87`
| Elo: [53.85][221013-elo1] ±3.0
WDL: 2425, 12075, 5500
nElo: 87.51 ±4.9
[\[raw statistics\]][221013-raw1]
| +| 2013‑10‑24 | [master][241013-master] vs [Stockfish 4]
`Bench: 8291883`
Retire mirror()
[\[differences\]][241013-dif] `94`
| Elo: [55.18][241013-elo1] ±3.0
WDL: 2385, 12080, 5535
nElo: 89.82 ±4.9
[\[raw statistics\]][241013-raw1]
| +| 2013‑10‑28 | [master][281013-master] vs [Stockfish 4]
`Bench: 8029334`
Tweak bishop pair and knight weight
[\[differences\]][281013-dif] `96`
| Elo: [52.84][281013-elo1] ±2.1
WDL: 4866, 24231, 10903
nElo: 86.04 ±3.5
[\[raw statistics\]][281013-raw1]
| +| 2013‑11‑01 | [master][011113-master] vs [Stockfish 4]
`Bench: 7995098`
Set timer to a fixed interval
[\[differences\]][011113-dif] `98`
| Elo: [59.73][011113-elo1] ±3.0
WDL: 2324, 11947, 5729
nElo: 96.77 ±5.0
[\[raw statistics\]][011113-raw1]
| +| 2013‑11‑09 | [master][091113-master] vs [Stockfish 4]
`Bench: 7243575`
Futility pruning simplification
[\[differences\]][091113-dif] `106`
| Elo: [60.68][091113-elo1] ±3.0
WDL: 2198, 12146, 5656
nElo: 99.73 ±5.0
[\[raw statistics\]][091113-raw1]
| +| 2013‑11‑10 | [master][101113-master] vs [Stockfish 4]
`Bench: 9282549`
Remove opposed flag for doubled pawns
[\[differences\]][101113-dif] `113`
| Elo: [61.23][101113-elo1] ±2.1
WDL: 4333, 24357, 11310
nElo: 100.91 ±3.5
[\[raw statistics\]][101113-raw1]
| +| 2013‑11‑11 | [master][111113-master] vs [Stockfish 4]
`Bench: 8331357`
Simplify generate\
[\[differences\]][111113-dif] `116`
| Elo: [63.85][111113-elo1] ±2.1
WDL: 4185, 24361, 11454
nElo: 105.53 ±3.5
[\[raw statistics\]][111113-raw1]
| +| 2013‑11‑29 | [master][291113A-master] vs [Stockfish 4]
`Bench: 8596156`
Add support for PPC 64bit on Linux
[\[differences\]][291113A-dif] `123`
| Elo: [67.44][291113A-elo1] ±2.1
WDL: 4119, 24094, 11787
nElo: 110.87 ±3.5
[\[raw statistics\]][291113A-raw1]
| +| 2013‑11‑29 | [Stockfish DD] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SFDDRN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SFDDDP]
`Bench: 8596156`
[\[differences\]][291113B-dif] `124`
| + +
+ +
+ Stockfish 5 Development (2013-11-29 - 2014-05-31)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2013‑12‑09 | [master][091213-master] vs [Stockfish DD]
`Bench: 7869223`
Research at intermediate depth if LMR is very high
[\[differences\]][091213-dif] `23`
| Elo: [7.94][091213-elo1] ±1.9
WDL: 5662, 27762, 6576
nElo: 14.37 ±3.4
[\[raw statistics\]][091213-raw1]
| +| 2013‑12‑19 | [master][191213-master] vs [Stockfish DD]
`Bench: 7425809`
Faster and simplified threat eval
[\[differences\]][191213-dif] `29`
| Elo: [11.35][191213-elo1] ±1.9
WDL: 5766, 27162, 7072
nElo: 20.06 ±3.4
[\[raw statistics\]][191213-raw1]
| +| 2013‑12‑23 | [master][231213-master] vs [Stockfish DD]
`Bench: 6835416`
Loosened trigger condition for king safety
[\[differences\]][231213-dif] `31`
| Elo: [18.62][231213-elo1] ±2.0
WDL: 5619, 26620, 7761
nElo: 32.31 ±3.4
[\[raw statistics\]][231213-raw1]
| +| 2013‑12‑29 | [master][291213-master] vs [Stockfish DD]
`Bench: 7762310`
Retire asymmThreshold
[\[differences\]][291213-dif] `36`
| Elo: [19.54][291213-elo1] ±2.0
WDL: 5580, 26593, 7827
nElo: 33.87 ±3.4
[\[raw statistics\]][291213-raw1]
| +| 2014‑01‑02 | [master][020114-master] vs [Stockfish DD]
`Bench: 7602383`
Ensure move_importance() is non-zero
[\[differences\]][020114-dif] `46`
| Elo: [25.36][020114-elo1] ±2.0
WDL: 5371, 26343, 8286
nElo: 43.67 ±3.4
[\[raw statistics\]][020114-raw1]
| +| 2014‑01‑08 | [master][080114-master] vs [Stockfish DD]
`Bench: 8502826`
Position::gives_check - use ci.ksq
[\[differences\]][080114-dif] `55`
| Elo: [29.85][080114-elo1] ±2.0
WDL: 5165, 26242, 8593
nElo: 51.32 ±3.4
[\[raw statistics\]][080114-raw1]
| +| 2014‑01‑14 | [master][140114-master] vs [Stockfish DD]
`Bench: 7205153`
Introduce 'follow up' moves
[\[differences\]][140114-dif] `59`
| Elo: [29.84][140114-elo1] ±2.0
WDL: 5222, 26129, 8649
nElo: 51.09 ±3.4
[\[raw statistics\]][140114-raw1]
| +| 2014‑01‑19 | [master][190114-master] vs [Stockfish DD]
`Bench: 7804908`
Small simplification to Position::see
[\[differences\]][190114-dif] `64`
| Elo: [32.49][190114-elo1] ±2.0
WDL: 5088, 26094, 8818
nElo: 55.65 ±3.4
[\[raw statistics\]][190114-raw1]
| +| 2014‑01‑29 | [master][290114-master] vs [Stockfish DD]
`Bench: 6875743`
Tweak bishop PSQT tables
[\[differences\]][290114-dif] `70`
| Elo: [36.66][290114-elo1] ±2.0
WDL: 4905, 25985, 9110
nElo: 62.70 ±3.4
[\[raw statistics\]][290114-raw1]
| +| 2014‑02‑09 | [master][090214-master] vs [Stockfish DD]
`Bench: 8347121`
Faster handling of king captures in Position::see
[\[differences\]][090214-dif] `79`
| Elo: [38.63][090214-elo1] ±2.0
WDL: 4956, 25659, 9385
nElo: 65.38 ±3.4
[\[raw statistics\]][090214-raw1]
| +| 2014‑02‑22 | [master][220214-master] vs [Stockfish DD]
`Bench: 8430785`
Fix a warning with Intel compiler
[\[differences\]][220214-dif] `99`
| Elo: [38.93][220214-elo1] ±2.0
WDL: 4944, 25649, 9407
nElo: 65.87 ±3.4
[\[raw statistics\]][220214-raw1]
| +| 2014‑02‑26 | [master][260214-master] vs [Stockfish DD]
`Bench: 7990513`
Dynamic draw value
[\[differences\]][260214-dif] `100`
| Elo: [39.25][260214-elo1] ±2.1
WDL: 5164, 25172, 9664
nElo: 65.32 ±3.4
[\[raw statistics\]][260214-raw1]
| +| 2014‑03‑14 | [master][140314-master] vs [Stockfish DD]
`Bench: 7451319`
Depth dependent aspiration window delta
[\[differences\]][140314-dif] `122`
| Elo: [40.85][140314-elo1] ±2.0
WDL: 4925, 25469, 9606
nElo: 68.77 ±3.5
[\[raw statistics\]][140314-raw1]
| +| 2014‑03‑24 | [master][240314-master] vs [Stockfish DD]
`Bench: 7682173`
Simplify TT replace strategy
[\[differences\]][240314-dif] `138`
| Elo: [43.70][240314-elo1] ±2.1
WDL: 4887, 25221, 9892
nElo: 73.08 ±3.5
[\[raw statistics\]][240314-raw1]
| +| 2014‑04‑08 | [master][080414-master] vs [Stockfish DD]
`Bench: 7533692`
Restrict queen mobility to safe squares
[\[differences\]][080414-dif] `159`
| Elo: [47.70][080414-elo1] ±2.1
WDL: 4689, 25165, 10146
nElo: 79.86 ±3.5
[\[raw statistics\]][080414-raw1]
| +| 2014‑04‑12 | [master][120414-master] vs [Stockfish DD]
`Bench: 6921356`
Move args parsing to UCI::loop
[\[differences\]][120414-dif] `174`
| Elo: [49.21][120414-elo1] ±2.1
WDL: 4717, 24938, 10345
nElo: 81.84 ±3.5
[\[raw statistics\]][120414-raw1]
| +| 2014‑04‑21 | [master][210414-master] vs [Stockfish DD]
`Bench: 7384368`
Reset DrawValue[] before new search
[\[differences\]][210414-dif] `184`
| Elo: [54.53][210414-elo1] ±2.1
WDL: 4742, 24289, 10969
nElo: 89.09 ±3.5
[\[raw statistics\]][210414-raw1]
| +| 2014‑04‑25 | [master][250414-master] vs [Stockfish DD]
`Bench: 7905850`
Speed up picking of killers
[\[differences\]][250414-dif] `191`
| Elo: [57.08][250414-elo1] ±2.2
WDL: 4858, 23771, 11371
nElo: 91.87 ±3.5
[\[raw statistics\]][250414-raw1]
| +| 2014‑05‑04 | [master][040514-master] vs [Stockfish DD]
`Bench: 8802105`
Revert dynamic contempt
[\[differences\]][040514-dif] `216`
| Elo: [53.27][040514-elo1] ±2.2
WDL: 5183, 23549, 11268
nElo: 84.84 ±3.5
[\[raw statistics\]][040514-raw1]
| +| 2014‑05‑13 | [master][130514-master] vs [Stockfish DD]
`Bench: 8739659`
Drop to qsearch at low depth in razoring
[\[differences\]][130514-dif] `227`
| Elo: [57.15][130514-elo1] ±1.8
WDL: 7289, 35641, 17070
nElo: 91.95 ±2.9
[\[raw statistics\]][130514-raw1]
| +| 2014‑05‑17 | [master][170514-master] vs [Stockfish DD]
`Bench: 8732553`
Fix an off-by-one bug in extract_pv_from_tt
[\[differences\]][170514-dif] `229`
| Elo: [55.26][170514-elo1] ±2.2
WDL: 5108, 23475, 11417
nElo: 87.95 ±3.5
[\[raw statistics\]][170514-raw1]
| +| 2014‑05‑24 | [master][240514-master] vs [Stockfish DD]
`Bench: 7396783`
Fix a warning with Intel compiler
[\[differences\]][240514-dif] `234`
| Elo: [53.28][240514-elo1] ±2.1
WDL: 4858, 24198, 10944
nElo: 86.68 ±3.5
[\[raw statistics\]][240514-raw1]
| +| 2014‑05‑31 | [Stockfish 5] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF5RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF5DP]
`Bench: 8732553`
[\[differences\]][310514-dif] `236`
| + +
+ +
+ Stockfish 6 Development (2014-05-31 - 2015-01-27)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2014‑06‑03 | [master][030614-master] vs [Stockfish 5]
`Bench: 8205159`
Symmetric King Safety: take 2
[\[differences\]][030614-dif] `8`
| Elo: [5.35][030614-elo1] ±1.8
WDL: 5386, 28612, 6002
nElo: 10.03 ±3.4
[\[raw statistics\]][030614-raw1]
| +| 2014‑06‑11 | [master][110614-master] vs [Stockfish 5]
`Bench: 7875814`
Simplify pawn threats and merge into ThreatenedByPawn[]
[\[differences\]][110614-dif] `22`
| Elo: [8.30][110614-elo1] ±2.0
WDL: 4589, 24491, 5413
nElo: 15.43 ±3.7
[\[raw statistics\]][110614-raw1]
| +| 2014‑06‑29 | [master][290614-master] vs [Stockfish 5]
`Bench: 8759675`
Fix Singular extension condition to handle mate scores
[\[differences\]][290614-dif] `46`
| Elo: [10.78][290614-elo1] ±1.8
WDL: 5152, 28455, 6393
nElo: 20.10 ±3.4
[\[raw statistics\]][290614-raw1]
| +| 2014‑07‑22 | [master][220714-master] vs [Stockfish 5]
`Bench: 7831429`
Outpost tuning
[\[differences\]][220714-dif] `62`
| Elo: [19.63][220714-elo1] ±1.8
WDL: 4775, 28192, 7033
nElo: 36.29 ±3.4
[\[raw statistics\]][220714-raw1]
| +| 2014‑08‑06 | [master][060814-master] vs [Stockfish 5]
`Bench: 7461881`
Remove insufficient material rule
[\[differences\]][060814-dif] `73`
| Elo: [19.68][060814-elo1] ±1.8
WDL: 4737, 28263, 7000
nElo: 36.49 ±3.4
[\[raw statistics\]][060814-raw1]
| +| 2014‑09‑04 | [master][040914-master] vs [Stockfish 5]
`Bench: 7461881`
Small tweak to idle_loop()
[\[differences\]][040914-dif] `82`
| Elo: [15.90][040914-elo1] ±1.8
WDL: 4986, 28199, 6815
nElo: 29.35 ±3.4
[\[raw statistics\]][040914-raw1]
| +| 2014‑09‑27 | [master][270914-master] vs [Stockfish 5]
`Bench: 6545733`
Remove use of half-ply reductions
[\[differences\]][270914-dif] `93`
| Elo: [22.80][270914-elo1] ±1.9
WDL: 4664, 28051, 7285
nElo: 41.96 ±3.4
[\[raw statistics\]][270914-raw1]
| +| 2014‑10‑15 | [master][151014-master] vs [Stockfish 5]
`Bench: 7328585`
Document why initing eval tables
[\[differences\]][151014-dif] `108`
| Elo: [27.58][151014-elo1] ±1.9
WDL: 4613, 27605, 7782
nElo: 49.96 ±3.4
[\[raw statistics\]][151014-raw1]
| +| 2014‑11‑01 | [master][011114-master] vs [Stockfish 5]
`Bench: 6564212`
Merge pull request #89 from official-stockfish/pull_no_pretty
[\[differences\]][011114-dif] `129`
| Elo: [31.00][011114-elo1] ±1.9
WDL: 4484, 27472, 8044
nElo: 55.96 ±3.4
[\[raw statistics\]][011114-raw1]
| +| 2014‑11‑10 | [master][101114-master] vs [Stockfish 5]
`Bench: 6807896`
Profile Build with Hash=16
[\[differences\]][101114-dif] `148`
| Elo: [36.21][101114-elo1] ±1.9
WDL: 4357, 27132, 8511
nElo: 64.71 ±3.4
[\[raw statistics\]][101114-raw1]
| +| 2014‑11‑25 | [master][251114-master] vs [Stockfish 5]
`Bench: 8255966`
Bitbase index() from ADD to OR
[\[differences\]][251114-dif] `168`
| Elo: [39.02][251114-elo1] ±1.9
WDL: 4151, 27225, 8624
nElo: 70.14 ±3.4
[\[raw statistics\]][251114-raw1]
| +| 2014‑12‑07 | [master][071214-master] vs [Stockfish 5]
`Bench: 9324905`
Simpler PRNG and faster magics search
[\[differences\]][071214-dif] `181`
| Elo: [41.42][071214-elo1] ±1.9
WDL: 4108, 27038, 8854
nElo: 74.04 ±3.4
[\[raw statistics\]][071214-raw1]
| +| 2014‑12‑22 | [master][221214-master] vs [Stockfish 5]
`Bench: 9498821`
Prefer names to numbers in storm code
[\[differences\]][221214-dif] `197`
| Elo: [46.26][221214-elo1] ±1.9
WDL: 4011, 26683, 9306
nElo: 81.89 ±3.4
[\[raw statistics\]][221214-raw1]
| +| 2015‑01‑07 | [master][070115-master] vs [Stockfish 5]
`Bench: 7604776`
Assorted formatting and comment tweaks in position.h
[\[differences\]][070115-dif] `213`
| Elo: [52.10][070115-elo1] ±2.0
WDL: 3913, 26688, 9948
nElo: 91.46 ±3.4
[\[raw statistics\]][070115-raw1]
| +| 2015‑01‑18 | [master][180115-master] vs [Stockfish 5]
`Bench: 8080602`
Stockfish 6 Release Candidate 1
[\[differences\]][180115-dif] `228`
| Elo: [51.71][180115-elo1] ±1.9
WDL: 3723, 26644, 9633
nElo: 91.89 ±3.5
[\[raw statistics\]][180115-raw1]
| +| 2015‑01‑27 | [Stockfish 6] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF6RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF6DP]
`Bench: 8918745`
[\[differences\]][270115-dif] `236`
| + +
+ +
+ Stockfish 7 Development (2015-01-27 - 2016-01-02)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2015‑02‑08 | [master][080215-master] vs [Stockfish 6]
`Bench: 7699138`
Pawn Center Bind Bonus
[\[differences\]][080215-dif] `12`
| Elo: [7.50][080215-elo1] ±2.0
WDL: 6423, 26291, 7286
nElo: 12.81 ±3.4
[\[raw statistics\]][080215-raw1]
| +| 2015‑03‑19 | [master][190315-master] vs [Stockfish 6]
`Bench: 8226843`
Retire ConditionVariable
[\[differences\]][190315-dif] `123`
| Elo: [15.92][190315-elo1] ±1.8
WDL: 4796, 28576, 6628
nElo: 29.89 ±3.4
[\[raw statistics\]][190315-raw1]
| +| 2015‑03‑29 | [master][290315-master] vs [Stockfish 6]
`Bench: 7658627`
Remove some difficult to understand C++11 constructs
[\[differences\]][290315-dif] `137`
| Elo: [19.02][290315-elo1] ±1.8
WDL: 4590, 28633, 6777
nElo: 35.82 ±3.4
[\[raw statistics\]][290315-raw1]
| +| 2015‑04‑10 | [master][100415-master] vs [Stockfish 6]
`Bench: 6985247`
Allow Position::init() to be called more than once
[\[differences\]][100415-dif] `151`
| Elo: [22.12][100415-elo1] ±1.8
WDL: 4480, 28497, 7023
nElo: 41.48 ±3.4
[\[raw statistics\]][100415-raw1]
| +| 2015‑05‑09 | [master][090515-master] vs [Stockfish 6]
`Bench: 8787152`
Smart TT save
[\[differences\]][090515-dif] `164`
| Elo: [28.82][090515-elo1] ±1.8
WDL: 4197, 28295, 7508
nElo: 53.80 ±3.4
[\[raw statistics\]][090515-raw1]
| +| 2015‑06‑07 | [master][070615-master] vs [Stockfish 6]
`Bench: 6716940`
Simplify outpost evaluation
[\[differences\]][070615-dif] `176`
| Elo: [27.85][070615-elo1] ±1.9
WDL: 4501, 27798, 7701
nElo: 50.86 ±3.4
[\[raw statistics\]][070615-raw1]
| +| 2015‑07‑16 | [master][160715-master] vs [Stockfish 6]
`Bench: 6943812`
Fix formatting of previous patch
[\[differences\]][160715-dif] `187`
| Elo: [30.74][160715-elo1] ±1.9
WDL: 4367, 27736, 7897
nElo: 56.09 ±3.4
[\[raw statistics\]][160715-raw1]
| +| 2015‑07‑30 | [master][300715-master] vs [Stockfish 6]
`Bench: 8040572`
Simplify IID depth formula
[\[differences\]][300715-dif] `192`
| Elo: [34.04][300715-elo1] ±1.9
WDL: 4283, 27527, 8190
nElo: 61.72 ±3.4
[\[raw statistics\]][300715-raw1]
| +| 2015‑10‑03 | [master][031015-master] vs [Stockfish 6]
`Bench: 8073614`
File based passed pawn bonus
[\[differences\]][031015-dif] `214`
| Elo: [44.23][031015-elo1] ±1.9
WDL: 3690, 27555, 8755
nElo: 80.99 ±3.4
[\[raw statistics\]][031015-raw1]
| +| 2015‑10‑25 | [master][251015-master] vs [Stockfish 6]
`Bench: 8004751`
Use atomics instead of volatile
[\[differences\]][251015-dif] `232`
| Elo: [61.76][251015-elo1] ±1.9
WDL: 3197, 26570, 10233
nElo: 110.69 ±3.5
[\[raw statistics\]][251015-raw1]
| +| 2015‑12‑27 | [master][271215-master] vs [Stockfish 6]
`Bench: 8355485`
Stockfish 7 Beta 1
[\[differences\]][271215-dif] `267`
| Elo: [62.62][271215-elo1] ±1.6
WDL: 4472, 40358, 15170
nElo: 113.94 ±2.8
[\[raw statistics\]][271215-raw1]
| +| 2016‑01‑02 | [Stockfish 7] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF7RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF7DP]
`Bench: 8355485`
[\[differences\]][020116-dif] `273`
| + +
+ +
+ Stockfish 8 Development (2016-01-02 - 2016-11-01)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2016‑01‑28 | [master][280116-master] vs [Stockfish 7]
`Bench: 7751425`
Time management simplification
[\[differences\]][280116-dif] `16`
| Elo: [4.93][280116-elo1] ±1.5
WDL: 3814, 31804, 4382
nElo: 10.90 ±3.4
[\[raw statistics\]][280116-raw1]
| +| 2016‑03‑10 | [master][100316-master] vs [Stockfish 7]
`Bench: 8261839`
Add follow up moves history for move ordering
[\[differences\]][100316-dif] `28`
| Elo: [12.83][100316-elo1] ±1.5
WDL: 3354, 31816, 4830
nElo: 28.44 ±3.4
[\[raw statistics\]][100316-raw1]
| +| 2016‑04‑08 | [master][080416-master] vs [Stockfish 7]
`Bench: 7482426`
Small passed pawn simplification
[\[differences\]][080416-dif] `42`
| Elo: [15.27][080416-elo1] ±1.5
WDL: 3251, 31741, 5008
nElo: 33.74 ±3.4
[\[raw statistics\]][080416-raw1]
| +| 2016‑05‑20 | [master][200516-master] vs [Stockfish 7]
`Bench: 8428997`
More detailed dependence of time allocation
[\[differences\]][200516-dif] `64`
| Elo: [29.44][200516-elo1] ±1.6
WDL: 2719, 31217, 6104
nElo: 63.61 ±3.4
[\[raw statistics\]][200516-raw1]
| +| 2016‑06‑10 | [master][100616-master] vs [Stockfish 7]
`Bench: 8276130`
Stat Formula Tweak
[\[differences\]][100616-dif] `76`
| Elo: [36.29][100616-elo1] ±1.6
WDL: 2606, 30625, 6769
nElo: 76.48 ±3.4
[\[raw statistics\]][100616-raw1]
| +| 2016‑07‑24 | [master][240716-master] vs [Stockfish 7]
`Bench: 8145304`
Allow null pruning at depth 1
[\[differences\]][240716-dif] `94`
| Elo: [49.73][240716-elo1] ±1.7
WDL: 2415, 29483, 8102
nElo: 100.27 ±3.4
[\[raw statistics\]][240716-raw1]
| +| 2016‑08‑18 | [master][180816-master] vs [Stockfish 7]
`Bench: 7662861`
Remove a stale assignment
[\[differences\]][180816-dif] `101`
| Elo: [52.59][180816-elo1] ±1.7
WDL: 2413, 29165, 8422
nElo: 104.74 ±3.4
[\[raw statistics\]][180816-raw1]
| +| 2016‑09‑07 | [master][070916-master] vs [Stockfish 7]
`Bench: 6024713`
Refactor previous patch
[\[differences\]][070916-dif] `124`
| Elo: [66.53][070916-elo1] ±1.8
WDL: 1893, 28647, 9460
nElo: 131.97 ±3.3
[\[raw statistics\]][070916-raw1]
| +| 2016‑10‑07 | [master][071016-master] vs [Stockfish 7]
`Bench: 6421663`
Optimisation of Position::see and Position::see_sign
[\[differences\]][071016-dif] `149`
| Elo: [76.60][071016-elo1] ±1.8
WDL: 1571, 28179, 10250
nElo: 151.24 ±3.3
[\[raw statistics\]][071016-raw1]
| +| 2016‑11‑01 | [Stockfish 8] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF8RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF8DP]
`Bench: 5926706`
[\[differences\]][011116-dif] `168`
| + +
+ +
+ Stockfish 9 Development (2016-11-01 - 2018-01-31)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2016‑12‑31 | [master][311216-master] vs [Stockfish 8]
`Bench: 5468995`
Small eval cleanup and renaming
[\[differences\]][311216-dif] `55`
| Elo: [5.21][311216-elo1] ±1.5
WDL: 3656, 32088, 4256
nElo: 11.72 ±3.4
[\[raw statistics\]][311216-raw1]
| +| 2017‑01‑29 | [master][290117-master] vs [Stockfish 8]
`Bench: 5941174`
Simplify TT penalty stat (#980)
[\[differences\]][290117-dif] `86`
| Elo: [8.82][290117-elo1] ±1.5
WDL: 3580, 31825, 4595
nElo: 19.53 ±3.4
[\[raw statistics\]][290117-raw1]
| +| 2017‑03‑08 | [master][080317-master] vs [Stockfish 8]
`Bench: 5803228`
Helper functions to count material for both sides
[\[differences\]][080317-dif] `106`
| Elo: [10.84][080317-elo1] ±1.6
WDL: 3569, 31614, 4817
nElo: 23.73 ±3.4
[\[raw statistics\]][080317-raw1]
| +| 2017‑04‑20 | [master][200417-master] vs [Stockfish 8]
`Bench: 6581936`
simplify logic for history based pruning
[\[differences\]][200417-dif] `127`
| Elo: [15.17][200417-elo1] ±1.6
WDL: 3430, 31395, 5175
nElo: 32.82 ±3.4
[\[raw statistics\]][200417-raw1]
| +| 2017‑05‑07 | [master][070517-master] vs [Stockfish 8]
`Bench: 6107863`
Linear Protector bonus by distance
[\[differences\]][070517-dif] `144`
| Elo: [20.25][070517-elo1] ±1.6
WDL: 3258, 31155, 5587
nElo: 43.35 ±3.4
[\[raw statistics\]][070517-raw1]
| +| 2017‑06‑21 | [master][210617-master] vs [Stockfish 8]
`Bench: 5725676`
Increase reduction if tt-move is a capture
[\[differences\]][210617-dif] `167`
| Elo: [27.41][210617-elo1] ±1.6
WDL: 2918, 31015, 6067
nElo: 58.52 ±3.4
[\[raw statistics\]][210617-raw1]
| +| 2017‑08‑26 | [master][260817-master] vs [Stockfish 8]
`Bench: 5965302`
Improve multi-threaded mate finding
[\[differences\]][260817-dif] `218`
| Elo: [29.32][260817-elo1] ±1.6
WDL: 2886, 30860, 6254
nElo: 62.17 ±3.4
[\[raw statistics\]][260817-raw1]
| +| 2017‑10‑02 | [master][021017-master] vs [Stockfish 8]
`Bench: 5620312`
Good bishops on the main diagonals
[\[differences\]][021017-dif] `237`
| Elo: [32.61][021017-elo1] ±1.6
WDL: 2688, 30881, 6431
nElo: 69.44 ±3.4
[\[raw statistics\]][021017-raw1]
| +| 2017‑11‑03 | [master][031117-master] vs [Stockfish 8]
`Bench: 5536775`
Introduce capture history table for capture move sorting
[\[differences\]][031117-dif] `247`
| Elo: [35.18][031117-elo1] ±1.6
WDL: 2682, 30600, 6718
nElo: 73.93 ±3.4
[\[raw statistics\]][031117-raw1]
| +| 2017‑12‑03 | [master][031217-master] vs [Stockfish 8]
`Bench: 5051254`
Use constexpr when makes sense
[\[differences\]][031217-dif] `261`
| Elo: [43.31][031217-elo1] ±1.7
WDL: 2406, 30227, 7367
nElo: 90.06 ±3.3
[\[raw statistics\]][031217-raw1]
| +| 2018‑01‑23 | [master][230118-master] vs [Stockfish 8]
`Bench: 5783344`
Contempt 20
[\[differences\]][230118-dif] `286`
| Elo: [57.25][230118-elo1] ±1.9
WDL: 2917, 27634, 9449
nElo: 106.75 ±3.4
[\[raw statistics\]][230118-raw1]
| +| 2018‑01‑31 | [Stockfish 9] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF9RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF9DP]
`Bench: 5023629`
[\[differences\]][310118-dif] `291`
| + +
+ +
+ Stockfish 10 Development (2018-01-31 - 2018-11-29)
+ +| `Date` | `Version` | `1 Thread` | +|:------:|:---------:|:----------:| +| 2018‑02‑28 | [master][280218-master] vs [Stockfish 9]
`Bench: 5765806`
Reintroduce depth 2 razoring (with additional margin)
[\[differences\]][280218-dif] `41`
| Elo: [5.16][280218-elo1] ±1.7
WDL: 5143, 30105, 5752
nElo: 10.02 ±3.4
[\[raw statistics\]][280218-raw1]
| +| 2018‑03‑07 | [master][070318-master] vs [Stockfish 9]
`Bench: 5544908`
Simplification: use Arctan for the optimism S-curve
[\[differences\]][070318-dif] `53`
| Elo: [6.94][070318-elo1] ±1.7
WDL: 4813, 29575, 5612
nElo: 13.60 ±3.4
[\[raw statistics\]][070318-raw1]
| +| 2018‑03‑13 | [master][130318-master] vs [Stockfish 9]
`Bench: 5741807`
Use intrinsics only for LSB/MSB
[\[differences\]][130318-dif] `64`
| Elo: [13.03][130318-elo1] ±1.7
WDL: 4463, 29574, 5963
nElo: 25.59 ±3.4
[\[raw statistics\]][130318-raw1]
| +| 2018‑03‑26 | [master][260318-master] vs [Stockfish 9]
`Bench: 5934103`
Make kingRing always 8 squares
[\[differences\]][260318-dif] `75`
| Elo: [13.77][260318-elo1] ±1.7
WDL: 4339, 29737, 5924
nElo: 27.26 ±3.4
[\[raw statistics\]][260318-raw1]
| +| 2018‑04‑03 | [master][030418-master] vs [Stockfish 9]
`Bench: 4989125`
Remove the Queen from the mobility area of minor pieces
[\[differences\]][030418-dif] `92`
| Elo: [16.59][030418-elo1] ±1.7
WDL: 4283, 29525, 6192
nElo: 32.54 ±3.4
[\[raw statistics\]][030418-raw1]
| +| 2018‑04‑07 | [master][070418-master] vs [Stockfish 9]
`Bench: 5170165`
Reset negative statScore on fail high
[\[differences\]][070418-dif] `94`
| Elo: [16.63][070418-elo1] ±1.8
WDL: 4397, 29293, 6310
nElo: 32.25 ±3.4
[\[raw statistics\]][070418-raw1]
| +| 2018‑04‑23 | [master][230418-master] vs [Stockfish 9]
`Bench: 5549801`
Alternative formula for dynamic contempt
[\[differences\]][230418-dif] `106`
| Elo: [15.90][230418-elo1] ±1.8
WDL: 4462, 29247, 6291
nElo: 30.76 ±3.4
[\[raw statistics\]][230418-raw1]
| +| 2018‑04‑29 | [master][290418-master] vs [Stockfish 9]
`Bench: 5254862`
Always scale using pawn contribution
[\[differences\]][290418-dif] `112`
| Elo: [14.61][290418-elo1] ±1.7
WDL: 4430, 29459, 6111
nElo: 28.54 ±3.4
[\[raw statistics\]][290418-raw1]
| +| 2018‑05‑03 | [master][030518-master] vs [Stockfish 9]
`Bench: 5186783`
Tweak the connected[] array value for pawns on rank 5
[\[differences\]][030518-dif] `116`
| Elo: [18.52][030518-elo1] ±1.8
WDL: 4385, 29100, 6515
nElo: 35.63 ±3.4
[\[raw statistics\]][030518-raw1]
| +| 2018‑05‑13 | [master][130518-master] vs [Stockfish 9]
`Bench: 5294316`
Update search.cpp
[\[differences\]][130518-dif] `128`
| Elo: [24.92][130518-elo1] ±1.8
WDL: 4175, 28786, 7039
nElo: 47.42 ±3.4
[\[raw statistics\]][130518-raw1]
| +| 2018‑05‑24 | [master][240518-master] vs [Stockfish 9]
`Bench: 5167159`
LMR Capture Tweak
[\[differences\]][240518-dif] `137`
| Elo: [26.72][240518-elo1] ±1.8
WDL: 4033, 28864, 7103
nElo: 51.08 ±3.4
[\[raw statistics\]][240518-raw1]
| +| 2018‑06‑05 | [master][050618-master] vs [Stockfish 9]
`Bench: 4326784`
Call cycle detection before qsearch()
[\[differences\]][050618-dif] `148`
| Elo: [28.16][050618-elo1] ±1.8
WDL: 3971, 28823, 7206
nElo: 53.79 ±3.4
[\[raw statistics\]][050618-raw1]
| +| 2018‑06‑11 | [master][110618-master] vs [Stockfish 9]
`Bench: 4980482`
Optimize an expression in endgame.cpp
[\[differences\]][110618-dif] `154`
| Elo: [29.72][110618-elo1] ±1.9
WDL: 4335, 27917, 7748
nElo: 54.60 ±3.4
[\[raw statistics\]][110618-raw1]
| +| 2018‑06‑23 | [master][230618-master] vs [Stockfish 9]
`Bench: 4557946`
Another set of tuned values after one million games
[\[differences\]][230618-dif] `162`
| Elo: [31.98][230618-elo1] ±1.9
WDL: 4354, 27621, 8025
nElo: 58.11 ±3.4
[\[raw statistics\]][230618-raw1]
| +| 2018‑07‑19 | [master][190718-master] vs [Stockfish 9]
`Bench: 4817583`
Better check evasion move sorting
[\[differences\]][190718-dif] `179`
| Elo: [36.70][190718-elo1] ±1.9
WDL: 4312, 27166, 8522
nElo: 65.70 ±3.4
[\[raw statistics\]][190718-raw1]
| +| 2018‑07‑27 | [master][270718-master] vs [Stockfish 9]
`Bench: 4905530`
Simplify cmh pruning
[\[differences\]][270718-dif] `199`
| Elo: [37.45][270718-elo1] ±1.9
WDL: 4183, 27339, 8478
nElo: 67.55 ±3.4
[\[raw statistics\]][270718-raw1]
| +| 2018‑07‑28 | [master][280718-master] vs [Stockfish 9]
`Bench: 4883742`
Increase the mg->eg gradient for the PawnlessFlank malus
[\[differences\]][280718-dif] `200`
| Elo: [35.84][280718-elo1] ±1.9
WDL: 4235, 27418, 8347
nElo: 64.78 ±3.4
[\[raw statistics\]][280718-raw1]
| +| 2018‑07‑31 | [master][310718-master] vs [Stockfish 9]
`Bench: 5591925`
Small tweaks to recent code changes
[\[differences\]][310718-dif] `203`
| Elo: [37.67][310718-elo1] ±1.9
WDL: 4019, 27642, 8339
nElo: 68.82 ±3.4
[\[raw statistics\]][310718-raw1]
| +| 2018‑08‑08 | [master][080818-master] vs [Stockfish 9]
`Bench: 4669050`
First check threshold in space evaluation
[\[differences\]][080818-dif] `207`
| Elo: [37.78][080818-elo1] ±1.9
WDL: 4224, 27220, 8556
nElo: 67.82 ±3.4
[\[raw statistics\]][080818-raw1]
| +| 2018‑08‑12 | [master][120818-master] vs [Stockfish 9]
`Bench: 4694813`
Combo of several promising parameter tweaks
[\[differences\]][120818-dif] `211`
| Elo: [40.88][120818-elo1] ±1.9
WDL: 4069, 27177, 8754
nElo: 73.46 ±3.4
[\[raw statistics\]][120818-raw1]
| +| 2018‑08‑14 | [master][140818-master] vs [Stockfish 9]
`Bench: 4272361`
Double weight of capture history
[\[differences\]][140818-dif] `214`
| Elo: [41.69][140818-elo1] ±1.9
WDL: 3942, 27339, 8719
nElo: 75.47 ±3.4
[\[raw statistics\]][140818-raw1]
| +| 2018‑08‑17 | [master][170818-master] vs [Stockfish 9]
`Bench: 4592766`
Use an affine formula to mix stats and eval
[\[differences\]][170818-dif] `217`
| Elo: [43.15][170818-elo1] ±1.9
WDL: 3922, 27213, 8865
nElo: 77.82 ±3.4
[\[raw statistics\]][170818-raw1]
| +| 2018‑08‑28 | [master][280818A-master] vs [Stockfish 9]
`Bench: 4172767`
Tweak stat bonus formula
[\[differences\]][280818A-dif] `220`
| Elo: [44.20][280818A-elo1] ±1.9
WDL: 3861, 27217, 8922
nElo: 79.79 ±3.4
[\[raw statistics\]][280818A-raw1]
| +| 2018‑08‑28 | [master][280818B-master] vs [Stockfish 9]
`Bench: 4413173`
Remove PawnsOnBothFlanks
[\[differences\]][280818B-dif] `225`
| Elo: [42.37][280818B-elo1] ±1.9
WDL: 3952, 27242, 8806
nElo: 76.44 ±3.4
[\[raw statistics\]][280818B-raw1]
| +| 2018‑09‑01 | [master][010918-master] vs [Stockfish 9]
`Bench: 4609645`
Re-introduce "keep pawns on both flanks"
[\[differences\]][010918-dif] `227`
| Elo: [46.46][010918-elo1] ±1.9
WDL: 3804, 27075, 9121
nElo: 83.56 ±3.4
[\[raw statistics\]][010918-raw1]
| +| 2018‑09‑10 | [master][100918-master] vs [Stockfish 9]
`Bench: 4248710`
Tweak opposite colored bishops endgame scaling
[\[differences\]][100918-dif] `230`
| Elo: [45.47][100918-elo1] ±1.9
WDL: 3911, 26973, 9116
nElo: 81.36 ±3.4
[\[raw statistics\]][100918-raw1]
| +| 2018‑09‑27 | [master][270918-master] vs [Stockfish 9]
`Bench: 4059356`
Fix two typos in comments
[\[differences\]][270918-dif] `235`
| Elo: [46.93][270918-elo1] ±1.9
WDL: 3883, 26864, 9253
nElo: 83.72 ±3.4
[\[raw statistics\]][270918-raw1]
| +| 2018‑10‑14 | [master][141018-master] vs [Stockfish 9]
`Bench: 4274207`
Simplify check extensions
[\[differences\]][141018-dif] `241`
| Elo: [49.01][141018-elo1] ±1.9
WDL: 3783, 26829, 9388
nElo: 87.49 ±3.4
[\[raw statistics\]][141018-raw1]
| +| 2018‑10‑25 | [master][251018-master] vs [Stockfish 9]
`Bench: 3314347`
On main thread: reduce depth after fail high
[\[differences\]][251018-dif] `245`
| Elo: [52.82][251018-elo1] ±1.9
WDL: 3514, 26937, 9549
nElo: 95.10 ±3.4
[\[raw statistics\]][251018-raw1]
| +| 2018‑11‑01 | [master][011118-master] vs [Stockfish 9]
`Bench: 3556672`
Fix issues from using adjustedDepth too broadly
[\[differences\]][011118-dif] `248`
| Elo: [51.68][011118-elo1] ±1.9
WDL: 3581, 26932, 9487
nElo: 92.90 ±3.4
[\[raw statistics\]][011118-raw1]
| +| 2018‑11‑08 | [master][081118-master] vs [Stockfish 9]
`Bench: 3647775`
Update list of top CPU contributors
[\[differences\]][081118-dif] `254`
| Elo: [50.43][081118-elo1] ±1.9
WDL: 3773, 26689, 9538
nElo: 89.65 ±3.5
[\[raw statistics\]][081118-raw1]
| +| 2018‑11‑19 | [master][191118-master] vs [Stockfish 9]
`Bench: 3717396`
Stockfish 10-beta
[\[differences\]][191118-dif] `267`
| Elo: [53.77][191118-elo1] ±1.9
WDL: 3612, 26634, 9754
nElo: 95.73 ±3.5
[\[raw statistics\]][191118-raw1]
| +| 2018‑11‑27 | [master][271118-master] vs [Stockfish 9]
`Bench: 3939338`
Simplify casting extension
[\[differences\]][271118-dif] `274`
| Elo: [54.21][271118-elo1] ±1.9
WDL: 3562, 26685, 9753
nElo: 96.75 ±3.5
[\[raw statistics\]][271118-raw1]
| +| 2018‑11‑29 | [Stockfish 10] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF10RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF10DP]
`Bench: 3939338`
[\[differences\]][291118-dif] `277`
| + +
+ +
+ Stockfish 11 Development (2018-11-29 - 2020-01-17)
+ +| `Date` | `Version` | `1 Thread` | `8 Threads` | +|:------:|:---------:|:----------:|:-----------:| +| 2018‑12‑06 | [master][061218-master] vs [Stockfish 10]
`Bench: 3773021`
Revert "pseudo_legal() and MOVE_NONE"
[\[differences\]][061218-dif] `8`
| Elo: [4.32][061218-elo1] ±1.7
WDL: 5033, 29437, 5530
nElo: 8.40 ±3.4
[\[raw statistics\]][061218-raw1]
| +| 2018‑12‑13 | [master][131218-master] vs [Stockfish 10]
`Bench: 3332460`
A combo of parameter tweaks
[\[differences\]][131218-dif] `16`
| Elo: [8.06][131218-elo1] ±1.8
WDL: 5020, 29032, 5948
nElo: 15.41 ±3.4
[\[raw statistics\]][131218-raw1]
| Elo: [12.70][131218-elo8] ±1.7
WDL: 4068, 30403, 5529
nElo: 25.98 ±3.4
[\[raw statistics\]][131218-raw8]
| +| 2018‑12‑16 | [master][161218-master] vs [Stockfish 10]
`Bench: 3646542`
Use stronglyProtected
[\[differences\]][161218-dif] `21`
| Elo: [7.85][161218-elo1] ±1.7
WDL: 5107, 30004, 6037
nElo: 15.10 ±3.4
[\[raw statistics\]][161218-raw1]
| +| 2019‑01‑04 | [master][040119-master] vs [Stockfish 10]
`Bench: 3559104`
Check tablebase files
[\[differences\]][040119-dif] `40`
| Elo: [7.20][040119-elo1] ±1.8
WDL: 5164, 28843, 5993
nElo: 13.64 ±3.4
[\[raw statistics\]][040119-raw1]
| +| 2019‑01‑10 | [master][100119-master] vs [Stockfish 10]
`Bench: 3739723`
Remove pvExact
[\[differences\]][100119-dif] `45`
| Elo: [13.14][100119-elo1] ±1.8
WDL: 4779, 28930, 6291
nElo: 25.03 ±3.4
[\[raw statistics\]][100119-raw1]
| Elo: [16.17][100119-elo8] ±1.7
WDL: 3887, 30366, 5747
nElo: 33.07 ±3.4
[\[raw statistics\]][100119-raw8]
| +| 2019‑01‑22 | [master][220119-master] vs [Stockfish 10]
`Bench: 3665090`
Simplify TrappedRook
[\[differences\]][220119-dif] `53`
| Elo: [13.98][220119-elo1] ±1.8
WDL: 4714, 28963, 6323
nElo: 26.68 ±3.4
[\[raw statistics\]][220119-raw1]
| +| 2019‑02‑03 | [master][030219-master] vs [Stockfish 10]
`Bench: 3653942`
Less king danger if we have a knight
[\[differences\]][030219-dif] `61`
| Elo: [17.71][030219-elo1] ±1.8
WDL: 4624, 28715, 6661
nElo: 33.46 ±3.4
[\[raw statistics\]][030219-raw1]
| Elo: [19.77][030219-elo8] ±1.7
WDL: 3728, 30270, 6002
nElo: 40.32 ±3.4
[\[raw statistics\]][030219-raw8]
| +| 2019‑03‑12 | [master][120319-master] vs [Stockfish 10]
`Bench: 3318033`
Increase thread stack for OS X (#2035)
[\[differences\]][120319-dif] `80`
| Elo: [16.58][120319-elo1] ±1.8
WDL: 4635, 28823, 6542
nElo: 31.46 ±3.4
[\[raw statistics\]][120319-raw1]
| +| 2019‑03‑31 | [master][310319-master] vs [Stockfish 10]
`Bench: 3548313`
Assorted trivial cleanups 3/2019 (#2030)
[\[differences\]][310319-dif] `91`
| Elo: [16.58][310319-elo1] ±1.8
WDL: 4742, 28609, 6649
nElo: 31.16 ±3.4
[\[raw statistics\]][310319-raw1]
| Elo: [24.33][310319-elo8] ±1.7
WDL: 3633, 29937, 6430
nElo: 48.91 ±3.4
[\[raw statistics\]][310319-raw8]
| +| 2019‑04‑24 | [master][240419-master] vs [Stockfish 10]
`Bench: 3402947`
Remove useless initializations (#2115)
[\[differences\]][240419-dif] `113`
| Elo: [16.39][240419-elo1] ±1.8
WDL: 4634, 28847, 6519
nElo: 31.13 ±3.4
[\[raw statistics\]][240419-raw1]
| +| 2019‑05‑05 | [master][050519-master] vs [Stockfish 10]
`Bench: 3644175`
LMR for captures not cracking alpha
[\[differences\]][050519-dif] `121`
| Elo: [16.65][050519-elo1] ±1.8
WDL: 4788, 28508, 6704
nElo: 31.17 ±3.4
[\[raw statistics\]][050519-raw1]
| +| 2019‑05‑15 | [master][150519-master] vs [Stockfish 10]
`Bench: 3824325`
Update failedHighCnt rule #2063
[\[differences\]][150519-dif] `136`
| Elo: [19.76][150519-elo1] ±1.8
WDL: 4665, 28397, 6938
nElo: 36.86 ±3.4
[\[raw statistics\]][150519-raw1]
| Elo: [28.93][150519-elo8] ±1.7
WDL: 3573, 29531, 6896
nElo: 57.18 ±3.4
[\[raw statistics\]][150519-raw8]
| +| 2019‑06‑09 | [master][090619-master] vs [Stockfish 10]
`Bench: 3424592`
Remove depth condition for ttPv (#2166)
[\[differences\]][090619-dif] `151`
| Elo: [19.87][090619-elo1] ±1.9
WDL: 4796, 28123, 7081
nElo: 36.62 ±3.4
[\[raw statistics\]][090619-raw1]
| +| 2019‑06‑20 | [master][200619-master] vs [Stockfish 10]
`Bench: 3398333`
More bonus for free passed pawn
[\[differences\]][200619-dif] `161`
| Elo: [24.06][200619-elo1] ±1.8
WDL: 4547, 28140, 7313
nElo: 44.48 ±3.4
[\[raw statistics\]][200619-raw1]
| Elo: [30.76][200619-elo8] ±1.7
WDL: 3462, 29544, 6994
nElo: 60.92 ±3.4
[\[raw statistics\]][200619-raw8]
| +| 2019‑06‑27 | [master][270619-master] vs [Stockfish 10]
`Bench: 3633546`
Bonus for double attacks
[\[differences\]][270619-dif] `167`
| Elo: [22.75][270619-elo1] ±1.9
WDL: 4644, 28096, 7260
nElo: 41.95 ±3.4
[\[raw statistics\]][270619-raw1]
| +| 2019‑07‑11 | [master][110719-master] vs [Stockfish 10]
`Bench: 3206912`
Assorted trivial cleanups June 2019
[\[differences\]][110719-dif] `176`
| Elo: [24.39][110719-elo1] ±1.9
WDL: 4596, 28005, 7399
nElo: 44.83 ±3.4
[\[raw statistics\]][110719-raw1]
| +| 2019‑07‑25 | [master][250719-master] vs [Stockfish 10]
`Bench: 3935523`
Tweak of SEE pruning condition
[\[differences\]][250719-dif] `192`
| Elo: [25.72][250719-elo1] ±1.9
WDL: 4519, 28006, 7475
nElo: 47.32 ±3.4
[\[raw statistics\]][250719-raw1]
| Elo: [37.49][250719-elo8] ±1.7
WDL: 3225, 29251, 7524
nElo: 73.63 ±3.4
[\[raw statistics\]][250719-raw8]
| +| 2019‑08‑14 | [master][140819-master] vs [Stockfish 10]
`Bench: 4139590`
Tweak unsafe checks
[\[differences\]][140819-dif] `198`
| Elo: [32.24][140819-elo1] ±1.9
WDL: 4168, 27963, 7869
nElo: 59.45 ±3.4
[\[raw statistics\]][140819-raw1]
| +| 2019‑08‑26 | [master][260819-master] vs [Stockfish 10]
`Bench: 3568210`
Tweak Late Move Reduction at root
[\[differences\]][260819-dif] `207`
| Elo: [35.63][260819-elo1] ±1.9
WDL: 4021, 27870, 8109
nElo: 65.62 ±3.4
[\[raw statistics\]][260819-raw1]
| Elo: [44.52][260819-elo8] ±1.8
WDL: 2958, 28986, 8056
nElo: 86.99 ±3.4
[\[raw statistics\]][260819-raw8]
| +| 2019‑09‑12 | [master][120919-master] vs [Stockfish 10]
`Bench: 3954190`
Scale down complexity
[\[differences\]][120919-dif] `211`
| Elo: [39.10][120919-elo1] ±1.9
WDL: 3824, 27869, 8307
nElo: 72.22 ±3.4
[\[raw statistics\]][120919-raw1]
| +| 2019‑09‑16 | [master][160919-master] vs [Stockfish 10]
`Bench: 4272173`
Raise stack size to 8MB for pthreads
[\[differences\]][160919-dif] `218`
| Elo: [37.63][160919-elo1] ±1.9
WDL: 4007, 27670, 8323
nElo: 68.83 ±3.4
[\[raw statistics\]][160919-raw1]
| Elo: [46.57][160919-elo8] ±1.8
WDL: 2764, 28492, 7994
nElo: 91.44 ±3.4
[\[raw statistics\]][160919-raw8]
| +| 2019‑09‑24 | [master][240919-master] vs [Stockfish 10]
`Bench: 3618154`
Increase weight for supported pawns
[\[differences\]][240919-dif] `226`
| Elo: [38.97][240919-elo1] ±1.9
WDL: 3857, 27818, 8325
nElo: 71.81 ±3.4
[\[raw statistics\]][240919-raw1]
| +| 2019‑10‑05 | [master][051019-master] vs [Stockfish 10]
`Bench: 4131643`
Introduce separate counter-move tables
[\[differences\]][051019-dif] `239`
| Elo: [41.96][051019-elo1] ±1.9
WDL: 3746, 27701, 8553
nElo: 77.13 ±3.4
[\[raw statistics\]][051019-raw1]
| Elo: [51.76][051019-elo8] ±1.8
WDL: 2628, 28829, 8543
nElo: 101.26 ±3.4
[\[raw statistics\]][051019-raw8]
| +| 2019‑10‑18 | [master][181019-master] vs [Stockfish 10]
`Bench: 4423737`
Current capture for Counter-Move history
[\[differences\]][181019-dif] `247`
| Elo: [44.63][181019-elo1] ±1.9
WDL: 3641, 27608, 8751
nElo: 81.93 ±3.4
[\[raw statistics\]][181019-raw1]
| +| 2019‑11‑04 | [master][041119-master] vs [Stockfish 10]
`Bench: 4707799`
Rook PSQT Tuned
[\[differences\]][041119-dif] `259`
| Elo: [42.20][041119-elo1] ±1.9
WDL: 3686, 27793, 8521
nElo: 77.91 ±3.4
[\[raw statistics\]][041119-raw1]
| Elo: [52.90][041119-elo8] ±1.8
WDL: 2601, 28754, 8645
nElo: 103.29 ±3.4
[\[raw statistics\]][041119-raw8]
| +| 2019‑11‑14 | [master][141119-master] vs [Stockfish 10]
`Bench: 4532366`
Prune before extension
[\[differences\]][141119-dif] `266`
| Elo: [43.12][141119-elo1] ±1.9
WDL: 3676, 27709, 8615
nElo: 79.39 ±3.4
[\[raw statistics\]][141119-raw1]
| +| 2019‑11‑21 | [master][211119-master] vs [Stockfish 10]
`Bench: 5067870`
Do lmr for more captures
[\[differences\]][211119-dif] `271`
| Elo: [46.56][211119-elo1] ±1.5
WDL: 5306, 41395, 13299
nElo: 85.60 ±2.8
[\[raw statistics\]][211119-raw1]
| Elo: [53.93][211119-elo8] ±1.8
WDL: 2502, 28837, 8661
nElo: 105.86 ±3.4
[\[raw statistics\]][211119-raw8]
| +| 2019‑12‑02 | [master][021219-master] vs [Stockfish 10]
`Bench: 5122362`
UnblockedStorm tuned
[\[differences\]][021219-dif] `278`
| Elo: [44.88][021219-elo1] ±1.5
WDL: 5273, 41746, 12981
nElo: 83.21 ±2.8
[\[raw statistics\]][021219-raw1]
| +| 2019‑12‑10 | [master][101219-master] vs [Stockfish 10]
`Bench: 5371271`
Refine improving-logic
[\[differences\]][101219-dif] `288`
| Elo: [47.27][101219-elo1] ±1.5
WDL: 5329, 41229, 13442
nElo: 86.56 ±2.8
[\[raw statistics\]][101219-raw1]
| Elo: [56.62][101219-elo8] ±1.8
WDL: 2365, 28809, 8826
nElo: 111.42 ±3.4
[\[raw statistics\]][101219-raw8]
| +| 2020‑01‑07 | [master][070120-master] vs [Stockfish 10]
`Bench: 4747984`
Tuned nullmove search
[\[differences\]][070120-dif] `294`
| Elo: [51.50][070120-elo1] ±1.5
Ptnml: 160, 3173, 15729, 9546, 1387
nElo: 98.05 ±2.8
PairsRatio: 3.28
[\[raw statistics\]][070120-raw1]
| Elo: [58.15][070120-elo8] ±1.7
Ptnml: 36, 1527, 11059, 6509, 860
nElo: 118.69 ±3.4
PairsRatio: 4.71
[\[raw statistics\]][070120-raw8]
| +| 2020‑01‑17 | [master][170120-master] vs [Stockfish 10]
`Bench: 5156767`
Stockfish 11
[\[differences\]][170120-dif] `307`
| Elo: [53.59][170120-elo1] ±1.5
Ptnml: 138, 2988, 15833, 9631, 1407
nElo: 102.99 ±2.8
PairsRatio: 3.53
[\[raw statistics\]][170120-raw1]
| Elo: [58.07][170120-elo8] ±1.7
Ptnml: 36, 1478, 11159, 6463, 854
nElo: 119.25 ±3.4
PairsRatio: 4.83
[\[raw statistics\]][170120-raw8]
| +| 2020‑01‑17 | [Stockfish 11] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF11RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF11DP]
`Bench: 5156767`
[\[differences\]][170120-dif] `307`
| + +
+ +
+ Stockfish 12 Development (2020-01-17 - 2020-09-02)
+ +| `Date` | `Version` | `1 Thread` | `8 Threads` | +|:------:|:---------:|:----------:|:-----------:| +| 2020‑01‑28 | [master][280120-master] vs [Stockfish 11]
`Bench: 5545845`
More bonus for bestMoves
[\[differences\]][280120-dif] `16`
| Elo: [-2.47][280120-elo1] ±1.3
Ptnml: 330, 5657, 18424, 5285, 303
nElo: -5.21 ±2.8
PairsRatio: 0.93
[\[raw statistics\]][280120-raw1]
| +| 2020‑01‑31 | [master][310120-master] vs [Stockfish 11]
`Bench: 5153165`
Revert 5 recent patches
[\[differences\]][310120-dif] `19`
| Elo: [0.85][310120-elo1] ±1.3
Ptnml: 306, 5327, 18593, 5457, 314
nElo: 1.80 ±2.8
PairsRatio: 1.02
[\[raw statistics\]][310120-raw1]
| +| 2020‑02‑27 | [master][270220-master] vs [Stockfish 11]
`Bench: 4923286`
Weak queen protection
[\[differences\]][270220-dif] `32`
| Elo: [1.33][270220-elo1] ±1.3
Ptnml: 327, 5308, 18486, 5567, 312
nElo: 2.80 ±2.8
PairsRatio: 1.04
[\[raw statistics\]][270220-raw1]
| Elo: [0.51][270220-elo8] ±1.4
Ptnml: 118, 3072, 13560, 3133, 117
nElo: 1.21 ±3.4
PairsRatio: 1.02
[\[raw statistics\]][270220-raw8]
| +| 2020‑03‑20 | [master][200320-master] vs [Stockfish 11]
`Bench: 5398277`
Adjust singular extension search depth
[\[differences\]][200320-dif] `48`
| Elo: [2.94][200320-elo1] ±1.3
Ptnml: 351, 5099, 18580, 5631, 339
nElo: 6.21 ±2.8
PairsRatio: 1.10
[\[raw statistics\]][200320-raw1]
| +| 2020‑04‑07 | [master][070420-master] vs [Stockfish 11]
`Bench: 4417023`
Introduce capture history pruning
[\[differences\]][070420-dif] `63`
| Elo: [5.74][070420-elo1] ±1.3
Ptnml: 274, 5058, 18460, 5818, 390
nElo: 12.11 ±2.8
PairsRatio: 1.16
[\[raw statistics\]][070420-raw1]
| Elo: [6.49][070420-elo8] ±1.5
Ptnml: 113, 2854, 13369, 3501, 163
nElo: 15.05 ±3.4
PairsRatio: 1.23
[\[raw statistics\]][070420-raw8]
| +| 2020‑04‑16 | [master][160420-master] vs [Stockfish 11]
`Bench: 4958027`
Remove one condition in probcut TTmove
[\[differences\]][160420-dif] `76`
| Elo: [11.33][160420-elo1] ±1.3
Ptnml: 281, 4681, 18282, 6313, 443
nElo: 23.65 ±2.8
PairsRatio: 1.36
[\[raw statistics\]][160420-raw1]
| +| 2020‑05‑02 | [master][020520-master] vs [Stockfish 11]
`Bench: 4247490`
Fishtest Tuning Framework
[\[differences\]][020520-dif] `84`
| Elo: [15.21][020520-elo1] ±1.3
Ptnml: 236, 4370, 18388, 6545, 461
nElo: 32.08 ±2.8
PairsRatio: 1.52
[\[raw statistics\]][020520-raw1]
| Elo: [16.71][020520-elo8] ±1.5
Ptnml: 105, 2457, 13100, 4087, 251
nElo: 37.85 ±3.4
PairsRatio: 1.69
[\[raw statistics\]][020520-raw8]
| +| 2020‑05‑21 | [master][210520-master] vs [Stockfish 11]
`Bench: 4778956`
Tweak knight mobility
[\[differences\]][210520-dif] `100`
| Elo: [15.97][210520-elo1] ±1.3
Ptnml: 230, 4544, 17963, 6766, 497
nElo: 33.08 ±2.8
PairsRatio: 1.52
[\[raw statistics\]][210520-raw1]
| +| 2020‑06‑06 | [master][060620-master] vs [Stockfish 11]
`Bench: 4582693`
Use lowply-history also on low depths
[\[differences\]][060620-dif] `117`
| Elo: [19.45][060620-elo1] ±1.4
Ptnml: 250, 4360, 17761, 7042, 587
nElo: 39.70 ±2.8
PairsRatio: 1.65
[\[raw statistics\]][060620-raw1]
| Elo: [23.70][060620-elo8] ±1.5
Ptnml: 88, 2142, 12987, 4524, 259
nElo: 53.99 ±3.4
PairsRatio: 2.14
[\[raw statistics\]][060620-raw8]
| +| 2020‑06‑13 | [master][130620-master] vs [Stockfish 11]
`Bench: 4246971`
Tuned values for search constants
[\[differences\]][130620-dif] `127`
| Elo: [20.91][130620-elo1] ±1.4
Ptnml: 195, 4263, 17878, 7069, 595
nElo: 43.14 ±2.8
PairsRatio: 1.72
[\[raw statistics\]][130620-raw1]
| Elo: [24.86][130620-elo8] ±1.5
Ptnml: 81, 2088, 13016, 4523, 292
nElo: 56.58 ±3.4
PairsRatio: 2.22
[\[raw statistics\]][130620-raw8]
| +| 2020‑06‑29 | [master][290620-master] vs [Stockfish 11]
`Bench: 4523573`
Tweak single queen endgame scaling
[\[differences\]][290620-dif] `148`
| Elo: [25.67][290620-elo1] ±1.3
Ptnml: 192, 3878, 17888, 7397, 645
nElo: 53.10 ±2.8
PairsRatio: 1.98
[\[raw statistics\]][290620-raw1]
| +| 2020‑07‑17 | [master][170720-master] vs [Stockfish 11]
`Bench: 4578298`
Do not overwrite valuable TT data
[\[differences\]][170720-dif] `163`
| Elo: [26.44][170720-elo1] ±1.3
Ptnml: 192, 3800, 17928, 7418, 662
nElo: 54.75 ±2.8
PairsRatio: 2.02
[\[raw statistics\]][170720-raw1]
| Elo: [30.71][170720-elo8] ±1.5
Ptnml: 63, 1923, 12759, 4935, 320
nElo: 69.50 ±3.4
PairsRatio: 2.65
[\[raw statistics\]][170720-raw8]
| +| 2020‑07‑31 | [master][310720-master] vs [Stockfish 11]
`Bench: 4746616`
Tweak cutnode reduction
[\[differences\]][310720-dif] `167`
| Elo: [25.49][310720-elo1] ±1.4
Ptnml: 203, 3910, 17861, 7342, 684
nElo: 52.38 ±2.8
PairsRatio: 1.95
[\[raw statistics\]][310720-raw1]
| Elo: [32.39][310720-elo8] ±1.5
Ptnml: 69, 1829, 12779, 4961, 362
nElo: 73.03 ±3.4
PairsRatio: 2.80
[\[raw statistics\]][310720-raw8]
| +| 2020‑08‑06 | [master][060820-master] vs [Stockfish 11]
`Bench: 4746616`
Add NNUE evaluation
[\[differences\]][060820-dif] `168`
| Elo: [83.42][060820-elo1] ±1.7
Ptnml: 172, 2656, 12724, 11761, 2687
nElo: 144.72 ±3.0
PairsRatio: 5.11
[\[raw statistics\]][060820-raw1]
| Elo: [86.10][060820-elo8] ±1.9
Ptnml: 36, 1192, 9342, 7881, 1549
nElo: 163.20 ±3.6
PairsRatio: 7.68
[\[raw statistics\]][060820-raw8]
| +| 2020‑08‑07 | [Add NNUE evaluation][060820-master] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SFNNUERN]
`Bench: 4746616`
[\[differences\]][070820-dif] `168`
| +| 2020‑08‑08 | [master][080820-master] vs [Stockfish 11]
`Bench: 4084753`
LMR search tweak
[\[differences\]][080820-dif] `185`
| Elo: [106.20][080820-elo1] ±1.7
Ptnml: 67, 1716, 11867, 13060, 3290
nElo: 189.91 ±3.2
PairsRatio: 9.17
[\[raw statistics\]][080820-raw1]
| +| 2020‑08‑11 | [master][110820-master] vs [Stockfish 11]
`Bench: 4290577`
This commit enables a mixed bench
[\[differences\]][110820-dif] `205`
| Elo: [125.60][110820-elo1] ±1.7
Ptnml: 48, 1240, 10613, 14070, 4029
nElo: 224.82 ±3.3
PairsRatio: 14.05
[\[raw statistics\]][110820-raw1]
| Elo: [111.78][110820-elo8] ±1.9
Ptnml: 11, 591, 8286, 9168, 1944
nElo: 217.93 ±3.8
PairsRatio: 18.46
[\[raw statistics\]][110820-raw8]
| +| 2020‑08‑18 | [master][180820-master] vs [Stockfish 11]
`Bench: 4026216`
Fix Makefile typo
[\[differences\]][180820-dif] `226`
| Elo: [121.54][180820-elo1] ±1.7
Ptnml: 52, 1373, 10930, 13640, 4005
nElo: 215.22 ±3.3
PairsRatio: 12.38
[\[raw statistics\]][180820-raw1]
| Elo: [111.40][180820-elo8] ±1.9
Ptnml: 20, 700, 8128, 9161, 1991
nElo: 213.20 ±3.8
PairsRatio: 15.49
[\[raw statistics\]][180820-raw8]
| +| 2020‑08‑30 | [master][300820-master] vs [Stockfish 11]
`Bench: 3736029`
Update parameters in classical evaluation
[\[differences\]][300820-dif] `255`
| Elo: [130.96][300820-elo1] ±1.7
Ptnml: 44, 1161, 10305, 14128, 4362
nElo: 232.58 ±3.4
PairsRatio: 15.34
[\[raw statistics\]][300820-raw1]
| +| 2020‑09‑02 | [master][020920-master] vs [Stockfish 11]
`Bench: 3624569`
Stockfish 12
[\[differences\]][020920-dif] `262`
| Elo: [133.65][020920-elo1] ±1.7
Ptnml: 32, 1088, 10158, 14286, 4436
nElo: 238.67 ±3.4
PairsRatio: 16.72
[\[raw statistics\]][020920-raw1]
| Elo: [117.62][020920-elo8] ±1.9
Ptnml: 10, 562, 8016, 9195, 2217
nElo: 224.93 ±3.8
PairsRatio: 19.95
[\[raw statistics\]][020920-raw8]
| +| 2020‑09‑02 | [Stockfish 12] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF12RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF12DP]
`Bench: 3624569`
[\[differences\]][020920-dif] `262`
| + +
+ +
+ Stockfish 13 Development (2020-09-02 - 2021-02-18)
+ +| `Date` | `Version` | `1 Thread` | `8 Threads` | +|:------:|:---------:|:----------:|:-----------:| +| 2020‑09‑08 | [master][080920-master] vs [Stockfish 12]
`Bench: 4161067`
Double probability of using classical eval
[\[differences\]][080920-dif] `10`
| Elo: [6.49][080920-elo1] ±1.0
Ptnml: 85, 3319, 22112, 4359, 125
nElo: 17.26 ±2.8
PairsRatio: 1.32
[\[raw statistics\]][080920-raw1]
| +| 2020‑09‑21 | [master][210920-master] vs [Stockfish 12]
`Bench: 3973739`
Add large page support for NNUE weights
[\[differences\]][210920-dif] `21`
| Elo: [13.88][210920-elo1] ±1.0
Ptnml: 49, 2767, 22106, 4896, 182
nElo: 37.07 ±2.8
PairsRatio: 1.80
[\[raw statistics\]][210920-raw1]
| Elo: [8.97][210920-elo8] ±1.1
Ptnml: 15, 1474, 16020, 2445, 46
nElo: 27.99 ±3.4
PairsRatio: 1.67
[\[raw statistics\]][210920-raw8]
| +| 2020‑09‑28 | [master][280920-master] vs [Stockfish 12]
`Bench: 3776081`
Include pawns in NNUE scaling
[\[differences\]][280920-dif] `33`
| Elo: [23.15][280920-elo1] ±1.0
Ptnml: 38, 1945, 22217, 5587, 213
nElo: 63.29 ±2.7
PairsRatio: 2.92
[\[raw statistics\]][280920-raw1]
| +| 2020‑10‑18 | [master][181020-master] vs [Stockfish 12]
`Bench: 4066972`
Do more reductions for late quiet moves
[\[differences\]][181020-dif] `41`
| Elo: [24.09][181020-elo1] ±1.0
Ptnml: 43, 2023, 21889, 5827, 218
nElo: 64.60 ±2.7
PairsRatio: 2.93
[\[raw statistics\]][181020-raw1]
| Elo: [21.17][181020-elo8] ±1.1
Ptnml: 12, 908, 15789, 3216, 75
nElo: 65.43 ±3.2
PairsRatio: 3.58
[\[raw statistics\]][181020-raw8]
| +| 2020‑11‑01 | [master][011120-master] vs [Stockfish 12]
`Bench: 3517795`
Update default net to nn-cb26f10b1fd9.nnue
[\[differences\]][011120-dif] `48`
| Elo: [28.02][011120-elo1] ±1.1
Ptnml: 63, 2079, 21107, 6469, 282
nElo: 71.59 ±2.7
PairsRatio: 3.15
[\[raw statistics\]][011120-raw1]
| +| 2020‑11‑15 | [master][151120-master] vs [Stockfish 12]
`Bench: 3597730`
Rook Mobility Tweak
[\[differences\]][151120-dif] `60`
| Elo: [29.99][151120-elo1] ±1.0
Ptnml: 32, 1667, 21658, 6389, 254
nElo: 80.38 ±2.7
PairsRatio: 3.91
[\[raw statistics\]][151120-raw1]
| Elo: [25.49][151120-elo8] ±1.1
Ptnml: 10, 799, 15532, 3570, 89
nElo: 77.27 ±3.2
PairsRatio: 4.52
[\[raw statistics\]][151120-raw8]
| +| 2020‑11‑29 | [master][291120-master] vs [Stockfish 12]
`Bench: 3561701`
Update default net to nn-62ef826d1a6d.nnue
[\[differences\]][291120-dif] `72`
| Elo: [30.61][291120-elo1] ±1.0
Ptnml: 19, 1645, 21655, 6407, 274
nElo: 82.09 ±2.7
PairsRatio: 4.02
[\[raw statistics\]][291120-raw1]
| +| 2020‑12‑14 | [master][141220-master] vs [Stockfish 12]
`Bench: 4050630`
Increase reduction in case of stable best move
[\[differences\]][141220-dif] `79`
| Elo: [32.09][141220-elo1] ±1.0
Ptnml: 33, 1581, 21474, 6651, 261
nElo: 85.57 ±2.7
PairsRatio: 4.28
[\[raw statistics\]][141220-raw1]
| Elo: [27.50][141220-elo8] ±1.1
Ptnml: 10, 706, 15480, 3723, 81
nElo: 83.75 ±3.1
PairsRatio: 5.31
[\[raw statistics\]][141220-raw8]
| +| 2020‑12‑31 | [master][311220-master] vs [Stockfish 12]
`Bench: 4109336`
WeakUnopposed penalty for backwards
[\[differences\]][311220-dif] `89`
| Elo: [33.71][311220-elo1] ±1.0
Ptnml: 23, 1475, 21475, 6730, 297
nElo: 90.01 ±2.6
PairsRatio: 4.69
[\[raw statistics\]][311220-raw1]
| +| 2021‑01‑13 | [master][130121-master] vs [Stockfish 12]
`Bench: 4287509`
Optimize generate_moves
[\[differences\]][130121-dif] `101`
| Elo: [32.40][130121-elo1] ±1.0
Ptnml: 31, 1487, 21588, 6660, 234
nElo: 87.55 ±2.7
PairsRatio: 4.54
[\[raw statistics\]][130121-raw1]
| Elo: [28.29][130121-elo8] ±1.1
Ptnml: 10, 671, 15468, 3761, 90
nElo: 86.06 ±3.1
PairsRatio: 5.65
[\[raw statistics\]][130121-raw8]
| +| 2021‑02‑15 | [master][150221-master] vs [Stockfish 12]
`Bench: 3766422`
Small trivial clean-ups, February 2021
[\[differences\]][150221-dif] `121`
| Elo: [36.03][150221-elo1] ±1.1
Ptnml: 29, 1395, 21210, 7079, 287
nElo: 95.63 ±2.7
PairsRatio: 5.17
[\[raw statistics\]][150221-raw1]
| Elo: [29.08][150221-elo8] ±1.1
Ptnml: 6, 626, 15476, 3806, 86
nElo: 89.08 ±3.1
PairsRatio: 6.16
[\[raw statistics\]][150221-raw8]
| +| 2021‑02‑18 | [Stockfish 13] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF13RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF13DP]
`Bench: 3766422`
[\[differences\]][180221-dif] `123`
| + +
+ +
+ Stockfish 14 Development (2021-02-18 - 2021-07-02)
+ +| `Date` | `Version` | `1 Thread` | `8 Threads` | +|:------:|:---------:|:----------:|:-----------:| +| 2021‑02‑26 | [master][260221-master] vs [Stockfish 13]
`Bench: 5037279`
Introduce DistanceFromPV
[\[differences\]][260221-dif] `4`
| Elo: [1.34][260221-elo1] ±0.8
Ptnml: 28, 2367, 24980, 2596, 29
nElo: 4.55 ±2.8
PairsRatio: 1.10
[\[raw statistics\]][260221-raw1]
| +| 2021‑03‑24 | [master][240321-master] vs [Stockfish 13]
`Bench: 4339126`
Small cleanups (march 2021)
[\[differences\]][240321-dif] `18`
| Elo: [0.61][240321-elo1] ±0.8
Ptnml: 18, 2457, 24938, 2575, 12
nElo: 2.09 ±2.8
PairsRatio: 1.05
[\[raw statistics\]][240321-raw1]
| +| 2021‑04‑15 | [master][150421-master] vs [Stockfish 13]
`Bench: 4503918`
Use classical eval for Bishop vs Pawns
[\[differences\]][150421-dif] `29`
| Elo: [1.47][150421-elo1] ±0.8
Ptnml: 39, 2384, 24886, 2667, 24
nElo: 4.93 ±2.8
PairsRatio: 1.11
[\[raw statistics\]][150421-raw1]
| Elo: [0.89][150421-elo8] ±0.9
Ptnml: 11, 1279, 17318, 1381, 11
nElo: 3.38 ±3.4
PairsRatio: 1.08
[\[raw statistics\]][150421-raw8]
| +| 2021‑05‑22 | [master][220521-master] vs [Stockfish 13]
`Bench: 3856635`
Sometimes change the balance
[\[differences\]][220521-dif] `62`
| Elo: [10.12][220521-elo1] ±1.0
Ptnml: 60, 2454, 23277, 4096, 113
nElo: 29.34 ±2.8
PairsRatio: 1.67
[\[raw statistics\]][220521-raw1]
| +| 2021‑06‑14 | [master][140621-master] vs [Stockfish 13]
`Bench: 4877339`
Update default net to nn-8e47cf062333.nnue
[\[differences\]][140621-dif] `90`
| Elo: [21.80][140621-elo1] ±1.1
Ptnml: 67, 2216, 21852, 5620, 245
nElo: 57.47 ±2.7
PairsRatio: 2.57
[\[raw statistics\]][140621-raw1]
| Elo: [16.96][140621-elo8] ±1.1
Ptnml: 15, 1083, 15923, 2894, 85
nElo: 52.38 ±3.3
PairsRatio: 2.71
[\[raw statistics\]][140621-raw8]
| +| 2021‑06‑18 | [master][180621-master] vs [Stockfish 13]
`Bench: 4900906`
Make net nn-50144f835024.nnue the default
[\[differences\]][180621-dif] `100`
| Elo: [25.56][180621-elo1] ±1.0
Ptnml: 30, 1814, 22084, 5864, 208
nElo: 69.95 ±2.7
PairsRatio: 3.29
[\[raw statistics\]][180621-raw1]
| +| 2021‑06‑29 | [master][290621-master] vs [Stockfish 13]
`Bench: 4770936`
Update Top CPU Contributors
[\[differences\]][290621-dif] `113`
| Elo: [30.27][290621-elo1] ±1.0
Ptnml: 14, 1443, 22127, 6146, 270
nElo: 83.66 ±2.6
PairsRatio: 4.40
[\[raw statistics\]][290621-raw1]
| Elo: [22.62][290621-elo8] ±1.1
Ptnml: 7, 755, 15977, 3153, 108
nElo: 71.15 ±3.1
PairsRatio: 4.28
[\[raw statistics\]][290621-raw8]
| +| 2021‑07‑02 | [Stockfish 14] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF14RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF14DP]
`Bench: 4770936`
[\[differences\]][020721-dif] `114`
| + +
+ +
+ Stockfish 15 Development (2021-07-02 - 2022-04-18)
+ +| `Date` | `Version` | `1 Thread` | `8 Threads` | `1 Thread (UHO)` | +|:---:|:---:|:---:|:---:|:---:| +| 2021‑07‑26 | [master][260721-master] vs [Stockfish 14]
`Bench: 5124774`
Update default net to nn-26abeed38351.nnue
[\[differences\]][260721-dif] `17`
| Elo: [2.74][260721-elo1] ±0.8
Ptnml: 24, 2000, 25476, 2478, 22
nElo: 9.85 ±2.8
PairsRatio: 1.24
[\[raw statistics\]][260721-raw1]
| | +| 2021‑08‑15 | [master][150821-master] vs [Stockfish 14]
`Bench: 5189338`
New NNUE architecture and net
[\[differences\]][150821-dif] `26`
| Elo: [9.31][150821-elo1] ±0.9
Ptnml: 24, 2044, 24321, 3522, 89
nElo: 29.61 ±2.7
PairsRatio: 1.75
[\[raw statistics\]][150821-raw1]
| Elo: [6.08][150821-elo8] ±0.9
Ptnml: 5, 1063, 17183, 1725, 24
nElo: 22.66 ±3.4
PairsRatio: 1.64
[\[raw statistics\]][150821-raw8]
| | +| 2021‑08‑31 | [master][310821-master] vs [Stockfish 14]
`Bench: 5600615`
Update default net to nn-735bba95dec0.nnue
[\[differences\]][310821-dif] `39`
| Elo: [15.04][310821-elo1] ±0.9
Ptnml: 17, 1601, 24272, 3990, 120
nElo: 47.86 ±2.7
PairsRatio: 2.54
[\[raw statistics\]][310821-raw1]
| | +| 2021‑09‑15 | [master][150921-master] vs [Stockfish 14]
`Bench: 6658747`
Update default net to nn-13406b1dcbe0.nnue
[\[differences\]][150921-dif] `46`
| Elo: [16.64][150921-elo1] ±0.9
Ptnml: 21, 1878, 23452, 4506, 143
nElo: 49.53 ±2.7
PairsRatio: 2.45
[\[raw statistics\]][150921-raw1]
| Elo: [12.39][150921-elo8] ±0.9
Ptnml: 1, 766, 17084, 2104, 45
nElo: 45.59 ±3.2
PairsRatio: 2.80
[\[raw statistics\]][150921-raw8]
| | +| 2021‑10‑06 | [master][061021-master] vs [Stockfish 14]
`Bench: 6261865`
Capping stat bonus at 2000
[\[differences\]][061021-dif] `57`
| Elo: [18.68][061021-elo1] ±0.9
Ptnml: 28, 1496, 23821, 4535, 120
nElo: 57.70 ±2.7
PairsRatio: 3.05
[\[raw statistics\]][061021-raw1]
| Elo: [11.74][061021-elo8] ±0.9
Ptnml: 4, 769, 17137, 2052, 38
nElo: 43.60 ±3.2
PairsRatio: 2.70
[\[raw statistics\]][061021-raw8]
| | +| 2021‑10‑18 | [master][181021-master] vs [Stockfish 14]
`Bench: 5005810`
Simplify probCutCount away
[\[differences\]][181021-dif] `67`
| Elo: [17.69][181021-elo1] ±0.9
Ptnml: 13, 1577, 23891, 4382, 137
nElo: 54.78 ±2.7
PairsRatio: 2.84
[\[raw statistics\]][181021-raw1]
| | +| 2021‑10‑23 | [master][231021-master] vs [Stockfish 14]
`Bench: 6334068`
Adjust ButterflyHistory decay parameter
[\[differences\]][231021-dif] `78`
| Elo: [17.87][231021-elo1] ±0.9
Ptnml: 11, 1377, 24244, 4253, 115
nElo: 57.35 ±2.6
PairsRatio: 3.15
[\[raw statistics\]][231021-raw1]
| Elo: [10.27][231021-elo8] ±0.9
Ptnml: 3, 696, 17438, 1842, 21
nElo: 40.55 ±3.2
PairsRatio: 2.67
[\[raw statistics\]][231021-raw8]
| | +| 2021‑10‑28 | [Stockfish 14.1] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF141RN]
`Bench: 6334068`
[\[differences\]][281021-dif] `80`
| | +| 2021‑11‑05 | [master][051121-master] vs [Stockfish 14]
`Bench: 6719976`
Tweak initial aspiration window
[\[differences\]][051121-dif] `89`
| Elo: [18.71][051121-elo1] ±0.9
Ptnml: 16, 1293, 24255, 4319, 117
nElo: 60.14 ±2.6
PairsRatio: 3.39
[\[raw statistics\]][051121-raw1]
| | +| 2021‑11‑23 | [master][231121-master] vs [Stockfish 14]
`Bench: 7334766`
Less futility pruning
[\[differences\]][231121-dif] `98`
| Elo: [19.35][231121-elo1] ±0.9
Ptnml: 19, 1405, 23923, 4524, 129
nElo: 60.39 ±2.6
PairsRatio: 3.27
[\[raw statistics\]][231121-raw1]
| Elo: [13.28][231121-elo8] ±0.9
Ptnml: 3, 696, 17112, 2148, 41
nElo: 49.26 ±3.2
PairsRatio: 3.13
[\[raw statistics\]][231121-raw8]
| | +| 2021‑11‑28 | [master][281121-master] vs [Stockfish 14]
`Bench: 6302543`
Refine futility pruning for parent nodes
[\[differences\]][281121-dif] `103`
| Elo: [24.44][281121-elo1] ±0.9
Ptnml: 9, 1054, 23776, 5037, 124
nElo: 76.95 ±2.5
PairsRatio: 4.86
[\[raw statistics\]][281121-raw1]
| | +| 2021‑12‑07 | [master][071221-master] vs [Stockfish 14]
`Bench: 4667742`
Update default net to nn-63376713ba63.nnue
[\[differences\]][071221-dif] `118`
| Elo: [26.99][071221-elo1] ±0.9
Ptnml: 11, 998, 23473, 5365, 153
nElo: 83.13 ±2.5
PairsRatio: 5.47
[\[raw statistics\]][071221-raw1]
| Elo: [17.80][071221-elo8] ±0.9
Ptnml: 6, 529, 16919, 2504, 42
nElo: 64.76 ±3.1
PairsRatio: 4.76
[\[raw statistics\]][071221-raw8]
| | +| 2021‑12‑14 | [master][141221-master] vs [Stockfish 14]
`Bench: 4735679`
Remove NNUE scaling term
[\[differences\]][141221-dif] `126`
| Elo: [26.45][141221-elo1] ±0.9
Ptnml: 9, 962, 23656, 5207, 166
nElo: 82.28 ±2.5
PairsRatio: 5.53
[\[raw statistics\]][141221-raw1]
| | +| 2021‑12‑22 | [master][221221-master] vs [Stockfish 14]
`Bench: 4633875`
Update default net to nn-ac07bd334b62.nnue
[\[differences\]][221221-dif] `139`
| Elo: [28.65][221221-elo1] ±0.9
Ptnml: 11, 1036, 23172, 5567, 214
nElo: 85.61 ±2.5
PairsRatio: 5.52
[\[raw statistics\]][221221-raw1]
| Elo: [17.66][221221-elo8] ±0.9
Ptnml: 2, 520, 16976, 2448, 54
nElo: 64.60 ±3.0
PairsRatio: 4.79
[\[raw statistics\]][221221-raw8]
| | +| 2022‑01‑10 | [master][100122-master] vs [Stockfish 14]
`Bench: 4572746`
Adjust pruning constants
[\[differences\]][100122-dif] `148`
| Elo: [30.51][100122-elo1] ±1.0
Ptnml: 13, 1065, 22809, 5879, 234
nElo: 89.03 ±2.5
PairsRatio: 5.67
[\[raw statistics\]][100122-raw1]
| | +| 2022‑01‑29 | [master][290122-master] vs [Stockfish 14]
`Bench: 4637392`
Do stats updates after LMR for captures
[\[differences\]][290122-dif] `159`
| Elo: [32.07][290122-elo1] ±1.0
Ptnml: 10, 1009, 22681, 6049, 251
nElo: 93.05 ±2.5
PairsRatio: 6.18
[\[raw statistics\]][290122-raw1]
| Elo: [22.31][290122-elo8] ±1.0
Ptnml: 3, 496, 16495, 2945, 61
nElo: 76.78 ±3.0
PairsRatio: 6.02
[\[raw statistics\]][290122-raw8]
| | +| 2022‑02‑10 | [master][100222-master] vs [Stockfish 14]
`Bench: 4919707`
Update architecture to "SFNNv4"
[\[differences\]][100222-dif] `166`
| Elo: [34.88][100222-elo1] ±1.0
Ptnml: 13, 884, 22430, 6432, 241
nElo: 100.85 ±2.5
PairsRatio: 7.44
[\[raw statistics\]][100222-raw1]
| Elo: [24.37][100222-elo8] ±1.0
Ptnml: 2, 416, 16441, 3061, 80
nElo: 83.29 ±2.9
PairsRatio: 7.51
[\[raw statistics\]][100222-raw8]
| | +| 2022‑02‑17 | [master][170222-master] vs [Stockfish 14]
`Bench: 6318903`
Tune search at very long time control
[\[differences\]][170222-dif] `168`
| Elo: [33.07][170222-elo1] ±1.0
Ptnml: 17, 984, 22522, 6243, 234
nElo: 95.49 ±2.5
PairsRatio: 6.47
[\[raw statistics\]][170222-raw1]
| Elo: [23.83][170222-elo8] ±1.0
Ptnml: 4, 410, 16496, 3023, 67
nElo: 82.31 ±2.9
PairsRatio: 7.46
[\[raw statistics\]][170222-raw8]
| | +| 2022‑03‑19 | [master][190322-master] vs [Stockfish 14]
`Bench: 7044203`
Remove ttPv tree shrinking
[\[differences\]][190322-dif] `180`
| Elo: [35.01][190322-elo1] ±1.0
Ptnml: 11, 801, 22566, 6395, 227
nElo: 102.62 ±2.5
PairsRatio: 8.16
[\[raw statistics\]][190322-raw1]
| | +| 2022‑04‑17 | [master][170422-master] vs [Stockfish 14]
`Bench: 8129754`
Decrease LMR at PV nodes with low depth
[\[differences\]][170422-dif] `189`
| Elo: [36.69][170422-elo1] ±1.0
Ptnml: 5, 711, 22489, 6557, 238
nElo: 107.69 ±2.4
PairsRatio: 9.49
[\[raw statistics\]][170422-raw1]
| Elo: [26.09][170422-elo8] ±1.0
Ptnml: 1, 341, 16379, 3217, 62
nElo: 89.84 ±2.8
PairsRatio: 9.59
[\[raw statistics\]][170422-raw8]
| Elo: [91.22][170422-elo1uho] ±1.3
Ptnml: 17, 1335, 12388, 15750, 510
nElo: 205.62 ±3.6
PairsRatio: 12.03
[\[raw statistics\]][170422-raw1uho]
| +| 2022‑04‑18 | [Stockfish 15] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF15RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF15DP]
`Bench: 8129754`
[\[differences\]][180422-dif] `190`
| | + +
+ +
+ Stockfish 16 Development (2022-04-18 - 2023-06-29)
+ +| `Date` | `Version` | `1 Thread` | `8 Threads` | `1 Thread (UHO)` | +|:---:|:---:|:---:|:---:|:---:| +| 2022‑05‑14 | [master][140522-master] vs [Stockfish 15]
`Bench: 6481017`
SE depth scaling using the previous depth
[\[differences\]][140522-dif] `9`
| Elo: [4.54][140522-elo1] ±0.8
Ptnml: 18, 1850, 25507, 2580, 45
nElo: 16.29 ±2.8
PairsRatio: 1.41
[\[raw statistics\]][140522-raw1]
| Elo: [3.12][140522-elo8] ±0.8
Ptnml: 5, 803, 18029, 1154, 9
nElo: 13.92 ±3.4
PairsRatio: 1.44
[\[raw statistics\]][140522-raw8]
| | +| 2022‑06‑16 | [master][160622-master] vs [Stockfish 15]
`Bench: 5845802`
Simplify away condition in ttSave in probCut
[\[differences\]][160622-dif] `25`
| Elo: [5.72][160622-elo1] ±0.8
Ptnml: 31, 1838, 25288, 2799, 44
nElo: 19.99 ±2.8
PairsRatio: 1.52
[\[raw statistics\]][160622-raw1]
| | | +| 2022‑07‑13 | [master][130722-master] vs [Stockfish 15]
`Bench: 5905619`
Update default net to nn-ad9b42354671.nnue
[\[differences\]][130722-dif] `34`
| Elo: [5.70][130722-elo1] ±0.8
Ptnml: 42, 1867, 25207, 2832, 52
nElo: 19.67 ±2.8
PairsRatio: 1.51
[\[raw statistics\]][130722-raw1]
| Elo: [4.86][130722-elo8] ±0.8
Ptnml: 3, 734, 17975, 1276, 12
nElo: 21.46 ±3.3
PairsRatio: 1.75
[\[raw statistics\]][130722-raw8]
| | +| 2022‑08‑12 | [master][120822-master] vs [Stockfish 15]
`Bench: 5868987`
Remove an unneeded randomization of evals
[\[differences\]][120822-dif] `43`
| Elo: [7.18][120822-elo1] ±0.8
Ptnml: 48, 1784, 25112, 2993, 63
nElo: 24.44 ±2.8
PairsRatio: 1.67
[\[raw statistics\]][120822-raw1]
| Elo: [4.60][120822-elo8] ±0.8
Ptnml: 5, 737, 17987, 1265, 6
nElo: 20.43 ±3.4
PairsRatio: 1.71
[\[raw statistics\]][120822-raw8]
| Elo: [14.61][120822-elo1uho] ±1.3
Ptnml: 149, 5043, 17073, 7608, 127
nElo: 30.73 ±2.8
PairsRatio: 1.49
[\[raw statistics\]][120822-raw1uho]
| +| 2022‑09‑07 | [master][070922-master] vs [Stockfish 15]
`Bench: 5609606`
VLTC tuning
[\[differences\]][070922-dif] `52`
| Elo: [6.87][070922-elo1] ±0.8
Ptnml: 40, 1796, 25154, 2958, 52
nElo: 23.61 ±2.8
PairsRatio: 1.64
[\[raw statistics\]][070922-raw1]
| Elo: [5.45][070922-elo8] ±0.8
Ptnml: 3, 732, 17913, 1339, 13
nElo: 23.68 ±3.3
PairsRatio: 1.84
[\[raw statistics\]][070922-raw8]
| | +| 2022‑10‑05 | [master][051022-master] vs [Stockfish 15]
`Bench: 4114228`
Revert "Mix alpha and statScore for reduction"
[\[differences\]][051022-dif] `66`
| Elo: [6.86][051022-elo1] ±0.8
Ptnml: 21, 1580, 25632, 2727, 40
nElo: 25.04 ±2.7
PairsRatio: 1.73
[\[raw statistics\]][051022-raw1]
| Elo: [5.98][051022-elo8] ±0.8
Ptnml: 6, 643, 18015, 1329, 7
nElo: 26.72 ±3.3
PairsRatio: 2.06
[\[raw statistics\]][051022-raw8]
| | +| 2022‑10‑30 | [master][301022-master] vs [Stockfish 15]
`Bench: 4271738`
Adjust reduction less at medium depths
[\[differences\]][301022-dif] `81`
| Elo: [8.52][301022-elo1] ±0.8
Ptnml: 23, 1469, 25573, 2884, 51
nElo: 30.84 ±2.7
PairsRatio: 1.97
[\[raw statistics\]][301022-raw1]
| Elo: [5.91][301022-elo8] ±0.7
Ptnml: 6, 589, 18129, 1271, 5
nElo: 27.24 ±3.3
PairsRatio: 2.14
[\[raw statistics\]][301022-raw8]
| Elo: [17.17][301022-elo1uho] ±1.8
Ptnml: 38, 2430, 8572, 3933, 27
nElo: 36.91 ±4.0
PairsRatio: 1.60
[\[raw statistics\]][301022-raw1uho]
| +| 2022‑12‑02 | [master][021222-master] vs [Stockfish 15]
`Bench: 3467381`
Fix bestThread selection
[\[differences\]][021222-dif] `97`
| Elo: [7.46][021222-elo1] ±0.7
Ptnml: 21, 1389, 25902, 2657, 31
nElo: 28.19 ±2.7
PairsRatio: 1.91
[\[raw statistics\]][021222-raw1]
| Elo: [5.97][021222-elo8] ±0.7
Ptnml: 3, 573, 18164, 1254, 6
nElo: 27.83 ±3.3
PairsRatio: 2.19
[\[raw statistics\]][021222-raw8]
| Elo: [17.04][021222-elo1uho] ±1.3
Ptnml: 73, 4844, 17208, 7820, 55
nElo: 36.73 ±2.8
PairsRatio: 1.60
[\[raw statistics\]][021222-raw1uho]
| +| 2022‑12‑04 | [Stockfish 15.1] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF151RN]
`Bench: 3467381`
[\[differences\]][041222-dif] `98`
| | | | +| 2022‑12‑19 | [master][191222-master] vs [Stockfish 15]
`Bench: 3727508`
Sometimes do a reduced search if LMR is skipped
[\[differences\]][191222-dif] `119`
| Elo: [8.55][191222-elo1] ±0.8
Ptnml: 16, 1420, 25675, 2850, 39
nElo: 31.50 ±2.7
PairsRatio: 2.01
[\[raw statistics\]][191222-raw1]
| | | +| 2023‑01‑01 | [master][010123-master] vs [Stockfish 15]
`Bench: 4015511`
Update default net to nn-60fa44e376d9.nnue
[\[differences\]][010123-dif] `126`
| Elo: [10.00][010123-elo1] ±0.8
Ptnml: 15, 1301, 25667, 2976, 41
nElo: 36.92 ±2.7
PairsRatio: 2.29
[\[raw statistics\]][010123-raw1]
| Elo: [7.10][010123-elo8] ±0.7
Ptnml: 6, 529, 18118, 1336, 11
nElo: 32.56 ±3.2
PairsRatio: 2.52
[\[raw statistics\]][010123-raw8]
| Elo: [23.55][010123-elo1uho] ±1.3
Ptnml: 59, 4354, 17132, 8377, 78
nElo: 51.05 ±2.8
PairsRatio: 1.92
[\[raw statistics\]][010123-raw1uho]
| +| 2023‑01‑23 | [master][230123-master] vs [Stockfish 15]
`Bench: 3941848`
Update default net to nn-bc24c101ada0.nnue
[\[differences\]][230123-dif] `143`
| Elo: [11.37][230123-elo1] ±0.7
Ptnml: 9, 1127, 25786, 3048, 30
nElo: 42.95 ±2.7
PairsRatio: 2.71
[\[raw statistics\]][230123-raw1]
| Elo: [9.09][230123-elo8] ±0.8
Ptnml: 2, 484, 17984, 1526, 4
nElo: 40.84 ±3.2
PairsRatio: 3.15
[\[raw statistics\]][230123-raw8]
| Elo: [29.49][230123-elo1uho] ±1.3
Ptnml: 43, 3893, 17099, 8870, 95
nElo: 64.58 ±2.9
PairsRatio: 2.28
[\[raw statistics\]][230123-raw1uho]
| +| 2023‑02‑09 | [master][090223-master] vs [Stockfish 15]
`Bench: 3841998`
Update default net to nn-1337b1adec5b.nnue
[\[differences\]][090223-dif] `162`
| Elo: [12.76][090223-elo1] ±0.8
Ptnml: 9, 1089, 25635, 3224, 43
nElo: 47.33 ±2.6
PairsRatio: 2.98
[\[raw statistics\]][090223-raw1]
| Elo: [8.49][090223-elo8] ±0.8
Ptnml: 2, 502, 18018, 1473, 5
nElo: 38.38 ±3.2
PairsRatio: 2.93
[\[raw statistics\]][090223-raw8]
| Elo: [32.71][090223-elo1uho] ±1.3
Ptnml: 44, 3760, 16821, 9269, 106
nElo: 71.26 ±2.9
PairsRatio: 2.46
[\[raw statistics\]][090223-raw1uho]
| +| 2023‑02‑18 | [master][180223-master] vs [Stockfish 15]
`Bench: 4283297`
Remove one reduction call
[\[differences\]][180223-dif] `168`
| Elo: [12.58][180223-elo1] ±0.8
Ptnml: 11, 1127, 25591, 3221, 50
nElo: 46.26 ±2.6
PairsRatio: 2.87
[\[raw statistics\]][180223-raw1]
| | Elo: [31.91][180223-elo1uho] ±1.3
Ptnml: 47, 3828, 16804, 9225, 96
nElo: 69.38 ±2.9
PairsRatio: 2.41
[\[raw statistics\]][180223-raw1uho]
| +| 2023‑02‑24 | [master][240223-master] vs [Stockfish 15]
`Bench: 4705194`
Search tuning at very long time control
[\[differences\]][240223-dif] `174`
| Elo: [11.69][240223-elo1] ±0.7
Ptnml: 10, 1151, 25692, 3105, 42
nElo: 43.51 ±2.6
PairsRatio: 2.71
[\[raw statistics\]][240223-raw1]
| Elo: [9.37][240223-elo8] ±0.8
Ptnml: 1, 455, 18013, 1526, 5
nElo: 42.48 ±3.1
PairsRatio: 3.36
[\[raw statistics\]][240223-raw8]
| Elo: [32.42][240223-elo1uho] ±1.3
Ptnml: 44, 3804, 16761, 9307, 84
nElo: 70.59 ±2.9
PairsRatio: 2.44
[\[raw statistics\]][240223-raw1uho]
| +| 2023‑03‑19 | [master][190323-master] vs [Stockfish 15]
`Bench: 4980082`
Remove 'si' StateInfo variable/parameter.
[\[differences\]][190323-dif] `196`
| Elo: [13.36][190323-elo1] ±0.8
Ptnml: 9, 1098, 25532, 3300, 61
nElo: 48.75 ±2.6
PairsRatio: 3.04
[\[raw statistics\]][190323-raw1]
| Elo: [10.10][190323-elo8] ±0.8
Ptnml: 0, 460, 17934, 1589, 17
nElo: 44.63 ±3.1
PairsRatio: 3.49
[\[raw statistics\]][190323-raw8]
| Elo: [35.27][190323-elo1uho] ±1.3
Ptnml: 48, 3610, 16657, 9594, 91
nElo: 76.91 ±2.9
PairsRatio: 2.65
[\[raw statistics\]][190323-raw1uho]
| +| 2023‑04‑01 | [master][010423-master] vs [Stockfish 15]
`Bench: 4380438`
Decrease Depth more for positions not in TT.
[\[differences\]][010423-dif] `211`
| Elo: [14.42][010423-elo1] ±0.8
Ptnml: 7, 1031, 25480, 3431, 51
nElo: 52.68 ±2.6
PairsRatio: 3.35
[\[raw statistics\]][010423-raw1]
| Elo: [9.90][010423-elo8] ±0.8
Ptnml: 2, 450, 17964, 1575, 9
nElo: 44.20 ±3.1
PairsRatio: 3.50
[\[raw statistics\]][010423-raw8]
| Elo: [37.37][010423-elo1uho] ±1.3
Ptnml: 48, 3485, 16555, 9815, 97
nElo: 81.54 ±2.9
PairsRatio: 2.81
[\[raw statistics\]][010423-raw1uho]
| +| 2023‑04‑22 | [master][220423-master] vs [Stockfish 15]
`Bench: 3548023`
Less reduction for tt move.
[\[differences\]][220423-dif] `235`
| Elo: [14.60][220423-elo1] ±0.8
Ptnml: 9, 967, 25569, 3405, 50
nElo: 53.91 ±2.6
PairsRatio: 3.54
[\[raw statistics\]][220423-raw1]
| Elo: [11.32][220423-elo8] ±0.8
Ptnml: 0, 386, 17939, 1661, 14
nElo: 50.39 ±3.0
PairsRatio: 4.34
[\[raw statistics\]][220423-raw8]
| Elo: [38.07][220423-elo1uho] ±1.3
Ptnml: 27, 3392, 16687, 9793, 101
nElo: 83.87 ±2.9
PairsRatio: 2.89
[\[raw statistics\]][220423-raw1uho]
| +| 2023‑05‑07 | [master][070523-master] vs [Stockfish 15]
`Bench: 3808503`
Refine deeper post-lmr searches
[\[differences\]][070523-dif] `244`
| Elo: [14.36][070523-elo1] ±0.8
Ptnml: 11, 1004, 25521, 3423, 41
nElo: 52.83 ±2.6
PairsRatio: 3.41
[\[raw statistics\]][070523-raw1]
| Elo: [10.63][070523-elo8] ±0.7
Ptnml: 0, 379, 18029, 1581, 11
nElo: 48.41 ±3.0
PairsRatio: 4.20
[\[raw statistics\]][070523-raw8]
| Elo: [39.62][070523-elo1uho] ±1.3
Ptnml: 34, 3309, 16568, 9988, 101
nElo: 87.17 ±2.9
PairsRatio: 3.02
[\[raw statistics\]][070523-raw1uho]
| +| 2023‑06‑04 | [master][040623-master] vs [Stockfish 15]
`Bench: 2551691`
Move internal iterative reduction before probcut
[\[differences\]][040623-dif] `265`
| Elo: [15.01][040623-elo1] ±0.8
Ptnml: 9, 1037, 25355, 3552, 47
nElo: 54.25 ±2.6
PairsRatio: 3.44
[\[raw statistics\]][040623-raw1]
| Elo: [11.87][040623-elo8] ±0.8
Ptnml: 3, 362, 17908, 1720, 7
nElo: 52.68 ±3.0
PairsRatio: 4.73
[\[raw statistics\]][040623-raw8]
| Elo: [38.10][040623-elo1uho] ±1.3
Ptnml: 48, 3387, 16634, 9826, 105
nElo: 83.49 ±2.9
PairsRatio: 2.89
[\[raw statistics\]][040623-raw1uho]
| +| 2023‑06‑12 | [master][120623-master] vs [Stockfish 15]
`Bench: 2370027`
Use block sparse input for the first layer.
[\[differences\]][120623-dif] `274`
| Elo: [17.57][120623-elo1] ±0.8
Ptnml: 10, 881, 25223, 3840, 46
nElo: 63.12 ±2.5
PairsRatio: 4.36
[\[raw statistics\]][120623-raw1]
| Elo: [13.95][120623-elo8] ±0.8
Ptnml: 1, 290, 17828, 1865, 16
nElo: 60.93 ±2.8
PairsRatio: 6.46
[\[raw statistics\]][120623-raw8]
| Elo: [44.18][120623-elo1uho] ±1.3
Ptnml: 45, 3031, 16345, 10449, 130
nElo: 97.20 ±3.0
PairsRatio: 3.44
[\[raw statistics\]][120623-raw1uho]
| +| 2023‑06‑22 | [master][220623-master] vs [Stockfish 15]
`Bench: 2593605`
Update default net to nn-5af11540bbfe.nnue
[\[differences\]][220623-dif] `289`
| Elo: [18.30][220623-elo1] ±0.8
Ptnml: 3, 812, 25265, 3864, 56
nElo: 66.19 ±2.5
PairsRatio: 4.81
[\[raw statistics\]][220623-raw1]
| Elo: [14.33][220623-elo8] ±0.8
Ptnml: 3, 297, 17767, 1914, 19
nElo: 61.59 ±2.9
PairsRatio: 6.44
[\[raw statistics\]][220623-raw8]
| Elo: [47.03][220623-elo1uho] ±1.3
Ptnml: 26, 2938, 16102, 10805, 129
nElo: 103.71 ±3.0
PairsRatio: 3.69
[\[raw statistics\]][220623-raw1uho]
| +| 2023‑06‑29 | [Stockfish 16] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF16RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF16DP]
`Bench: 2593605`
[\[differences\]][290623-dif] `290`
| | + +
+ +
+ Stockfish 17 Development (2023-06-29 - 2024-09-06)
+ +| `Date` | `Version` | `1 Thread` | `8 Threads` | +|:---:|:---:|:---:|:---:| +| 2023‑07‑19 | [master][190723-master] vs [Stockfish 16]
`Bench: 1727577`
Do more futility pruning for cutNodes that are not in TT
[\[differences\]][190723-dif] `41`
| Elo: [0.10][190723-elo1] ±1.4
Ptnml: 96, 6835, 16121, 6851, 97
nElo: 0.21 ±2.8
PairsRatio: 1.00
[\[raw statistics\]][190723-raw1]
| Elo: [2.10][190723-elo8] ±1.3
Ptnml: 30, 5872, 17820, 6261, 17
nElo: 4.64 ±2.8
PairsRatio: 1.06
[\[raw statistics\]][190723-raw8]
+| 2023‑08‑13 | [master][130823-master] vs [Stockfish 16]
`Bench: 1447866`
Simplify material difference in evaluate
[\[differences\]][130823-dif] `68`
| Elo: [0.58][130823-elo1] ±1.4
Ptnml: 120, 6787, 16066, 6927, 100
nElo: 1.17 ±2.8
PairsRatio: 1.02
[\[raw statistics\]][130823-raw1]
| Elo: [2.08][130823-elo8] ±1.2
Ptnml: 15, 5797, 18021, 6147, 20
nElo: 4.65 ±2.8
PairsRatio: 1.06
[\[raw statistics\]][130823-raw8]
+| 2023‑09‑11 | [master][110923-master] vs [Stockfish 16]
`Bench: 1603079`
Cleanup code after dropping ICC support in favor of ICX
[\[differences\]][110923-dif] `93`
| Elo: [7.66][110923-elo1] ±1.4
Ptnml: 86, 6160, 16212, 7429, 113
nElo: 15.68 ±2.8
PairsRatio: 1.21
[\[raw statistics\]][110923-raw1]
| Elo: [5.65][110923-elo8] ±1.2
Ptnml: 16, 5524, 17944, 6500, 16
nElo: 12.57 ±2.8
PairsRatio: 1.18
[\[raw statistics\]][110923-raw8]
+| 2023‑09‑22 | [master][220923-master] vs [Stockfish 16]
`Bench: 1246812`
Update NNUE architecture to SFNNv8: L1-2560 nn-ac1dbea57aa3.nnue
[\[differences\]][220923-dif] `103`
| Elo: [3.33][220923-elo1] ±1.4
Ptnml: 106, 6534, 16134, 7131, 95
nElo: 6.78 ±2.8
PairsRatio: 1.09
[\[raw statistics\]][220923-raw1]
| Elo: [6.61][220923-elo8] ±1.3
Ptnml: 21, 5553, 17713, 6689, 24
nElo: 14.56 ±2.8
PairsRatio: 1.20
[\[raw statistics\]][220923-raw8]
+| 2023‑10‑08 | [master][081023-master] vs [Stockfish 16]
`Bench: 1246560`
Skip futility pruning if ttMove has bad history
[\[differences\]][081023-dif] `119`
| Elo: [9.68][081023-elo1] ±1.4
Ptnml: 100, 6094, 15972, 7702, 132
nElo: 19.61 ±2.8
PairsRatio: 1.26
[\[raw statistics\]][081023-raw1]
| Elo: [10.30][081023-elo8] ±1.3
Ptnml: 13, 5168, 17868, 6930, 21
nElo: 22.90 ±2.8
PairsRatio: 1.34
[\[raw statistics\]][081023-raw8]
+| 2023‑10‑23 | [master][231023-master] vs [Stockfish 16]
`Bench: 1241996`
Follow up Makefile changes for clang-format
[\[differences\]][231023-dif] `136`
| Elo: [10.47][231023-elo1] ±1.4
Ptnml: 94, 6071, 15901, 7801, 133
nElo: 21.17 ±2.8
PairsRatio: 1.29
[\[raw statistics\]][231023-raw1]
| Elo: [9.16][231023-elo8] ±1.3
Ptnml: 19, 5311, 17763, 6884, 23
nElo: 20.24 ± 2.8
ParsRatio: 1.30
[\[raw statistics\]][231023-raw8]
+| 2023‑11‑03 | [master][031123-master] vs [Stockfish 16]
`Bench: 1330590`
Update pawn history based on static eval difference
[\[differences\]][031123-dif] `150`
| Elo: [10.57][031123-elo1] ±1.4
Ptnml: 112, 6018, 15922, 7829, 119
nElo: 21.38 ±2.8
PairsRatio: 1.30
[\[raw statistics\]][031123-raw1]
| Elo: [9.16][031123-elo8] ±1.3
Ptnml: 27, 5330, 17701, 6919, 23
nElo: 20.17 ±2.8
PairsRatio: 1.30
[\[raw statistics\]][031123-raw8]
+| 2023‑12‑02 | [master][021223-master] vs [Stockfish 16]
`Bench: 1403703`
Tweak return value in futility pruning
[\[differences\]][021223-dif] `172`
| Elo: [12.59][021223-elo1] ±1.4
Ptnml: 93, 5810, 16029, 7966, 102
nElo: 25.70 ±2.8
PairsRatio: 1.37
[\[raw statistics\]][021223-raw1]
| Elo: [10.08][021223-elo8] ±1.3
Ptnml: 18, 5168, 17893, 6897, 24
nElo: 22.42 ±2.8
PairsRatio: 1.33
[\[raw statistics\]][021223-raw8]
+| 2023‑12‑31 | [master][311223-master] vs [Stockfish 16]
`Bench: 1392883`
Tweak static eval history update
[\[differences\]][311223-dif] `202`
| Elo: [19.19][311223-elo1] ±1.4
Ptnml: 74, 5393, 15884, 8446, 203
nElo: 38.89 ±2.8
PairsRatio: 1.58
[\[raw statistics\]][311223-raw1]
| Elo: [16.82][311223-elo8] ±1.3
Ptnml: 13, 4672, 17747, 7535, 33
nElo: 37.42 ±2.8
PairsRatio: 1.62
[\[raw statistics\]][311223-raw8]
+| 2024‑01‑07 | [master][070124-master] vs [Stockfish 16]
`Bench: 1438336`
Prefix abs with std::
[\[differences\]][070124-dif] `219`
| Elo: [25.53][070124-elo1] ±1.4
Ptnml: 58, 4917, 15789, 9038, 198
nElo: 52.14 ±2.8
PairsRatio: 1.86
[\[raw statistics\]][070124-raw1]
| Elo: [18.88][070124-elo8] ±1.3
Ptnml: 12, 4602, 17534, 7820, 32
nElo: 41.76 ±2.8
PairsRatio: 1.70
[\[raw statistics\]][070124-raw8]
+| 2024‑01‑21 | [master][210124-master] vs [Stockfish 16]
`Bench: 1235377`
VLTC search tune
[\[differences\]][210124-dif] `242`
| Elo: [26.49][210124-elo1] ±1.4
Ptnml: 64, 4791, 15823, 9159, 163
nElo: 54.42 ±2.8
PairsRatio: 1.92
[\[raw statistics\]][210124-raw1]
| Elo: [22.91][210124-elo8] ±1.2
Ptnml: 12, 4114, 17807, 8046, 21
nElo: 51.64 ±2.8
PairsRatio: 1.96
[\[raw statistics\]][210124-raw8]
+| 2024‑02‑11 | [master][110224-master] vs [Stockfish 16]
`Bench: 1027182`
Format code using clang-format
[\[differences\]][110224-dif] `269`
| Elo: [22.81][110224-elo1] ±1.4
Ptnml: 81, 5104, 15792, 8846, 177
nElo: 46.39 ±2.8
PairsRatio: 1.74
[\[raw statistics\]][110224-raw1]
| Elo: [22.13][110224-elo8] ±1.3
Ptnml: 22, 4312, 17524, 8112, 30
nElo: 49.11 ±2.8
PairsRatio: 1.88
[\[raw statistics\]][110224-raw8]
+| 2024‑02‑17 | [master][170224-master] vs [Stockfish 16]
`Bench: 1303971`
Simplify PV node reduction
[\[differences\]][170224-dif] `276`
| Elo: [27.04][170224-elo1] ±1.4
Ptnml: 65, 4832, 15656, 9272, 175
nElo: 55.20 ±2.9
PairsRatio: 1.93
[\[raw statistics\]][170224-raw1]
| Elo: [27.03][170224-elo8] ±1.2
Ptnml: 16, 3891, 17544, 8517, 32
nElo: 60.62 ±2.9
PairsRatio: 2.19
[\[raw statistics\]][170224-raw8]
+| 2024‑02‑24 | [Stockfish 16.1] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF161RN]
`Bench: 1303971`
[\[differences\]][240224-dif] `280`
| | | | +| 2024‑03‑12 | [master][120324-master] vs [Stockfish 16]
`Bench: 1715522`
Search + Eval + Movepick Tune
[\[differences\]][120324-dif] `302`
| Elo: [27.13][120324-elo1] ±1.4
Ptnml: 79, 4801, 15674, 9257, 189
nElo: 55.27 ±2.9
PairsRatio: 1.94
[\[raw statistics\]][120324-raw1]
| Elo: [29.99][120324-elo8] ±1.2
Ptnml: 15, 3656, 17515, 8775, 39
nElo: 67.59 ±2.9
PairsRatio: 2.40
[\[raw statistics\]][120324-raw8]
+| 2024‑03‑29 | [master][290324-master] vs [Stockfish 16]
`Bench: 1759189`
Simplify NMP Condition
[\[differences\]][290324-dif] `322`
| Elo: [27.73][290324-elo1] ±1.4
Ptnml: 80, 4736, 15703, 9287, 194
nElo: 56.57 ±2.9
PairsRatio: 1.97
[\[raw statistics\]][290324-raw1]
| Elo: [30.03][290324-elo8] ±1.2
Ptnml: 16, 3637, 17534, 8784, 29
nElo: 67.81 ±2.9
PairsRatio: 2.41
[\[raw statistics\]][290324-raw8]
+| 2024‑04‑11 | [master][110424-master] vs [Stockfish 16]
`Bench: 1479416`
Simplify the depth-dependent part of the best value adjustment formula in main search
[\[differences\]][110424-dif] `333`
| Elo: [23.53][110424-elo1] ±1.4
Ptnml: 75, 5095, 15696, 8965, 169
nElo: 47.82 ±2.8
PairsRatio: 1.77
[\[raw statistics\]][110424-raw1]
| Elo: [35.27][110424-elo8] ±1.2
Ptnml: 10, 3235, 17465, 9255, 35
nElo: 80.49 ±2.9
PairsRatio: 2.86
[\[raw statistics\]][110424-raw8]
+| 2024‑04‑24 | [master][240424-master] vs [Stockfish 16]
`Bench: 1836777`
Implement accumulator refresh table
[\[differences\]][240424-dif] `348`
| Elo: [32.04][240424-elo1] ±1.4
Ptnml: 51, 4471, 15578, 9709, 191
nElo: 65.84 ±2.9
PairsRatio: 2.19
[\[raw statistics\]][240424-raw1]
| Elo: [37.86][240424-elo8] ±1.2
Ptnml: 7, 3255, 17012, 9670, 56
nElo: 85.18 ±2.9
PairsRatio: 2.98
[\[raw statistics\]][240424-raw8]
+| 2024‑05‑05 | [master][050524-master] vs [Stockfish 16]
`Bench: 2180675`
VVLTC search tune
[\[differences\]][050524-dif] `369`
| Elo: [28.90][050524-elo1] ±1.4
Ptnml: 70, 4716, 15571, 9451, 192
nElo: 58.87 ±2.9
PairsRatio: 2.01
[\[raw statistics\]][050524-raw1]
| Elo: [40.41][050524-elo8] ±1.2
Ptnml: 6, 3047, 16994, 9899, 54
nElo: 91.59 ±2.9
PairsRatio: 3.26
[\[raw statistics\]][050524-raw8]
+| 2024‑05‑13 | [master][130524-master] vs [Stockfish 16]
`Bench: 1876282`
Optimize update_accumulator_refresh_cache()
[\[differences\]][130524-dif] `386`
| Elo: [28.23][130524-elo1] ±1.4
Ptnml: 94, 4694, 15651, 9376, 185
nElo: 57.50 ±2.9
PairsRatio: 2.00
[\[raw statistics\]][130524-raw1]
| Elo: [39.08][130524-elo8] ±1.2
Ptnml: 12, 3037, 17207, 9707, 37
nElo: 89.14 ±2.9
PairsRatio: 3.20
[\[raw statistics\]][130524-raw8]
+| 2024‑05‑18 | [master][180524-master] vs [Stockfish 16]
`Bench: 1198142`
VVLTC search tune
[\[differences\]][180524-dif] `405`
| Elo: [26.54][180524-elo1] ±1.4
Ptnml: 78, 4876, 15650, 9185, 211
nElo: 53.85 ±2.8
PairsRatio: 1.90
[\[raw statistics\]][180524-raw1]
| Elo: [38.55][180524-elo8] ±1.2
Ptnml: 11, 3150, 17089, 9698, 52
nElo: 87.18 ±2.9
PairsRatio: 3.08
[\[raw statistics\]][180524-raw8]
+| 2024‑05‑28 | [master][280524-master] vs [Stockfish 16]
`Bench: 1856147`
Improve performance on NUMA systems
[\[differences\]][280524-dif] `433`
| Elo: [30.13][280524-elo1] ±1.4
Ptnml: 65, 4557, 15687, 9504, 187
nElo: 61.85 ±2.9
PairsRatio: 2.10
[\[raw statistics\]][280524-raw1]
| Elo: [39.34][280524-elo8] ±1.2
Ptnml: 6, 3094, 17063, 9803, 34
nElo: 89.36 ±2.9
PairsRatio: 3.17
[\[raw statistics\]][280524-raw8]
+| 2024‑06‑08 | [master][080624-master] vs [Stockfish 16]
`Bench: 1174094`
Make repeated bench runs identical
[\[differences\]][080624-dif] `490`
| Elo: [35.60][080624-elo1] ±1.4
Ptnml: 70, 4218, 15445, 10049, 218
nElo: 73.01 ±2.9
PairsRatio: 2.39
[\[raw statistics\]][080624-raw1]
| Elo: [39.31][080624-elo8] ±1.2
Ptnml: 15, 3169, 16895, 9884, 37
nElo: 88.49 ±2.9
PairsRatio: 3.12
[\[raw statistics\]][080624-raw8]
+| 2024‑07‑01 | [master][010724-master] vs [Stockfish 16]
`Bench: 1227870`
Probcut in check no matter if pv or capture
[\[differences\]][010724-dif] `517`
| Elo: [40.07][010724-elo1] ±1.4
Ptnml: 83, 3933, 15243, 10493, 248
nElo: 82.11 ±2.9
PairsRatio: 2.67
[\[raw statistics\]][010724-raw1]
| Elo: [40.34][010724-elo8] ±1.2
Ptnml: 11, 3043, 16987, 9918, 41
nElo: 91.47 ±2.9
PairsRatio: 3.26
[\[raw statistics\]][010724-raw8]
+| 2024‑07‑09 | [master][090724-master] vs [Stockfish 16]
`Bench: 1300471`
Move Loop Consistency in Probcut
[\[differences\]][090724-dif] `548`
| Elo: [41.51][090724-elo1] ±1.4
Ptnml: 68, 3769, 15319, 10649, 195
nElo: 86.22 ±3.0
PairsRatio: 2.83
[\[raw statistics\]][090724-raw1]
| Elo: [43.42][090724-elo8] ±1.2
Ptnml: 15, 2811, 16912, 10224, 38
nElo: 99.12 ±3.0
PairsRatio: 3.63
[\[raw statistics\]][090724-raw8]
+| 2024‑07‑23 | [master][230724-master] vs [Stockfish 16]
`Bench: 1371485`
Update default main net to nn-31337bea577c.nnue
[\[differences\]][230724-dif] `578`
| Elo: [42.30][230724-elo1] ±1.4
Ptnml: 59, 3800, 15131, 10833, 177
nElo: 87.72 ±3.0
PairsRatio: 2.85
[\[raw statistics\]][230724-raw1]
| Elo: [45.06][230724-elo8] ±1.2
Ptnml: 14, 2685, 16907, 10337, 57
nElo: 103.20 ±3.0
PairsRatio: 3.85
[\[raw statistics\]][230724-raw8]
+| 2024‑08‑20 | [master][200824-master] vs [Stockfish 16]
`Bench: 1484730`
Tweak late move extensions
[\[differences\]][200824-dif] `595`
| Elo: [44.12][200824-elo1] ±1.4
Ptnml: 69, 3686, 15064, 10960, 221
nElo: 91.20 ±3.0
PairsRatio: 2.98
[\[raw statistics\]][200824-raw1]
| Elo: [44.32][200824-elo8] ±1.2
Ptnml: 16, 2814, 16741, 10400, 29
nElo: 100.85 ±3.0
PairsRatio: 3.69
[\[raw statistics\]][200824-raw8]
+| 2024‑09‑03 | [master][030924-master] vs [Stockfish 16]
`Bench: 1484730`
Update Top CPU Contributors
[\[differences\]][030924-dif] `601`
| Elo: [46.04][030924-elo1] ±1.3
Ptnml: 50, 3562, 15030, 11150, 208
nElo: 95.92 ±3.0
PairsRatio: 3.14
[\[raw statistics\]][030924-raw1]
| Elo: [42.62][030924-elo8] ±1.2
Ptnml: 15, 2882, 16912, 10147, 44
nElo: 96.95 ±3.0
PairsRatio: 3.52
[\[raw statistics\]][030924-raw8]
+| 2024‑09‑06 | [Stockfish 17] [[[https://stockfishchess.org/images/logo/icon_128x128.png\|width=20px]]][SF17RN] [[[https://github.githubassets.com/images/icons/emoji/unicode/1f4c8.png\|width=20px]]][SF17DP]
`Bench: 1484730`
[\[differences\]][060924-dif] `602`
| | + +
+ +--- + +## External Links + +There are several pages on the web run by chess engine fans. Some collect progress of Stockfish +over previous stable versions and development builds. Some compare Stockfish progress to other +chess engines. Here is a collection of some useful links in that regard. + +* [Computer Chess Rating Lists (CCRL)](https://computerchess.org.uk/ccrl/4040/) +* [FastGMs Rating Lists (FGRL)](http://www.fastgm.de) +* [Ipman Chess](http://ipmanchess.yolasite.com) +* [Mate Finding Effectiveness](https://github.com/vondele/matetrack) +* [Next Chess Move (NCM)](https://nextchessmove.com/dev-builds) +* [Stefan Pohl Computer Chess (SPCC)](https://www.sp-cc.de) + +[book-8mv3]: https://github.com/official-stockfish/books/blob/master/8moves_v3.pgn.zip +[book-uho21epd]: https://github.com/official-stockfish/books/blob/master/UHO_XXL_%2B0.90_%2B1.19.epd.zip +[book-uho4060v2]: https://github.com/official-stockfish/books/blob/master/UHO_4060_v2.epd.zip +[book-uho4060v3]: https://github.com/official-stockfish/books/blob/master/UHO_4060_v3.epd.zip +[Fishtest]: https://tests.stockfishchess.org/tests +[Stockfish 2.3.1]:https://github.com/official-stockfish/Stockfish/commit/3caeabf73b12ad53ac7ba64122a2feab819c6527 +[040313-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...10429dd616 +[040313-elo1]: https://tests.stockfishchess.org/tests/view/51345f228f0c3e28913c9cf0 +[040313-master]: https://github.com/official-stockfish/Stockfish/commit/10429dd616b97250107a64c1b91fdffee03e4790 +[040313-raw1]: https://tests.stockfishchess.org/tests/stats/51345f228f0c3e28913c9cf0 +[110313A-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...3698d9aa55 +[110313A-elo1]: https://tests.stockfishchess.org/tests/view/5146bf698f0c3e3cad8ee607 +[110313A-master]: https://github.com/official-stockfish/Stockfish/commit/3698d9aa5573ca666c238b7d31a48b2aeede43dd +[110313A-raw1]: https://tests.stockfishchess.org/tests/stats/5146bf698f0c3e3cad8ee607 +[110313B-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...a24da071f0 +[110313B-elo1]: https://tests.stockfishchess.org/tests/view/513e4c258f0c3e7dc24b8186 +[110313B-master]: https://github.com/official-stockfish/Stockfish/commit/a24da071f0d6128c633febab7df55f14475217c3 +[110313B-raw1]: https://tests.stockfishchess.org/tests/stats/513e4c258f0c3e7dc24b8186 +[160313-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...0586b51f9c +[160313-elo1]: https://tests.stockfishchess.org/tests/view/51445c42e4721c1de2dc1246 +[160313-master]: https://github.com/official-stockfish/Stockfish/commit/0586b51f9c398008f264d78a2888c0d68d9561cb +[160313-raw1]: https://tests.stockfishchess.org/tests/stats/51445c42e4721c1de2dc1246 +[240313-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...0b4ea54da9 +[240313-elo1]: https://tests.stockfishchess.org/tests/view/514f7f3b8f0c3e4a8a33bf78 +[240313-master]: https://github.com/official-stockfish/Stockfish/commit/0b4ea54da999e591284aaeec702b6239ca219b81 +[240313-raw1]: https://tests.stockfishchess.org/tests/stats/514f7f3b8f0c3e4a8a33bf78 +[300313-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...7d42d02ec7 +[300313-elo1]: https://tests.stockfishchess.org/tests/view/515759308f0c3e3b5303952e +[300313-master]: https://github.com/official-stockfish/Stockfish/commit/7d42d02ec77a03c4c1e1b399df30ef8b363c1237 +[300313-raw1]: https://tests.stockfishchess.org/tests/stats/515759308f0c3e3b5303952e +[030413-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...36c82b751c +[030413-elo1]: https://tests.stockfishchess.org/tests/view/515c654a8f0c3e0a8a4b9d79 +[030413-master]: https://github.com/official-stockfish/Stockfish/commit/36c82b751ce227c05bfb0dc74c311a469f7f8ec4 +[030413-raw1]: https://tests.stockfishchess.org/tests/stats/515c654a8f0c3e0a8a4b9d79 +[060413-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...889922041b +[060413-elo1]: https://tests.stockfishchess.org/tests/view/515fb1708f0c3e20bb6b7b43 +[060413-master]: https://github.com/official-stockfish/Stockfish/commit/889922041be317f26a2547498b6751ed55f0ee22 +[060413-raw1]: https://tests.stockfishchess.org/tests/stats/515fb1708f0c3e20bb6b7b43 +[070413-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...9498b2af82 +[070413-elo1]: https://tests.stockfishchess.org/tests/view/516141648f0c3e3124ab6c73 +[070413-master]: https://github.com/official-stockfish/Stockfish/commit/9498b2af82e51a42b5baf6579faeb66589be9ebb +[070413-raw1]: https://tests.stockfishchess.org/tests/stats/516141648f0c3e3124ab6c73 +[100413-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...fe72c93141 +[100413-elo1]: https://tests.stockfishchess.org/tests/view/5165cbb68f0c3e5968ed27e9 +[100413-master]: https://github.com/official-stockfish/Stockfish/commit/fe72c93141627c8109761da6546014a8d0461450 +[100413-raw1]: https://tests.stockfishchess.org/tests/stats/5165cbb68f0c3e5968ed27e9 +[190413-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...f84f04742a +[190413-elo1]: https://tests.stockfishchess.org/tests/view/517105698f0c3e2dd765906d +[190413-master]: https://github.com/official-stockfish/Stockfish/commit/f84f04742a30166c2751de28245e11922da132fb +[190413-raw1]: https://tests.stockfishchess.org/tests/stats/517105698f0c3e2dd765906d +[260413-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...e508494a99 +[260413-elo1]: https://tests.stockfishchess.org/tests/view/517a569e8f0c3e13d8c85d32 +[260413-master]: https://github.com/official-stockfish/Stockfish/commit/e508494a9985a5d54e77df694e8f160bb3346de3 +[260413-raw1]: https://tests.stockfishchess.org/tests/stats/517a569e8f0c3e13d8c85d32 +[280413-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...06b9140e5c +[280413-elo1]: https://tests.stockfishchess.org/tests/view/517f5f538f0c3e0fd9df8d35 +[280413-master]: https://github.com/official-stockfish/Stockfish/commit/06b9140e5ccd9e3579315ea2abb2ba93126c48fa +[280413-raw1]: https://tests.stockfishchess.org/tests/stats/517f5f538f0c3e0fd9df8d35 +[Stockfish 3]: https://github.com/official-stockfish/Stockfish/commit/aa2368a6878a867fe63247ee2adf2fde3dfe22be +[SF3DP]: https://user-images.githubusercontent.com/64992190/156417306-225679a1-f73b-4dd8-9fc7-390cca2c6e4f.png "Development Progress" +[SF3RN]: https://stockfishchess.org/blog/2013/stockfish-3/ "Release Notes" +[300413-dif]: https://github.com/official-stockfish/Stockfish/compare/3caeabf73b...aa2368a687 +[160513-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...f7c013edd0 +[160513-elo1]: https://tests.stockfishchess.org/tests/view/519547768f0c3e3efd273e4f +[160513-master]: https://github.com/official-stockfish/Stockfish/commit/f7c013edd08a0e2d26491eb087c145e103e0f708 +[160513-raw1]: https://tests.stockfishchess.org/tests/stats/519547768f0c3e3efd273e4f +[230513-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...d4a02b135d +[230513-elo1]: https://tests.stockfishchess.org/tests/view/51a02c038f0c3e3acadfbb11 +[230513-master]: https://github.com/official-stockfish/Stockfish/commit/d4a02b135deade2f3273716ccedb6f8a97316263 +[230513-raw1]: https://tests.stockfishchess.org/tests/stats/51a02c038f0c3e3acadfbb11 +[310513-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...d8b266af8b +[310513-elo1]: https://tests.stockfishchess.org/tests/view/51a8c12a8f0c3e6ac564d865 +[310513-master]: https://github.com/official-stockfish/Stockfish/commit/d8b266af8b714a86b815bb83d2b47f038137d604 +[310513-raw1]: https://tests.stockfishchess.org/tests/stats/51a8c12a8f0c3e6ac564d865 +[230613-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...17d41b3861 +[230613-elo1]: https://tests.stockfishchess.org/tests/view/51c6daa18f0c3e355c8e0aef +[230613-master]: https://github.com/official-stockfish/Stockfish/commit/17d41b386117f3b93daeb3a183f7a12e46812cdb +[230613-raw1]: https://tests.stockfishchess.org/tests/stats/51c6daa18f0c3e355c8e0aef +[030713-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...a55fb76dcc +[030713-elo1]: https://tests.stockfishchess.org/tests/view/51d459d10ebc590f75531fef +[030713-master]: https://github.com/official-stockfish/Stockfish/commit/a55fb76dcc66c9cc17a81a9a99dd506108ee1fee +[030713-raw1]: https://tests.stockfishchess.org/tests/stats/51d459d10ebc590f75531fef +[130713-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...4ede49cd85 +[130713-elo1]: https://tests.stockfishchess.org/tests/view/51e17aad0ebc595218f6a77f +[130713-master]: https://github.com/official-stockfish/Stockfish/commit/4ede49cd850392f28bc9da9537c111d2c3f0b297 +[130713-raw1]: https://tests.stockfishchess.org/tests/stats/51e17aad0ebc595218f6a77f +[190713-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...4b3a0fdab0 +[190713-elo1]: https://tests.stockfishchess.org/tests/view/51e8da3c0ebc59080383cfde +[190713-master]: https://github.com/official-stockfish/Stockfish/commit/4b3a0fdab03a7529ede42891963d3036712a0bd5 +[190713-raw1]: https://tests.stockfishchess.org/tests/stats/51e8da3c0ebc59080383cfde +[250713-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...7487eb0dca +[250713-elo1]: https://tests.stockfishchess.org/tests/view/51f20d9b0ebc59080383d06a +[250713-master]: https://github.com/official-stockfish/Stockfish/commit/7487eb0dcae93731330f06c7d289ca156487a16f +[250713-raw1]: https://tests.stockfishchess.org/tests/stats/51f20d9b0ebc59080383d06a +[030813-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...f31847302d +[030813-elo1]: https://tests.stockfishchess.org/tests/view/51ff4e6d0ebc59344346bf77 +[030813-master]: https://github.com/official-stockfish/Stockfish/commit/f31847302d4ec62f4da7d22447d6c9fbf36230dc +[030813-raw1]: https://tests.stockfishchess.org/tests/stats/51ff4e6d0ebc59344346bf77 +[180813-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...91c2c44fb1 +[180813-elo1]: https://tests.stockfishchess.org/tests/view/521077fb0ebc593f4bb9a39d +[180813-master]: https://github.com/official-stockfish/Stockfish/commit/91c2c44fb1987e3587a9b1037ce6a34369995ba2 +[180813-raw1]: https://tests.stockfishchess.org/tests/stats/521077fb0ebc593f4bb9a39d +[Stockfish 4]: https://github.com/official-stockfish/Stockfish/commit/4d120ee02edff250a6661e63d913e70efc37e2b6 +[SF4DP]: https://user-images.githubusercontent.com/64992190/156417121-89472d65-0d79-496d-a660-8073de2f1e45.png "Development Progress" +[SF4RN]: https://stockfishchess.org/blog/2013/stockfish-4/ "Release Notes" +[200813-dif]: https://github.com/official-stockfish/Stockfish/compare/aa2368a687...4d120ee02e +[290813-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...5d90c149b5 +[290813-elo1]: https://tests.stockfishchess.org/tests/view/521fbafc0ebc5972cf4733d6 +[290813-master]: https://github.com/official-stockfish/Stockfish/commit/5d90c149b5804403e5e8c1a25d0b37577b059712 +[290813-raw1]: https://tests.stockfishchess.org/tests/stats/521fbafc0ebc5972cf4733d6 +[010913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...849b089a63 +[010913-elo1]: https://tests.stockfishchess.org/tests/view/5224c2350ebc594ff262acc7 +[010913-master]: https://github.com/official-stockfish/Stockfish/commit/849b089a63cb40833006704fb6e3fc66e8010dfa +[010913-raw1]: https://tests.stockfishchess.org/tests/stats/5224c2350ebc594ff262acc7 +[050913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...10b53e1c5e +[050913-elo1]: https://tests.stockfishchess.org/tests/view/5228bc7b0ebc5909cbaa79be +[050913-master]: https://github.com/official-stockfish/Stockfish/commit/10b53e1c5e6aeba156eb5c02afccfd7db1f84d16 +[050913-raw1]: https://tests.stockfishchess.org/tests/stats/5228bc7b0ebc5909cbaa79be +[070913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...0515ad0fb0 +[070913-elo1]: https://tests.stockfishchess.org/tests/view/522bcb1c0ebc592ee68dc04a +[070913-master]: https://github.com/official-stockfish/Stockfish/commit/0515ad0fb0f2d46ee60288b0541cea495e6c90ef +[070913-raw1]: https://tests.stockfishchess.org/tests/stats/522bcb1c0ebc592ee68dc04a +[110913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...4803d5772c +[110913-elo1]: https://tests.stockfishchess.org/tests/view/5230d4630ebc5963f25cba15 +[110913-master]: https://github.com/official-stockfish/Stockfish/commit/4803d5772c120121dad6bad78cc2b6be5c24fb1f +[110913-raw1]: https://tests.stockfishchess.org/tests/stats/5230d4630ebc5963f25cba15 +[120913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...27f2ce8f6e +[120913-elo1]: https://tests.stockfishchess.org/tests/view/5231631d0ebc5963f25cba49 +[120913-master]: https://github.com/official-stockfish/Stockfish/commit/27f2ce8f6e8462bd9be4b201dd95fc2df17aafe6 +[120913-raw1]: https://tests.stockfishchess.org/tests/stats/5231631d0ebc5963f25cba49 +[130913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...fc17d0de77 +[130913-elo1]: https://tests.stockfishchess.org/tests/view/523339200ebc59749a54ac84 +[130913-master]: https://github.com/official-stockfish/Stockfish/commit/fc17d0de7748b68bddc5cd7f97a6c15ebc7adaac +[130913-raw1]: https://tests.stockfishchess.org/tests/stats/523339200ebc59749a54ac84 +[160913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...77b5ee0117 +[160913-elo1]: https://tests.stockfishchess.org/tests/view/5236af460ebc59749a54ad38 +[160913-master]: https://github.com/official-stockfish/Stockfish/commit/77b5ee0117e86736817cf90f64055dce1ddbc55e +[160913-raw1]: https://tests.stockfishchess.org/tests/stats/5236af460ebc59749a54ad38 +[230913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...7b2cda95d9 +[230913-elo1]: https://tests.stockfishchess.org/tests/view/5240800d0ebc595ff2f50ab9 +[230913-master]: https://github.com/official-stockfish/Stockfish/commit/7b2cda95d9d697a047ac8df33d3805ba77590a8f +[230913-raw1]: https://tests.stockfishchess.org/tests/stats/5240800d0ebc595ff2f50ab9 +[280913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...cca34e234c +[280913-elo1]: https://tests.stockfishchess.org/tests/view/52479d200ebc591a31ad9203 +[280913-master]: https://github.com/official-stockfish/Stockfish/commit/cca34e234cc98ed4b61e75a25f8cd0d917c2a3fa +[280913-raw1]: https://tests.stockfishchess.org/tests/stats/52479d200ebc591a31ad9203 +[290913-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...bd1c3ed7e3 +[290913-elo1]: https://tests.stockfishchess.org/tests/view/524baf1b0ebc5963752e0503 +[290913-master]: https://github.com/official-stockfish/Stockfish/commit/bd1c3ed7e32d3df0ceb29cb6959599e3cac2057c +[290913-raw1]: https://tests.stockfishchess.org/tests/stats/524baf1b0ebc5963752e0503 +[081013A-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...984ee9d05b +[081013A-elo1]: https://tests.stockfishchess.org/tests/view/525446ab0ebc593ba5ae7100 +[081013A-master]: https://github.com/official-stockfish/Stockfish/commit/984ee9d05b6b1916e564047b2a0b8f117f911bca +[081013A-raw1]: https://tests.stockfishchess.org/tests/stats/525446ab0ebc593ba5ae7100 +[081013B-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...bb83a417cb +[081013B-elo1]: https://tests.stockfishchess.org/tests/view/52545c850ebc593ba5ae7104 +[081013B-master]: https://github.com/official-stockfish/Stockfish/commit/bb83a417cb708e105c88052809ddfdf308b55aa9 +[081013B-raw1]: https://tests.stockfishchess.org/tests/stats/52545c850ebc593ba5ae7104 +[091013-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...b15e148b5e +[091013-elo1]: https://tests.stockfishchess.org/tests/view/52558f3b0ebc593ba5ae7135 +[091013-master]: https://github.com/official-stockfish/Stockfish/commit/b15e148b5e8929cfc17a388c79fbf4acdc0712f6 +[091013-raw1]: https://tests.stockfishchess.org/tests/stats/52558f3b0ebc593ba5ae7135 +[141013-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...5aeb907fa1 +[141013-elo1]: https://tests.stockfishchess.org/tests/view/525c61c80ebc5951f87d21f3 +[141013-master]: https://github.com/official-stockfish/Stockfish/commit/5aeb907fa19afb22c92b3076d3ff73386cf7755c +[141013-raw1]: https://tests.stockfishchess.org/tests/stats/525c61c80ebc5951f87d21f3 +[181013-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...25cb851f8a +[181013-elo1]: https://tests.stockfishchess.org/tests/view/5260f1e60ebc5951f87d229e +[181013-master]: https://github.com/official-stockfish/Stockfish/commit/25cb851f8aa914634666789473a7809695dec6d1 +[181013-raw1]: https://tests.stockfishchess.org/tests/stats/5260f1e60ebc5951f87d229e +[191013-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...4bc2374450 +[191013-elo1]: https://tests.stockfishchess.org/tests/view/52625f270ebc5951f87d22ce +[191013-master]: https://github.com/official-stockfish/Stockfish/commit/4bc2374450e30101392510006373a4a9ae2da4fd +[191013-raw1]: https://tests.stockfishchess.org/tests/stats/52625f270ebc5951f87d22ce +[201013-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...97015afce8 +[201013-elo1]: https://tests.stockfishchess.org/tests/view/526450660ebc594568789520 +[201013-master]: https://github.com/official-stockfish/Stockfish/commit/97015afce834a68be4a955768caab7152834d7d8 +[201013-raw1]: https://tests.stockfishchess.org/tests/stats/526450660ebc594568789520 +[221013-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...2c825294ec +[221013-elo1]: https://tests.stockfishchess.org/tests/view/5266a0280ebc595f6d3486d3 +[221013-master]: https://github.com/official-stockfish/Stockfish/commit/2c825294ecbc7c959af9bc05300efd137d9ec7c6 +[221013-raw1]: https://tests.stockfishchess.org/tests/stats/5266a0280ebc595f6d3486d3 +[241013-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...48f38f3092 +[241013-elo1]: https://tests.stockfishchess.org/tests/view/5269abe70ebc597fb21bb8fc +[241013-master]: https://github.com/official-stockfish/Stockfish/commit/48f38f3092626f0dfef3728568ad5d85ca6c2f92 +[241013-raw1]: https://tests.stockfishchess.org/tests/stats/5269abe70ebc597fb21bb8fc +[281013-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...3cc47edf62 +[281013-elo1]: https://tests.stockfishchess.org/tests/view/52705c1a0ebc5936609edfcc +[281013-master]: https://github.com/official-stockfish/Stockfish/commit/3cc47edf622b1d12a37b3637cae503d6862437c4 +[281013-raw1]: https://tests.stockfishchess.org/tests/stats/52705c1a0ebc5936609edfcc +[011113-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...a3a0df92a3 +[011113-elo1]: https://tests.stockfishchess.org/tests/view/5273658c0ebc5936609ee076 +[011113-master]: https://github.com/official-stockfish/Stockfish/commit/a3a0df92a3ed5ce7c98ff596e687d3d6533590c8 +[011113-raw1]: https://tests.stockfishchess.org/tests/stats/5273658c0ebc5936609ee076 +[091113-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...eed508b444 +[091113-elo1]: https://tests.stockfishchess.org/tests/view/527e00050ebc5945a2478d7b +[091113-master]: https://github.com/official-stockfish/Stockfish/commit/eed508b4445057cd26bfb95ab5cd754ac96629fd +[091113-raw1]: https://tests.stockfishchess.org/tests/stats/527e00050ebc5945a2478d7b +[101113-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...4ef6b2c32a +[101113-elo1]: https://tests.stockfishchess.org/tests/view/52803e7a0ebc594ba1fac466 +[101113-master]: https://github.com/official-stockfish/Stockfish/commit/4ef6b2c32a0f4e7b6193349312da5e335d48416b +[101113-raw1]: https://tests.stockfishchess.org/tests/stats/52803e7a0ebc594ba1fac466 +[111113-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...9763c69fa5 +[111113-elo1]: https://tests.stockfishchess.org/tests/view/5281e2820ebc594ba1fac4a5 +[111113-master]: https://github.com/official-stockfish/Stockfish/commit/9763c69fa5683accd7e81786977be4b195370a7b +[111113-raw1]: https://tests.stockfishchess.org/tests/stats/5281e2820ebc594ba1fac4a5 +[291113A-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...67e5581e37 +[291113A-elo1]: https://tests.stockfishchess.org/tests/view/529939330ebc5903719161c0 +[291113A-master]: https://github.com/official-stockfish/Stockfish/commit/67e5581e37df2c7481be6261dcefa9fb41439c81 +[291113A-raw1]: https://tests.stockfishchess.org/tests/stats/529939330ebc5903719161c0 +[Stockfish DD]: https://github.com/official-stockfish/Stockfish/commit/c5bb9b9da943c49fbead1507ba7213bd7fb5e415 +[SFDDDP]: https://user-images.githubusercontent.com/64992190/156417012-2f1670a7-583d-4519-b86b-f3604c38d0d6.png "Development Progress" +[SFDDRN]: https://stockfishchess.org/blog/2013/stockfish-dd/ "Release Notes" +[291113B-dif]: https://github.com/official-stockfish/Stockfish/compare/4d120ee02e...c5bb9b9da9 +[091213-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...8e9d4081ee +[091213-elo1]: https://tests.stockfishchess.org/tests/view/52a61e9e0ebc5949c4e733f6 +[091213-master]: https://github.com/official-stockfish/Stockfish/commit/8e9d4081ee9def12f50dbd3169b765839fcb4c86 +[091213-raw1]: https://tests.stockfishchess.org/tests/stats/52a61e9e0ebc5949c4e733f6 +[191213-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...26689d8c2a +[191213-elo1]: https://tests.stockfishchess.org/tests/view/52b462600ebc5954c3432bbb +[191213-master]: https://github.com/official-stockfish/Stockfish/commit/26689d8c2ae506e4da1a5654fcfdfe04886c3692 +[191213-raw1]: https://tests.stockfishchess.org/tests/stats/52b462600ebc5954c3432bbb +[231213-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...899a2c033e +[231213-elo1]: https://tests.stockfishchess.org/tests/view/52b8fab60ebc5954c3432c6e +[231213-master]: https://github.com/official-stockfish/Stockfish/commit/899a2c033e2e74d3da97c7aefff74fb05a59db0a +[231213-raw1]: https://tests.stockfishchess.org/tests/stats/52b8fab60ebc5954c3432c6e +[291213-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...f7742669cb +[291213-elo1]: https://tests.stockfishchess.org/tests/view/52c0bf040ebc5954c3432e1d +[291213-master]: https://github.com/official-stockfish/Stockfish/commit/f7742669cb52dff7a64bd1a9ba466e333abb87bc +[291213-raw1]: https://tests.stockfishchess.org/tests/stats/52c0bf040ebc5954c3432e1d +[020114-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...8454d871ec +[020114-elo1]: https://tests.stockfishchess.org/tests/view/52c5ad780ebc5954c3432f07 +[020114-master]: https://github.com/official-stockfish/Stockfish/commit/8454d871ec105749fb5e2e7e9aea9e7c25cfdf6e +[020114-raw1]: https://tests.stockfishchess.org/tests/stats/52c5ad780ebc5954c3432f07 +[080114-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...262c380c4b +[080114-elo1]: https://tests.stockfishchess.org/tests/view/52ce26de0ebc594082ffce98 +[080114-master]: https://github.com/official-stockfish/Stockfish/commit/262c380c4bf8dcadf07741ec458a9e6565ecf8b4 +[080114-raw1]: https://tests.stockfishchess.org/tests/stats/52ce26de0ebc594082ffce98 +[140114-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...53ab32ef0b +[140114-elo1]: https://tests.stockfishchess.org/tests/view/52d584ce0ebc590891e7abc5 +[140114-master]: https://github.com/official-stockfish/Stockfish/commit/53ab32ef0b6e47d8d962f8c1fccd32d3c22f138c +[140114-raw1]: https://tests.stockfishchess.org/tests/stats/52d584ce0ebc590891e7abc5 +[190114-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...a08a21d5a0 +[190114-elo1]: https://tests.stockfishchess.org/tests/view/52de28590ebc59025698f817 +[190114-master]: https://github.com/official-stockfish/Stockfish/commit/a08a21d5a0b5aafcdb340364bf73b13d852349a0 +[190114-raw1]: https://tests.stockfishchess.org/tests/stats/52de28590ebc59025698f817 +[290114-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...77341f67f3 +[290114-elo1]: https://tests.stockfishchess.org/tests/view/52e936590ebc59025698f9ce +[290114-master]: https://github.com/official-stockfish/Stockfish/commit/77341f67f30456dc456177062309ad5e73e2e1a4 +[290114-raw1]: https://tests.stockfishchess.org/tests/stats/52e936590ebc59025698f9ce +[090214-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...ddeb01612b +[090214-elo1]: https://tests.stockfishchess.org/tests/view/52fa72e90ebc5901df50f9c8 +[090214-master]: https://github.com/official-stockfish/Stockfish/commit/ddeb01612b122bbeeb59a26ba6f8c6f1e4283199 +[090214-raw1]: https://tests.stockfishchess.org/tests/stats/52fa72e90ebc5901df50f9c8 +[220214-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...0949f06a60 +[220214-elo1]: https://tests.stockfishchess.org/tests/view/530fbd8a0ebc591a7be1a0a8 +[220214-master]: https://github.com/official-stockfish/Stockfish/commit/0949f06a60579a4dd70ad5bf66c694988528596e +[220214-raw1]: https://tests.stockfishchess.org/tests/stats/530fbd8a0ebc591a7be1a0a8 +[260214-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...b917cd275e +[260214-elo1]: https://tests.stockfishchess.org/tests/view/530eebad0ebc591a7be1a097 +[260214-master]: https://github.com/official-stockfish/Stockfish/commit/b917cd275e6f9825bc860d2fc7b7d67dacef2915 +[260214-raw1]: https://tests.stockfishchess.org/tests/stats/530eebad0ebc591a7be1a097 +[140314-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...36c381154b +[140314-elo1]: https://tests.stockfishchess.org/tests/view/532352350ebc590d680856df +[140314-master]: https://github.com/official-stockfish/Stockfish/commit/36c381154bcfb23b1ba9f1178c9eb9232099b821 +[140314-raw1]: https://tests.stockfishchess.org/tests/stats/532352350ebc590d680856df +[240314-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...865b71309c +[240314-elo1]: https://tests.stockfishchess.org/tests/view/53311b1a0ebc597b9673c33b +[240314-master]: https://github.com/official-stockfish/Stockfish/commit/865b71309c4dd5c6c67c9c9422df5790cbb22440 +[240314-raw1]: https://tests.stockfishchess.org/tests/stats/53311b1a0ebc597b9673c33b +[080414-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...5c75455c8e +[080414-elo1]: https://tests.stockfishchess.org/tests/view/53447c7c0ebc593d8c927638 +[080414-master]: https://github.com/official-stockfish/Stockfish/commit/5c75455c8ea998bb3e60bafbc3fab850bb877187 +[080414-raw1]: https://tests.stockfishchess.org/tests/stats/53447c7c0ebc593d8c927638 +[120414-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...b2c0634d48 +[120414-elo1]: https://tests.stockfishchess.org/tests/view/534931e80ebc5909cec073da +[120414-master]: https://github.com/official-stockfish/Stockfish/commit/b2c0634d4898a78a9e82fca9197467d442e5cb95 +[120414-raw1]: https://tests.stockfishchess.org/tests/stats/534931e80ebc5909cec073da +[210414-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...56273fca1e +[210414-elo1]: https://tests.stockfishchess.org/tests/view/5355380a0ebc5949818c04c0 +[210414-master]: https://github.com/official-stockfish/Stockfish/commit/56273fca1edc51ffa0efc73715609f428c000c97 +[210414-raw1]: https://tests.stockfishchess.org/tests/stats/5355380a0ebc5949818c04c0 +[250414-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...7ddbcf7e87 +[250414-elo1]: https://tests.stockfishchess.org/tests/view/535b388d0ebc593bee1c3bc2 +[250414-master]: https://github.com/official-stockfish/Stockfish/commit/7ddbcf7e87a0b0c882d8d33841bd7ae53969619d +[250414-raw1]: https://tests.stockfishchess.org/tests/stats/535b388d0ebc593bee1c3bc2 +[040514-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...5413fda739 +[040514-elo1]: https://tests.stockfishchess.org/tests/view/536bfe840ebc597126891867 +[040514-master]: https://github.com/official-stockfish/Stockfish/commit/5413fda7397f8ffe32e41b9c7f13297c39929f5c +[040514-raw1]: https://tests.stockfishchess.org/tests/stats/536bfe840ebc597126891867 +[130514-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...5ec63eb6b6 +[130514-elo1]: https://tests.stockfishchess.org/tests/view/53752dc30ebc5935dbfeea67 +[130514-master]: https://github.com/official-stockfish/Stockfish/commit/5ec63eb6b6f43cbd4e25b8f8b97bc8980dbbabef +[130514-raw1]: https://tests.stockfishchess.org/tests/stats/53752dc30ebc5935dbfeea67 +[170514-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...5e03734eac +[170514-elo1]: https://tests.stockfishchess.org/tests/view/5382e5190ebc59646b252a28 +[170514-master]: https://github.com/official-stockfish/Stockfish/commit/5e03734eacc8e52a6c92be19e73b69ab3e398518 +[170514-raw1]: https://tests.stockfishchess.org/tests/stats/5382e5190ebc59646b252a28 +[240514-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...495a0fa699 +[240514-elo1]: https://tests.stockfishchess.org/tests/view/53828e440ebc595e74193f03 +[240514-master]: https://github.com/official-stockfish/Stockfish/commit/495a0fa699fb3f8ed36fe343fbd910479df4e2f1 +[240514-raw1]: https://tests.stockfishchess.org/tests/stats/53828e440ebc595e74193f03 +[Stockfish 5]: https://github.com/official-stockfish/Stockfish/commit/54f8a9cb138a1bc0b0054b98f911fafd8d1b03ad +[SF5DP]: https://user-images.githubusercontent.com/64992190/156416921-bd44dc86-a612-4963-b067-ba2b5e3547f5.png "Development Progress" +[SF5RN]: https://stockfishchess.org/blog/2014/stockfish-5/ "Release Notes" +[310514-dif]: https://github.com/official-stockfish/Stockfish/compare/c5bb9b9da9...54f8a9cb13 +[030614-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...adeded29fb +[030614-elo1]: https://tests.stockfishchess.org/tests/view/538e10500ebc5940a3b7f018 +[030614-master]: https://github.com/official-stockfish/Stockfish/commit/adeded29fb6ce483bbbafaa0f67aa086cad968f9 +[030614-raw1]: https://tests.stockfishchess.org/tests/stats/538e10500ebc5940a3b7f018 +[110614-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...84dabe5982 +[110614-elo1]: https://tests.stockfishchess.org/tests/view/539d0ccf0ebc59659be39682 +[110614-master]: https://github.com/official-stockfish/Stockfish/commit/84dabe5982dab5ee65770eeb77c4f966db9514f8 +[110614-raw1]: https://tests.stockfishchess.org/tests/stats/539d0ccf0ebc59659be39682 +[290614-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...ffedfa3354 +[290614-elo1]: https://tests.stockfishchess.org/tests/view/53b06b140ebc5948a2398082 +[290614-master]: https://github.com/official-stockfish/Stockfish/commit/ffedfa33542a7de7d87fd545ea0a4b2fef8f8c6e +[290614-raw1]: https://tests.stockfishchess.org/tests/stats/53b06b140ebc5948a2398082 +[220714-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...4758fd31b1 +[220714-elo1]: https://tests.stockfishchess.org/tests/view/53cff3620ebc592c34a4a383 +[220714-master]: https://github.com/official-stockfish/Stockfish/commit/4758fd31b17129530dfeb81119b423c5bdde704a +[220714-raw1]: https://tests.stockfishchess.org/tests/stats/53cff3620ebc592c34a4a383 +[060814-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...9da015517c +[060814-elo1]: https://tests.stockfishchess.org/tests/view/53e207dd0ebc592db1a06475 +[060814-master]: https://github.com/official-stockfish/Stockfish/commit/9da015517c20e9c5b8e0ef6e7103e60404211baa +[060814-raw1]: https://tests.stockfishchess.org/tests/stats/53e207dd0ebc592db1a06475 +[040914-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...cd065dd584 +[040914-elo1]: https://tests.stockfishchess.org/tests/view/54144bf40ebc5923f6d66d54 +[040914-master]: https://github.com/official-stockfish/Stockfish/commit/cd065dd584d080f56735007f7ff52bd1ada2fea3 +[040914-raw1]: https://tests.stockfishchess.org/tests/stats/54144bf40ebc5923f6d66d54 +[270914-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...ea9c424bba +[270914-elo1]: https://tests.stockfishchess.org/tests/view/54276b500ebc59568afa4265 +[270914-master]: https://github.com/official-stockfish/Stockfish/commit/ea9c424bba07d522d4acff54f2883fc124574e50 +[270914-raw1]: https://tests.stockfishchess.org/tests/stats/54276b500ebc59568afa4265 +[151014-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...480682b670 +[151014-elo1]: https://tests.stockfishchess.org/tests/view/54411bc40ebc59731a7ea6ae +[151014-master]: https://github.com/official-stockfish/Stockfish/commit/480682b67097160bfc25e52ab02facffeec104f0 +[151014-raw1]: https://tests.stockfishchess.org/tests/stats/54411bc40ebc59731a7ea6ae +[011114-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...79fa72f392 +[011114-elo1]: https://tests.stockfishchess.org/tests/view/545559640ebc59410ea4e5fa +[011114-master]: https://github.com/official-stockfish/Stockfish/commit/79fa72f392343fb93c16c133dedc3dbdf795e746 +[011114-raw1]: https://tests.stockfishchess.org/tests/stats/545559640ebc59410ea4e5fa +[101114-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...c6d45c60b5 +[101114-elo1]: https://tests.stockfishchess.org/tests/view/5463d2e40ebc592ab9e50ff7 +[101114-master]: https://github.com/official-stockfish/Stockfish/commit/c6d45c60b516e799526f1163733b74b23fc1b63c +[101114-raw1]: https://tests.stockfishchess.org/tests/stats/5463d2e40ebc592ab9e50ff7 +[251114-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...fe07ae4cb4 +[251114-elo1]: https://tests.stockfishchess.org/tests/view/5479f1ef0ebc5910c1f22551 +[251114-master]: https://github.com/official-stockfish/Stockfish/commit/fe07ae4cb4c2553fb48cab44c502ba766d1f09ce +[251114-raw1]: https://tests.stockfishchess.org/tests/stats/5479f1ef0ebc5910c1f22551 +[071214-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...158864270a +[071214-elo1]: https://tests.stockfishchess.org/tests/view/548716860ebc59615b9c1cda +[071214-master]: https://github.com/official-stockfish/Stockfish/commit/158864270a055fe20dca4a87f4b7a8aa9cedfeb9 +[071214-raw1]: https://tests.stockfishchess.org/tests/stats/548716860ebc59615b9c1cda +[221214-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...296534f234 +[221214-elo1]: https://tests.stockfishchess.org/tests/view/549e0cfa0ebc595444a9aa4f +[221214-master]: https://github.com/official-stockfish/Stockfish/commit/296534f23489e6d95ba7ce1bb35e8a2cbf9a5a9d +[221214-raw1]: https://tests.stockfishchess.org/tests/stats/549e0cfa0ebc595444a9aa4f +[070115-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...aea2fde611 +[070115-elo1]: https://tests.stockfishchess.org/tests/view/54ae6da20ebc59608c5caffe +[070115-master]: https://github.com/official-stockfish/Stockfish/commit/aea2fde6117be2fbda1caa62c842dea766780be5 +[070115-raw1]: https://tests.stockfishchess.org/tests/stats/54ae6da20ebc59608c5caffe +[180115-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...97a034ad3e +[180115-elo1]: https://tests.stockfishchess.org/tests/view/54bbf1c10ebc59238ddbd4d0 +[180115-master]: https://github.com/official-stockfish/Stockfish/commit/97a034ad3e8b25f0e8e1dd03d31f4689180f7547 +[180115-raw1]: https://tests.stockfishchess.org/tests/stats/54bbf1c10ebc59238ddbd4d0 +[Stockfish 6]: https://github.com/official-stockfish/Stockfish/commit/5b555525d2f9cbff446b7461d1317948e8e21cd1 +[SF6DP]: https://user-images.githubusercontent.com/64992190/156416818-2c46d7cf-6cf1-40be-ba6e-c19ceed3e6b0.png "Development Progress" +[SF6RN]: https://stockfishchess.org/blog/2015/stockfish-6/ "Release Notes" +[270115-dif]: https://github.com/official-stockfish/Stockfish/compare/54f8a9cb13...5b555525d2 +[080215-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...e118570038 +[080215-elo1]: https://tests.stockfishchess.org/tests/view/54d869130ebc592194804134 +[080215-master]: https://github.com/official-stockfish/Stockfish/commit/e118570038f2d9b668b445fe6d31df94151a717b +[080215-raw1]: https://tests.stockfishchess.org/tests/stats/54d869130ebc592194804134 +[190315-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...ebf3735754 +[190315-elo1]: https://tests.stockfishchess.org/tests/view/550c678f0ebc5902160ec2f7 +[190315-master]: https://github.com/official-stockfish/Stockfish/commit/ebf3735754d015dfda72930a676b8b43f0614086 +[190315-raw1]: https://tests.stockfishchess.org/tests/stats/550c678f0ebc5902160ec2f7 +[290315-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...60beb18efc +[290315-elo1]: https://tests.stockfishchess.org/tests/view/5517c0790ebc5902160ec596 +[290315-master]: https://github.com/official-stockfish/Stockfish/commit/60beb18efcb3c19b36a164a50e32b6ba6e24e7c4 +[290315-raw1]: https://tests.stockfishchess.org/tests/stats/5517c0790ebc5902160ec596 +[100415-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...a66c73deef +[100415-elo1]: https://tests.stockfishchess.org/tests/view/55282b5c0ebc596367ee97e3 +[100415-master]: https://github.com/official-stockfish/Stockfish/commit/a66c73deef420104e74b6645ee60e20b37fd8549 +[100415-raw1]: https://tests.stockfishchess.org/tests/stats/55282b5c0ebc596367ee97e3 +[090515-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...eaeb63f1d0 +[090515-elo1]: https://tests.stockfishchess.org/tests/view/554e65190ebc5940ca5d6980 +[090515-master]: https://github.com/official-stockfish/Stockfish/commit/eaeb63f1d03aa71edf719605a31d121bf086a03d +[090515-raw1]: https://tests.stockfishchess.org/tests/stats/554e65190ebc5940ca5d6980 +[070615-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...ad87d707ff +[070615-elo1]: https://tests.stockfishchess.org/tests/view/5573eeed0ebc5940ca5d7081 +[070615-master]: https://github.com/official-stockfish/Stockfish/commit/ad87d707fffeeac9aa1ae3e3e8d6fa2449ea1df9 +[070615-raw1]: https://tests.stockfishchess.org/tests/stats/5573eeed0ebc5940ca5d7081 +[160715-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...4095ff0ee5 +[160715-elo1]: https://tests.stockfishchess.org/tests/view/55ab57b80ebc590abbe1bb68 +[160715-master]: https://github.com/official-stockfish/Stockfish/commit/4095ff0ee51bdc76c247bd11d5f3a7008974e2ad +[160715-raw1]: https://tests.stockfishchess.org/tests/stats/55ab57b80ebc590abbe1bb68 +[300715-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...68d61b80c6 +[300715-elo1]: https://tests.stockfishchess.org/tests/view/55bab2280ebc590abbe1bdeb +[300715-master]: https://github.com/official-stockfish/Stockfish/commit/68d61b80c60a81055a2ffb2e251a237b979e9b31 +[300715-raw1]: https://tests.stockfishchess.org/tests/stats/55bab2280ebc590abbe1bdeb +[031015-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...83e19fbed5 +[031015-elo1]: https://tests.stockfishchess.org/tests/view/5611c5070ebc597e4f23e550 +[031015-master]: https://github.com/official-stockfish/Stockfish/commit/83e19fbed539fc05626d82afefde730bdcb344ab +[031015-raw1]: https://tests.stockfishchess.org/tests/stats/5611c5070ebc597e4f23e550 +[251015-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...00d9e9fd28 +[251015-elo1]: https://tests.stockfishchess.org/tests/view/562df8850ebc5964d34460a6 +[251015-master]: https://github.com/official-stockfish/Stockfish/commit/00d9e9fd283b31e63389af091b158dbc3fedfc0e +[251015-raw1]: https://tests.stockfishchess.org/tests/stats/562df8850ebc5964d34460a6 +[271215-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...a5c76d69c3 +[271215-elo1]: https://tests.stockfishchess.org/tests/view/5681810d0ebc5966b711a940 +[271215-master]: https://github.com/official-stockfish/Stockfish/commit/a5c76d69c346d620b6f7a300d4a33cc5867f4d64 +[271215-raw1]: https://tests.stockfishchess.org/tests/stats/5681810d0ebc5966b711a940 +[Stockfish 7]: https://github.com/official-stockfish/Stockfish/commit/dd9cf305816c84c2acfa11cae09a31c4d77cc5a5 +[SF7DP]: https://user-images.githubusercontent.com/64992190/156416613-a26e434d-37f9-47fc-a87e-7c3969d54dca.png "Development Progress" +[SF7RN]: https://stockfishchess.org/blog/2016/stockfish-7/ "Release Notes" +[020116-dif]: https://github.com/official-stockfish/Stockfish/compare/5b555525d2...dd9cf30581 +[280116-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...aedebe35cf +[280116-elo1]: https://tests.stockfishchess.org/tests/view/56af108a0ebc590247cdfa63 +[280116-master]: https://github.com/official-stockfish/Stockfish/commit/aedebe35cfa38b543041bae97e91e8194738b202 +[280116-raw1]: https://tests.stockfishchess.org/tests/stats/56af108a0ebc590247cdfa63 +[100316-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...a273b6ef8c +[100316-elo1]: https://tests.stockfishchess.org/tests/view/56e274540ebc59301a353b37 +[100316-master]: https://github.com/official-stockfish/Stockfish/commit/a273b6ef8c899f546cf585ace584a1b498c04144 +[100316-raw1]: https://tests.stockfishchess.org/tests/stats/56e274540ebc59301a353b37 +[080416-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...1cbba8d6fa +[080416-elo1]: https://tests.stockfishchess.org/tests/view/5707ffdd0ebc59301a3543dc +[080416-master]: https://github.com/official-stockfish/Stockfish/commit/1cbba8d6fadaae2c6e6df5c541c4e2b8a8bf5a0a +[080416-raw1]: https://tests.stockfishchess.org/tests/stats/5707ffdd0ebc59301a3543dc +[200516-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...71bfbb22fc +[200516-elo1]: https://tests.stockfishchess.org/tests/view/573fd5a90ebc59301a354f9c +[200516-master]: https://github.com/official-stockfish/Stockfish/commit/71bfbb22fce23f56b57d69b59a5cec1ff4b5aa03 +[200516-raw1]: https://tests.stockfishchess.org/tests/stats/573fd5a90ebc59301a354f9c +[100616-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...7d2a79f037 +[100616-elo1]: https://tests.stockfishchess.org/tests/view/575c6ff20ebc59029919b409 +[100616-master]: https://github.com/official-stockfish/Stockfish/commit/7d2a79f0372d67f3d66bd07bf637eae02038831c +[100616-raw1]: https://tests.stockfishchess.org/tests/stats/575c6ff20ebc59029919b409 +[240716-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...f2f3a06a1a +[240716-elo1]: https://tests.stockfishchess.org/tests/view/579475b00ebc59099c1a6632 +[240716-master]: https://github.com/official-stockfish/Stockfish/commit/f2f3a06a1acfa14b3054bfd73d6c3966c326a7cc +[240716-raw1]: https://tests.stockfishchess.org/tests/stats/579475b00ebc59099c1a6632 +[180816-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...e3af492142 +[180816-elo1]: https://tests.stockfishchess.org/tests/view/57b59da90ebc591c761f64e1 +[180816-master]: https://github.com/official-stockfish/Stockfish/commit/e3af492142c8aff71f50dbec025722d69b84f85e +[180816-raw1]: https://tests.stockfishchess.org/tests/stats/57b59da90ebc591c761f64e1 +[070916-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...d909d10f33 +[070916-elo1]: https://tests.stockfishchess.org/tests/view/57d0e7b70ebc59030fbe5050 +[070916-master]: https://github.com/official-stockfish/Stockfish/commit/d909d10f33df023be46a2633608bdf655d1f5a62 +[070916-raw1]: https://tests.stockfishchess.org/tests/stats/57d0e7b70ebc59030fbe5050 +[071016-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...073eed590e +[071016-elo1]: https://tests.stockfishchess.org/tests/view/57f9f64d0ebc59038170fb15 +[071016-master]: https://github.com/official-stockfish/Stockfish/commit/073eed590edf992ed3aeb6c754cb0b3b394fe79d +[071016-raw1]: https://tests.stockfishchess.org/tests/stats/57f9f64d0ebc59038170fb15 +[Stockfish 8]: https://github.com/official-stockfish/Stockfish/commit/369eff437cc081eb4b8ab5f519cf3f86b79e87d0 +[SF8DP]: https://user-images.githubusercontent.com/64992190/156416476-2ce4e02c-62a1-4f0d-9430-d14368bca1a1.png "Development Progress" +[SF8RN]: https://stockfishchess.org/blog/2016/stockfish-8/ "Release Notes" +[011116-dif]: https://github.com/official-stockfish/Stockfish/compare/dd9cf30581...369eff437c +[311216-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...43f6b33e50 +[311216-elo1]: https://tests.stockfishchess.org/tests/view/5867b55c0ebc5903140c639c +[311216-master]: https://github.com/official-stockfish/Stockfish/commit/43f6b33e50c4bea354b963b0e1e47445c8298299 +[311216-raw1]: https://tests.stockfishchess.org/tests/stats/5867b55c0ebc5903140c639c +[290117-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...fa24cc25a4 +[290117-elo1]: https://tests.stockfishchess.org/tests/view/588dc7620ebc5915193f7d19 +[290117-master]: https://github.com/official-stockfish/Stockfish/commit/fa24cc25a43da5ac41a086edda02dfc2e8e9b830 +[290117-raw1]: https://tests.stockfishchess.org/tests/stats/588dc7620ebc5915193f7d19 +[080317-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...c3d2e6aba9 +[080317-elo1]: https://tests.stockfishchess.org/tests/view/58c11dcf0ebc59035df32b75 +[080317-master]: https://github.com/official-stockfish/Stockfish/commit/c3d2e6aba981ecc0caf82f81a1d44e8c4954e151 +[080317-raw1]: https://tests.stockfishchess.org/tests/stats/58c11dcf0ebc59035df32b75 +[200417-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...ced29248c9 +[200417-elo1]: https://tests.stockfishchess.org/tests/view/58f92a310ebc59035df33d48 +[200417-master]: https://github.com/official-stockfish/Stockfish/commit/ced29248c93de7fc5a4e284807f8f052006e647c +[200417-raw1]: https://tests.stockfishchess.org/tests/stats/58f92a310ebc59035df33d48 +[070517-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...6b4959e3e0 +[070517-elo1]: https://tests.stockfishchess.org/tests/view/59100d7c0ebc59035df343ef +[070517-master]: https://github.com/official-stockfish/Stockfish/commit/6b4959e3e00035dbcabd74a6d49ce7d04008d62c +[070517-raw1]: https://tests.stockfishchess.org/tests/stats/59100d7c0ebc59035df343ef +[210617-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...77342126d8 +[210617-elo1]: https://tests.stockfishchess.org/tests/view/594b324e0ebc593ea732d332 +[210617-master]: https://github.com/official-stockfish/Stockfish/commit/77342126d8417469bd6a398cfc6c0594b1f02f82 +[210617-raw1]: https://tests.stockfishchess.org/tests/stats/594b324e0ebc593ea732d332 +[260817-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...d5f883ab29 +[260817-elo1]: https://tests.stockfishchess.org/tests/view/59a1a55a0ebc5916ff64aba4 +[260817-master]: https://github.com/official-stockfish/Stockfish/commit/d5f883ab29d43b35746ff605cf13c3722df56041 +[260817-raw1]: https://tests.stockfishchess.org/tests/stats/59a1a55a0ebc5916ff64aba4 +[021017-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...452e5154cf +[021017-elo1]: https://tests.stockfishchess.org/tests/view/59d1e0770ebc5916ff64baf5 +[021017-master]: https://github.com/official-stockfish/Stockfish/commit/452e5154cf29ee46aa35a12dfb54bd24e4ed61de +[021017-raw1]: https://tests.stockfishchess.org/tests/stats/59d1e0770ebc5916ff64baf5 +[031117-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...4bc11984fc +[031117-elo1]: https://tests.stockfishchess.org/tests/view/59fcc7570ebc590ccbb8a151 +[031117-master]: https://github.com/official-stockfish/Stockfish/commit/4bc11984fc5a148ee8f1b55d6ac47c4a397cc8b8 +[031117-raw1]: https://tests.stockfishchess.org/tests/stats/59fcc7570ebc590ccbb8a151 +[031217-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...28b6a457c2 +[031217-elo1]: https://tests.stockfishchess.org/tests/view/5a23e7c10ebc590ccbb8b6d8 +[031217-master]: https://github.com/official-stockfish/Stockfish/commit/28b6a457c24d9202ba43a6d6703221250f0f8749 +[031217-raw1]: https://tests.stockfishchess.org/tests/stats/5a23e7c10ebc590ccbb8b6d8 +[230118-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...254d995e18 +[230118-elo1]: https://tests.stockfishchess.org/tests/view/5a673c8a0ebc590d945d5815 +[230118-master]: https://github.com/official-stockfish/Stockfish/commit/254d995e187d8ecd02c3e5613e43aab525e41e22 +[230118-raw1]: https://tests.stockfishchess.org/tests/stats/5a673c8a0ebc590d945d5815 +[Stockfish 9]: https://github.com/official-stockfish/Stockfish/commit/378c8bdbb8f930472fc4316aa6c417802294bbad +[SF9DP]: https://user-images.githubusercontent.com/64992190/156416383-f40f1fda-6577-4b5e-b359-c5dd97620086.png "Development Progress" +[SF9RN]: https://stockfishchess.org/blog/2018/stockfish-9/ "Release Notes" +[310118-dif]: https://github.com/official-stockfish/Stockfish/compare/369eff437c...378c8bdbb8 +[280218-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...94abc2a0cf +[280218-elo1]: https://tests.stockfishchess.org/tests/view/5a96cfdb0ebc590297cc8d2b +[280218-master]: https://github.com/official-stockfish/Stockfish/commit/94abc2a0cfa262e2e040886394c782af226bc1bd +[280218-raw1]: https://tests.stockfishchess.org/tests/stats/5a96cfdb0ebc590297cc8d2b +[070318-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...82697f1193 +[070318-elo1]: https://tests.stockfishchess.org/tests/view/5aa06d050ebc590297cb62d9 +[070318-master]: https://github.com/official-stockfish/Stockfish/commit/82697f1193cc8c99c99c282361a3ada25c758243 +[070318-raw1]: https://tests.stockfishchess.org/tests/stats/5aa06d050ebc590297cb62d9 +[130318-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...edf4c07d25 +[130318-elo1]: https://tests.stockfishchess.org/tests/view/5aa8e2540ebc5902978101c8 +[130318-master]: https://github.com/official-stockfish/Stockfish/commit/edf4c07d251f1d6c709d47969bfe1452194d9430 +[130318-raw1]: https://tests.stockfishchess.org/tests/stats/5aa8e2540ebc5902978101c8 +[260318-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...f0f6da2d30 +[260318-elo1]: https://tests.stockfishchess.org/tests/view/5aba52ab0ebc5902a4743e6e +[260318-master]: https://github.com/official-stockfish/Stockfish/commit/f0f6da2d30fc005fd0fa126ee1eefd11fe10a604 +[260318-raw1]: https://tests.stockfishchess.org/tests/stats/5aba52ab0ebc5902a4743e6e +[030418-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...04a228f9c8 +[030418-elo1]: https://tests.stockfishchess.org/tests/view/5ac5f0220ebc590305f0f5bd +[030418-master]: https://github.com/official-stockfish/Stockfish/commit/04a228f9c83dafde1953e43b1906fc0929832976 +[030418-raw1]: https://tests.stockfishchess.org/tests/stats/5ac5f0220ebc590305f0f5bd +[070418-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...b88374b14a +[070418-elo1]: https://tests.stockfishchess.org/tests/view/5ae5b0240ebc59442aa387d4 +[070418-master]: https://github.com/official-stockfish/Stockfish/commit/b88374b14a7baa2f8e4c37b16a2e653e7472adcc +[070418-raw1]: https://tests.stockfishchess.org/tests/stats/5ae5b0240ebc59442aa387d4 +[230418-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...aef7076c34 +[230418-elo1]: https://tests.stockfishchess.org/tests/view/5ae5f3090ebc59442aa387ea +[230418-master]: https://github.com/official-stockfish/Stockfish/commit/aef7076c344881954b4f586bd4779594d0b29037 +[230418-raw1]: https://tests.stockfishchess.org/tests/stats/5ae5f3090ebc59442aa387ea +[290418-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...213166ba22 +[290418-elo1]: https://tests.stockfishchess.org/tests/view/5ae55fdd0ebc5902b3f347f7 +[290418-master]: https://github.com/official-stockfish/Stockfish/commit/213166ba225bcefbbe7dbecdacfd726dfb6c34f9 +[290418-raw1]: https://tests.stockfishchess.org/tests/stats/5ae55fdd0ebc5902b3f347f7 +[030518-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...d4cb80b210 +[030518-elo1]: https://tests.stockfishchess.org/tests/view/5aebee200ebc5962311adb36 +[030518-master]: https://github.com/official-stockfish/Stockfish/commit/d4cb80b2106efb58db87495090a3898d902075d6 +[030518-raw1]: https://tests.stockfishchess.org/tests/stats/5aebee200ebc5962311adb36 +[130518-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...aacee91a5a +[130518-elo1]: https://tests.stockfishchess.org/tests/view/5afb21bd0ebc591fdf408d59 +[130518-master]: https://github.com/official-stockfish/Stockfish/commit/aacee91a5a295f1d9de2ea6dc0ca4a48e934e3b6 +[130518-raw1]: https://tests.stockfishchess.org/tests/stats/5afb21bd0ebc591fdf408d59 +[240518-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...3d6995eae8 +[240518-elo1]: https://tests.stockfishchess.org/tests/view/5b06f59b0ebc5914abc12bb1 +[240518-master]: https://github.com/official-stockfish/Stockfish/commit/3d6995eae8039b2bf4141cbc02d87d5a6c2a1905 +[240518-raw1]: https://tests.stockfishchess.org/tests/stats/5b06f59b0ebc5914abc12bb1 +[050618-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...e4f8a4fa7f +[050618-elo1]: https://tests.stockfishchess.org/tests/view/5b1777950ebc596dfa6acbe6 +[050618-master]: https://github.com/official-stockfish/Stockfish/commit/e4f8a4fa7f5da8287579c0c74e292974c6acfd8d +[050618-raw1]: https://tests.stockfishchess.org/tests/stats/5b1777950ebc596dfa6acbe6 +[110618-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...fc3af7c4fb +[110618-elo1]: https://tests.stockfishchess.org/tests/view/5b1e41190ebc5902ab9c778b +[110618-master]: https://github.com/official-stockfish/Stockfish/commit/fc3af7c4fbeaa3c5b85424077829223f9d18184e +[110618-raw1]: https://tests.stockfishchess.org/tests/stats/5b1e41190ebc5902ab9c778b +[230618-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...efd4ca27c4 +[230618-elo1]: https://tests.stockfishchess.org/tests/view/5b2ecd830ebc5902b2e57650 +[230618-master]: https://github.com/official-stockfish/Stockfish/commit/efd4ca27c4d7abad41e0469aa9b3b26b12068914 +[230618-raw1]: https://tests.stockfishchess.org/tests/stats/5b2ecd830ebc5902b2e57650 +[190718-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...12e79be910 +[190718-elo1]: https://tests.stockfishchess.org/tests/view/5b50bec70ebc5902bdb7bf97 +[190718-master]: https://github.com/official-stockfish/Stockfish/commit/12e79be91039796299187ba1b2f1559552642ea4 +[190718-raw1]: https://tests.stockfishchess.org/tests/stats/5b50bec70ebc5902bdb7bf97 +[270718-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...6184d2b2ac +[270718-elo1]: https://tests.stockfishchess.org/tests/view/5b5b2de00ebc5902bdb8dcce +[270718-master]: https://github.com/official-stockfish/Stockfish/commit/6184d2b2ac25691dc171560903a81e6d38a0593a +[270718-raw1]: https://tests.stockfishchess.org/tests/stats/5b5b2de00ebc5902bdb8dcce +[280718-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...c08e05b494 +[280718-elo1]: https://tests.stockfishchess.org/tests/view/5b5c02e70ebc5902bdb8f4d0 +[280718-master]: https://github.com/official-stockfish/Stockfish/commit/c08e05b494d54c7fc28621204382d77d3595d436 +[280718-raw1]: https://tests.stockfishchess.org/tests/stats/5b5c02e70ebc5902bdb8f4d0 +[310718-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...fae57273b2 +[310718-elo1]: https://tests.stockfishchess.org/tests/view/5b60e7a70ebc5902bdb946b0 +[310718-master]: https://github.com/official-stockfish/Stockfish/commit/fae57273b20468f534cce5843152a21214b5da05 +[310718-raw1]: https://tests.stockfishchess.org/tests/stats/5b60e7a70ebc5902bdb946b0 +[080818-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...bd4d2b0576 +[080818-elo1]: https://tests.stockfishchess.org/tests/view/5b6b16d90ebc5902bdb9df94 +[080818-master]: https://github.com/official-stockfish/Stockfish/commit/bd4d2b0576ec320367769d5720c7a5b4d094ceef +[080818-raw1]: https://tests.stockfishchess.org/tests/stats/5b6b16d90ebc5902bdb9df94 +[120818-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...b5581b7779 +[120818-elo1]: https://tests.stockfishchess.org/tests/view/5b6fedde0ebc5902bdba1d10 +[120818-master]: https://github.com/official-stockfish/Stockfish/commit/b5581b7779b6e286fa2277625572996477d74b10 +[120818-raw1]: https://tests.stockfishchess.org/tests/stats/5b6fedde0ebc5902bdba1d10 +[140818-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...881cab2525 +[140818-elo1]: https://tests.stockfishchess.org/tests/view/5b72955f0ebc5902bdba4b07 +[140818-master]: https://github.com/official-stockfish/Stockfish/commit/881cab252530c8711e942f7936a3eb41b2956a6b +[140818-raw1]: https://tests.stockfishchess.org/tests/stats/5b72955f0ebc5902bdba4b07 +[170818-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...f3b8a69919 +[170818-elo1]: https://tests.stockfishchess.org/tests/view/5b775ad60ebc5902bdbaa8cb +[170818-master]: https://github.com/official-stockfish/Stockfish/commit/f3b8a699194515e0b74f5349cf84175a97f824e8 +[170818-raw1]: https://tests.stockfishchess.org/tests/stats/5b775ad60ebc5902bdbaa8cb +[280818A-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...6307fd08e6 +[280818A-elo1]: https://tests.stockfishchess.org/tests/view/5b85d4520ebc5902bdbbb871 +[280818A-master]: https://github.com/official-stockfish/Stockfish/commit/6307fd08e6e8c315802301fd35b22ca2a67071a9 +[280818A-raw1]: https://tests.stockfishchess.org/tests/stats/5b85d4520ebc5902bdbbb871 +[280818B-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...e846a9306d +[280818B-elo1]: https://tests.stockfishchess.org/tests/view/5b8915fb0ebc592cf2747bf8 +[280818B-master]: https://github.com/official-stockfish/Stockfish/commit/e846a9306d6108fb24cb216689867777ac2b0c4f +[280818B-raw1]: https://tests.stockfishchess.org/tests/stats/5b8915fb0ebc592cf2747bf8 +[010918-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...2bfaf45455 +[010918-elo1]: https://tests.stockfishchess.org/tests/view/5b8dccf60ebc592cf274d4b0 +[010918-master]: https://github.com/official-stockfish/Stockfish/commit/2bfaf454551ae5a9d99d271d0d87d2a6c829c7e4 +[010918-raw1]: https://tests.stockfishchess.org/tests/stats/5b8dccf60ebc592cf274d4b0 +[100918-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...0fa957cf66 +[100918-elo1]: https://tests.stockfishchess.org/tests/view/5b976bbc0ebc592cf275a312 +[100918-master]: https://github.com/official-stockfish/Stockfish/commit/0fa957cf66069c4499d9fe793cf07a11c4ccb87c +[100918-raw1]: https://tests.stockfishchess.org/tests/stats/5b976bbc0ebc592cf275a312 +[270918-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...8141bdd179 +[270918-elo1]: https://tests.stockfishchess.org/tests/view/5bad44590ebc592439f6654d +[270918-master]: https://github.com/official-stockfish/Stockfish/commit/8141bdd179da8f36c04f99d51812b19bbd1a8efd +[270918-raw1]: https://tests.stockfishchess.org/tests/stats/5bad44590ebc592439f6654d +[141018-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...738a6dfd4c +[141018-elo1]: https://tests.stockfishchess.org/tests/view/5bc38fda0ebc592439f7f4dd +[141018-master]: https://github.com/official-stockfish/Stockfish/commit/738a6dfd4c71c3ce11d614076117793b4cdf119e +[141018-raw1]: https://tests.stockfishchess.org/tests/stats/5bc38fda0ebc592439f7f4dd +[251018-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...081af90805 +[251018-elo1]: https://tests.stockfishchess.org/tests/view/5bd36e170ebc595e0ae1e776 +[251018-master]: https://github.com/official-stockfish/Stockfish/commit/081af9080542a0d076a5482da37103a96ee15f64 +[251018-raw1]: https://tests.stockfishchess.org/tests/stats/5bd36e170ebc595e0ae1e776 +[011118-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...3f1eb85a1c +[011118-elo1]: https://tests.stockfishchess.org/tests/view/5bdb1a140ebc595e0ae2620a +[011118-master]: https://github.com/official-stockfish/Stockfish/commit/3f1eb85a1ceb1b408f8f51cb82064b69e095399d +[011118-raw1]: https://tests.stockfishchess.org/tests/stats/5bdb1a140ebc595e0ae2620a +[081118-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...05aa34e00e +[081118-elo1]: https://tests.stockfishchess.org/tests/view/5be464190ebc595e0ae303be +[081118-master]: https://github.com/official-stockfish/Stockfish/commit/05aa34e00ed78adfa943242026a00dc497acd563 +[081118-raw1]: https://tests.stockfishchess.org/tests/stats/5be464190ebc595e0ae303be +[191118-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...cf5d683408 +[191118-elo1]: https://tests.stockfishchess.org/tests/view/5bf29f4a0ebc5902bbbddfeb +[191118-master]: https://github.com/official-stockfish/Stockfish/commit/cf5d683408a2ef8a1c80be9bf7d6790a38b16277 +[191118-raw1]: https://tests.stockfishchess.org/tests/stats/5bf29f4a0ebc5902bbbddfeb +[271118-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...7b6fa353a3 +[271118-elo1]: https://tests.stockfishchess.org/tests/view/5bfcfa2b0ebc5902bced8acf +[271118-master]: https://github.com/official-stockfish/Stockfish/commit/7b6fa353a3858b092e1a43ef69b3035cb5d3b5c0 +[271118-raw1]: https://tests.stockfishchess.org/tests/stats/5bfcfa2b0ebc5902bced8acf +[Stockfish 10]: https://github.com/official-stockfish/Stockfish/commit/b4c239b625285307c5871f1104dc3fb80aa6d5d2 +[SF10DP]: https://user-images.githubusercontent.com/64992190/156416236-9dc49028-5cf4-4cb3-9314-a3c3a3d7b12b.png "Development Progress" +[SF10RN]: https://stockfishchess.org/blog/2018/stockfish-10/ "Release Notes" +[291118-dif]: https://github.com/official-stockfish/Stockfish/compare/378c8bdbb8...b4c239b625 +[061218-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...5c2fbcd09b +[061218-elo1]: https://tests.stockfishchess.org/tests/view/5c092f8b0ebc5902bcee8877 +[061218-master]: https://github.com/official-stockfish/Stockfish/commit/5c2fbcd09b43bd6867780a3ace303ffb1e09d763 +[061218-raw1]: https://tests.stockfishchess.org/tests/stats/5c092f8b0ebc5902bcee8877 +[131218-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...31ac538f96 +[131218-elo1]: https://tests.stockfishchess.org/tests/view/5c126a140ebc5902ba11afb0 +[131218-elo8]: https://tests.stockfishchess.org/tests/view/5d01302d0ebc5925cf09d044 +[131218-master]: https://github.com/official-stockfish/Stockfish/commit/31ac538f96a54b294e79213d33aacf5d8a182c87 +[131218-raw1]: https://tests.stockfishchess.org/tests/stats/5c126a140ebc5902ba11afb0 +[131218-raw8]: https://tests.stockfishchess.org/tests/stats/5d01302d0ebc5925cf09d044 +[161218-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...7240a90bf9 +[161218-elo1]: https://tests.stockfishchess.org/tests/view/5c17830c0ebc5902ba1234c4 +[161218-master]: https://github.com/official-stockfish/Stockfish/commit/7240a90bf9be3f39f3ca4f34921dd644c5cebb3a +[161218-raw1]: https://tests.stockfishchess.org/tests/stats/5c17830c0ebc5902ba1234c4 +[040119-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...bb843a00c1 +[040119-elo1]: https://tests.stockfishchess.org/tests/view/5c2f7d4f0ebc596a450be7dc +[040119-master]: https://github.com/official-stockfish/Stockfish/commit/bb843a00c1ef381162dc9c2491b5436b6cf5563f +[040119-raw1]: https://tests.stockfishchess.org/tests/stats/5c2f7d4f0ebc596a450be7dc +[100119-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...5446e6f408 +[100119-elo1]: https://tests.stockfishchess.org/tests/view/5c376bd30ebc596a450c8216 +[100119-elo8]: https://tests.stockfishchess.org/tests/view/5ced50b50ebc5925cf07eabf +[100119-master]: https://github.com/official-stockfish/Stockfish/commit/5446e6f408f2ed7fa281dbe0097c46674d193260 +[100119-raw1]: https://tests.stockfishchess.org/tests/stats/5c376bd30ebc596a450c8216 +[100119-raw8]: https://tests.stockfishchess.org/tests/stats/5ced50b50ebc5925cf07eabf +[220119-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...2d0af36753 +[220119-elo1]: https://tests.stockfishchess.org/tests/view/5c46dbef0ebc5902bb5d6b6f +[220119-master]: https://github.com/official-stockfish/Stockfish/commit/2d0af36753a2f1acdf43b4e1d30d56ad8effc429 +[220119-raw1]: https://tests.stockfishchess.org/tests/stats/5c46dbef0ebc5902bb5d6b6f +[030219-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...6514500236 +[030219-elo1]: https://tests.stockfishchess.org/tests/view/5c5777a00ebc592015e0db3b +[030219-elo8]: https://tests.stockfishchess.org/tests/view/5cf9eadf0ebc5925cf09264e +[030219-master]: https://github.com/official-stockfish/Stockfish/commit/651450023619ddea590f301f040286151004df66 +[030219-raw1]: https://tests.stockfishchess.org/tests/stats/5c5777a00ebc592015e0db3b +[030219-raw8]: https://tests.stockfishchess.org/tests/stats/5cf9eadf0ebc5925cf09264e +[120319-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...bad18bccb6 +[120319-elo1]: https://tests.stockfishchess.org/tests/view/5c8bbd6b0ebc5925cffed484 +[120319-master]: https://github.com/official-stockfish/Stockfish/commit/bad18bccb60c874410edd3f61624696d3abc3cbc +[120319-raw1]: https://tests.stockfishchess.org/tests/stats/5c8bbd6b0ebc5925cffed484 +[310319-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...82ad9ce9cf +[310319-elo1]: https://tests.stockfishchess.org/tests/view/5ca294f90ebc5925cf000e4d +[310319-elo8]: https://tests.stockfishchess.org/tests/view/5cba4ebd0ebc5925cf020fae +[310319-master]: https://github.com/official-stockfish/Stockfish/commit/82ad9ce9cfb0eff33f1d781f329f7c5dc0b277eb +[310319-raw1]: https://tests.stockfishchess.org/tests/stats/5ca294f90ebc5925cf000e4d +[310319-raw8]: https://tests.stockfishchess.org/tests/stats/5cba4ebd0ebc5925cf020fae +[240419-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...6373fd56e9 +[240419-elo1]: https://tests.stockfishchess.org/tests/view/5cc0b7520ebc5925cf02af43 +[240419-master]: https://github.com/official-stockfish/Stockfish/commit/6373fd56e90bc6114230a70cacad804248d955e2 +[240419-raw1]: https://tests.stockfishchess.org/tests/stats/5cc0b7520ebc5925cf02af43 +[050519-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...b6d11028bb +[050519-elo1]: https://tests.stockfishchess.org/tests/view/5ccf55560ebc5925cf04273a +[050519-master]: https://github.com/official-stockfish/Stockfish/commit/b6d11028bbb5e428cdbd709ba46d8b14bab17c88 +[050519-raw1]: https://tests.stockfishchess.org/tests/stats/5ccf55560ebc5925cf04273a +[150519-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...3a572ffb48 +[150519-elo1]: https://tests.stockfishchess.org/tests/view/5cdc33ac0ebc5925cf05b8bc +[150519-elo8]: https://tests.stockfishchess.org/tests/view/5cdc35160ebc5925cf05b90e +[150519-master]: https://github.com/official-stockfish/Stockfish/commit/3a572ffb4840bfea3c587c9c81a0008515f02a32 +[150519-raw1]: https://tests.stockfishchess.org/tests/stats/5cdc33ac0ebc5925cf05b8bc +[150519-raw8]: https://tests.stockfishchess.org/tests/stats/5cdc35160ebc5925cf05b90e +[090619-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...2ead74d1e2 +[090619-elo1]: https://tests.stockfishchess.org/tests/view/5cfd00570ebc5925cf096317 +[090619-master]: https://github.com/official-stockfish/Stockfish/commit/2ead74d1e2b0c5edbb0da5887e56bbddb5b2a905 +[090619-raw1]: https://tests.stockfishchess.org/tests/stats/5cfd00570ebc5925cf096317 +[200619-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...37ffacf209 +[200619-elo1]: https://tests.stockfishchess.org/tests/view/5d0b9f950ebc5925cf0a9c6b +[200619-elo8]: https://tests.stockfishchess.org/tests/view/5d0ba0010ebc5925cf0a9c84 +[200619-master]: https://github.com/official-stockfish/Stockfish/commit/37ffacf2094594314346bf9c3d7d8a61911b34d5 +[200619-raw1]: https://tests.stockfishchess.org/tests/stats/5d0b9f950ebc5925cf0a9c6b +[200619-raw8]: https://tests.stockfishchess.org/tests/stats/5d0ba0010ebc5925cf0a9c84 +[270619-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...d889bb4718 +[270619-elo1]: https://tests.stockfishchess.org/tests/view/5d1479580ebc5925cf0b421f +[270619-master]: https://github.com/official-stockfish/Stockfish/commit/d889bb47185e33012b45cab63359952247bc86e2 +[270619-raw1]: https://tests.stockfishchess.org/tests/stats/5d1479580ebc5925cf0b421f +[110719-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...4ae5a7b45a +[110719-elo1]: https://tests.stockfishchess.org/tests/view/5d274ae90ebc5925cf0d3cd4 +[110719-master]: https://github.com/official-stockfish/Stockfish/commit/4ae5a7b45a430aea5f4b21f9455b4db74ed1c44a +[110719-raw1]: https://tests.stockfishchess.org/tests/stats/5d274ae90ebc5925cf0d3cd4 +[250719-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...acdda38b93 +[250719-elo1]: https://tests.stockfishchess.org/tests/view/5d395a840ebc5925cf0f075e +[250719-elo8]: https://tests.stockfishchess.org/tests/view/5d3964d20ebc5925cf0f078b +[250719-master]: https://github.com/official-stockfish/Stockfish/commit/acdda38b93361f10e331a6d951d6870577efdace +[250719-raw1]: https://tests.stockfishchess.org/tests/stats/5d395a840ebc5925cf0f075e +[250719-raw8]: https://tests.stockfishchess.org/tests/stats/5d3964d20ebc5925cf0f078b +[140819-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...66a3c2968b +[140819-elo1]: https://tests.stockfishchess.org/tests/view/5d545fd20ebc5925cf10a1ee +[140819-master]: https://github.com/official-stockfish/Stockfish/commit/66a3c2968b8552efce8c7e670d7ceabb28f9c1eb +[140819-raw1]: https://tests.stockfishchess.org/tests/stats/5d545fd20ebc5925cf10a1ee +[260819-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...8fec883471 +[260819-elo1]: https://tests.stockfishchess.org/tests/view/5d6394260ebc5939d09f436c +[260819-elo8]: https://tests.stockfishchess.org/tests/view/5d63961a0ebc5939d09f4382 +[260819-master]: https://github.com/official-stockfish/Stockfish/commit/8fec8834715a440ac18e24e130888c2c60bab352 +[260819-raw1]: https://tests.stockfishchess.org/tests/stats/5d6394260ebc5939d09f436c +[260819-raw8]: https://tests.stockfishchess.org/tests/stats/5d63961a0ebc5939d09f4382 +[120919-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...8aecf26981 +[120919-elo1]: https://tests.stockfishchess.org/tests/view/5d7a0b7c0ebc5902d385654a +[120919-master]: https://github.com/official-stockfish/Stockfish/commit/8aecf2698184babce57ccfd2ba5948342e29c325 +[120919-raw1]: https://tests.stockfishchess.org/tests/stats/5d7a0b7c0ebc5902d385654a +[160919-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...a858defd33 +[160919-elo1]: https://tests.stockfishchess.org/tests/view/5d809d540ebc5971531d0b66 +[160919-elo8]: https://tests.stockfishchess.org/tests/view/5d80d2240ebc5971531d1e46 +[160919-master]: https://github.com/official-stockfish/Stockfish/commit/a858defd332bddd828c9280a9e326a0b750b3dda +[160919-raw1]: https://tests.stockfishchess.org/tests/stats/5d809d540ebc5971531d0b66 +[160919-raw8]: https://tests.stockfishchess.org/tests/stats/5d80d2240ebc5971531d1e46 +[240919-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...667d24f227 +[240919-elo1]: https://tests.stockfishchess.org/tests/view/5d8d2c030ebc590f3beac64d +[240919-master]: https://github.com/official-stockfish/Stockfish/commit/667d24f22743959ceddda6af53718619ea5c551d +[240919-raw1]: https://tests.stockfishchess.org/tests/stats/5d8d2c030ebc590f3beac64d +[051019-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...2e96c513ad +[051019-elo1]: https://tests.stockfishchess.org/tests/view/5d99322f0ebc5902b6cef9fa +[051019-elo8]: https://tests.stockfishchess.org/tests/view/5d9932830ebc5902b6cef9fc +[051019-master]: https://github.com/official-stockfish/Stockfish/commit/2e96c513ad6113abb6bc4fdd4962cc1f6eed3d4a +[051019-raw1]: https://tests.stockfishchess.org/tests/stats/5d99322f0ebc5902b6cef9fa +[051019-raw8]: https://tests.stockfishchess.org/tests/stats/5d9932830ebc5902b6cef9fc +[181019-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...472de897cb +[181019-elo1]: https://tests.stockfishchess.org/tests/view/5da9d8470ebc597ba8edbb52 +[181019-master]: https://github.com/official-stockfish/Stockfish/commit/472de897cb7efb66cb3518f3f4924716bd8abaee +[181019-raw1]: https://tests.stockfishchess.org/tests/stats/5da9d8470ebc597ba8edbb52 +[041119-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...3804effb34 +[041119-elo1]: https://tests.stockfishchess.org/tests/view/5dc0b9660ebc5904493b0107 +[041119-elo8]: https://tests.stockfishchess.org/tests/view/5dc0ba190ebc5904493b0110 +[041119-master]: https://github.com/official-stockfish/Stockfish/commit/3804effb341b3008326a1613923177eb83d02826 +[041119-raw1]: https://tests.stockfishchess.org/tests/stats/5dc0b9660ebc5904493b0107 +[041119-raw8]: https://tests.stockfishchess.org/tests/stats/5dc0ba190ebc5904493b0110 +[141119-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...a00a336946 +[141119-elo1]: https://tests.stockfishchess.org/tests/view/5dcdb0170ebc590256324986 +[141119-master]: https://github.com/official-stockfish/Stockfish/commit/a00a336946fa9e6dcfa39f8b656413d2de032a89 +[141119-raw1]: https://tests.stockfishchess.org/tests/stats/5dcdb0170ebc590256324986 +[211119-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...3f4191392c +[211119-elo1]: https://tests.stockfishchess.org/tests/view/5dd888329b6a7a2aa0a9005a +[211119-elo8]: https://tests.stockfishchess.org/tests/view/5dd888a09b6a7a2aa0a9005c +[211119-master]: https://github.com/official-stockfish/Stockfish/commit/3f4191392c18f08011294aab880c31b15fc6f61c +[211119-raw1]: https://tests.stockfishchess.org/tests/stats/5dd888329b6a7a2aa0a9005a +[211119-raw8]: https://tests.stockfishchess.org/tests/stats/5dd888a09b6a7a2aa0a9005c +[021219-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...97a0e4e817 +[021219-elo1]: https://tests.stockfishchess.org/tests/view/5de59f7db407ee7bfda68a58 +[021219-master]: https://github.com/official-stockfish/Stockfish/commit/97a0e4e8170df33b927c48d734e0132e9ef8a22f +[021219-raw1]: https://tests.stockfishchess.org/tests/stats/5de59f7db407ee7bfda68a58 +[101219-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...b6482472a0 +[101219-elo1]: https://tests.stockfishchess.org/tests/view/5def70363cff9a249bb9e4c5 +[101219-elo8]: https://tests.stockfishchess.org/tests/view/5def70ae3cff9a249bb9e4c8 +[101219-master]: https://github.com/official-stockfish/Stockfish/commit/b6482472a03833287dc21bdaa783f156978ac63e +[101219-raw1]: https://tests.stockfishchess.org/tests/stats/5def70363cff9a249bb9e4c5 +[101219-raw8]: https://tests.stockfishchess.org/tests/stats/5def70ae3cff9a249bb9e4c8 +[070120-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...44f79bdf5a +[070120-elo1]: https://tests.stockfishchess.org/tests/view/5e1472da61fe5f83a67dd84f +[070120-elo8]: https://tests.stockfishchess.org/tests/view/5e14734d61fe5f83a67dd851 +[070120-master]: https://github.com/official-stockfish/Stockfish/commit/44f79bdf5a092c3acec0a8bf8f2c1440e5a9da90 +[070120-raw1]: https://tests.stockfishchess.org/tests/stats/5e1472da61fe5f83a67dd84f +[070120-raw8]: https://tests.stockfishchess.org/tests/stats/5e14734d61fe5f83a67dd851 +[170120-dif]: https://github.com/official-stockfish/Stockfish/compare/b4c239b625...c3483fa9a7 +[170120-elo1]: https://tests.stockfishchess.org/tests/view/5e2258dd346e35ac603b7d8c +[170120-elo8]: https://tests.stockfishchess.org/tests/view/5e22593d346e35ac603b7d8f +[170120-master]: https://github.com/official-stockfish/Stockfish/commit/c3483fa9a7d7c0ffa9fcc32b467ca844cfb63790 +[170120-raw1]: https://tests.stockfishchess.org/tests/stats/5e2258dd346e35ac603b7d8c +[170120-raw8]: https://tests.stockfishchess.org/tests/stats/5e22593d346e35ac603b7d8f +[Stockfish 11]: https://github.com/official-stockfish/Stockfish/commit/c3483fa9a7d7c0ffa9fcc32b467ca844cfb63790 +[SF11DP]: https://user-images.githubusercontent.com/64992190/156415721-adef6287-f523-4cc4-b7e8-cc920bdd7bdc.png "Development Progress" +[SF11RN]: https://stockfishchess.org/blog/2020/stockfish-11/ "Release Notes" +[280120-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...71e0b5385e +[280120-elo1]: https://tests.stockfishchess.org/tests/view/5e307251ab2d69d58394fdb9 +[280120-master]: https://github.com/official-stockfish/Stockfish/commit/71e0b5385e2717679a57c6b77d8c7ac5fff3b89f +[280120-raw1]: https://tests.stockfishchess.org/tests/stats/5e307251ab2d69d58394fdb9 +[310120-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...f10498be40 +[310120-elo1]: https://tests.stockfishchess.org/tests/view/5e334851708b13464ceea33c +[310120-master]: https://github.com/official-stockfish/Stockfish/commit/6ccb1cac5aaaf7337da8b1738448793be63fdfdb +[310120-raw1]: https://tests.stockfishchess.org/tests/stats/5e334851708b13464ceea33c +[270220-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...09f53dbfa5 +[270220-elo1]: https://tests.stockfishchess.org/tests/view/5e5801a584a82b4acd414a28 +[270220-elo8]: https://tests.stockfishchess.org/tests/view/5e58020384a82b4acd414a2a +[270220-master]: https://github.com/official-stockfish/Stockfish/commit/09f53dbfa5b55e761ca8070960345ab140baad04 +[270220-raw1]: https://tests.stockfishchess.org/tests/stats/5e5801a584a82b4acd414a28 +[270220-raw8]: https://tests.stockfishchess.org/tests/stats/5e58020384a82b4acd414a2a +[200320-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...6ecab03dee +[200320-elo1]: https://tests.stockfishchess.org/tests/view/5e790d12e42a5c3b3ca2e9b7 +[200320-master]: https://github.com/official-stockfish/Stockfish/commit/6ecab03dee15fe30bc0237919180a2e51e0ce4b1 +[200320-raw1]: https://tests.stockfishchess.org/tests/stats/5e790d12e42a5c3b3ca2e9b7 +[070420-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...195a4fec6d +[070420-elo1]: https://tests.stockfishchess.org/tests/view/5e8ca31b0ffd2be7f15e54b3 +[070420-elo8]: https://tests.stockfishchess.org/tests/view/5e8ca37e0ffd2be7f15e54b5 +[070420-master]: https://github.com/official-stockfish/Stockfish/commit/195a4fec6d6bd1f9e43f5b3e83a3dcf57dc73744 +[070420-raw1]: https://tests.stockfishchess.org/tests/stats/5e8ca31b0ffd2be7f15e54b3 +[070420-raw8]: https://tests.stockfishchess.org/tests/stats/5e8ca37e0ffd2be7f15e54b5 +[160420-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...345b2d153a +[160420-elo1]: https://tests.stockfishchess.org/tests/view/5e98b9e130be947a14e9db1b +[160420-master]: https://github.com/official-stockfish/Stockfish/commit/345b2d153a8092cff93ad660c9d107cd66fda43b +[160420-raw1]: https://tests.stockfishchess.org/tests/stats/5e98b9e130be947a14e9db1b +[020520-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...c527c3ad44 +[020520-elo1]: https://tests.stockfishchess.org/tests/view/5eadc1526ffeed51f6e3277e +[020520-elo8]: https://tests.stockfishchess.org/tests/view/5eadc2156ffeed51f6e32781 +[020520-master]: https://github.com/official-stockfish/Stockfish/commit/c527c3ad44f7465c79cef93f1e8cfebd998dc627 +[020520-raw1]: https://tests.stockfishchess.org/tests/stats/5eadc1526ffeed51f6e3277e +[020520-raw8]: https://tests.stockfishchess.org/tests/stats/5eadc2156ffeed51f6e32781 +[210520-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...09c6917d05 +[210520-elo1]: https://tests.stockfishchess.org/tests/view/5ec6c48ec23f5b0710632b04 +[210520-master]: https://github.com/official-stockfish/Stockfish/commit/09c6917d053582267a2960e8c375883e0d9461da +[210520-raw1]: https://tests.stockfishchess.org/tests/stats/5ec6c48ec23f5b0710632b04 +[060620-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...15e190e942 +[060620-elo1]: https://tests.stockfishchess.org/tests/view/5edf5ffdf29b40b0fc95ad62 +[060620-elo8]: https://tests.stockfishchess.org/tests/view/5edf6220f29b40b0fc95ad6e +[060620-master]: https://github.com/official-stockfish/Stockfish/commit/15e190e9428b21fbfe29ce020c456077dc5fdd04 +[060620-raw1]: https://tests.stockfishchess.org/tests/stats/5edf5ffdf29b40b0fc95ad62 +[060620-raw8]: https://tests.stockfishchess.org/tests/stats/5edf6220f29b40b0fc95ad6e +[130620-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...42b7dbcb5e +[130620-elo1]: https://tests.stockfishchess.org/tests/view/5ee5b14dca6c451633a9a06f +[130620-elo8]: https://tests.stockfishchess.org/tests/view/5ee5b1c3ca6c451633a9a075 +[130620-master]: https://github.com/official-stockfish/Stockfish/commit/42b7dbcb5e20ae9015122601522be8b455787a4a +[130620-raw1]: https://tests.stockfishchess.org/tests/stats/5ee5b14dca6c451633a9a06f +[130620-raw8]: https://tests.stockfishchess.org/tests/stats/5ee5b1c3ca6c451633a9a075 +[290620-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...69d3be42a1 +[290620-elo1]: https://tests.stockfishchess.org/tests/view/5efa336b020eec13834a975d +[290620-master]: https://github.com/official-stockfish/Stockfish/commit/69d3be42a112645a9e599df615f730d61a5dca8c +[290620-raw1]: https://tests.stockfishchess.org/tests/stats/5efa336b020eec13834a975d +[170720-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...d89730d5c8 +[170720-elo1]: https://tests.stockfishchess.org/tests/view/5f14adebda64229ef7dc1788 +[170720-elo8]: https://tests.stockfishchess.org/tests/view/5f14ae36da64229ef7dc178a +[170720-master]: https://github.com/official-stockfish/Stockfish/commit/d89730d5c8dcf10eb9e1d91a81f903d9fc3c949a +[170720-raw1]: https://tests.stockfishchess.org/tests/stats/5f14adebda64229ef7dc1788 +[170720-raw8]: https://tests.stockfishchess.org/tests/stats/5f14ae36da64229ef7dc178a +[310720-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...9587eeeb5e +[310720-elo1]: https://tests.stockfishchess.org/tests/view/5f29002aa5abc164f05e4c4f +[310720-elo8]: https://tests.stockfishchess.org/tests/view/5f291999a5abc164f05e4c67 +[310720-master]: https://github.com/official-stockfish/Stockfish/commit/9587eeeb5ed29f834d4f956b92e0e732877c47a7 +[310720-raw1]: https://tests.stockfishchess.org/tests/stats/5f29002aa5abc164f05e4c4f +[310720-raw8]: https://tests.stockfishchess.org/tests/stats/5f291999a5abc164f05e4c67 +[060820-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...84f3e86790 +[060820-elo1]: https://tests.stockfishchess.org/tests/view/5f2c5a25b3ebe5cbfee85b8e +[060820-elo8]: https://tests.stockfishchess.org/tests/view/5f2c5a52b3ebe5cbfee85b91 +[060820-master]: https://github.com/official-stockfish/Stockfish/commit/84f3e867903f62480c33243dd0ecbffd342796fc +[060820-raw1]: https://tests.stockfishchess.org/tests/stats/5f2c5a25b3ebe5cbfee85b8e +[060820-raw8]: https://tests.stockfishchess.org/tests/stats/5f2c5a52b3ebe5cbfee85b91 +[SFNNUERN]: https://stockfishchess.org/blog/2020/introducing-nnue-evaluation/ "Release Notes" +[070820-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...84f3e86790 +[080820-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...add890a10b +[080820-elo1]: https://tests.stockfishchess.org/tests/view/5f2f0ff49081672066536b29 +[080820-master]: https://github.com/official-stockfish/Stockfish/commit/add890a10b8fe03e091520cd0af7383615c6c386 +[080820-raw1]: https://tests.stockfishchess.org/tests/stats/5f2f0ff49081672066536b29 +[110820-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...ea6220f381 +[110820-elo1]: https://tests.stockfishchess.org/tests/view/5f32844966a893ef3a025dde +[110820-elo8]: https://tests.stockfishchess.org/tests/view/5f32846e66a893ef3a025de0 +[110820-master]: https://github.com/official-stockfish/Stockfish/commit/ea6220f3813e5b76b444a02905eaf2c556bdb368 +[110820-raw1]: https://tests.stockfishchess.org/tests/stats/5f32844966a893ef3a025dde +[110820-raw8]: https://tests.stockfishchess.org/tests/stats/5f32846e66a893ef3a025de0 +[180820-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...fbae5614eb +[180820-elo1]: https://tests.stockfishchess.org/tests/view/5f3b7ff8b38d442594aabebc +[180820-elo8]: https://tests.stockfishchess.org/tests/view/5f3b8008b38d442594aabebe +[180820-master]: https://github.com/official-stockfish/Stockfish/commit/fbae5614eb1e82bccd37fbcfb0d2ca388b7a9a7d +[180820-raw1]: https://tests.stockfishchess.org/tests/stats/5f3b7ff8b38d442594aabebc +[180820-raw8]: https://tests.stockfishchess.org/tests/stats/5f3b8008b38d442594aabebe +[300820-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...e0bafa1911 +[300820-elo1]: https://tests.stockfishchess.org/tests/view/5f4b9b24ba100690c5cc5c7f +[300820-master]: https://github.com/official-stockfish/Stockfish/commit/e0bafa1911ede61b9268e0b461a5d8856d6cd6be +[300820-raw1]: https://tests.stockfishchess.org/tests/stats/5f4b9b24ba100690c5cc5c7f +[020920-dif]: https://github.com/official-stockfish/Stockfish/compare/c3483fa9a7...c306d83869 +[020920-elo1]: https://tests.stockfishchess.org/tests/view/5f4faef2ba100690c5cc5e79 +[020920-elo8]: https://tests.stockfishchess.org/tests/view/5f4faf0eba100690c5cc5e7b +[020920-master]: https://github.com/official-stockfish/Stockfish/commit/c306d838697011da0a960758dde3f7ede6849060 +[020920-raw1]: https://tests.stockfishchess.org/tests/stats/5f4faef2ba100690c5cc5e79 +[020920-raw8]: https://tests.stockfishchess.org/tests/stats/5f4faf0eba100690c5cc5e7b +[Stockfish 12]: https://github.com/official-stockfish/Stockfish/commit/c306d838697011da0a960758dde3f7ede6849060 +[SF12DP]: https://user-images.githubusercontent.com/64992190/156415498-30505e38-cfaa-4656-af7d-c5a007b168dd.png "Development Progress" +[SF12RN]: https://stockfishchess.org/blog/2020/stockfish-12/ "Release Notes" +[080920-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...0405f35403 +[080920-elo1]: https://tests.stockfishchess.org/tests/view/5f57f1762f5ee9c2a401eb7c +[080920-master]: https://github.com/official-stockfish/Stockfish/commit/0405f3540366cc16245d51531881c55d3726c8b5 +[080920-raw1]: https://tests.stockfishchess.org/tests/stats/5f57f1762f5ee9c2a401eb7c +[210920-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...485d517c68 +[210920-elo1]: https://tests.stockfishchess.org/tests/view/5f684d23ded68c240be72173 +[210920-elo8]: https://tests.stockfishchess.org/tests/view/5f684d42ded68c240be7217c +[210920-master]: https://github.com/official-stockfish/Stockfish/commit/485d517c687a2d3cb0b88cc8c198483759eaf2c7 +[210920-raw1]: https://tests.stockfishchess.org/tests/stats/5f684d23ded68c240be72173 +[210920-raw8]: https://tests.stockfishchess.org/tests/stats/5f684d42ded68c240be7217c +[280920-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...5af09cfda5 +[280920-elo1]: https://tests.stockfishchess.org/tests/view/5f724c7fc96941256e85618f +[280920-master]: https://github.com/official-stockfish/Stockfish/commit/5af09cfda5b71f9470ef233298e0f4233651337d +[280920-raw1]: https://tests.stockfishchess.org/tests/stats/5f724c7fc96941256e85618f +[181020-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...560c776397 +[181020-elo1]: https://tests.stockfishchess.org/tests/view/5f8c3442e669321c019e509f +[181020-elo8]: https://tests.stockfishchess.org/tests/view/5f8c346ae669321c019e50a1 +[181020-master]: https://github.com/official-stockfish/Stockfish/commit/560c776397483feaaa0deb5b666f46ff3f5b655f +[181020-raw1]: https://tests.stockfishchess.org/tests/stats/5f8c3442e669321c019e509f +[181020-raw8]: https://tests.stockfishchess.org/tests/stats/5f8c346ae669321c019e50a1 +[011120-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...dfc7f88650 +[011120-elo1]: https://tests.stockfishchess.org/tests/view/5f9e7c786a2c112b60691e39 +[011120-master]: https://github.com/official-stockfish/Stockfish/commit/dfc7f88650bf8bda4a381d36e209209cf63a9bcc +[011120-raw1]: https://tests.stockfishchess.org/tests/stats/5f9e7c786a2c112b60691e39 +[151120-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...f9595828eb +[151120-elo1]: https://tests.stockfishchess.org/tests/view/5fb11f8467cbf42301d6ab62 +[151120-elo8]: https://tests.stockfishchess.org/tests/view/5fb11fac67cbf42301d6ab64 +[151120-master]: https://github.com/official-stockfish/Stockfish/commit/f9595828eb7e5e970b0be3ee5f84ddd726845523 +[151120-raw1]: https://tests.stockfishchess.org/tests/stats/5fb11f8467cbf42301d6ab62 +[151120-raw8]: https://tests.stockfishchess.org/tests/stats/5fb11fac67cbf42301d6ab64 +[291120-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...7364006757 +[291120-elo1]: https://tests.stockfishchess.org/tests/view/5fc3ca3442a050a89f02c9bf +[291120-master]: https://github.com/official-stockfish/Stockfish/commit/736400675746c6b84a0bdf131babce1b07ade0df +[291120-raw1]: https://tests.stockfishchess.org/tests/stats/5fc3ca3442a050a89f02c9bf +[141220-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...a88a38c3a9 +[141220-elo1]: https://tests.stockfishchess.org/tests/view/5fd70ce51ac1691201888628 +[141220-elo8]: https://tests.stockfishchess.org/tests/view/5fd70d211ac169120188862d +[141220-master]: https://github.com/official-stockfish/Stockfish/commit/a88a38c3a91749181ffa5d6dc0af7314a70a1c41 +[141220-raw1]: https://tests.stockfishchess.org/tests/stats/5fd70ce51ac1691201888628 +[141220-raw8]: https://tests.stockfishchess.org/tests/stats/5fd70d211ac169120188862d +[311220-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...d21e421ad7 +[311220-elo1]: https://tests.stockfishchess.org/tests/view/5fee0bee6019e097de3eea80 +[311220-master]: https://github.com/official-stockfish/Stockfish/commit/d21e421ad74cff3b157d156d6ea8fdee3634e75b +[311220-raw1]: https://tests.stockfishchess.org/tests/stats/5fee0bee6019e097de3eea80 +[130121-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...6dddcecb09 +[130121-elo1]: https://tests.stockfishchess.org/tests/view/6000a2616019e097de3ef77d +[130121-elo8]: https://tests.stockfishchess.org/tests/view/6000a2796019e097de3ef77f +[130121-master]: https://github.com/official-stockfish/Stockfish/commit/6dddcecb09df268d93810a1a38deb116f97672af +[130121-raw1]: https://tests.stockfishchess.org/tests/stats/6000a2616019e097de3ef77d +[130121-raw8]: https://tests.stockfishchess.org/tests/stats/6000a2796019e097de3ef77f +[150221-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...40cb0f076a +[150221-elo1]: https://tests.stockfishchess.org/tests/view/602bcccf7f517a561bc49b11 +[150221-elo8]: https://tests.stockfishchess.org/tests/view/602bd7b27f517a561bc49b1e +[150221-master]: https://github.com/official-stockfish/Stockfish/commit/40cb0f076a62115af030c4524825d9ba73d61023 +[150221-raw1]: https://tests.stockfishchess.org/tests/stats/602bcccf7f517a561bc49b11 +[150221-raw8]: https://tests.stockfishchess.org/tests/stats/602bd7b27f517a561bc49b1e +[Stockfish 13]: https://github.com/official-stockfish/Stockfish/commit/3597f1942ec6f2cfbd50b905683739b0900ff5dd +[SF13DP]: https://user-images.githubusercontent.com/64992190/156415393-f800b8c2-344b-4de1-b74a-c9f1715012d5.png "Development Progress" +[SF13RN]: https://stockfishchess.org/blog/2021/stockfish-13/ "Release Notes" +[180221-dif]: https://github.com/official-stockfish/Stockfish/compare/c306d83869...3597f1942e +[260221-dif]: https://github.com/official-stockfish/Stockfish/compare/3597f1942e...0f3f5d85fb +[260221-elo1]: https://tests.stockfishchess.org/tests/view/603c9ed1e8971688486fff85 +[260221-master]: https://github.com/official-stockfish/Stockfish/commit/0f3f5d85fb5c9f75199f27fbf7a725ff3e8bb4dc +[260221-raw1]: https://tests.stockfishchess.org/tests/stats/603c9ed1e8971688486fff85 +[240321-dif]: https://github.com/official-stockfish/Stockfish/compare/3597f1942e...83eac08e75 +[240321-elo1]: https://tests.stockfishchess.org/tests/view/605c304bfa3cc97981cfaab7 +[240321-master]: https://github.com/official-stockfish/Stockfish/commit/83eac08e7562d93787f75eccd4b7781c4bd45dd3 +[240321-raw1]: https://tests.stockfishchess.org/tests/stats/605c304bfa3cc97981cfaab7 +[150421-dif]: https://github.com/official-stockfish/Stockfish/compare/3597f1942e...a7ab92ec25 +[150421-elo1]: https://tests.stockfishchess.org/tests/view/60795147162adf76afa5b7a1 +[150421-elo8]: https://tests.stockfishchess.org/tests/view/607951a5162adf76afa5b7a6 +[150421-master]: https://github.com/official-stockfish/Stockfish/commit/a7ab92ec25c91e8413630c52cfc2db6b4ecacf0c +[150421-raw1]: https://tests.stockfishchess.org/tests/stats/60795147162adf76afa5b7a1 +[150421-raw8]: https://tests.stockfishchess.org/tests/stats/607951a5162adf76afa5b7a6 +[220521-dif]: https://github.com/official-stockfish/Stockfish/compare/3597f1942e...a2f01c07eb +[220521-elo1]: https://tests.stockfishchess.org/tests/view/60a959adce8ea25a3ef04161 +[220521-master]: https://github.com/official-stockfish/Stockfish/commit/a2f01c07eb91524fc372bd82d6513ab058d3e043 +[220521-raw1]: https://tests.stockfishchess.org/tests/stats/60a959adce8ea25a3ef04161 +[140621-dif]: https://github.com/official-stockfish/Stockfish/compare/3597f1942e...f8c779dbe5 +[140621-elo1]: https://tests.stockfishchess.org/tests/view/60c70ba5457376eb8bcaaede +[140621-elo8]: https://tests.stockfishchess.org/tests/view/60c711ab457376eb8bcaaef5 +[140621-master]: https://github.com/official-stockfish/Stockfish/commit/f8c779dbe538315aa6f65556d0acf11640558504 +[140621-raw1]: https://tests.stockfishchess.org/tests/stats/60c70ba5457376eb8bcaaede +[140621-raw8]: https://tests.stockfishchess.org/tests/stats/60c711ab457376eb8bcaaef5 +[180621-dif]: https://github.com/official-stockfish/Stockfish/compare/3597f1942e...adfb23c029 +[180621-elo1]: https://tests.stockfishchess.org/tests/view/60cd9589457376eb8bcab62d +[180621-master]: https://github.com/official-stockfish/Stockfish/commit/adfb23c029e54c7522aadca1adf3e0b15fdcebcd +[180621-raw1]: https://tests.stockfishchess.org/tests/stats/60cd9589457376eb8bcab62d +[290621-dif]: https://github.com/official-stockfish/Stockfish/compare/3597f1942e...2275923d3c +[290621-elo1]: https://tests.stockfishchess.org/tests/view/60dae5363beab81350aca077 +[290621-elo8]: https://tests.stockfishchess.org/tests/view/60dae6033beab81350aca07b +[290621-master]: https://github.com/official-stockfish/Stockfish/commit/2275923d3cbca85b433a4d16d40fae5c8de6784a +[290621-raw1]: https://tests.stockfishchess.org/tests/stats/60dae5363beab81350aca077 +[290621-raw8]: https://tests.stockfishchess.org/tests/stats/60dae6033beab81350aca07b +[Stockfish 14]: https://github.com/official-stockfish/Stockfish/commit/773dff020968f7a6f590cfd53e8fd89f12e15e36 +[SF14DP]: https://user-images.githubusercontent.com/64992190/156415128-ee9d81c9-7360-47b2-a800-d11a7518bbb1.png "Development Progress" +[SF14RN]: https://stockfishchess.org/blog/2021/stockfish-14/ "Release Notes" +[020721-dif]: https://github.com/official-stockfish/Stockfish/compare/3597f1942e...773dff0209 +[260721-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...237ed1ef8f +[260721-elo1]: https://tests.stockfishchess.org/tests/view/60fe4fa2d8a6b65b2f3a7a59 +[260721-master]: https://github.com/official-stockfish/Stockfish/commit/237ed1ef8fddea77779e7fdcde8e4195d92f123b +[260721-raw1]: https://tests.stockfishchess.org/tests/stats/60fe4fa2d8a6b65b2f3a7a59 +[150821-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...d61d38586e +[150821-elo1]: https://tests.stockfishchess.org/tests/view/6118e9ae4977aa1525c9c746 +[150821-elo8]: https://tests.stockfishchess.org/tests/view/6118e9f04977aa1525c9c748 +[150821-master]: https://github.com/official-stockfish/Stockfish/commit/d61d38586ee35fd4d93445eb547e4af27cc86e6b +[150821-raw1]: https://tests.stockfishchess.org/tests/stats/6118e9ae4977aa1525c9c746 +[150821-raw8]: https://tests.stockfishchess.org/tests/stats/6118e9f04977aa1525c9c748 +[310821-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...2807dcfab6 +[310821-elo1]: https://tests.stockfishchess.org/tests/view/612e0b9fbb4956d8b78eb635 +[310821-master]: https://github.com/official-stockfish/Stockfish/commit/2807dcfab671bfc7a1bea79f5639dbbd505703ad +[310821-raw1]: https://tests.stockfishchess.org/tests/stats/612e0b9fbb4956d8b78eb635 +[150921-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...723f48dec0 +[150921-elo1]: https://tests.stockfishchess.org/tests/view/6142172c7315e7c73204a614 +[150921-elo8]: https://tests.stockfishchess.org/tests/view/614217467315e7c73204a616 +[150921-master]: https://github.com/official-stockfish/Stockfish/commit/723f48dec0eb05204b51cace54b033a5a85a66b9 +[150921-raw1]: https://tests.stockfishchess.org/tests/stats/6142172c7315e7c73204a614 +[150921-raw8]: https://tests.stockfishchess.org/tests/stats/614217467315e7c73204a616 +[061021-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...54a989930e +[061021-elo1]: https://tests.stockfishchess.org/tests/view/615dcb711a32f4036ac7fc47 +[061021-elo8]: https://tests.stockfishchess.org/tests/view/615dcb871a32f4036ac7fc49 +[061021-master]: https://github.com/official-stockfish/Stockfish/commit/54a989930ebed200c3278c725151e26a2c0da37a +[061021-raw1]: https://tests.stockfishchess.org/tests/stats/615dcb711a32f4036ac7fc47 +[061021-raw8]: https://tests.stockfishchess.org/tests/stats/615dcb871a32f4036ac7fc49 +[181021-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...67d0616483 +[181021-elo1]: https://tests.stockfishchess.org/tests/view/616ddbf44f95b438f7a860af +[181021-master]: https://github.com/official-stockfish/Stockfish/commit/67d06164833857d3497010e952fd0d2c5f00c095 +[181021-raw1]: https://tests.stockfishchess.org/tests/stats/616ddbf44f95b438f7a860af +[231021-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...2c86ae196d +[231021-elo1]: https://tests.stockfishchess.org/tests/view/6175c320af70c2be1788fa2b +[231021-elo8]: https://tests.stockfishchess.org/tests/view/6175c334af70c2be1788fa2d +[231021-master]: https://github.com/official-stockfish/Stockfish/commit/2c86ae196df8b2a1197e0de1853a6458e404a976 +[231021-raw1]: https://tests.stockfishchess.org/tests/stats/6175c320af70c2be1788fa2b +[231021-raw8]: https://tests.stockfishchess.org/tests/stats/6175c334af70c2be1788fa2d +[Stockfish 14.1]: https://github.com/official-stockfish/Stockfish/commit/7262fd5d14810b7b495b5038e348a448fda1bcc3 +[SF141RN]: https://stockfishchess.org/blog/2021/stockfish-14-1/ "Release Notes" +[281021-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...7262fd5d14 +[051121-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...a0259d8ab9 +[051121-elo1]: https://tests.stockfishchess.org/tests/view/6185b066d7a085ad008eefdf +[051121-master]: https://github.com/official-stockfish/Stockfish/commit/a0259d8ab9661b7f625474d2cbe18481ef69bbf2 +[051121-raw1]: https://tests.stockfishchess.org/tests/stats/6185b066d7a085ad008eefdf +[231121-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...092b27a6d0 +[231121-elo1]: https://tests.stockfishchess.org/tests/view/619d4e89c0a4ea18ba95a3b8 +[231121-elo8]: https://tests.stockfishchess.org/tests/view/619d4ea9c0a4ea18ba95a3ba +[231121-master]: https://github.com/official-stockfish/Stockfish/commit/092b27a6d0174f619fff9a53099ac9fdc5c2cb4e +[231121-raw1]: https://tests.stockfishchess.org/tests/stats/619d4e89c0a4ea18ba95a3b8 +[231121-raw8]: https://tests.stockfishchess.org/tests/stats/619d4ea9c0a4ea18ba95a3ba +[281121-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...af050e5eed +[281121-elo1]: https://tests.stockfishchess.org/tests/view/61a385323ce29cae572b920f +[281121-master]: https://github.com/official-stockfish/Stockfish/commit/af050e5eed43f2d360bc6d38a9d9ef64b6ce6ad8 +[281121-raw1]: https://tests.stockfishchess.org/tests/stats/61a385323ce29cae572b920f +[071221-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...b82d93ece4 +[071221-elo1]: https://tests.stockfishchess.org/tests/view/61af497356fcf33bce7dd428 +[071221-elo8]: https://tests.stockfishchess.org/tests/view/61af49ae56fcf33bce7dd430 +[071221-master]: https://github.com/official-stockfish/Stockfish/commit/b82d93ece484f833c994b40d9eddd959ba20ef92 +[071221-raw1]: https://tests.stockfishchess.org/tests/stats/61af497356fcf33bce7dd428 +[071221-raw8]: https://tests.stockfishchess.org/tests/stats/61af49ae56fcf33bce7dd430 +[141221-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...c6edf33f53 +[141221-elo1]: https://tests.stockfishchess.org/tests/view/61b896f8dffbe89a358145cb +[141221-master]: https://github.com/official-stockfish/Stockfish/commit/c6edf33f539f160d4a7009f3aac25e7ec5668eda +[141221-raw1]: https://tests.stockfishchess.org/tests/stats/61b896f8dffbe89a358145cb +[221221-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...7d82f0d1f4 +[221221-elo1]: https://tests.stockfishchess.org/tests/view/61c2f921a66b89b0007fff35 +[221221-elo8]: https://tests.stockfishchess.org/tests/view/61c2f986a66b89b0007fff54 +[221221-master]: https://github.com/official-stockfish/Stockfish/commit/7d82f0d1f4e25892e1901d25cf5cf6f6a2606c2a +[221221-raw1]: https://tests.stockfishchess.org/tests/stats/61c2f921a66b89b0007fff35 +[221221-raw8]: https://tests.stockfishchess.org/tests/stats/61c2f986a66b89b0007fff54 +[100122-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...44b1ba89a9 +[100122-elo1]: https://tests.stockfishchess.org/tests/view/61dc7d27aad48d052737b7a4 +[100122-master]: https://github.com/official-stockfish/Stockfish/commit/44b1ba89a95f394f3e180eb508f2d7798417c86e +[100122-raw1]: https://tests.stockfishchess.org/tests/stats/61dc7d27aad48d052737b7a4 +[290122-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...90d051952f +[290122-elo1]: https://tests.stockfishchess.org/tests/view/61f4f474f7fba9f1a4f17837 +[290122-elo8]: https://tests.stockfishchess.org/tests/view/61f4f485f7fba9f1a4f1783c +[290122-master]: https://github.com/official-stockfish/Stockfish/commit/90d051952f4fce415f09f316e24e1701aafa7a92 +[290122-raw1]: https://tests.stockfishchess.org/tests/stats/61f4f474f7fba9f1a4f17837 +[290122-raw8]: https://tests.stockfishchess.org/tests/stats/61f4f485f7fba9f1a4f1783c +[100222-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...cb9c2594fc +[100222-elo1]: https://tests.stockfishchess.org/tests/view/620562ffd71106ed12a449a6 +[100222-elo8]: https://tests.stockfishchess.org/tests/view/620822bed71106ed12a4af70 +[100222-master]: https://github.com/official-stockfish/Stockfish/commit/cb9c2594fcedc881ae8f8bfbfdf130cf89840e4c +[100222-raw1]: https://tests.stockfishchess.org/tests/stats/620562ffd71106ed12a449a6 +[100222-raw8]: https://tests.stockfishchess.org/tests/stats/620822bed71106ed12a4af70 +[170222-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...84b1940fca +[170222-elo1]: https://tests.stockfishchess.org/tests/view/6210f2e5b1792e8985f86e01 +[170222-elo8]: https://tests.stockfishchess.org/tests/view/62115f93b1792e8985f87eb3 +[170222-master]: https://github.com/official-stockfish/Stockfish/commit/84b1940fcae95bb0a641dda9e85cb96f8c21cd22 +[170222-raw1]: https://tests.stockfishchess.org/tests/stats/6210f2e5b1792e8985f86e01 +[170222-raw8]: https://tests.stockfishchess.org/tests/stats/62115f93b1792e8985f87eb3 +[190322-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...e31f97e3ba +[190322-elo1]: https://tests.stockfishchess.org/tests/view/6235d03aceed04483e6f16d8 +[190322-master]: https://github.com/official-stockfish/Stockfish/commit/e31f97e3baa52042fe60d6f4eeb50fe0d9e61013 +[190322-raw1]: https://tests.stockfishchess.org/tests/stats/6235d03aceed04483e6f16d8 +[170422-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...df2f7e7527 +[170422-elo1]: https://tests.stockfishchess.org/tests/view/625d156dff677a888877d1be +[170422-elo1uho]: https://tests.stockfishchess.org/tests/view/625d2400ff677a888877d3df +[170422-elo8]: https://tests.stockfishchess.org/tests/view/625d1586ff677a888877d1c5 +[170422-master]: https://github.com/official-stockfish/Stockfish/commit/df2f7e75276cc93a8cb8c70057903ab0edbd92bd +[170422-raw1]: https://tests.stockfishchess.org/tests/stats/625d156dff677a888877d1be +[170422-raw1uho]: https://tests.stockfishchess.org/tests/stats/625d2400ff677a888877d3df +[170422-raw8]: https://tests.stockfishchess.org/tests/stats/625d1586ff677a888877d1c5 +[Stockfish 15]: https://github.com/official-stockfish/Stockfish/commit/e6e324eb28fd49c1fc44b3b65784f85a773ec61c +[SF15DP]: https://user-images.githubusercontent.com/64992190/163892421-aa025052-53ab-4a53-9162-4043b6f021b0.png "Development Progress" +[SF15RN]: https://stockfishchess.org/blog/2022/stockfish-15/ "Release Notes" +[180422-dif]: https://github.com/official-stockfish/Stockfish/compare/773dff0209...e6e324eb28 +[140522-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...5372f81cc8 +[140522-elo1]: https://tests.stockfishchess.org/tests/view/627f9a5c56be8ced122d7df7 +[140522-elo8]: https://tests.stockfishchess.org/tests/view/627f9a7356be8ced122d7dfe +[140522-master]: https://github.com/official-stockfish/Stockfish/commit/5372f81cc81d5be3040db6f2dbfff108c460baf9 +[140522-raw1]: https://tests.stockfishchess.org/tests/stats/627f9a5c56be8ced122d7df7 +[140522-raw8]: https://tests.stockfishchess.org/tests/stats/627f9a7356be8ced122d7dfe +[160622-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...6edc29d720 +[160622-elo1]: https://tests.stockfishchess.org/tests/view/62aac16750949cfc241b0f15 +[160622-master]: https://github.com/official-stockfish/Stockfish/commit/6edc29d720b43383a04bd2208e9666a6f3173a64 +[160622-raw1]: https://tests.stockfishchess.org/tests/stats/62aac16750949cfc241b0f15 +[130722-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...4b4b7d1209 +[130722-elo1]: https://tests.stockfishchess.org/tests/view/62ceecfc877d5077fe6e4714 +[130722-elo8]: https://tests.stockfishchess.org/tests/view/62ceed0e877d5077fe6e4718 +[130722-master]: https://github.com/official-stockfish/Stockfish/commit/4b4b7d1209259811537634c68555d2a8f4af197c +[130722-raw1]: https://tests.stockfishchess.org/tests/stats/62ceecfc877d5077fe6e4714 +[130722-raw8]: https://tests.stockfishchess.org/tests/stats/62ceed0e877d5077fe6e4718 +[120822-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...1054a483ca +[120822-elo1]: https://tests.stockfishchess.org/tests/view/62f64a886f0a08af9f7767aa +[120822-elo1uho]: https://tests.stockfishchess.org/tests/view/62f9d9e623d42b50a8daf3b2 +[120822-elo8]: https://tests.stockfishchess.org/tests/view/62f64a9c6f0a08af9f7767ae +[120822-master]: https://github.com/official-stockfish/Stockfish/commit/1054a483ca0c560d30fb61617c0192cb4cd31528 +[120822-raw1]: https://tests.stockfishchess.org/tests/stats/62f64a886f0a08af9f7767aa +[120822-raw1uho]: https://tests.stockfishchess.org/tests/stats/62f9d9e623d42b50a8daf3b2 +[120822-raw8]: https://tests.stockfishchess.org/tests/stats/62f64a9c6f0a08af9f7767ae +[070922-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...5eeb96d0e7 +[070922-elo1]: https://tests.stockfishchess.org/tests/view/631835ed37f41b13973d7d81 +[070922-elo8]: https://tests.stockfishchess.org/tests/view/6318361537f41b13973d7d8a +[070922-master]: https://github.com/official-stockfish/Stockfish/commit/5eeb96d0e7d54686376305029c477c42478aa1d5 +[070922-raw1]: https://tests.stockfishchess.org/tests/stats/631835ed37f41b13973d7d81 +[070922-raw8]: https://tests.stockfishchess.org/tests/stats/6318361537f41b13973d7d8a +[051022-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...da937e219e +[051022-elo1]: https://tests.stockfishchess.org/tests/view/633df173fb7ccb2ea9bd732a +[051022-elo8]: https://tests.stockfishchess.org/tests/view/633df17efb7ccb2ea9bd732c +[051022-master]: https://github.com/official-stockfish/Stockfish/commit/da937e219ee7981966ac29fc11c43470a505ff18 +[051022-raw1]: https://tests.stockfishchess.org/tests/stats/633df173fb7ccb2ea9bd732a +[051022-raw8]: https://tests.stockfishchess.org/tests/stats/633df17efb7ccb2ea9bd732c +[301022-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...d09653df0d +[301022-elo1]: https://tests.stockfishchess.org/tests/view/635e9692a7c06f6479d40009 +[301022-elo1uho]: https://tests.stockfishchess.org/tests/view/635efedba7c06f6479d40ae6 +[301022-elo8]: https://tests.stockfishchess.org/tests/view/635e96b4a7c06f6479d4000c +[301022-master]: https://github.com/official-stockfish/Stockfish/commit/d09653df0d1bfec0af05ab2e8975e0d8e5cccba8 +[301022-raw1]: https://tests.stockfishchess.org/tests/stats/635e9692a7c06f6479d40009 +[301022-raw1uho]: https://tests.stockfishchess.org/tests/stats/635efedba7c06f6479d40ae6 +[301022-raw8]: https://tests.stockfishchess.org/tests/stats/635e96b4a7c06f6479d4000c +[021222-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...d60f5de967 +[021222-elo1]: https://tests.stockfishchess.org/tests/view/638a4dd7d2b9c924c4c6297b +[021222-elo1uho]: https://tests.stockfishchess.org/tests/view/638a5275d2b9c924c4c62a3c +[021222-elo8]: https://tests.stockfishchess.org/tests/view/638a4ddfd2b9c924c4c6297e +[021222-master]: https://github.com/official-stockfish/Stockfish/commit/d60f5de9670cc84ba7940b5815bc3e128da9423f +[021222-raw1]: https://tests.stockfishchess.org/tests/stats/638a4dd7d2b9c924c4c6297b +[021222-raw1uho]: https://tests.stockfishchess.org/tests/stats/638a5275d2b9c924c4c62a3c +[021222-raw8]: https://tests.stockfishchess.org/tests/stats/638a4ddfd2b9c924c4c6297e +[Stockfish 15.1]: https://github.com/official-stockfish/Stockfish/commit/758f9c9350abee36a5865ec701560db8ea62004d +[SF151RN]: https://stockfishchess.org/blog/2022/stockfish-15-1/ "Release Notes" +[041222-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...758f9c9350 +[191222-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...c2d507005c +[191222-elo1]: https://tests.stockfishchess.org/tests/view/63a0c0d3586179db6eb3fce6 +[191222-master]: https://github.com/official-stockfish/Stockfish/commit/c2d507005c51145211a7963af7c41026bd759d08 +[191222-raw1]: https://tests.stockfishchess.org/tests/stats/63a0c0d3586179db6eb3fce6 +[010123-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...be9bc420af +[010123-elo1]: https://tests.stockfishchess.org/tests/view/63b16fcc9b0bd9e1091ceab3 +[010123-elo1uho]: https://tests.stockfishchess.org/tests/view/63cd134d344bb01c191b48ae +[010123-elo8]: https://tests.stockfishchess.org/tests/view/63b16fd69b0bd9e1091ceab9 +[010123-master]: https://github.com/official-stockfish/Stockfish/commit/be9bc420afa11314c886c0aede4c4ae3d76f8a50 +[010123-raw1]: https://tests.stockfishchess.org/tests/stats/63b16fcc9b0bd9e1091ceab3 +[010123-raw1uho]: https://tests.stockfishchess.org/tests/stats/63cd134d344bb01c191b48ae +[010123-raw8]: https://tests.stockfishchess.org/tests/stats/63b16fd69b0bd9e1091ceab9 +[230123-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...596a528c6a +[230123-elo1]: https://tests.stockfishchess.org/tests/view/63ce238dc93e8828d0f03aaa +[230123-elo1uho]: https://tests.stockfishchess.org/tests/view/63ce23bcc93e8828d0f03ab6 +[230123-elo8]: https://tests.stockfishchess.org/tests/view/63ce2398c93e8828d0f03ab0 +[230123-master]: https://github.com/official-stockfish/Stockfish/commit/596a528c6a9ace6fb1a8407c86d972d96653418d +[230123-raw1]: https://tests.stockfishchess.org/tests/stats/63ce238dc93e8828d0f03aaa +[230123-raw1uho]: https://tests.stockfishchess.org/tests/stats/63ce23bcc93e8828d0f03ab6 +[230123-raw8]: https://tests.stockfishchess.org/tests/stats/63ce2398c93e8828d0f03ab0 +[090223-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...05dea2ca46 +[090223-elo1]: https://tests.stockfishchess.org/tests/view/63e498e6b5f425d71f771dbc +[090223-elo1uho]: https://tests.stockfishchess.org/tests/view/63e49921b5f425d71f771dc7 +[090223-elo8]: https://tests.stockfishchess.org/tests/view/63e498f0b5f425d71f771dbe +[090223-master]: https://github.com/official-stockfish/Stockfish/commit/05dea2ca4657dec10637bb53c4ad583f680e0677 +[090223-raw1]: https://tests.stockfishchess.org/tests/stats/63e498e6b5f425d71f771dbc +[090223-raw1uho]: https://tests.stockfishchess.org/tests/stats/63e49921b5f425d71f771dc7 +[090223-raw8]: https://tests.stockfishchess.org/tests/stats/63e498f0b5f425d71f771dbe +[180223-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...037ef3e18d +[180223-elo1]: https://tests.stockfishchess.org/tests/view/63f0d381e74a12625bcc61e5 +[180223-elo1uho]: https://tests.stockfishchess.org/tests/view/63f0d392e74a12625bcc61eb +[180223-master]: https://github.com/official-stockfish/Stockfish/commit/037ef3e18dc7f5455cc671995ae38d5b4d1fce4a +[180223-raw1]: https://tests.stockfishchess.org/tests/stats/63f0d381e74a12625bcc61e5 +[180223-raw1uho]: https://tests.stockfishchess.org/tests/stats/63f0d392e74a12625bcc61eb +[240223-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...472e726bff +[240223-elo1]: https://tests.stockfishchess.org/tests/view/63f90ecee74a12625bcdd191 +[240223-elo1uho]: https://tests.stockfishchess.org/tests/view/63f90edce74a12625bcdd197 +[240223-elo8]: https://tests.stockfishchess.org/tests/view/63f90ee7e74a12625bcdd19c +[240223-master]: https://github.com/official-stockfish/Stockfish/commit/472e726bff0d0e496dc8359cc071726a76317a72 +[240223-raw1]: https://tests.stockfishchess.org/tests/stats/63f90ecee74a12625bcdd191 +[240223-raw1uho]: https://tests.stockfishchess.org/tests/stats/63f90edce74a12625bcdd197 +[240223-raw8]: https://tests.stockfishchess.org/tests/stats/63f90ee7e74a12625bcdd19c +[190323-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...02e4697055 +[190323-elo1]: https://tests.stockfishchess.org/tests/view/6416e54965775d3b539ebf93 +[190323-elo1uho]: https://tests.stockfishchess.org/tests/view/6416e57365775d3b539ebf9e +[190323-elo8]: https://tests.stockfishchess.org/tests/view/6416e55265775d3b539ebf96 +[190323-master]: https://github.com/official-stockfish/Stockfish/commit/02e4697055519ed206fa76e4ef9abb9f156cd1a0 +[190323-raw1]: https://tests.stockfishchess.org/tests/stats/6416e54965775d3b539ebf93 +[190323-raw1uho]: https://tests.stockfishchess.org/tests/stats/6416e57365775d3b539ebf9e +[190323-raw8]: https://tests.stockfishchess.org/tests/stats/6416e55265775d3b539ebf96 +[010423-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...6a6e32dfc8 +[010423-elo1]: https://tests.stockfishchess.org/tests/view/6428409fdb43ab2ba6fa2e8d +[010423-elo1uho]: https://tests.stockfishchess.org/tests/view/642840b9db43ab2ba6fa2e97 +[010423-elo8]: https://tests.stockfishchess.org/tests/view/642840a8db43ab2ba6fa2e93 +[010423-master]: https://github.com/official-stockfish/Stockfish/commit/6a6e32dfc80488dfdcd6c23e6001063b47729e890 +[010423-raw1]: https://tests.stockfishchess.org/tests/stats/6428409fdb43ab2ba6fa2e8d +[010423-raw1uho]: https://tests.stockfishchess.org/tests/stats/642840b9db43ab2ba6fa2e97 +[010423-raw8]: https://tests.stockfishchess.org/tests/stats/642840a8db43ab2ba6fa2e93 +[220423-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...ba06c480a7 +[220423-elo1]: https://tests.stockfishchess.org/tests/view/6443a560bc6f5930d1d36ba4 +[220423-elo1uho]: https://tests.stockfishchess.org/tests/view/6443a581bc6f5930d1d36baa +[220423-elo8]: https://tests.stockfishchess.org/tests/view/6443a569bc6f5930d1d36ba6 +[220423-master]: https://github.com/official-stockfish/Stockfish/commit/ba06c480a752458a8159db0c9110bd3b7e34145a +[220423-raw1]: https://tests.stockfishchess.org/tests/stats/6443a560bc6f5930d1d36ba4 +[220423-raw1uho]: https://tests.stockfishchess.org/tests/stats/6443a581bc6f5930d1d36baa +[220423-raw8]: https://tests.stockfishchess.org/tests/stats/6443a569bc6f5930d1d36ba6 +[070523-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...65e2150501 +[070523-elo1]: https://tests.stockfishchess.org/tests/view/64580c878ab4042ca7893e81 +[070523-elo1uho]: https://tests.stockfishchess.org/tests/view/64580ca68ab4042ca7893e86 +[070523-elo8]: https://tests.stockfishchess.org/tests/view/64580c8e8ab4042ca7893e83 +[070523-master]: https://github.com/official-stockfish/Stockfish/commit/65e2150501b87e6ce00fae4e3f056444f39462fd +[070523-raw1]: https://tests.stockfishchess.org/tests/stats/64580c878ab4042ca7893e81 +[070523-raw1uho]: https://tests.stockfishchess.org/tests/stats/64580ca68ab4042ca7893e86 +[070523-raw8]: https://tests.stockfishchess.org/tests/stats/64580c8e8ab4042ca7893e83 +[040623-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...8dea070538 +[040623-elo1]: https://tests.stockfishchess.org/tests/view/647d01b97cf638f0f53fa028 +[040623-elo1uho]: https://tests.stockfishchess.org/tests/view/647d01c97cf638f0f53fa02b +[040623-elo8]: https://tests.stockfishchess.org/tests/view/647d01db7cf638f0f53fa02e +[040623-master]: https://github.com/official-stockfish/Stockfish/commit/8dea070538dcad790de3c5b9720bdbb836a32440 +[040623-raw1]: https://tests.stockfishchess.org/tests/stats/647d01b97cf638f0f53fa028 +[040623-raw1uho]: https://tests.stockfishchess.org/tests/stats/647d01c97cf638f0f53fa02b +[040623-raw8]: https://tests.stockfishchess.org/tests/stats/647d01db7cf638f0f53fa02e +[120623-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...38e61663d8 +[120623-elo1]: https://tests.stockfishchess.org/tests/view/64876b43713491385c8030ec +[120623-elo1uho]: https://tests.stockfishchess.org/tests/view/64876b6b713491385c8030f6 +[120623-elo8]: https://tests.stockfishchess.org/tests/view/64876b51713491385c8030f0 +[120623-master]: https://github.com/official-stockfish/Stockfish/commit/38e61663d836e062af0bc002814ad5149c4b7729 +[120623-raw1]: https://tests.stockfishchess.org/tests/stats/64876b43713491385c8030ec +[120623-raw1uho]: https://tests.stockfishchess.org/tests/stats/64876b6b713491385c8030f6 +[120623-raw8]: https://tests.stockfishchess.org/tests/stats/64876b51713491385c8030f0 +[220623-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...a49b3ba7ed +[220623-elo1]: https://tests.stockfishchess.org/tests/view/6494094adc7002ce609c99a4 +[220623-elo1uho]: https://tests.stockfishchess.org/tests/view/6494097ddc7002ce609c99b7 +[220623-elo8]: https://tests.stockfishchess.org/tests/view/64940956dc7002ce609c99a7 +[220623-master]: https://github.com/official-stockfish/Stockfish/commit/a49b3ba7ed5d9be9151c8ceb5eed40efe3387c75 +[220623-raw1]: https://tests.stockfishchess.org/tests/stats/6494094adc7002ce609c99a4 +[220623-raw1uho]: https://tests.stockfishchess.org/tests/stats/6494097ddc7002ce609c99b7 +[220623-raw8]: https://tests.stockfishchess.org/tests/stats/64940956dc7002ce609c99a7 + +[Stockfish 16]: https://github.com/official-stockfish/Stockfish/commit/68e1e9b381 +[SF16DP]: https://github.com/official-stockfish/Stockfish/assets/63931154/5297318e-89fb-407f-a8a8-9e4278d90eda "Development Progress" +[SF16RN]: https://stockfishchess.org/blog/2023/stockfish-16/ "Release Notes" +[290623-dif]: https://github.com/official-stockfish/Stockfish/compare/e6e324eb28...68e1e9b381 + +[190723-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...5ea1cbc778 +[190723-elo1]: https://tests.stockfishchess.org/tests/view/64b83cd1dc56e1650abad318 +[190723-elo8]: https://tests.stockfishchess.org/tests/view/64b83cdbdc56e1650abad31a +[190723-master]: https://github.com/official-stockfish/Stockfish/commit/5ea1cbc778508a9a7b720becaf22dd96a4472826 +[190723-raw1]: https://tests.stockfishchess.org/tests/stats/64b83cd1dc56e1650abad318 +[190723-raw8]: https://tests.stockfishchess.org/tests/stats/64b83cdbdc56e1650abad31a +[130823-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...9b80897657 +[130823-elo1]: https://tests.stockfishchess.org/tests/view/64d8af545b17f7c21c0e854c +[130823-elo8]: https://tests.stockfishchess.org/tests/view/64d8af655b17f7c21c0e854f +[130823-master]: https://github.com/official-stockfish/Stockfish/commit/9b80897657bde99cfb6568d8bd3386c3999f22c4 +[130823-raw1]: https://tests.stockfishchess.org/tests/stats/64d8af545b17f7c21c0e854c +[130823-raw8]: https://tests.stockfishchess.org/tests/stats/64d8af655b17f7c21c0e854f +[110923-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...b9319c4fa4 +[110923-elo1]: https://tests.stockfishchess.org/tests/view/64ff804d2cd016da89ab747f +[110923-elo8]: https://tests.stockfishchess.org/tests/view/64ff80602cd016da89ab7484 +[110923-master]: https://github.com/official-stockfish/Stockfish/commit/b9319c4fa4f42438f484d144be9a1306765cf998 +[110923-raw1]: https://tests.stockfishchess.org/tests/stats/64ff804d2cd016da89ab747f +[110923-raw8]: https://tests.stockfishchess.org/tests/stats/64ff80602cd016da89ab7484 +[220923-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...70ba9de85c +[220923-elo1]: https://tests.stockfishchess.org/tests/view/650dcf1bfb151d43ae6d704f +[220923-elo8]: https://tests.stockfishchess.org/tests/view/650dcf25fb151d43ae6d7052 +[220923-master]: https://github.com/official-stockfish/Stockfish/commit/70ba9de85cddc5460b1ec53e0a99bee271e26ece +[220923-raw1]: https://tests.stockfishchess.org/tests/stats/650dcf1bfb151d43ae6d704f +[220923-raw8]: https://tests.stockfishchess.org/tests/stats/650dcf25fb151d43ae6d7052 +[081023-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...7a4de96159 +[081023-elo1]: https://tests.stockfishchess.org/tests/view/652244f53125598fc7eb225b +[081023-elo8]: https://tests.stockfishchess.org/tests/view/652245013125598fc7eb225d +[081023-master]: https://github.com/official-stockfish/Stockfish/commit/7a4de96159f76f2465d474d76e08a1c8ca3383b8 +[081023-raw1]: https://tests.stockfishchess.org/tests/stats/652244f53125598fc7eb225b +[081023-raw8]: https://tests.stockfishchess.org/tests/stats/652245013125598fc7eb225d +[231023-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...49ece9f791 +[231023-elo1]: https://tests.stockfishchess.org/tests/view/6536be80cc309ae83955ad1a +[231023-elo8]: https://tests.stockfishchess.org/tests/view/6536be88cc309ae83955ad1d +[231023-master]: https://github.com/official-stockfish/Stockfish/commit/49ece9f791b84a261f2a8865d2de51c20a520bc6 +[231023-raw1]: https://tests.stockfishchess.org/tests/stats/6536be80cc309ae83955ad1a +[231023-raw8]: https://tests.stockfishchess.org/tests/stats/6536be88cc309ae83955ad1d +[031123-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...b4b704e686 +[031123-elo1]: https://tests.stockfishchess.org/tests/view/65456b94136acbc573523e28 +[031123-elo8]: https://tests.stockfishchess.org/tests/view/65456b9e136acbc573523e2c +[031123-master]: https://github.com/official-stockfish/Stockfish/commit/b4b704e6866bde21c69cd53457a6a91a15182b36 +[031123-raw1]: https://tests.stockfishchess.org/tests/stats/65456b94136acbc573523e28 +[031123-raw8]: https://tests.stockfishchess.org/tests/stats/65456b9e136acbc573523e2c +[021223-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...08cdbca56f +[021223-elo1]: https://tests.stockfishchess.org/tests/view/656b4e3a136acbc573556df1 +[021223-elo8]: https://tests.stockfishchess.org/tests/view/656b4e3e136acbc573556df3 +[021223-master]: https://github.com/official-stockfish/Stockfish/commit/08cdbca56fac98513481683a92eb1ecdc00d3f6e +[021223-raw1]: https://tests.stockfishchess.org/tests/stats/656b4e3a136acbc573556df1 +[021223-raw8]: https://tests.stockfishchess.org/tests/stats/656b4e3e136acbc573556df3 +[311223-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...3cfaef7431 +[311223-elo1]: https://tests.stockfishchess.org/tests/view/6591bf1a79aa8af82b958776 +[311223-elo8]: https://tests.stockfishchess.org/tests/view/6591bf2c79aa8af82b95877a +[311223-master]: https://github.com/official-stockfish/Stockfish/commit/3cfaef74311e943298a9a82bce5717d272338e66 +[311223-raw1]: https://tests.stockfishchess.org/tests/stats/6591bf1a79aa8af82b958776 +[311223-raw8]: https://tests.stockfishchess.org/tests/stats/6591bf2c79aa8af82b95877a +[070124-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...7c5e3f2865 +[070124-elo1]: https://tests.stockfishchess.org/tests/view/659b0cee79aa8af82b963dab +[070124-elo8]: https://tests.stockfishchess.org/tests/view/659b0cf179aa8af82b963dae +[070124-master]: https://github.com/official-stockfish/Stockfish/commit/7c5e3f28655607288a980645e6b2ce600a627b11 +[070124-raw1]: https://tests.stockfishchess.org/tests/stats/659b0cee79aa8af82b963dab +[070124-raw8]: https://tests.stockfishchess.org/tests/stats/659b0cf179aa8af82b963dae +[210124-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...a6fd17f27d +[210124-elo1]: https://tests.stockfishchess.org/tests/view/65ad061879aa8af82b979da1 +[210124-elo8]: https://tests.stockfishchess.org/tests/view/65ad061f79aa8af82b979da3 +[210124-master]: https://github.com/official-stockfish/Stockfish/commit/a6fd17f27d7675332166e9e6ea8210237281fc77 +[210124-raw1]: https://tests.stockfishchess.org/tests/stats/65ad061879aa8af82b979da1 +[210124-raw8]: https://tests.stockfishchess.org/tests/stats/65ad061f79aa8af82b979da3 +[110224-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...7ccde25baf +[110224-elo1]: https://tests.stockfishchess.org/tests/view/65c9391b1d8e83c78bfcdd46 +[110224-elo8]: https://tests.stockfishchess.org/tests/view/65c939221d8e83c78bfcdd48 +[110224-master]: https://github.com/official-stockfish/Stockfish/commit/7ccde25baf03e77926644b282fed68ba0b5ddf95 +[110224-raw1]: https://tests.stockfishchess.org/tests/stats/65c9391b1d8e83c78bfcdd46 +[110224-raw8]: https://tests.stockfishchess.org/tests/stats/65c939221d8e83c78bfcdd48 +[170224-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...fc41f64dfd +[170224-elo1]: https://tests.stockfishchess.org/tests/view/65d666051d8e83c78bfddbd6 +[170224-elo8]: https://tests.stockfishchess.org/tests/view/65d666051d8e83c78bfddbd8 +[170224-master]: https://github.com/official-stockfish/Stockfish/commit/fc41f64dfd8a61d0e275ddbecec292833458b86a +[170224-raw1]: https://tests.stockfishchess.org/tests/stats/65d666051d8e83c78bfddbd6 +[170224-raw8]: https://tests.stockfishchess.org/tests/stats/65d666051d8e83c78bfddbd8 + +[Stockfish 16.1]: https://github.com/official-stockfish/Stockfish/commit/e67cc979fd2c0e66dfc2b2f2daa0117458cfc462 +[SF161RN]: https://stockfishchess.org/blog/2024/stockfish-16-1/ "Release Notes" +[240224-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...e67cc979fd + +[120324-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...627974c99f +[120324-elo1]: https://tests.stockfishchess.org/tests/view/65f0798e0ec64f0526c463e5 +[120324-elo8]: https://tests.stockfishchess.org/tests/view/65f079910ec64f0526c463e7 +[120324-master]: https://github.com/official-stockfish/Stockfish/commit/627974c99fcd5a3dcbd5a8e0eb12f2afeb2d0a9a +[120324-raw1]: https://tests.stockfishchess.org/tests/stats/65f0798e0ec64f0526c463e5 +[120324-raw8]: https://tests.stockfishchess.org/tests/stats/65f079910ec64f0526c463e7 +[290324-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...e13e4cfb83 +[290324-elo1]: https://tests.stockfishchess.org/tests/view/660688190ec64f0526c59e5a +[290324-elo8]: https://tests.stockfishchess.org/tests/view/660688240ec64f0526c59e5c +[290324-master]: https://github.com/official-stockfish/Stockfish/commit/e13e4cfb8340cdb26a00679681a0f163c6b4f0a9 +[290324-raw1]: https://tests.stockfishchess.org/tests/stats/660688190ec64f0526c59e5a +[290324-raw8]: https://tests.stockfishchess.org/tests/stats/660688240ec64f0526c59e5c +[110424-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...249eec6715 +[110424-elo1]: https://tests.stockfishchess.org/tests/view/66184ce85a4693796d966cba +[110424-elo8]: https://tests.stockfishchess.org/tests/view/66184cf15a4693796d966cbc +[110424-master]: https://github.com/official-stockfish/Stockfish/commit/249eec67152d334d76c0f981907a6f5787289443 +[110424-raw1]: https://tests.stockfishchess.org/tests/stats/66184ce85a4693796d966cba +[110424-raw8]: https://tests.stockfishchess.org/tests/stats/66184cf15a4693796d966cbc +[240424-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...49ef4c935a +[240424-elo1]: https://tests.stockfishchess.org/tests/view/66293d2a3fe04ce4cefc7a5f +[240424-elo8]: https://tests.stockfishchess.org/tests/view/66293d2e3fe04ce4cefc7a61 +[240424-master]: https://github.com/official-stockfish/Stockfish/commit/49ef4c935a5cb0e4d94096e6354caa06b36b3e3c +[240424-raw1]: https://tests.stockfishchess.org/tests/stats/66293d2a3fe04ce4cefc7a5f +[240424-raw8]: https://tests.stockfishchess.org/tests/stats/66293d2e3fe04ce4cefc7a61 +[050524-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...070e564c38 +[050524-elo1]: https://tests.stockfishchess.org/tests/view/663787949819650825aa6773 +[050524-elo8]: https://tests.stockfishchess.org/tests/view/663787959819650825aa6777 +[050524-master]: https://github.com/official-stockfish/Stockfish/commit/070e564c389eb2c263f3982060ab5899b67d0a62 +[050524-raw1]: https://tests.stockfishchess.org/tests/stats/663787949819650825aa6773 +[050524-raw8]: https://tests.stockfishchess.org/tests/stats/663787959819650825aa6777 +[130524-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...e608eab8dd +[130524-elo1]: https://tests.stockfishchess.org/tests/view/6641a8bef9f4e8fc783cb991 +[130524-elo8]: https://tests.stockfishchess.org/tests/view/6641a8c8f9f4e8fc783cb993 +[130524-master]: https://github.com/official-stockfish/Stockfish/commit/e608eab8dd9f7bd68f192d56d742f621674b8fa8 +[130524-raw1]: https://tests.stockfishchess.org/tests/stats/6641a8bef9f4e8fc783cb991 +[130524-raw8]: https://tests.stockfishchess.org/tests/stats/6641a8c8f9f4e8fc783cb993 +[180524-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...99f1bacfd6 +[180524-elo1]: https://tests.stockfishchess.org/tests/view/6648e29e02679895021d0594 +[180524-elo8]: https://tests.stockfishchess.org/tests/view/6648e2a602679895021d0596 +[180524-master]: https://github.com/official-stockfish/Stockfish/commit/99f1bacfd6864afca86ae74f33232b9cdfb3828c +[180524-raw1]: https://tests.stockfishchess.org/tests/stats/6648e29e02679895021d0594 +[180524-raw8]: https://tests.stockfishchess.org/tests/stats/6648e2a602679895021d0596 +[280524-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...a169c78b6d +[280524-elo1]: https://tests.stockfishchess.org/tests/view/6656c2f46b0e318cefa8bbd8 +[280524-elo8]: https://tests.stockfishchess.org/tests/view/6656c30a6b0e318cefa8bbdb +[280524-master]: https://github.com/official-stockfish/Stockfish/commit/a169c78b6d3b082068deb49a39aaa1fd75464c7f +[280524-raw1]: https://tests.stockfishchess.org/tests/stats/6656c2f46b0e318cefa8bbd8 +[280524-raw8]: https://tests.stockfishchess.org/tests/stats/6656c30a6b0e318cefa8bbdb +[080624-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...e271059e08 +[080624-elo1]: https://tests.stockfishchess.org/tests/view/6664d56822234461cef58e71 +[080624-elo8]: https://tests.stockfishchess.org/tests/view/6664d56b22234461cef58e73 +[080624-master]: https://github.com/official-stockfish/Stockfish/commit/e271059e08c6258420af12897367ea2149220171 +[080624-raw1]: https://tests.stockfishchess.org/tests/stats/6664d56822234461cef58e71 +[080624-raw8]: https://tests.stockfishchess.org/tests/stats/6664d56b22234461cef58e73 +[010724-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...6138a0fd0e +[010724-elo1]: https://tests.stockfishchess.org/tests/view/6683a08cc4f539faa03268e3 +[010724-elo8]: https://tests.stockfishchess.org/tests/view/6683a096c4f539faa03268e5 +[010724-master]: https://github.com/official-stockfish/Stockfish/commit/6138a0fd0e43753a86e4a170a5f6e2b7b6752677 +[010724-raw1]: https://tests.stockfishchess.org/tests/stats/6683a08cc4f539faa03268e3 +[010724-raw8]: https://tests.stockfishchess.org/tests/stats/6683a096c4f539faa03268e5 +[090724-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...362a77a345 +[090724-elo1]: https://tests.stockfishchess.org/tests/view/668d79a65034141ae5999e43 +[090724-elo8]: https://tests.stockfishchess.org/tests/view/668d79ae5034141ae5999e45 +[090724-master]: https://github.com/official-stockfish/Stockfish/commit/362a77a3450335e1940020c080bf3b7b361c594a +[090724-raw1]: https://tests.stockfishchess.org/tests/stats/668d79a65034141ae5999e43 +[090724-raw8]: https://tests.stockfishchess.org/tests/stats/668d79ae5034141ae5999e45 +[230724-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...b55217fd02 +[230724-elo1]: https://tests.stockfishchess.org/tests/view/669feadf4ff211be9d4ecaf4 +[230724-elo8]: https://tests.stockfishchess.org/tests/view/669feae74ff211be9d4ecaf6 +[230724-master]: https://github.com/official-stockfish/Stockfish/commit/b55217fd02d8e5bc0754e5f27bc84df7b01479a6 +[230724-raw1]: https://tests.stockfishchess.org/tests/stats/669feadf4ff211be9d4ecaf4 +[230724-raw8]: https://tests.stockfishchess.org/tests/stats/669feae74ff211be9d4ecaf6 +[200824-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...9fb58328e3 +[200824-elo1]: https://tests.stockfishchess.org/tests/view/66c4f20021503a509c13b8b7 +[200824-elo8]: https://tests.stockfishchess.org/tests/view/66c4f21721503a509c13b8b9 +[200824-master]: https://github.com/official-stockfish/Stockfish/commit/9fb58328e363d84e3cf720b018e639b139ba95c2 +[200824-raw1]: https://tests.stockfishchess.org/tests/stats/66c4f20021503a509c13b8b7 +[200824-raw8]: https://tests.stockfishchess.org/tests/stats/66c4f21721503a509c13b8b9 +[030924-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...38e0cc7b90 +[030924-elo1]: https://tests.stockfishchess.org/tests/view/66d738ba9de3e7f9b33d159a +[030924-elo8]: https://tests.stockfishchess.org/tests/view/66d738c69de3e7f9b33d159c +[030924-master]: https://github.com/official-stockfish/Stockfish/commit/38e0cc7b909796c1a71d9c07b636698b69420975 +[030924-raw1]: https://tests.stockfishchess.org/tests/stats/66d738ba9de3e7f9b33d159a +[030924-raw8]: https://tests.stockfishchess.org/tests/stats/66d738c69de3e7f9b33d159c + +[Stockfish 17]: https://github.com/official-stockfish/Stockfish/commit/e0bfc4b69bbe928d6f474a46560bcc3b3f6709aa +[SF17DP]: https://github.com/user-attachments/assets/9e3c74ea-93bf-4f4d-94ca-a344efbc2da6 "Development Progress" +[SF17RN]: https://stockfishchess.org/blog/2024/stockfish-17/ "Release Notes" +[060924-dif]: https://github.com/official-stockfish/Stockfish/compare/68e1e9b381...e0bfc4b69b + +[121024-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...9766db8139 +[121024-elo1]: https://tests.stockfishchess.org/tests/view/670b8b7f86d5ee47d953c2c3 +[121024-elo8]: https://tests.stockfishchess.org/tests/view/670b8b8086d5ee47d953c2c5 +[121024-master]: https://github.com/official-stockfish/Stockfish/commit/9766db8139ce8815110c15bdde8381d0564a63fa +[121024-raw1]: https://tests.stockfishchess.org/tests/stats/670b8b7f86d5ee47d953c2c3 +[121024-raw8]: https://tests.stockfishchess.org/tests/stats/670b8b8086d5ee47d953c2c5 +[131124-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...82b092ca48 +[131124-elo1]: https://tests.stockfishchess.org/tests/view/6735001986d5ee47d953ea7c +[131124-elo8]: https://tests.stockfishchess.org/tests/view/6735001986d5ee47d953ea7e +[131124-master]: https://github.com/official-stockfish/Stockfish/commit/82b092ca48c2efeadf2108a8351bb1309c4b7780 +[131124-raw1]: https://tests.stockfishchess.org/tests/stats/6735001986d5ee47d953ea7c +[131124-raw8]: https://tests.stockfishchess.org/tests/stats/6735001986d5ee47d953ea7e +[081224-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...a8b6bf1b1a +[081224-elo1]: https://tests.stockfishchess.org/tests/view/6755ed2e86d5ee47d9541a2a +[081224-elo8]: https://tests.stockfishchess.org/tests/view/6755ed2e86d5ee47d9541a2c +[081224-master]: https://github.com/official-stockfish/Stockfish/commit/a8b6bf1b1a978775ad15ae677d8d425ccd05304b +[081224-raw1]: https://tests.stockfishchess.org/tests/stats/6755ed2e86d5ee47d9541a2a +[081224-raw8]: https://tests.stockfishchess.org/tests/stats/6755ed2e86d5ee47d9541a2c +[060125-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...c76c179361 +[060125-elo1]: https://tests.stockfishchess.org/tests/view/677b1aaf3b4d66d285666284 +[060125-elo8]: https://tests.stockfishchess.org/tests/view/677b1ab53b4d66d285666286 +[060125-master]: https://github.com/official-stockfish/Stockfish/commit/c76c1793615b17b97dd504f8c81c1ff2c63c232a +[060125-raw1]: https://tests.stockfishchess.org/tests/stats/677b1aaf3b4d66d285666284 +[060125-raw8]: https://tests.stockfishchess.org/tests/stats/677b1ab53b4d66d285666286 +[120125-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...c085670b84 +[120125-elo1]: https://tests.stockfishchess.org/tests/view/678429ab6ddf09c0b4b70de2 +[120125-elo8]: https://tests.stockfishchess.org/tests/view/678429b06ddf09c0b4b70de5 +[120125-master]: https://github.com/official-stockfish/Stockfish/commit/c085670b8474dd2137446ff278f6b73f4374cc68 +[120125-raw1]: https://tests.stockfishchess.org/tests/stats/678429ab6ddf09c0b4b70de2 +[120125-raw8]: https://tests.stockfishchess.org/tests/stats/678429b06ddf09c0b4b70de5 +[250125-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...27e747d1d7 +[250125-elo1]: https://tests.stockfishchess.org/tests/view/67962374f6281b7d7b186dae +[250125-elo8]: https://tests.stockfishchess.org/tests/view/6796237cf6281b7d7b186dc4 +[250125-master]: https://github.com/official-stockfish/Stockfish/commit/27e747d1d727386bec6eea01456fcbeae604bfe3 +[250125-raw1]: https://tests.stockfishchess.org/tests/stats/67962374f6281b7d7b186dae +[250125-raw8]: https://tests.stockfishchess.org/tests/stats/6796237cf6281b7d7b186dc4 +[040225-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...e852d9880a +[040225-elo1]: https://tests.stockfishchess.org/tests/view/67a27b0ceb183d11c659458b +[040225-elo8]: https://tests.stockfishchess.org/tests/view/67a27b0eeb183d11c659458d +[040225-master]: https://github.com/official-stockfish/Stockfish/commit/e852d9880a6d3e25d92b6db8216f497ad98c2c57 +[040225-raw1]: https://tests.stockfishchess.org/tests/stats/67a27b0ceb183d11c659458b +[040225-raw8]: https://tests.stockfishchess.org/tests/stats/67a27b0eeb183d11c659458d +[050225-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...d66e603070 +[050225-elo1]: https://tests.stockfishchess.org/tests/view/67a3a7df19f522d3866d2b68 +[050225-elo8]: https://tests.stockfishchess.org/tests/view/67a3a7ed19f522d3866d2b6a +[050225-master]: https://github.com/official-stockfish/Stockfish/commit/d66e603070a4ae76dcc8aeae69882d7d10ac3846 +[050225-raw1]: https://tests.stockfishchess.org/tests/stats/67a3a7df19f522d3866d2b68 +[050225-raw8]: https://tests.stockfishchess.org/tests/stats/67a3a7ed19f522d3866d2b6a +[240225-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...93b966829b +[240225-elo1]: https://tests.stockfishchess.org/tests/view/67bcb7fe28436466447786c7 +[240225-elo8]: https://tests.stockfishchess.org/tests/view/67bcb80028436466447786c9 +[240225-master]: https://github.com/official-stockfish/Stockfish/commit/93b966829bd7d2d9d9dce49f11e20125f48d0cfd +[240225-raw1]: https://tests.stockfishchess.org/tests/stats/67bcb7fe28436466447786c7 +[240225-raw8]: https://tests.stockfishchess.org/tests/stats/67bcb80028436466447786c9 +[210325-dif]: https://github.com/official-stockfish/Stockfish/compare/e0bfc4b69b...6ceaca4c7b +[210325-elo1]: https://tests.stockfishchess.org/tests/view/67dd3f518c7f315cc372ac68 +[210325-elo8]: https://tests.stockfishchess.org/tests/view/67dd3f508c7f315cc372ac66 +[210325-master]: https://github.com/official-stockfish/Stockfish/commit/6ceaca4c7b2cc1fa87617b1b9e83d38d8e880924 +[210325-raw1]: https://tests.stockfishchess.org/tests/stats/67dd3f518c7f315cc372ac68 +[210325-raw8]: https://tests.stockfishchess.org/tests/stats/67dd3f508c7f315cc372ac66 + +[graph-current]: https://docs.google.com/spreadsheets/u/2/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=429149495&format=image +[graph-elo1]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=1631702142&format=image +[graph-elo8]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=991230633&format=image +[graph-nelo1]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=1045014049&format=image +[graph-nelo8]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=1302996667&format=image +[graph-gpr1]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=65689441&format=image +[graph-gpr8]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=1638166821&format=image +[graph-dve1]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=699890902&format=image +[graph-dve8]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=2097713806&format=image +[graph-thirty1]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=291372362&format=image +[graph-thirty8]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=1969585471&format=image +[graph-total]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQqw86SXD_-zzP39DzfjBQ1eLBGyZMPyVLPuZDTY7zSNxBvxxj9CUXpd_AHRKy1aCpCCXGsznolmMVs/pubchart?oid=2059970959&format=image \ No newline at end of file diff --git a/stockfish/wiki/Stockfish-FAQ.md b/stockfish/wiki/Stockfish-FAQ.md new file mode 100644 index 0000000000000000000000000000000000000000..c2e0801837b3f2712b2169819917d04f4ab2b677 --- /dev/null +++ b/stockfish/wiki/Stockfish-FAQ.md @@ -0,0 +1,166 @@ +## Interpretation of the Stockfish evaluation + +### Centipawns + +The evaluation of a position that results from search has traditionally been measured in `pawns` or `centipawns` (1 pawn = 100 centipawns). A value of 1, implied a 1 pawn advantage. However, with engines being so strong, and the NNUE evaluation being much less tied to material value, a new scheme was needed. The new normalized evaluation is now linked to the probability of winning, with a 1.0 pawn advantage being a 0.5 (that is 50%) win probability. An evaluation of 0.0 means equal chances for a win or a loss, but also nearly 100% chance of a draw. + +Some GUIs will be able to show the win/draw/loss probabilities directly when the `UCI_ShowWDL` engine option is set to `True`. + +The full plots of win, loss, and draw probability are given below. From these probabilities, one can also obtain the expected match score. + +| Probabilities | Expected match score | +|---|---| +| | | + +The probability of winning or drawing a game, of course, depends on the opponent and the time control. With bullet games, the draw rate will be lower, and against a weak opponent, even a negative evaluation could result in a win. These graphs have been generated from a model derived from Fishtest data for Stockfish playing against Stockfish (so an equally strong opponent), at 60+0.6s per game. The curves are expected to evolve, i.e. as the engines get stronger, an evaluation of 0.0 will approach the 100% draw limit. These curves are for SF15.1 (Dec 2022). + +### Tablebase scores + +Since Stockfish 16 ([def2966](https://github.com/official-stockfish/Stockfish/commit/def2966)) a value of 200.00 pawns is reported when entering a tablebase won position. +Values close to 200.00 refer to the distance in plies from the root to the probed position, where 1 cp is 1 ply distance. This means that a score of, for example, 199.50 means that the engine found a forced way to get from the current position to a tablebase winning position in 25 moves (50 ply). + +## Optimal settings + +To get the best possible evaluation or the strongest move for a given position, the key is to let Stockfish analyze long enough, using a recent release (or development version), properly selected for the CPU architecture. + +The following settings are important as well: + +### Threads + +**Set it to the maximum minus 1 or 2 threads.** + +Set the number of threads to the maximum available, possibly leaving 1 or 2 threads free for other tasks. +SMT or Hyper-threading is beneficial, so normally the number of threads available is twice the number of cores available. +Consumer hardware typically has at least 4-8 threads, Stockfish supports hundreds of threads. + +> [!NOTE] +> [More detailed results](Useful-data#threading-efficiency-and-elo-gain) on the efficiency of threading are available. + +### Hash + +> [!TIP] +> The Hash can be any value, not just powers of two. The value is specified in MiB. + +#### Gameplay + +Some general guidelines (for games played using 1 thread) are: + +- Ultra-bullet games (10s+0.1s) a value of 16 (default). +- Bullet games (60s+0.6s, aprox. 1+0 or 1+1) a value of 64. +- Blitz games (180s+1.8s, aprox. 3+2) a value of 192. + +Longer time controls or games played with more threads will require more hash. +60s+0.6s with 8 threads would require a hash of 64 * 8 = 512. + +> [!NOTE] +> The data suggests that keeping the average hashfull below 30% is best to maintain strength. +> [More detailed results](Useful-data#elo-cost-of-small-hash) on the cost of too little hash are available. + +#### Analysis + +Depending on how long you want to leave Stockfish analyzing a position for the amount of hash needed will vary. +For shallow analysis (e.g. depth 24 or 1 million nodes), a hash of 64 or 128 should be enough. +For deep analysis set it to as much as you can afford given the **available** memory in your system, leaving some memory for the operating system and other applications. + +### MultiPV + +**Set it to 1.** + +A value higher than 1 weakens the quality of the best move computed, as resources are used to compute other moves. + +> [!NOTE] +> [More detailed results](Useful-data#elo-cost-of-using-multipv) on the cost of MultiPV are available. + +## The Elo rating of Stockfish + +"What is the Elo of Stockfish?": A seemingly simple question, with no easy answer. First, the obvious: it is higher than any human Elo, and when SF 15.1 ranked with more than 4000 Elo on some rating lists, [YouTube knew](https://www.youtube.com/watch?v=wFALbm4gFoc). To answer the question in more detail, some background information is needed. + +### Background information + +In its simplest form, the [Elo rating system](https://en.wikipedia.org/wiki/Elo_rating_system) predicts the score of a match between two players, and conversely, a match between two players gives information about the Elo difference between them. + +The Elo difference depends on the conditions of the match. For human players, time control (blitz vs. classical TC) or variant (standard chess vs. Fischer random chess) are well-known factors that influence the Elo difference between two players. + +Finally, given an Elo difference between two players, one needs to know the Elo rating of one of them to know the Elo rating of the other. More generally, you need an anchor or reference within a group of opponents, and if that reference is different in different groups, the Elo value cannot be compared. + +### Caveats + +The same observations apply to the calculation of Stockfish's Elo rating, with the caveat that top engines play at an extremely high level and, from the starting position or any balanced opening, have achieved a draw rate approaching 100% against other engines of similar strength, especially at rapid or longer time controls, and even more so on powerful hardware. This results in small Elo differences between top engines. For instance in a match between two engines, 19 wins, 2 losses and 79 draws is a convincing win for the first engine, but results in a small Elo difference between the two because of the 79 draws. + +Carefully constructed books of starting positions with a clear advantage for one side can significantly reduce this draw rate and increase Elo differences. The book used in the match is therefore an important factor in the computed Elo difference. + +Similarly, the pool of opponents and their rankings has a large impact on the Elo rating, and Elo ratings computed with different pools of opponents are difficult to compare, especially if weaker (but different) engines are part of that pool. + +Finally, to accurately compute Elo differences at this level, a very large number of games (typically tens of thousands of games) are needed, as small samples of games (regardless of time control) will lead to large relative errors. + +Having introduced all these caveats, accurately measuring Elo differences is central to the development of Stockfish, and our Fishtest framework constantly measures with great precision the Elo difference of Stockfish and its proposed improvements. These performance improvements are accurately tracked over time on the [regression testing](Regression-Tests) wiki page. The same page also links to [various external websites](Regression-Tests#external-links) that rank Stockfish against a wide range of other engines. + +### Conclusion + +Rating Stockfish on a human scale (e.g. FIDE Elo) has become an almost impossible task, as the difference in strength between it and humans is now so large that this difference can hardly be measured. This would require a human to play Stockfish long enough to have at least a handful of draws and wins. + +## Move annotations + +Stockfish does not provide move annotations such as blunders or brilliants, it only provides evaluations and what it considers the best move in the position. Websites and GUIs often add move annotations separately, using Stockfish's output as a basis for them. + +## Why is Stockfish regressing? + +Sometimes, Elo gain may seem to be decreasing over time in [regression tests](Regression-Tests), but this does not necessarily mean that Stockfish is regressing. Regression tests don't provide an exact number for Elo gain, instead, they return a range dictated by an error bound (usually displayed next to the Elo) where the true Elo likely lies on. As long as the error bounds overlap, there is a substantial likelihood that Stockfish did not regress, even if the amount of Elo gain decreased between regression tests. + +## Stockfish crashed + +Stockfish may crash if fed incorrect fens, or fens with illegal positions. Full validation code is complex to write, and within the UCI protocol, there is no established mechanism to communicate such an error back to the GUI. Therefore Stockfish is written with the expectation that the input fen is correct. + +On the other hand, the GUI must carefully check fens. If you find a GUI through which you can crash Stockfish or any other engine, then by all means report it to that GUI's developers. + +## Does Stockfish support chess variants? + +The official Stockfish engine only supports standard chess and Chess960 or Fischer Random Chess (FRC) as well as Double Fischer Random Chess (DFRC). However, various forks based on Stockfish support more variants, most notably the [Fairy-Stockfish project](https://github.com/ianfab/Fairy-Stockfish). + +## Can Stockfish use my GPU? + +No, Stockfish is a chess engine that uses the CPU only for chess evaluation. Its NNUE evaluation ([see this in-depth description](https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md)) is very effective on CPUs. With extremely short inference times (sub-micro-second), this network can not be efficiently evaluated on GPUs, in particular with the alpha-beta search that Stockfish employs. However, for training networks, Stockfish employs GPUs with effective code that is part of the [NNUE pytorch trainer](https://github.com/official-stockfish/nnue-pytorch). Other chess engines require GPUs for effective evaluation, as they are based on large convolutional or transformer networks, and use a search algorithm that allows for batching evaluations. See also the [Leela Chess Zero (Lc0) project](https://github.com/LeelaChessZero/lc0). + +## Executing Stockfish opens a CMD window + +![image](https://github.com/user-attachments/assets/08ad3bae-2fa5-4b77-a65e-ec272727a7d5) + +Stockfish is a command line program, so **this behavior is intentional** and serves as the interface for interacting with the engine. + +### User-friendly experience + +If you prefer a **more user-friendly experience** with a **chessboard and additional features**, you can consider using a graphical user interface (GUI) alongside Stockfish. To set up a GUI, you can visit the [Download and Usage](Download-and-usage#download-a-chess-gui) page. + +### Available commands + +The CMD window allows you to input various commands and receive corresponding outputs from Stockfish. If you want to explore the available commands and their explanations, you can refer to the [Commands](UCI-&-Commands) page but this is **only recommended for advanced users and developers**. + +## What is depth? + +First, we need to understand how minimax search works. We will go with the vanilla one because explaining what Alpha-beta is does not do much. + +### Minimax + +**Each player tries to maximize the score in their favor**. White wants the evaluation to be as positive as it can, and Black as negative as it can - we do this all the time when we play chess. Search works in a similar way - you explore your moves, explore the opponent's replies, assign a value called evaluation to each resulting board position (which is not precise but tries to be), and find a sequence where White plays some move that has the maximum evaluation for the best opponent's reply. + +Then you search one ply (half-move) deeper - exploring your reply to the last opponent's replies. This process is called iterative deepening - you explore a position up to depth 1, then to depth 2, then to depth 3, and so on - you deepen your search with each iteration and this is why it is called this way. + +So, for now, "depth" is a perfect thing - it means you fully calculated the search tree up to this "depth" and you know everything that can happen within it. For a mate in 5, you will need depth 9 to see it (because depth is written in half-moves). But chess has a lot of possible moves, 20 from the starting position and usually many more from any middlegame position. Even if you can evaluate millions of positions per second as engines like Stockfish do, you will still hit a wall in what depths you can realistically reach, and it would not be that high - depth 8, maybe 10. + +### Pruning + +How to combat this? With a thing called pruning. Pruning splits into quite a lot of different heuristics, but they mostly serve one purpose - **remove branches in search that do not look too desirable**, to re-explore them later when iterative deepening depth goes higher. So this is where "depth" starts to mean less - because you do not search the entire game tree and modern engines prune large percentages of branches. + +### Reductions + +Another way we combat this is with reductions, techniques to reduce the depth of a certain position. While pruning results in the complete elimination of a branch, a reduction merely reduces its size. We often use these on positions that are not too desirable, but we give them the benefit of the doubt and search them at a shallow depth to confirm whether they were desirable or not. An advantage over pruning is that it allows us to double-check positions to avoid missing winning tactics while reducing the game tree. + +### Extensions + +But then there is also a thing called "**extensions**" which is more or less the opposite of reductions. With extensions, you start by **searching "important" branches deeper** (for example, checks) than what is needed to complete the iterative deepening iteration. + +### Conclusion + +With all of this, instead of a search tree that is strictly cut off at this "depth", you have **most of the branches ending really early and a lot of branches searched deeper** than the given "depth". Stockfish is the most aggressive engine in both pruning and extensions, so its search tree looks nothing like what you usually see on [Wikipedia](https://en.wikipedia.org/wiki/Minimax). + +Coming back to how much Stockfish prunes, there is some data [here](Useful-data#branching-factor-of-stockfish). The branching factor indicates how many moves you calculate on average per depth increase, calculated as $nodes^{\frac{1}{depth}}$. Stockfish 15.1 with higher depths goes all the way down to 1.5, so at depth 50 considers approximately 1.5 moves per ply from the full 20-30-40 moves we usually have. And this is why it [misses some short mates up to high depths](https://www.reddit.com/r/chess/comments/1028i0e/stockfishs_search_tree_trying_to_find_a_mate_in_2/) while vanilla minimax would have found them at lower ones. It just throws away 90%+ of the moves. diff --git a/stockfish/wiki/Terminology.md b/stockfish/wiki/Terminology.md new file mode 100644 index 0000000000000000000000000000000000000000..1c6fb07244f4137e5d797102a71e2a0fc75a51df --- /dev/null +++ b/stockfish/wiki/Terminology.md @@ -0,0 +1,109 @@ +## General terms + +### Threads + +Also known as "cores", "CPUs" or "vCPUs". +The amount of CPU threads that the engine will use, usually the higher the better. +Note that a modern CPU will have multiple cores and typically can run two threads each. + +### Hash + +Also known as "memory". +The amount of Hash is the amount of RAM that the engine will use to store positions in the game tree. +This information can be reused during the analysis, for example after a transposition, +which means usually [the higher the Hash the better](Useful-data#elo-cost-of-small-hash). See also [Transposition Table](#transposition-table). + +### Depth + +Counter of [iterative deepening](#iterative-deepening) loop at the root of the game tree, thus also known as rootDepth. Regardless of the name, there is no simple connection to the actual depth searched in the game tree. The deepest lines searched are usually quite a bit deeper than the rootDepth. See also [a discussion on rootDepth vs depth](https://github.com/official-stockfish/Stockfish/discussions/3888). + +### Selective Depth + +Also known as "seldepth". +Is the depth of the deepest principal variation line in the search. + +### Multiple PVs + +Also known as "number of lines" or "multiple lines". +The top N moves and their [principal variations](#principal-variation) can be computed. This gives additional insight in the options of the position. However, in Stockfish, this weakens the quality of the best move computed, as resources are used to compute other moves. + +### Transposition Table + +Also known as "TT". +A database / hash table that stores results of previously performed searches. See also [Hash](#hash). + +### Lazy Symmetric Multiprocessing + +Also known as "Lazy SMP". +The idea of executing the search function on N many threads and share the Transposition table. This allows for faster filling of the transposition table, resulting in a faster and wider search. +This approach to parallelism is [known to scale well](Useful-data#threading-efficiency-and-elo-gain), especially with longer searches. + +### Iterative deepening + +The idea of performing consecutive searches at higher depths each time. + +### Null Move + +Also known as "passing move", it is when you don't make a move, "passing" the ability to move to the opponent. Although not a legal move, it is used in Null Move Pruning. + +### Time Control + +Also known as "TC". How the time limits are set for playing a game. For chess engine testing, Stockfish uses 10+0.1s (10 seconds for the game, 0.1 seconds for every move), and 60+0.6s. These TCs are known as STC (short TC) and LTC (long TC). + +## Principal Variation Search + +### Principal Variation + +Also known as "PV". +The sequence of moves that the engine considers best and therefore expects to be played. + +### Pruning + +The idea of ignoring certain parts of the search tree in order to reduce the amount of time it takes to execute a search. + +### Null Move Pruning + +Based on the assumption that, if we can reach beta by not making a move at lower depths, we will most likely be able to reach it anyway. + +### Futility Pruning + +The idea that quiet moves don't tend to improve positions significantly, therefore we can safely prune quiet moves in positions where the current position is evaluated to be below alpha by a margin. + +### Late Move Pruning + +Also known as "LMP". +The idea that all quiet moves can be pruned after searching the first few given by the move ordering algorithm. + +### Late Move Reductions + +Also known as "LMR". +A way of proving if a move is lower than alpha quickly. This is done by searching moves that are expected to underperform at a lower depth. + +## Extensions + +The idea of extending certain moves, usually by one ply, to try to find better moves faster. + +### Check Extensions + +They can have two distinct forms: one of them extends when giving check, the other when evading it. The reason behind check extension is that we are in a forcing sequence, so that it is desirable to know its outcome with more certainty. + +### Move Ordering + +In order to maximize the efficiency of alpha-beta search, we optimally want to try the best moves first. + +### Quiescence Search + +Also known as "qSearch". +Performed at the end of the main search, the purpose of this search is to only evaluate "quiet" positions, or positions where there are no winning tactical moves to be made. + +## Evaluation + +### Handcrafted Evaluation + +Also known as "classic", "classical" or "HCE". + +This is the older evaluation method that is generally not used today in most engines and was [removed from Stockfish in July 2023](https://github.com/official-stockfish/Stockfish/commit/af110e0). It uses various heuristics and rules (e.g. material, pawn structure, king safety, mobility, etc.) to assign the evaluation. Although it is faster than a NNUE evaluation, it is much less accurate. + +### Efficiently Updatable Neural Network + +Also known as "NNUE". Introduced in Stockfish in [August 2020](https://stockfishchess.org/blog/2020/introducing-nnue-evaluation/) and first shipped with Stockfish 12. This implementation evaluates positions using a neural network, which is trained on a large set of training data. NNUE is typically much more accurate than classical evaluation, gaining hundreds of Elo. diff --git a/stockfish/wiki/UCI-&-Commands.md b/stockfish/wiki/UCI-&-Commands.md new file mode 100644 index 0000000000000000000000000000000000000000..fccc62d2fbae3617fcf5dd857f50934e07326311 --- /dev/null +++ b/stockfish/wiki/UCI-&-Commands.md @@ -0,0 +1,873 @@ +The [Universal Chess Interface](https://backscattering.de/chess/uci/) (UCI) is a standard text-based protocol used to communicate with a chess engine and is the recommended way to do so for typical graphical user interfaces (GUI) or chess tools. Stockfish implements the majority of its options. + +Developers can see the default values for the UCI options available in Stockfish by typing `./stockfish uci` in a terminal, but most users should typically use a chess GUI to interact with Stockfish. + +## Standard commands + +### `quit` + +Quit the program as soon as possible. + +### `uci` + +Tell the engine to use the UCI (universal chess interface). +This will be sent once, by a GUI, as a first command after the program boots to tell the engine to switch to UCI mode. +After receiving the `uci` command the engine will identify itself with the `id` command and send the `option` commands to tell the GUI which engine settings the engine supports. +After that, the engine will send `uciok` to acknowledge the UCI mode. +If no `uciok` is sent within a certain time period, the engine task will be killed by the GUI. + +
+ Example + Note that a different Stockfish version than the one used below may have a different output, with different values and other available options. Please run this command yourself to know what values are available in your specific version. + + ``` + > uci + id name Stockfish 16.1 + id author the Stockfish developers (see AUTHORS file) + + option name Debug Log File type string default + option name Threads type spin default 1 min 1 max 1024 + option name Hash type spin default 16 min 1 max 33554432 + option name Clear Hash type button + option name Ponder type check default false + option name MultiPV type spin default 1 min 1 max 256 + option name Skill Level type spin default 20 min 0 max 20 + option name Move Overhead type spin default 10 min 0 max 5000 + option name nodestime type spin default 0 min 0 max 10000 + option name UCI_Chess960 type check default false + option name UCI_LimitStrength type check default false + option name UCI_Elo type spin default 1320 min 1320 max 3190 + option name UCI_ShowWDL type check default false + option name SyzygyPath type string default + option name SyzygyProbeDepth type spin default 1 min 1 max 100 + option name Syzygy50MoveRule type check default true + option name SyzygyProbeLimit type spin default 7 min 0 max 7 + option name EvalFile type string default nn-b1a57edbea57.nnue + option name EvalFileSmall type string default nn-baff1ede1f90.nnue + uciok + ``` +
+ +### `setoption` + +Usage: `setoption name [value ]` + +This is sent to the engine when the user wants to change the internal parameters of the engine. For the `button` type no value is needed. +One string will be sent for each parameter and this will only be sent when the engine is waiting. + +Examples: +``` +> setoption name Threads value 6 +> setoption name SyzygyPath value C:\Chess\tb\tb345;C:\Chess\tb\wdl6;C:\Chess\tb\wdl7 +> setoption name UCI_ShowWDL value true +> setoption name Clear Hash +``` + +List of options: + + * #### `Threads` + + `type spin default 1 min 1 max 1024` + + The number of CPU threads used for searching a position. For best performance, set this equal to the number of CPU cores available. + + * #### `Hash` + + `type spin default 16 min 1 max 33554432` + + The size of the hash table in MB. It is recommended to set Hash after setting Threads. + + * #### `MultiPV` + + `type spin default 1 min 1 max 500` + + Output the N best lines (principal variations, PVs) when searching. + Leave at 1 for the best performance. + + * #### `NumaPolicy` + + `type string default auto` + + Binds threads to a specific NUMA node to enhance performance on multi-CPU or multi-NUMA domain systems. Options: + * `none` - assumes a single NUMA node, no thread binding + * `system` - uses NUMA information available from the system and binds the threads accordingly + * `auto` - default; automatically selects `system` or `none` based on the system + * `hardware` - uses NUMA information from the underlying hardware and binds the threads accordingly, overrides any previous affinities. + _Should be used if Stockfish doesn't utilize all threads, e.g. Windows 10 or certain GUI's like ChessBase._ + * `[[custom]]` - precisely specify the available CPUs per numa domain. ':' separates numa nodes; ',' separates cpu indices; supports "first-last" range syntax for cpu indices, for example `0-15,32-47:16-31,48-63` + + * #### `Clear Hash` + + `type button` + + Clear the hash table. + + * #### `Ponder` + + `type check default false` + + Let Stockfish ponder its next move while the opponent is thinking. + + * #### `EvalFile` + + `type string default nn-[SHA256 first 12 digits].nnue` + + The name of the file of the NNUE evaluation parameters. Depending on the GUI the filename might have to include the full path to the folder/directory that contains the file. Other locations, such as the directory that contains the binary and the working directory, are also searched. + + * #### `EvalFileSmall` + + `type string default nn-[SHA256 first 12 digits].nnue` + + Same as EvalFile. + + * #### `UCI_Chess960` + + `type check default false` + + An option handled by your GUI. If true, Stockfish will play Chess960. + + * #### `UCI_ShowWDL` + + `type check default false` + + If enabled, show approximate WDL statistics as part of the engine output. + These WDL numbers model expected game outcomes for a given evaluation and game ply for engine self-play at fishtest LTC conditions (60+0.6s per game). + + * #### `UCI_LimitStrength` + + `type check default false` + + Enable weaker play aiming for an Elo rating as set by `UCI_Elo`. This option overrides `Skill Level`. + + * #### `UCI_Elo` + + `type spin default 1320 min 1320 max 3190` + + If `UCI_LimitStrength` is enabled, it aims for an engine strength of the given Elo. + This Elo rating has been calibrated at a time control of 120s+1s and anchored to CCRL 40/4. + + * #### `Skill Level` + + `type spin default 20 min 0 max 20` + + Lower the `Skill Level` in order to make Stockfish play weaker (see also `UCI_LimitStrength`). + Internally, MultiPV is enabled, and with a certain probability depending on the `Skill Level`, a weaker move will be played. + + * #### `SyzygyPath` + + `type string default ` + + Path to the folders/directories storing the Syzygy tablebase files. Multiple directories are to be separated by `;` on Windows and by `:` on Unix-based operating systems. Do not use spaces around the `;` or `:`. + + Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6` + + It is recommended to store .rtbw files on an SSD. There is no loss in storing the .rtbz files on a regular HDD. It is recommended to verify all md5 checksums of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will lead to engine crashes. + + * #### `SyzygyProbeDepth` + + `type spin default 1 min 1 max 100` + + Minimum remaining search depth for which a position is probed. Set this option to a higher value to probe less aggressively if you experience too much slowdown (in terms of nps) due to tablebase probing. + + * #### `Syzygy50MoveRule` + + `type check default true` + + Disable to let fifty-move rule draws detected by Syzygy tablebase probes count as wins or losses. This is useful for ICCF correspondence games. + + * #### `SyzygyProbeLimit` + + `type spin default 7 min 0 max 7` + + Limit Syzygy tablebase probing to positions with at most this many pieces left (including kings and pawns). + + * #### `Move Overhead` + + `type spin default 10 min 0 max 5000` + + Assume a time delay of x ms due to network and GUI overheads. Specifying a value larger than the default is needed to avoid time losses or near instantaneous moves, in particular for time controls without increment (e.g. sudden death). The default is suitable for engine-engine matches played locally on dedicated hardware, while it needs to be increased on a loaded system, when playing over a network, or when using certain GUIs such as Arena or ChessGUI. + + * #### `nodestime` + + `type spin default 0 min 0 max 10000` + + Tells the engine to use nodes searched instead of wall time to account for elapsed time. Useful for engine testing. When this option is set, the engine is only limited by the total amount of nodes searched per game; this limit is calculated once per game. The initial time control values in milliseconds (time `time` and increment per move `inc`) are used as input values to calculate the total number of nodes per game (`totalnodes`). The increment per move `inc` is used as if it was just one move per game. The formula is `totalnodes = (time + inc * 1) * nodestime`. Suppose you specified `nodestime = 600`, and the time control per game is 300 seconds plus 3 seconds increment per move ("300+3s"), or 300000 milliseconds plus 3000 milliseconds increment per move. In that case, the maximum total number of nodes searched per game by the engine is `totalnodes = (300000 + 3000 * 1) * 600 = 181800000` (one hundred eighty-one million, eight hundred thousand) nodes, regardless of how much wall time it will actually take. + + + * #### `Debug Log File` + + `type string default` + + Write all communication to and from the engine into a text file. + +### `position` + +Usage: `position [fen | startpos ] moves .... ` + +Set up the position described in `fenstring`. +If the game was played from the start position the string `startpos` must be sent. +> [!NOTE] +> If this position is from a different game than the last position sent to the engine, the GUI should have sent a `ucinewgame` in between. + +Examples: +``` +> position startpos +> position startpos moves e2e4 e7e5 g1f3 b8c6 f1b5 +> position fen 8/1B6/8/5p2/8/8/5Qrq/1K1R2bk w - - 0 1 +> position fen 8/3P3k/n2K3p/2p3n1/1b4N1/2p1p1P1/8/3B4 w - - 0 1 moves g4f6 h7g7 f6h5 g7g6 d1c2 +``` + +### `ucinewgame` + +This is sent to the engine when the next search (started with `position` and `go`) will be from a different game. This can be a new game the engine should play or a new game it should analyze but also the next position from a test suite with positions only. +If the GUI hasn't sent a `ucinewgame` before the first `position` command, the engine won't expect any further `ucinewgame` commands as the GUI is probably not supporting the `ucinewgame` command. +So the engine will not rely on this command even though all new GUIs should support it. +As the engine's reaction to `ucinewgame` can take some time the GUI should always send `isready` after `ucinewgame` to wait for the engine to finish its operation. The engine will respond with `readyok`. + +> [!NOTE] +> This clears the hash and any information which was collected during the previous search. + +
+ Example + + ``` + > ucinewgame + > isready + readyok + > position startpos + > go depth 1 + info string NNUE evaluation using nn-ad9b42354671.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp 18 nodes 20 nps 10000 hashfull 0 tbhits 0 time 2 pv e2e4 + bestmove e2e4 + > ucinewgame + > isready + readyok + > position fen r2q1rk1/p2bbppp/Q7/2p1P2P/8/2p1B3/PPP2P1P/2KR3R w - - 0 17 + ``` +
+ +### `isready` + +This is used to synchronize the engine with the GUI. +When the GUI has sent a command or multiple commands that can take some time to complete, this command can be used to wait for the engine to be ready again or to ping the engine to find out if it is still alive. +e.g. this should be sent after setting the path to the tablebases as this can take some time. +This command is also required once, before the engine is asked to do any searching, to wait for the engine to finish initializing. +This command will always be answered with `readyok` and can be sent also when the engine is calculating in which case the engine will also immediately answer with `readyok` without stopping the search. + +Example: +``` +> isready +readyok +``` + +### `go` + +Start calculating on the current position set up with the `position` command. +There are a number of parameters that can follow this command and all will be sent in the same string. + +> [!NOTE] +> If no parameter is sent, then `go depth 245` will be executed. + +> [!NOTE] +> Mixing and matching different ways of limiting the search time (depth, nodes, movetime, wtime + winc + btime + binc) will result in the search ending when it hits any one of those limits. +> For example, you can limit the search to a maximum depth and time with `go depth x movetime y`. + +
+ Example: go infinite + + ``` + > position startpos + > go infinite + info string NNUE evaluation using nn-ad9b42354671.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp 18 nodes 20 nps 4000 hashfull 0 tbhits 0 time 5 pv e2e4 + info depth 2 seldepth 2 multipv 1 score cp 46 nodes 66 nps 11000 hashfull 0 tbhits 0 time 6 pv d2d4 + info depth 3 seldepth 2 multipv 1 score cp 51 nodes 120 nps 20000 hashfull 0 tbhits 0 time 6 pv e2e4 + info depth 4 seldepth 2 multipv 1 score cp 58 nodes 144 nps 18000 hashfull 0 tbhits 0 time 8 pv d2d4 + info depth 5 seldepth 2 multipv 1 score cp 58 nodes 174 nps 15818 hashfull 0 tbhits 0 time 11 pv d2d4 a7a6 + info depth 6 seldepth 7 multipv 1 score cp 34 nodes 1303 nps 81437 hashfull 0 tbhits 0 time 16 pv e2e4 c7c5 g1f3 b8c6 c2c3 + info depth 7 seldepth 6 multipv 1 score cp 29 nodes 3126 nps 120230 hashfull 1 tbhits 0 time 26 pv d2d4 g8f6 e2e3 d7d5 c2c4 d5c4 + info depth 8 seldepth 7 multipv 1 score cp 26 nodes 5791 nps 152394 hashfull 4 tbhits 0 time 38 pv g1f3 g8f6 d2d4 d7d5 e2e3 + info depth 9 seldepth 9 multipv 1 score cp 31 nodes 8541 nps 174306 hashfull 5 tbhits 0 time 49 pv g1f3 c7c5 e2e4 e7e6 d2d4 c5d4 f3d4 + info depth 10 seldepth 13 multipv 1 score cp 25 nodes 20978 nps 209780 hashfull 10 tbhits 0 time 100 pv e2e4 c7c5 g1f3 b8c6 f1c4 e7e6 e1g1 g8f6 + info depth 11 seldepth 13 multipv 1 score cp 32 nodes 29040 nps 220000 hashfull 14 tbhits 0 time 132 pv e2e4 c7c5 c2c3 g8f6 e4e5 f6d5 d2d4 + info depth 12 seldepth 14 multipv 1 score cp 38 nodes 41207 nps 242394 hashfull 18 tbhits 0 time 170 pv e2e4 e7e6 d2d4 d7d5 b1c3 d5e4 c3e4 + > stop + info depth 13 seldepth 14 multipv 1 score cp 38 nodes 45531 nps 247451 hashfull 21 tbhits 0 time 184 pv e2e4 e7e6 d2d4 d7d5 b1c3 d5e4 c3e4 + bestmove e2e4 ponder e7e6 + ``` +
+ +
+ Example: go depth + + ``` + > position startpos + > go depth 25 + info string NNUE evaluation using nn-ad9b42354671.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp 18 nodes 20 nps 10000 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 2 seldepth 2 multipv 1 score cp 46 nodes 66 nps 33000 hashfull 0 tbhits 0 time 2 pv d2d4 + info depth 3 seldepth 2 multipv 1 score cp 51 nodes 120 nps 60000 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 4 seldepth 2 multipv 1 score cp 58 nodes 144 nps 72000 hashfull 0 tbhits 0 time 2 pv d2d4 + info depth 5 seldepth 2 multipv 1 score cp 58 nodes 174 nps 58000 hashfull 0 tbhits 0 time 3 pv d2d4 a7a6 + info depth 6 seldepth 7 multipv 1 score cp 34 nodes 1303 nps 325750 hashfull 0 tbhits 0 time 4 pv e2e4 c7c5 g1f3 b8c6 c2c3 + info depth 7 seldepth 6 multipv 1 score cp 29 nodes 3126 nps 521000 hashfull 1 tbhits 0 time 6 pv d2d4 g8f6 e2e3 d7d5 c2c4 d5c4 + info depth 8 seldepth 7 multipv 1 score cp 26 nodes 5791 nps 643444 hashfull 4 tbhits 0 time 9 pv g1f3 g8f6 d2d4 d7d5 e2e3 + info depth 9 seldepth 9 multipv 1 score cp 31 nodes 8541 nps 711750 hashfull 5 tbhits 0 time 12 pv g1f3 c7c5 e2e4 e7e6 d2d4 c5d4 f3d4 + info depth 10 seldepth 13 multipv 1 score cp 25 nodes 20978 nps 839120 hashfull 10 tbhits 0 time 25 pv e2e4 c7c5 g1f3 b8c6 f1c4 e7e6 e1g1 g8f6 + info depth 11 seldepth 13 multipv 1 score cp 32 nodes 29040 nps 854117 hashfull 14 tbhits 0 time 34 pv e2e4 c7c5 c2c3 g8f6 e4e5 f6d5 d2d4 + info depth 12 seldepth 14 multipv 1 score cp 38 nodes 41207 nps 895804 hashfull 18 tbhits 0 time 46 pv e2e4 e7e6 d2d4 d7d5 b1c3 d5e4 c3e4 + info depth 13 seldepth 15 multipv 1 score cp 31 nodes 60308 nps 927815 hashfull 25 tbhits 0 time 65 pv e2e4 c7c5 g1f3 d7d6 d2d4 c5d4 f3d4 b8c6 c1e3 g8f6 b1c3 g7g6 + info depth 14 seldepth 18 multipv 1 score cp 35 nodes 97789 nps 922537 hashfull 44 tbhits 0 time 106 pv d2d4 g8f6 c2c4 e7e6 g1f3 d7d5 g2g3 f8e7 b1c3 c7c5 + info depth 15 seldepth 21 multipv 1 score cp 31 nodes 142447 nps 901563 hashfull 64 tbhits 0 time 158 pv d2d4 g8f6 c2c4 e7e6 g1f3 d7d5 c1g5 f8e7 b1c3 e8g8 e2e3 h7h6 g5h4 f6e4 h4e7 d8e7 + info depth 16 seldepth 21 multipv 1 score cp 40 nodes 199887 nps 896354 hashfull 88 tbhits 0 time 223 pv d2d4 d7d5 c2c4 d5c4 g1f3 a7a6 b1c3 c7c6 a2a4 + info depth 17 seldepth 19 multipv 1 score cp 32 nodes 268004 nps 896334 hashfull 120 tbhits 0 time 299 pv d2d4 g8f6 c2c4 e7e6 g2g3 d7d5 g1f3 f8e7 f1g2 e8g8 e1g1 c7c5 c4d5 e6d5 b1c3 h7h6 d4c5 e7c5 + info depth 18 seldepth 21 multipv 1 score cp 29 nodes 298071 nps 903245 hashfull 131 tbhits 0 time 330 pv d2d4 g8f6 c2c4 e7e6 g2g3 d7d5 g1f3 f8e7 f1g2 e8g8 e1g1 c7c5 c4d5 e6d5 c1g5 b8c6 d4c5 e7c5 g5f6 d8f6 + info depth 19 seldepth 25 multipv 1 score cp 28 nodes 332202 nps 910142 hashfull 142 tbhits 0 time 365 pv d2d4 g8f6 c2c4 e7e6 g1f3 d7d5 c1g5 f8e7 b1c3 e8g8 e2e3 h7h6 g5f4 c7c5 d4c5 e7c5 c4d5 f6d5 c3d5 e6d5 + info depth 20 seldepth 23 multipv 1 score cp 37 nodes 557706 nps 900978 hashfull 243 tbhits 0 time 619 pv d2d4 g8f6 c2c4 e7e6 g2g3 d7d5 f1g2 f8e7 g1f3 e8g8 e1g1 d5c4 d1a4 a7a6 a4c4 b7b5 c4c2 c8b7 c1f4 f6d5 + info depth 21 seldepth 23 multipv 1 score cp 31 nodes 753633 nps 895051 hashfull 326 tbhits 0 time 842 pv d2d4 g8f6 c2c4 g7g6 b1c3 d7d5 g1f3 f8g7 c4d5 f6d5 e2e4 d5c3 b2c3 c7c5 c1e3 c5d4 c3d4 e8g8 f1c4 c8g4 e1g1 + info depth 22 seldepth 28 multipv 1 score cp 29 nodes 1197489 nps 896995 hashfull 487 tbhits 0 time 1335 pv d2d4 d7d5 c2c4 e7e6 b1c3 f8b4 e2e3 g8f6 d1a4 b8c6 c1d2 c8d7 a4c2 e8g8 g1f3 d5c4 f1c4 + info depth 23 seldepth 27 multipv 1 score cp 30 nodes 1586189 nps 855088 hashfull 610 tbhits 0 time 1855 pv d2d4 d7d5 c2c4 e7e6 g1f3 g8f6 g2g3 f8e7 f1g2 e8g8 e1g1 d5c4 d1a4 a7a6 a4c4 b7b5 c4c2 c8b7 c1f4 c7c5 d4c5 b8d7 b2b4 a6a5 + info depth 24 seldepth 29 multipv 1 score cp 28 nodes 2305729 nps 859384 hashfull 776 tbhits 0 time 2683 pv d2d4 d7d5 c2c4 e7e6 g1f3 g8f6 g2g3 f8b4 c1d2 b4e7 f1g2 e8g8 e1g1 b8d7 d1c2 c7c6 a2a4 a7a5 b1c3 d5c4 e2e4 h7h6 d2f4 + info depth 24 currmove e2e4 currmovenumber 2 + info depth 24 currmove g1f3 currmovenumber 3 + info depth 24 currmove a2a4 currmovenumber 4 + info depth 24 currmove b1c3 currmovenumber 5 + info depth 24 currmove h2h3 currmovenumber 6 + info depth 24 currmove e2e3 currmovenumber 7 + info depth 24 currmove c2c4 currmovenumber 8 + info depth 24 currmove d2d3 currmovenumber 9 + info depth 24 currmove a2a3 currmovenumber 10 + info depth 24 currmove b2b3 currmovenumber 11 + info depth 24 currmove c2c3 currmovenumber 12 + info depth 24 currmove f2f3 currmovenumber 13 + info depth 24 currmove g2g3 currmovenumber 14 + info depth 24 currmove b2b4 currmovenumber 15 + info depth 24 currmove f2f4 currmovenumber 16 + info depth 24 currmove g2g4 currmovenumber 17 + info depth 24 currmove h2h4 currmovenumber 18 + info depth 24 currmove b1a3 currmovenumber 19 + info depth 24 currmove g1h3 currmovenumber 20 + info depth 25 seldepth 28 multipv 1 score cp 22 upperbound nodes 3071086 nps 856887 hashfull 880 tbhits 0 time 3584 pv d2d4 g8f6 + info depth 25 currmove d2d4 currmovenumber 1 + info depth 25 seldepth 28 multipv 1 score cp 27 lowerbound nodes 3211064 nps 855143 hashfull 895 tbhits 0 time 3755 pv d2d4 + info depth 24 currmove d2d4 currmovenumber 1 + info depth 24 currmove e2e4 currmovenumber 2 + info depth 24 currmove c2c3 currmovenumber 3 + info depth 24 currmove c2c4 currmovenumber 4 + info depth 24 currmove d2d3 currmovenumber 5 + info depth 24 currmove g1f3 currmovenumber 6 + info depth 24 currmove b1c3 currmovenumber 7 + info depth 24 currmove e2e3 currmovenumber 8 + info depth 24 currmove b2b4 currmovenumber 9 + info depth 24 currmove f2f3 currmovenumber 10 + info depth 24 currmove a2a3 currmovenumber 11 + info depth 24 currmove b2b3 currmovenumber 12 + info depth 24 currmove g2g3 currmovenumber 13 + info depth 24 currmove h2h3 currmovenumber 14 + info depth 24 currmove a2a4 currmovenumber 15 + info depth 24 currmove f2f4 currmovenumber 16 + info depth 24 currmove g2g4 currmovenumber 17 + info depth 24 currmove h2h4 currmovenumber 18 + info depth 24 currmove b1a3 currmovenumber 19 + info depth 24 currmove g1h3 currmovenumber 20 + info depth 25 seldepth 28 multipv 1 score cp 26 nodes 3251912 nps 848620 hashfull 899 tbhits 0 time 3832 pv d2d4 g8f6 c2c4 e7e6 g2g3 f8b4 c1d2 b4e7 f1g2 d7d5 g1f3 e8g8 e1g1 c7c6 d1b3 b8d7 d2f4 h7h6 b1c3 d7b6 c4c5 b6c4 b3c2 b7b6 + bestmove d2d4 ponder g8f6 + ``` +
+ +
+ Example: go nodes + + ``` + > position startpos + > go nodes 100000 + info string NNUE evaluation using nn-ad9b42354671.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp 18 nodes 20 nps 10000 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 2 seldepth 2 multipv 1 score cp 46 nodes 66 nps 33000 hashfull 0 tbhits 0 time 2 pv d2d4 + info depth 3 seldepth 2 multipv 1 score cp 51 nodes 120 nps 60000 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 4 seldepth 2 multipv 1 score cp 58 nodes 144 nps 72000 hashfull 0 tbhits 0 time 2 pv d2d4 + info depth 5 seldepth 2 multipv 1 score cp 58 nodes 174 nps 87000 hashfull 0 tbhits 0 time 2 pv d2d4 a7a6 + info depth 6 seldepth 7 multipv 1 score cp 34 nodes 1303 nps 260600 hashfull 0 tbhits 0 time 5 pv e2e4 c7c5 g1f3 b8c6 c2c3 + info depth 7 seldepth 6 multipv 1 score cp 29 nodes 3126 nps 284181 hashfull 1 tbhits 0 time 11 pv d2d4 g8f6 e2e3 d7d5 c2c4 d5c4 + info depth 8 seldepth 7 multipv 1 score cp 26 nodes 5791 nps 340647 hashfull 4 tbhits 0 time 17 pv g1f3 g8f6 d2d4 d7d5 e2e3 + info depth 9 seldepth 9 multipv 1 score cp 31 nodes 8541 nps 371347 hashfull 5 tbhits 0 time 23 pv g1f3 c7c5 e2e4 e7e6 d2d4 c5d4 f3d4 + info depth 10 seldepth 13 multipv 1 score cp 25 nodes 20978 nps 349633 hashfull 10 tbhits 0 time 60 pv e2e4 c7c5 g1f3 b8c6 f1c4 e7e6 e1g1 g8f6 + info depth 11 seldepth 13 multipv 1 score cp 32 nodes 29040 nps 319120 hashfull 14 tbhits 0 time 91 pv e2e4 c7c5 c2c3 g8f6 e4e5 f6d5 d2d4 + info depth 12 seldepth 14 multipv 1 score cp 38 nodes 41207 nps 321929 hashfull 18 tbhits 0 time 128 pv e2e4 e7e6 d2d4 d7d5 b1c3 d5e4 c3e4 + info depth 13 seldepth 15 multipv 1 score cp 31 nodes 60308 nps 307693 hashfull 25 tbhits 0 time 196 pv e2e4 c7c5 g1f3 d7d6 d2d4 c5d4 f3d4 b8c6 c1e3 g8f6 b1c3 g7g6 + info depth 14 seldepth 18 multipv 1 score cp 35 nodes 97789 nps 322735 hashfull 44 tbhits 0 time 303 pv d2d4 g8f6 c2c4 e7e6 g1f3 d7d5 g2g3 f8e7 b1c3 c7c5 + info depth 15 seldepth 18 multipv 1 score cp 35 nodes 100009 nps 320541 hashfull 46 tbhits 0 time 312 pv d2d4 g8f6 c2c4 e7e6 g1f3 d7d5 g2g3 f8e7 b1c3 c7c5 + bestmove d2d4 ponder g8f6 + ``` +
+ + +
+ Example: go mate + + ``` + > position startpos moves g2g4 e7e5 f2f3 + > go mate 1 + info depth 1 seldepth 1 multipv 1 score mate 1 nodes 31 nps 10333 hashfull 0 tbhits 0 time 3 pv d8h4 + bestmove d8h4 + ``` + ``` + > position fen rn1q1r2/p4pk1/1p3R1p/2ppP2Q/3P4/2P4P/P1P3P1/1R4K1 w - - 0 1 moves h5h6 + > go mate 2 + info depth 1 seldepth 3 multipv 1 score cp -536 nodes 2 nps 400 hashfull 0 tbhits 0 time 5 pv g7g8 + info depth 2 seldepth 3 multipv 1 score cp -536 nodes 4 nps 800 hashfull 0 tbhits 0 time 5 pv g7g8 + info depth 3 seldepth 3 multipv 1 score cp -536 nodes 6 nps 1200 hashfull 0 tbhits 0 time 5 pv g7g8 + info depth 4 seldepth 3 multipv 1 score cp -536 nodes 8 nps 1600 hashfull 0 tbhits 0 time 5 pv g7g8 + info depth 5 seldepth 5 multipv 1 score cp -551 nodes 14 nps 2333 hashfull 0 tbhits 0 time 6 pv g7g8 h6g5 g8h7 + info depth 6 seldepth 4 multipv 1 score cp -551 nodes 23 nps 3833 hashfull 0 tbhits 0 time 6 pv g7g8 h6g5 g8h7 + info depth 7 seldepth 5 multipv 1 score mate -2 nodes 36 nps 6000 hashfull 0 tbhits 0 time 6 pv g7g8 h6g5 g8h7 f6h6 + bestmove g7g8 ponder h6g5 + ``` +
+ +
+ Example: MultiPV + + ``` + > setoption name MultiPV value 2 + > position startpos + > go depth 5 + info string NNUE evaluation using nn-ad9b42354671.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp 18 nodes 39 nps 19500 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 1 seldepth 1 multipv 2 score cp 12 nodes 39 nps 19500 hashfull 0 tbhits 0 time 2 pv g1f3 + info depth 2 seldepth 2 multipv 1 score cp 43 nodes 113 nps 56500 hashfull 0 tbhits 0 time 2 pv g1f3 + info depth 2 seldepth 2 multipv 2 score cp 18 nodes 113 nps 56500 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 3 seldepth 3 multipv 1 score cp 75 nodes 169 nps 56333 hashfull 0 tbhits 0 time 3 pv d2d4 c7c6 b1d2 + info depth 3 seldepth 2 multipv 2 score cp 43 nodes 169 nps 56333 hashfull 0 tbhits 0 time 3 pv g1f3 + info depth 4 seldepth 4 multipv 1 score cp 75 nodes 326 nps 108666 hashfull 0 tbhits 0 time 3 pv d2d4 c7c6 b1d2 + info depth 4 seldepth 3 multipv 2 score cp 47 nodes 326 nps 108666 hashfull 0 tbhits 0 time 3 pv b1c3 e7e5 e2e4 + info depth 5 seldepth 4 multipv 1 score cp 30 nodes 933 nps 233250 hashfull 0 tbhits 0 time 4 pv e2e4 g8f6 + info depth 5 seldepth 5 multipv 2 score cp 12 nodes 933 nps 233250 hashfull 0 tbhits 0 time 4 pv b1c3 e7e5 e2e4 c7c6 + bestmove e2e4 ponder g8f6 + ``` +
+ +
+ Example: UCI_ShowWDL + + ``` + > setoption name UCI_ShowWDL value true + > position startpos + > go depth 5 + info string NNUE evaluation using nn-ad9b42354671.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp 18 wdl 22 974 4 nodes 20 nps 10000 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 2 seldepth 2 multipv 1 score cp 46 wdl 82 917 1 nodes 66 nps 33000 hashfull 0 tbhits 0 time 2 pv d2d4 + info depth 3 seldepth 2 multipv 1 score cp 51 wdl 105 894 1 nodes 120 nps 60000 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 4 seldepth 2 multipv 1 score cp 58 wdl 140 859 1 nodes 144 nps 48000 hashfull 0 tbhits 0 time 3 pv d2d4 + info depth 5 seldepth 2 multipv 1 score cp 58 wdl 140 859 1 nodes 174 nps 58000 hashfull 0 tbhits 0 time 3 pv d2d4 a7a6 + bestmove d2d4 ponder a7a6 + ``` +
+ +Parameters: + + * #### `searchmoves .... ` + + Restrict search to these moves only. + Example: After `position startpos` and `go infinite searchmoves e2e4 d2d4` the engine will only search the two moves e2e4 and d2d4 in the initial position. + + * #### `ponder` + + Start searching in pondering mode. It won't exit the search in ponder mode, even if it's mate! + This means that the last move sent in in the position string is the ponder move. + The engine can do what it wants to do, but after a `ponderhit` command it will execute the suggested move to ponder on. + This means that the ponder move sent by the GUI can be interpreted as a recommendation about which move to ponder. + However, if the engine decides to ponder on a different move, it won't display any mainlines as they are likely to be misinterpreted by the GUI because the GUI expects the engine to ponder on the suggested move. + + * #### `wtime ` + + Tell the engine that White has x ms left on the clock. + + * #### `btime ` + + Tell the engine that Black has x ms left on the clock. + + * #### `winc ` + + Tell the engine that White's increment per move in ms if x > 0. + + * #### `binc ` + + Tell the engine that Black's increment per move in ms if x > 0. + + * #### `movestogo ` + + Tell the engine that there are x moves to the next time control + _Note: this will only be sent if x > 0, if you don't get this and get the wtime and btime it's sudden death._ + + * #### `depth ` + + Stop the search when depth x has been reached. + + * #### `nodes ` + + Stop the search when approximately x number of nodes have been reached. + + * #### `mate ` + + Stop the search when/if a mate in x or less moves is found. + It will stop if the side to move is mating and since Stockfish 17 when getting mated too. + + * #### `movetime ` + + Stop the search when approximately x ms have passed. + + * #### `infinite` + + Search until the `stop` command is given. Stockfish won't exit the search without being told so in this mode! + + * #### `perft ` + + A debugging function to walk the move generation tree of strictly legal moves to count all the leaf nodes of a certain depth. + +### `stop` + +Stop calculating as soon as possible + +
+ Example + + ``` + > position startpos + > go infinite + info string NNUE evaluation using nn-ad9b42354671.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp 18 nodes 20 nps 20000 hashfull 0 tbhits 0 time 1 pv e2e4 + info depth 2 seldepth 2 multipv 1 score cp 46 nodes 66 nps 33000 hashfull 0 tbhits 0 time 2 pv d2d4 + info depth 3 seldepth 2 multipv 1 score cp 51 nodes 120 nps 60000 hashfull 0 tbhits 0 time 2 pv e2e4 + info depth 4 seldepth 2 multipv 1 score cp 58 nodes 144 nps 72000 hashfull 0 tbhits 0 time 2 pv d2d4 + info depth 5 seldepth 2 multipv 1 score cp 58 nodes 174 nps 87000 hashfull 0 tbhits 0 time 2 pv d2d4 a7a6 + info depth 6 seldepth 7 multipv 1 score cp 34 nodes 1303 nps 217166 hashfull 0 tbhits 0 time 6 pv e2e4 c7c5 g1f3 b8c6 c2c3 + info depth 7 seldepth 6 multipv 1 score cp 29 nodes 3126 nps 260500 hashfull 1 tbhits 0 time 12 pv d2d4 g8f6 e2e3 d7d5 c2c4 d5c4 + info depth 8 seldepth 7 multipv 1 score cp 26 nodes 5791 nps 304789 hashfull 4 tbhits 0 time 19 pv g1f3 g8f6 d2d4 d7d5 e2e3 + info depth 9 seldepth 9 multipv 1 score cp 31 nodes 8541 nps 294517 hashfull 5 tbhits 0 time 29 pv g1f3 c7c5 e2e4 e7e6 d2d4 c5d4 f3d4 + info depth 10 seldepth 13 multipv 1 score cp 25 nodes 20978 nps 299685 hashfull 10 tbhits 0 time 70 pv e2e4 c7c5 g1f3 b8c6 f1c4 e7e6 e1g1 g8f6 + info depth 11 seldepth 13 multipv 1 score cp 32 nodes 29040 nps 296326 hashfull 14 tbhits 0 time 98 pv e2e4 c7c5 c2c3 g8f6 e4e5 f6d5 d2d4 + > stop + info depth 12 seldepth 14 multipv 1 score cp 38 nodes 41207 nps 300781 hashfull 18 tbhits 0 time 137 pv e2e4 e7e6 d2d4 d7d5 b1c3 d5e4 c3e4 + info depth 13 seldepth 15 multipv 1 score cp 32 upperbound nodes 51476 nps 301029 hashfull 21 tbhits 0 time 171 pv e2e4 c7c5 + bestmove e2e4 ponder c7c5 + ``` +
+ +### `ponderhit` + +The user has played the expected move. +This will be sent if the engine was told to ponder on the same move the user has played. +The engine will continue searching but switch from pondering to normal search. + +
+ Example + + ``` + > setoption name Ponder value true + > position startpos moves e2e4 + > go movetime 1000 + info string NNUE evaluation using nn-52471d67216a.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp -13 nodes 22 nps 22000 hashfull 0 tbhits 0 time 1 pv e7e5 + info depth 2 seldepth 2 multipv 1 score cp -11 nodes 71 nps 71000 hashfull 0 tbhits 0 time 1 pv e7e6 + info depth 3 seldepth 2 multipv 1 score cp -11 nodes 189 nps 94500 hashfull 0 tbhits 0 time 2 pv e7e6 + info depth 4 seldepth 2 multipv 1 score cp -11 nodes 248 nps 124000 hashfull 0 tbhits 0 time 2 pv e7e6 + info depth 5 seldepth 5 multipv 1 score cp -37 nodes 1383 nps 345750 hashfull 1 tbhits 0 time 4 pv d7d5 e4d5 d8d5 + info depth 6 seldepth 5 multipv 1 score cp -30 nodes 2545 nps 318125 hashfull 1 tbhits 0 time 8 pv c7c5 g1f3 e7e6 + info depth 7 seldepth 7 multipv 1 score cp -30 nodes 4201 nps 350083 hashfull 2 tbhits 0 time 12 pv c7c5 g1f3 e7e6 d2d4 c5d4 f3d4 + info depth 8 seldepth 10 multipv 1 score cp -43 nodes 10574 nps 377642 hashfull 4 tbhits 0 time 28 pv c7c5 g1f3 e7e6 d2d4 c5d4 f1e2 + info depth 9 seldepth 11 multipv 1 score cp -35 nodes 16924 nps 360085 hashfull 6 tbhits 0 time 47 pv e7e5 g1f3 b8c6 d2d4 e5d4 f3d4 g8f6 + info depth 10 seldepth 13 multipv 1 score cp -41 nodes 34866 nps 325850 hashfull 12 tbhits 0 time 107 pv e7e5 g1f3 b8c6 f1b5 g8f6 b1c3 f8c5 e1g1 d7d6 d2d4 e5d4 f3d4 + info depth 11 seldepth 14 multipv 1 score cp -38 nodes 43562 nps 325089 hashfull 15 tbhits 0 time 134 pv e7e6 d2d4 d7d5 b1c3 g8f6 c1g5 d5e4 c3e4 + info depth 12 seldepth 16 multipv 1 score cp -41 nodes 56507 nps 326630 hashfull 23 tbhits 0 time 173 pv e7e6 d2d4 d7d5 b1c3 f8b4 g1e2 d5e4 a2a3 b4c3 e2c3 + info depth 13 seldepth 15 multipv 1 score cp -32 nodes 73728 nps 323368 hashfull 28 tbhits 0 time 228 pv e7e6 d2d4 d7d5 b1c3 g8f6 e4e5 f6d7 f2f4 c7c5 c3e2 c5d4 e2d4 + info depth 14 seldepth 17 multipv 1 score cp -31 nodes 90766 nps 318477 hashfull 37 tbhits 0 time 285 pv e7e6 d2d4 d7d5 b1c3 g8f6 e4e5 f6d7 f2f4 c7c5 c3e2 b8c6 g1f3 f8e7 c2c3 e8g8 + info depth 15 seldepth 17 multipv 1 score cp -35 nodes 193951 nps 317432 hashfull 76 tbhits 0 time 611 pv e7e5 g1f3 b8c6 f1c4 g8f6 d2d3 f8e7 b1c3 d7d6 h2h3 e8g8 + info depth 16 seldepth 17 multipv 1 score cp -23 nodes 255750 nps 322916 hashfull 98 tbhits 0 time 792 pv e7e5 g1f3 b8c6 f1c4 g8f6 b1c3 f8c5 d2d3 h7h6 c1e3 c5e3 f2e3 d7d6 c4b3 + info depth 17 seldepth 20 multipv 1 score cp -27 upperbound nodes 323628 nps 322338 hashfull 132 tbhits 0 time 1004 pv e7e5 g1f3 + bestmove e7e5 ponder g1f3 + ``` + Stockfish plays `1. ... e5` and expects `2. Nf3` + ``` + > position startpos moves e2e4 e7e5 g1f3 + > go ponder movetime 1000 + info string NNUE evaluation using nn-52471d67216a.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp -30 nodes 47 nps 23500 hashfull 0 tbhits 0 time 2 pv g8f6 d2d4 + info depth 2 seldepth 2 multipv 1 score cp -30 nodes 86 nps 43000 hashfull 0 tbhits 0 time 2 pv g8f6 d2d4 + info depth 3 seldepth 4 multipv 1 score cp -30 nodes 144 nps 72000 hashfull 0 tbhits 0 time 2 pv g8f6 d2d4 e5d4 e4e5 + info depth 4 seldepth 5 multipv 1 score cp -30 nodes 189 nps 94500 hashfull 0 tbhits 0 time 2 pv g8f6 d2d4 e5d4 e4e5 f6e4 + info depth 5 seldepth 6 multipv 1 score cp -29 nodes 252 nps 126000 hashfull 0 tbhits 0 time 2 pv g8f6 d2d4 e5d4 e4e5 f6e4 d1d4 + info depth 6 seldepth 7 multipv 1 score cp -29 nodes 355 nps 177500 hashfull 0 tbhits 0 time 2 pv g8f6 d2d4 e5d4 e4e5 f6e4 d1d4 d7d5 e5d6 + info depth 7 seldepth 8 multipv 1 score cp -29 nodes 591 nps 295500 hashfull 0 tbhits 0 time 2 pv g8f6 d2d4 e5d4 e4e5 f6e4 d1d4 d7d5 e5d6 + info depth 8 seldepth 11 multipv 1 score cp -29 nodes 1676 nps 279333 hashfull 0 tbhits 0 time 6 pv g8f6 d2d4 e5d4 e4e5 f6e4 d1d4 d7d5 e5d6 + info depth 9 seldepth 10 multipv 1 score cp -29 nodes 2414 nps 301750 hashfull 0 tbhits 0 time 8 pv g8f6 d2d4 e5d4 e4e5 f6e4 d1d4 d7d5 e5d6 e4d6 + info depth 10 seldepth 12 multipv 1 score cp -26 nodes 5045 nps 296764 hashfull 1 tbhits 0 time 17 pv g8f6 d2d4 e5d4 e4e5 f6e4 d1d4 d7d5 e5d6 e4d6 c1g5 b8c6 + info depth 11 seldepth 12 multipv 1 score cp -26 nodes 8612 nps 277806 hashfull 2 tbhits 0 time 31 pv g8f6 d2d4 e5d4 e4e5 f6e4 d1d4 d7d5 e5d6 e4d6 d4c3 d8e7 f1e2 + info depth 12 seldepth 17 multipv 1 score cp -34 nodes 18839 nps 303854 hashfull 6 tbhits 0 time 62 pv g8f6 d2d4 f6e4 f1d3 d7d5 f3e5 f8d6 b1d2 e4d2 c1d2 b8c6 d1h5 g7g6 e5c6 b7c6 h5e2 d8e7 + info depth 13 seldepth 19 multipv 1 score cp -36 nodes 59966 nps 310704 hashfull 25 tbhits 0 time 193 pv b8c6 f1c4 g8f6 d2d3 f8e7 e1g1 e8g8 f1e1 d7d6 a2a4 c8e6 + info depth 14 seldepth 19 multipv 1 score cp -28 nodes 128004 nps 312968 hashfull 61 tbhits 0 time 409 pv g8f6 d2d4 f6e4 f1d3 d7d5 f3e5 f8d6 c2c4 e8g8 c4d5 d6b4 b1d2 e4d2 c1d2 + info depth 15 seldepth 22 multipv 1 score cp -31 nodes 191379 nps 314251 hashfull 92 tbhits 0 time 609 pv b8c6 f1c4 g8f6 d2d3 f8e7 e1g1 d7d6 a2a4 e8g8 f1e1 c6a5 c4a2 c7c5 b1c3 a5c6 a2c4 c8e6 c3d5 e6d5 e4d5 + info depth 16 seldepth 24 multipv 1 score cp -32 nodes 319240 nps 316392 hashfull 150 tbhits 0 time 1009 pv b8c6 f1c4 g8f6 d2d3 f8e7 e1g1 d7d6 a2a4 e8g8 a4a5 c8e6 c4e6 f7e6 a5a6 b7b5 c2c3 d8c8 b1d2 a8b8 b2b4 + ``` + The opponent plays the expected `2. Nf3` + ``` + > ponderhit + info depth 17 seldepth 24 multipv 1 score cp -32 nodes 351706 nps 307973 hashfull 161 tbhits 0 time 1142 pv b8c6 f1c4 g8f6 d2d3 f8e7 e1g1 d7d6 a2a4 e8g8 a4a5 c8e6 c4e6 f7e6 a5a6 b7b5 c2c3 d8c8 b1d2 a8b8 b2b4 + bestmove b8c6 ponder f1c4 + ``` + Stockfish plays `2. ... Nc6` and expects `3. Bc4` + ``` + > position startpos moves e2e4 e7e5 g1f3 b8c6 f1c4 + > go ponder movetime 1000 + info string NNUE evaluation using nn-52471d67216a.nnue enabled + info depth 1 seldepth 1 multipv 1 score cp -34 nodes 39 nps 39000 hashfull 0 tbhits 0 time 1 pv d7d6 + info depth 2 seldepth 2 multipv 1 score cp -38 nodes 95 nps 47500 hashfull 0 tbhits 0 time 2 pv d7d6 d2d4 e5d4 + info depth 3 seldepth 3 multipv 1 score cp -38 nodes 164 nps 82000 hashfull 0 tbhits 0 time 2 pv d7d6 d2d4 e5d4 + info depth 4 seldepth 4 multipv 1 score cp -38 nodes 231 nps 115500 hashfull 0 tbhits 0 time 2 pv d7d6 d2d4 e5d4 f3d4 + ... + ``` +
+ +--- + +## Non-standard commands + +### `bench` + +This runs a standard search benchmark on a pre-selected assortment of positions. It prints the total combined nodes searched, as well as time taken. This command serves two primary purposes: + +* it can be used as a basic nodes-per-second speed benchmark + +* the total number of nodes searched can be used as a "signature" or "fingerprint" of the exact search algorithm version in the binary + +The main utility of the nodecount signature is to ensure that, when testing possible new patches on Fishtest, the author and workers are working on the exact same code. It also can be used to verify which version or release you have locally. Each functional commit in the Stockfish commit history includes a standardized nodecount signature. (For example, the nodecount signature of [Stockfish 15](https://github.com/official-stockfish/Stockfish/commit/e6e324eb28fd49c1fc44b3b65784f85a773ec61c) is 8129754.) + +There are several parameters which can be used to tweak exactly what sort of benchmark is run: + +Usage: `bench [ttSize] [threads] [limit] [fenFile] [limitType]` + +The standardized nodecount signature of a version is obtained using all default parameters. + +
+ Example + + ``` + > position startpos + > bench 16 1 1 current depth + + Position: 1/1 (rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1) + info string NNUE evaluation using nn-c38c3d8d3920.nnue + info depth 1 seldepth 1 multipv 1 score cp 25 nodes 20 nps 10000 hashfull 0 tbhits 0 time 2 pv d2d4 + bestmove d2d4 + + =========================== + Total time (ms) : 2 + Nodes searched : 21 + Nodes/second : 10500 + ``` +
+ +The bench command may also be used in the command line when executing Stockfish. + +
+ Examples + + ``` + ./stockfish bench + ``` + Running the bench with specific parameters and redirecting the output of the benchmark to a file called "outputFile". + ``` + ./stockfish bench 16 1 1 inputFile > outputFile + ``` + Running the benchmark with specific parameters in the starting position indefinitely and redirecting the output. + Note than in this case, the `limit` is ignored. + ``` + ./stockfish bench 4096 16 _ current infinite > outputFile + ``` +
+ +| Parameter | Default | Values | Meaning | +|-------------|:---------:|-----------------------------------------------------------|----------------------------------| +| `ttSize` | `16` | | Hash value | +| `threads` | `1` | | Number of threads | +| `limit` | `13` | | The limit of `limitType` | +| `fenFile` | `default` | `default`, `current` or `[file path]` | The positions used for the bench | +| `limitType` | `depth` | A [go parameter](#go) (e.g. `depth` or `nodes`) or `eval` | The type of limit | + +> [!NOTE] +> * **String parameters are case-sensitive**. In case of invalid values of string parameters, the error is not given, and the behavior is undefined (the program does not fall back to a default value). +> * The `[file path]` may contain **one or more positions**, each on a separate line. + +### `speedtest` + +> [!NOTE] +> This command is currently not available in any major or minor release. It is only available in [pre-releases](https://github.com/official-stockfish/Stockfish/releases?q=prerelease%3Atrue) since [3ac75cd (2024-09-28)](https://github.com/official-stockfish/Stockfish/commit/3ac75cd27d914da29280163c9d391bbca414d766). + +Measures the speed of the computer with a realistic and stable hardware benchmark. By default, Stockfish will run on all available threads, using a reasonable hash, on a number of positions that represent at typical game. The output is the achieved Nodes/second. + +The simplest and intended usage is: +`./stockfish speedtest` + +The advanced usage is `speedtest [threads] [hash (MiB)] [runtime (s)]` + +
+ Example with output + +``` +C:\dev\stockfish-master\src>stockfish.exe speedtest +Stockfish dev-20240928-nogit by the Stockfish developers (see AUTHORS file) +info string Using 16 threads +Warmup position 3/3 +Position 258/258 +=========================== +Version : Stockfish dev-20240928-nogit +Compiled by : g++ (GNUC) 13.2.0 on MinGW64 +Compilation architecture : x86-64-vnni256 +Compilation settings : 64bit VNNI BMI2 AVX2 SSE41 SSSE3 SSE2 POPCNT +Compiler __VERSION__ macro : 13.2.0 +Large pages : yes +User invocation : speedtest +Filled invocation : speedtest 16 2048 150 +Available processors : 0-15 +Thread count : 16 +Thread binding : none +TT size [MiB] : 2048 +Hash max, avg [per mille] : + single search : 40, 21 + single game : 631, 428 +Total nodes searched : 2099917842 +Total search time [s] : 153.937 +Nodes/second : 13641410 +``` + +
+ +| Parameter | Default | +|-----------|:-------------:| +| `threads` | all | +| `hash` | threads * 128 | +| `runtime` | 150 | + +### `d` + +Display the current position, with ASCII art and FEN. + +
+ Example + + ``` + > d + + +---+---+---+---+---+---+---+---+ + | r | n | b | q | k | b | n | r | 8 + +---+---+---+---+---+---+---+---+ + | p | p | p | p | p | p | p | p | 7 + +---+---+---+---+---+---+---+---+ + | | | | | | | | | 6 + +---+---+---+---+---+---+---+---+ + | | | | | | | | | 5 + +---+---+---+---+---+---+---+---+ + | | | | | | | | | 4 + +---+---+---+---+---+---+---+---+ + | | | | | | | | | 3 + +---+---+---+---+---+---+---+---+ + | P | P | P | P | P | P | P | P | 2 + +---+---+---+---+---+---+---+---+ + | R | N | B | Q | K | B | N | R | 1 + +---+---+---+---+---+---+---+---+ + a b c d e f g h + + Fen: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 + Key: 8F8F01D4562F59FB + Checkers: + ``` +
+ +### `eval` + +Display the static evaluation of the current position. + +
+Example + +``` +info string NNUE evaluation using nn-c38c3d8d3920.nnue + + + NNUE derived piece values: ++-------+-------+-------+-------+-------+-------+-------+-------+ +| r | n | b | q | k | b | n | r | +| -5.28 | -4.92 | -5.11 | -8.99 | | -5.12 | -4.96 | -5.55 | ++-------+-------+-------+-------+-------+-------+-------+-------+ +| p | p | p | p | p | p | p | p | +| -0.70 | -1.17 | -1.15 | -1.14 | -1.27 | -1.69 | -1.41 | -0.72 | ++-------+-------+-------+-------+-------+-------+-------+-------+ +| | | | | | | | | +| | | | | | | | | ++-------+-------+-------+-------+-------+-------+-------+-------+ +| | | | | | | | | +| | | | | | | | | ++-------+-------+-------+-------+-------+-------+-------+-------+ +| | | | | | | | | +| | | | | | | | | ++-------+-------+-------+-------+-------+-------+-------+-------+ +| | | | | | | | | +| | | | | | | | | ++-------+-------+-------+-------+-------+-------+-------+-------+ +| P | P | P | P | P | P | P | P | +| +0.64 | +1.04 | +1.03 | +0.98 | +1.14 | +1.49 | +1.23 | +0.61 | ++-------+-------+-------+-------+-------+-------+-------+-------+ +| R | N | B | Q | K | B | N | R | +| +4.38 | +4.13 | +4.52 | +7.67 | | +4.37 | +4.11 | +4.65 | ++-------+-------+-------+-------+-------+-------+-------+-------+ + + NNUE network contributions (White to move) ++------------+------------+------------+------------+ +| Bucket | Material | Positional | Total | +| | (PSQT) | (Layers) | | ++------------+------------+------------+------------+ +| 0 | 0.00 | - 2.93 | - 2.93 | +| 1 | 0.00 | - 0.16 | - 0.16 | +| 2 | 0.00 | + 0.39 | + 0.39 | +| 3 | 0.00 | + 0.43 | + 0.43 | +| 4 | 0.00 | + 0.20 | + 0.20 | +| 5 | 0.00 | + 0.26 | + 0.26 | +| 6 | 0.00 | + 0.27 | + 0.27 | +| 7 | 0.00 | + 0.10 | + 0.10 | <-- this bucket is used ++------------+------------+------------+------------+ + +NNUE evaluation +0.10 (white side) +Final evaluation +0.11 (white side) [with scaled NNUE, ...] +``` +
+ +### `compiler` + +Give information about the compiler and environment used for building a binary. + +Example: +``` +> compiler + +Compiled by g++ (GNUC) 13.1.0 on MinGW64 +Compilation settings include: 64bit AVX2 SSE41 SSSE3 SSE2 POPCNT +__VERSION__ macro expands to: 13.1.0 +``` + +### `export_net [filenameBigNet] [filenameSmallNet]` + +Exports the currently loaded network to a file. +If the currently loaded network is the embedded network and the filename is not specified then the network is saved to the file matching the name of the embedded network, as defined in `evaluate.h`. +If the currently loaded network is not the embedded network (some net set through the UCI `setoption`) then the filename parameter is required and the network is saved into that file. + +### `flip` + +Flips the side to move. + +### `help` + +Gives version info, describes Stockfish as a chess engine using UCI, and points to the GitHub page for more details. + +### `license` + +Gives version info, describes Stockfish as a chess engine using UCI, and points to the GitHub page for more details. diff --git a/stockfish/wiki/Useful-data.md b/stockfish/wiki/Useful-data.md new file mode 100644 index 0000000000000000000000000000000000000000..0caab27fd1b38c00c9455a2dfdf9abffa9706694 --- /dev/null +++ b/stockfish/wiki/Useful-data.md @@ -0,0 +1,502 @@ + +## Depth vs. TC + +Newer and older results showing the average depth for games at fishtest conditions + +| New | Old | +|:----------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------:| +| | | + + + +--- + +## Elo cost of small Hash + +We measure the influence of Hash on the playing strength, using games of SF15.1 at LTC (60+0.6s) and VLTC (240+2.4s) on the UHO book. +Hash is varied between 1 and 64 MB and 256MB in powers of two, leading to as average hashfull between 100 and 950 per thousand. +The data suggests that keeping the average hashfull below 30% is best to maintain strength. + + + + +
+ Raw data for the above graph
+ +| Hash | Hashfull | Elo | Elo-err | +|-----:|:--------:|-------:|:-------:| +| 64 | 109 | 0.00 | 0.00 | +| 32 | 199 | -3.80 | 13.00 | +| 16 | 336 | 0.70 | 12.80 | +| 8 | 513 | -10.70 | 11.00 | +| 4 | 689 | -21.50 | 13.30 | +| 2 | 825 | -29.50 | 13.10 | +| 1 | 902 | -47.80 | 8.80 | + +| Hash | Hashfull | Elo | Elo-err | +|-----:|:--------:|-------:|:-------:| +| 256 | 131 | 0.00 | 0.00 | +| 128 | 239 | -1.00 | 7.50 | +| 64 | 397 | -0.80 | 6.60 | +| 32 | 591 | -12.10 | 6.10 | +| 16 | 766 | -21.40 | 7.30 | +| 8 | 865 | -32.30 | 4.20 | +| 4 | 931 | -52.40 | 6.20 | +| 2 | 943 | -67.40 | 5.70 | +| 1 | 947 | -95.20 | 6.60 | + +
+ +--- + +## Elo cost of using MultiPV + +MultiPV provides the N best moves and their principal variations. This is a great tool for understanding the options available in a given position. However, this information does not come for free and the cost of computing it reduces the quality of the best move found compared to a search that only needs to find a single line. + +| MultiPV | Elo | Elo-err | +|--------:|-------:|:-------:| +| 1 | 0.0 | 0.0 | +| 2 | -97.2 | 2.1 | +| 3 | -156.7 | 2.8 | +| 4 | -199.3 | 2.9 | +| 5 | -234.5 | 2.8 | + +Engine: Stockfish 15.1 +Time control: 60s+0.6s +Book: UHO + +--- + +## Elo gain using MultiPV at fixed depth + +| MultiPV | Elo | Elo-err | Points | Played | +|--------:|-----:|:-------:|--------:|-------:| +| 1 | 0.0 | | 13496.5 | 30614 | +| 2 | 45.7 | 3.1 | 15388.0 | 30697 | +| 3 | 53.9 | 3.5 | 15732.5 | 30722 | +| 4 | 59.5 | 3.2 | 15862.5 | 30479 | +| 5 | 63.7 | 3.6 | 16078.5 | 30604 | + +Time control: 580s+5.8s +Depth: 18 + +--- + +## Elo gain using syzygy + +### TB6 testing for various versions of SF + +Consistent measurement of Elo gain (syzygy 6men vs none) for various SF versions: + + + +TB are in RAM (so fast access), TC is 10+0.1s (STC), book UHO_XXL_+0.90_+1.19.epd. No adjudication. The introduction of NNUE (with SF12) is clearly visible. With SF15, there is just 2.7 Elo gain. + +
+ Raw data for the above graph
+ +| SF | Elo | Elo-err | +|---:|-----:|--------:| +| 6 | 14.5 | 1.4 | +| 7 | 15.6 | 1.3 | +| 8 | 15.8 | 1.3 | +| 9 | 16.5 | 1.5 | +| 10 | 16.2 | 1.5 | +| 11 | 15.8 | 1.5 | +| 12 | 7.2 | 1.4 | +| 13 | 11.1 | 1.4 | +| 14 | 7.3 | 1.4 | +| 15 | 2.7 | 1.4 | + +
+ +### Testing depending on number of pieces and TC + +Tested at 10+0.1, with all syzygy WDL files on tmpfs (i.e. RAM), testing using none(0), 4, 5, and 6 man TB in a round-robin tournament (SF10dev). + +| Rank | Name | Elo | +/- | Games | Score | Draws | +|-----:|---------|----:|:---:|:-----:|:-----:|:-----:| +| 1 | syzygy6 | 13 | 2 | 82591 | 51.8% | 59.5% | +| 2 | syzygy5 | 2 | 2 | 82590 | 50.3% | 59.4% | +| 3 | syzygy4 | -7 | 2 | 82591 | 49.0% | 59.3% | +| 4 | syzygy0 | -7 | 2 | 82592 | 48.9% | 59.4% | + +Tested at 60+0.6, with all syzygy WDL files on tmpfs (i.e. RAM), testing using none(0) against 6 man TB: + +Score of syzygy6 vs syzygy0: 4084 - 3298 - 18510 [0.515] 25892 +Elo difference: 10.55 +/- 2.25 + +--- + +## Threading efficiency and Elo gain. + +### Efficiency + +Here we look at the threading efficiency of the lazySMP parallelization scheme. To focus on the algorithm we play games with a given budget of nodes rather than at a given TC. In principle, lazySMP has excellent scaling of the nps with cores, but practical measurement is influenced by e.g. frequency adjustments, SMT/hyperthreading, and sometimes hardware limitation. + +#### Equivalent nodestime + +In these tests, matches are played at a fixed nodes budget (using the nodestime feature of SF), and equivalence in strength between the serial player and the threaded player (for x threads in the graph below) is found by adjusting the number of nodes given to the threaded player (e.g. with 16 threads, the threaded player might need 200% of the nodes of the serial player to match the strength of the serial player). This 'equivalent nodestime' is determined for various number of threads and various nodes budgets (60+0.6Mnodes/game is somewhat similar to our usual LTC at 60+0.6s/game, if we assume 1Mnps). + + + +The interesting observation one can make immediately is that this 'equivalent nodestime' grows with the number of threads, but not too steeply, and further more that the 'equivalent nodestime' decreases with increasing nodes budget. The data shows that with 64 threads, the equivalent nodestime is about 200% for a node budget of 240+2.4Mn, i.e. despite such games being much faster than STC (10+0.1s), efficiency is still around 50%. + +The curves are sufficiently smooth to be fitted with a model having 1 parameter that is different between the curves (`f(x)`, parameter `a`, see caption). A smaller value of `a` means a higher efficiency. + +#### A fit for the `a` parameter, and extrapolation to long TCs. + +The above parameter `a` from the model, can be fit as a function of nodes budget, this allows for extrapolating the parameter, and to arrive at and estimate for the 'equivalent nodestime' at large TC / nodes budgets: + + + +The fit is again fairly good. Taking a leap of faith, these measurements at up to 240+2.4Mn can be extrapolated to node budgets typical of TCEC or CCC (up to 500Gn). This allows us to predict speedup and/or efficiency. + +| Speedup | Efficiency | +|:----------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------:| +| | | + +These extrapolations suggest that even at thread counts of >300, at TCEC TCs efficiency could be 80% or higher, provided the nps scales with the number of threads. + +### Elo results (older) + +### LTC +Playing 8 threads vs 1 thread at LTC (60+0.6, 8moves_v3.pgn): + +``` +Score of t8 vs seq: 476 - 3 - 521 [0.737] 1000 +Elo difference: 178.6 +/- 14.0, LOS: 100.0 %, DrawRatio: 52.1 % +``` + +Playing 1 thread at 8xLTC (480+4.8) vs (60+0.6) (8moves_v3.pgn): + +``` +Score of seq8 vs seq: 561 - 5 - 434 [0.778] 1000 +Elo difference: 217.9 +/- 15.8, LOS: 100.0 %, DrawRatio: 43.4 % +``` + +Which is roughly 82% efficiency (178/218). + +#### STC +Playing 8 threads vs 1 thread at STC (10+0.1): + +``` +Score of threads vs serial: 1606 - 15 - 540 [0.868] 2161 +Elo difference: 327.36 +/- 14.59 +``` + +Playing 8 threads @ 10+0.1 vs 1 thread @ 80+0.8: + +``` +Score of threads vs time: 348 - 995 - 2104 [0.406] 3447 +Elo difference: -66.00 +/- 7.15 +``` + +So, 1 -> 8 threads has about 83% scaling efficiency (327 / (327 + 66)) using this test. + +--- + +## Elo from speedups + + + +For small speedups (<~5%) the linear estimate can be used that gives Elo gain as a function of speedup percentage (x) as: + +``` +Elo_stc(x) = 2.10 x +Elo_ltc(x) = 1.43 x +``` + +To have 50% passing chance at STC<-0.5,1.5>, we need a 0.24% speedup, while at LTC<0.25,1.75> we need 0.70% speedup. A 1% speedup has nearly 85% passing chance at LTC. + +Raw data: +``` +tc 10+0.1: +16 32.42 3.06 + 8 13.67 3.05 + 4 8.99 3.04 + 2 3.52 3.05 + +tc 60+0.6: +16 20.85 2.59 + 8 12.20 2.57 + 4 4.67 2.57 +``` + +_Note: Numbers will depend on the precise hardware. The model was verified quite accurately on fishtest see https://github.com/locutus2/Stockfish-old/commit/82958c97214b6d418e5bc95e3bf1961060cd6113#commitcomment-38646654_ + +--- + +## Distribution of lengths of games at LTC (60+0.6) on fishtest + +In a collection of a few million games, the longest was [902 plies](https://github.com/protonspring/Stockfish/files/4532568/long.pgn.txt). + + + +--- + +## Win-Draw-Loss statistics of LTC games on fishtest + +The following graph gives information on the Win-Draw-Loss (WDL) statistics, relating them to score and material count. It answers the question 'What fraction of positions that have a given score (and material count) in fishtest LTC, have a Win or a Draw or a Loss ?'. + +[](https://raw.githubusercontent.com/official-stockfish/WDL_model/master/WDL_model_summary.png) + +This model is used when Stockfish provides WDL statistics during analysis with the `UCI_ShowWDL` option set to True, as well as for the normalization of Stockfish's evaluation that ensures that a score of "100 centipawns" means the engine has a 50% probability to win from this position in selfplay at fishtest LTC time control. For details see the [WDL model repo](https://github.com/official-stockfish/WDL_model). + +--- +## Equivalent time odds and normalized game pair Elo + + + +A suitable measure to define the Elo difference between two engines is `normalized game pair Elo` as defined from the pentanomial statistics by: + +```python +def normalized_game_pair_elo(row): + return -100 * np.log10((2 * row['pntl0'] + row['pntl1']) / (2 * row['pntl4'] + row['pntl3'])) +``` + +It is nearly book independent, and thus a good measure of relative strength of two engines at a given TC. To express more clearly what a given strength difference implies. We use 'equivalent time odds', i.e. the TC factor needed to have equivalent strength, i.e. zero Elo difference in a match between two engines (which is independent of the definition of Elo used). + +We see that at STC the equivalent time odds is about 6x for SF14 vs SF17, while at LTC this time odds factor has become 16x. + + +
+ Raw data for the above graph
+ +``` +======================================= UHO_Lichess_4852_v1 ======================================= + engine1 tc1 engine2 tc2 elo pntl0 pntl1 pntl2 pntl3 pntl4 ngp_Elo + sf17 10+0.1 sf14 10.0+0.1 165.29 10 432 6509 25598 3291 185.24 + sf17 10+0.1 sf14 40.0+0.4 41.74 251 5110 16632 13516 331 40.25 + sf17 10+0.1 sf14 60.0+0.6 7.47 395 7724 17826 9736 159 7.22 + sf17 10+0.1 sf14 80.0+0.8 -17.52 569 10075 17691 7408 97 -16.88 +======================================= noob_3moves ======================================= + engine1 tc1 engine2 tc2 elo pntl0 pntl1 pntl2 pntl3 pntl4 ngp_Elo + sf17 10+0.1 sf14 10.0+0.1 108.47 6 610 16073 16012 3139 155.43 + sf17 10+0.1 sf14 40.0+0.4 10.85 100 3004 27471 5089 176 23.00 + sf17 10+0.1 sf14 60.0+0.6 -4.87 165 4048 28312 3257 58 -11.33 + sf17 10+0.1 sf14 80.0+0.8 -15.02 219 4892 28520 2184 25 -37.76 +======================================= UHO_Lichess_4852_v1 ======================================= + engine1 tc1 engine2 tc2 elo pntl0 pntl1 pntl2 pntl3 pntl4 ngp_Elo + sf17 60+0.6 sf14 60.0+0.6 163.96 1 194 5269 29060 1316 220.87 + sf17 60+0.6 sf14 240.0+2.4 88.09 25 2021 14134 19482 178 98.13 + sf17 60+0.6 sf14 360.0+3.6 63.06 41 3212 16546 15938 103 69.03 + sf17 60+0.6 sf14 480.0+4.8 46.39 72 4243 17703 13760 62 50.03 +======================================= noob_3moves ======================================= + engine1 tc1 engine2 tc2 elo pntl0 pntl1 pntl2 pntl3 pntl4 ngp_Elo + sf17 60+0.6 sf14 60.0+0.6 71.55 0 131 22234 12279 1196 204.92 + sf17 60+0.6 sf14 240.0+2.4 19.23 0 436 31090 4231 83 100.37 + sf17 60+0.6 sf14 360.0+3.6 11.51 3 616 32255 2938 28 68.25 + sf17 60+0.6 sf14 480.0+4.8 7.08 6 716 32949 2149 20 47.81 +``` + +
+ +--- + +## Elo gain with time odds + +See also: https://github.com/official-stockfish/Stockfish/discussions/3402 + +| New | Old | +|:-----------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------:| +| | | + +--- + +## One year of NNUE speed improvements + +Presents nodes per second (nps) measurements for all SF version between the first NNUE commit (SF_NNUE, Aug 2th 2020) and end of July 2021 on a AMD Ryzen 9 3950X compiled with `make -j ARCH=x86-64-avx2 profile-build`. The last nps reported for a depth 22 search from startpos using NNUE (best over about 20 measurements) is shown in the graph. For reference, the last classical evaluation (SF_classical, July 30 2020) has 2.30 Mnps. + + + +--- + +## The impact of efficient (incremental) updates (NNUE) + +As measured with SF17dev (dev-20230824-4c4cb185), disabling the update_accumulator_incremental() functionality. + +Speedup: +``` +Result of 10 runs +================== +base (./stockfish.master ) = 1287575 +/- 8703 +test (./stockfish.patch ) = 696064 +/- 3451 +diff = -591511 +/- 7318 + +speedup = -0.4594 +P(speedup > 0) = 0.0000 + +CPU: 16 x AMD Ryzen 9 3950X 16-Core Processor +``` + +Which corresponds to [-67.55 ± 9.5 Elo on fishtest](https://tests.stockfishchess.org/tests/view/64e8f64085e3e95030fd80bf) with the UHO book at LTC. + +--- +## Round-robin tournament with SF releases, impact of book and time odds + +Measured playing games of 5+0.05s, with SF 7 - 15, using the three different books. Each version plays once with the base TC, and once with 20% time odds. + + + +
+ Raw data for the above graph
+ + +### UHO + +| SF | Elo | 20%-odds | Elo-err | Odds-err | +|-----:|------:|---------:|--------:|---------:| +| SF7 | 0.0 | 38.1 | 0.0 | 4.1 | +| SF8 | 95.8 | 40.3 | 4.2 | 5.9 | +| SF9 | 142.3 | 38.8 | 3.9 | 5.5 | +| SF10 | 199.5 | 38.2 | 4.0 | 5.7 | +| SF11 | 231.2 | 40.5 | 4.3 | 5.7 | +| SF12 | 405.6 | 37.5 | 4.0 | 5.9 | +| SF13 | 476.5 | 28.4 | 4.2 | 6.0 | +| SF14 | 553.4 | 27.8 | 4.5 | 6.3 | +| SF15 | 627.6 | 24.5 | 4.6 | 6.7 | + +### noob + +| SF | Elo | 20%-odds | Elo-err | Odds-err | +|-----:|------:|---------:|--------:|---------:| +| SF7 | 0.0 | 37.8 | 0.0 | 3.8 | +| SF8 | 97.2 | 39.8 | 4.3 | 5.9 | +| SF9 | 146.8 | 40.5 | 3.9 | 5.9 | +| SF10 | 211.1 | 39.3 | 4.3 | 6.2 | +| SF11 | 241.8 | 43.0 | 4.4 | 6.0 | +| SF12 | 458.4 | 32.1 | 4.3 | 6.2 | +| SF13 | 536.2 | 31.9 | 4.2 | 6.3 | +| SF14 | 611.3 | 29.1 | 4.5 | 6.5 | +| SF15 | 660.9 | 24.8 | 4.3 | 6.2 | + +### 8moves + +| SF | Elo | 20%-odds | Elo-err | Odds-err | +|-----:|------:|---------:|--------:|---------:| +| SF7 | 0.0 | 33.0 | 0.0 | 4.2 | +| SF8 | 86.7 | 32.2 | 4.2 | 5.8 | +| SF9 | 126.7 | 37.3 | 4.0 | 5.6 | +| SF10 | 182.3 | 33.7 | 4.3 | 5.6 | +| SF11 | 206.5 | 42.6 | 4.0 | 5.4 | +| SF12 | 380.7 | 31.6 | 4.1 | 5.6 | +| SF13 | 445.8 | 25.0 | 4.0 | 5.7 | +| SF14 | 512.4 | 23.8 | 4.1 | 5.9 | +| SF15 | 554.5 | 26.4 | 4.1 | 5.9 | + +
+ +--- + +## Branching factor of Stockfish + +The branching factor ($`B_f`$) of Stockfish is defined such that $`\text{nodes} = B_f^{\text{rootDepth}}`$ or equivalently $`B_f = \exp\left(\frac{\log(\text{nodes})}{\text{rootDepth}}\right)`$. Here, this has been measured with a single search from the starting position. + +The trend is the deeper one searches the lower the branching factor, and newer versions of SF have a lower branching factor. A small difference in branching factor leads to very large differences in number of nodes searched. For example, Stockfish 10 needs about 338x more nodes than Stockfish 17 to reach depth 49. + +![chartbf](https://github.com/user-attachments/assets/ae892d6b-e06b-405f-90aa-afc7c44596ec) + +
+ Raw data for the above graph
+ +| Depth | SF_9 | SF_10 | SF_11 | SF_Classical | SF_12 | SF_13 | SF_14 | SF_14.1 | SF_15 | SF_15.1 | SF_16 | SF_16.1 | SF_17 | +|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| 1 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | 20 | +| 2 | 47 | 54 | 54 | 56 | 42 | 42 | 48 | 51 | 45 | 66 | 40 | 44 | 48 | +| 3 | 152 | 136 | 147 | 150 | 153 | 84 | 181 | 154 | 191 | 120 | 70 | 69 | 76 | +| 4 | 495 | 247 | 574 | 479 | 303 | 241 | 630 | 807 | 264 | 144 | 101 | 92 | 97 | +| 5 | 1036 | 1157 | 782 | 989 | 532 | 572 | 741 | 1061 | 1449 | 174 | 131 | 123 | 124 | +| 6 | 2148 | 2250 | 2734 | 1161 | 1004 | 741 | 2448 | 1761 | 3954 | 1303 | 489 | 1815 | 197 | +| 7 | 3836 | 4481 | 5780 | 2808 | 1680 | 1169 | 3488 | 5459 | 6269 | 3126 | 1560 | 2096 | 309 | +| 8 | 6480 | 7849 | 9568 | 10224 | 4717 | 1849 | 5172 | 6998 | 11049 | 5791 | 2105 | 2565 | 1984 | +| 9 | 12958 | 11846 | 14134 | 15801 | 7299 | 3894 | 10602 | 12053 | 15347 | 8541 | 4500 | 5901 | 3415 | +| 10 | 27933 | 27338 | 18613 | 25231 | 15571 | 10144 | 23676 | 28785 | 23259 | 20978 | 7548 | 7103 | 3634 | +| 11 | 89387 | 61978 | 37332 | 36205 | 32300 | 29662 | 44751 | 34551 | 40064 | 29040 | 13058 | 22753 | 8402 | +| 12 | 161734 | 80917 | 99541 | 98957 | 79712 | 57232 | 60151 | 103152 | 57767 | 41207 | 35836 | 27955 | 11157 | +| 13 | 257254 | 160903 | 161123 | 159227 | 97905 | 89986 | 99443 | 166195 | 97551 | 60308 | 59384 | 31553 | 14979 | +| 14 | 292516 | 263420 | 235857 | 188774 | 135890 | 114602 | 140890 | 226798 | 152736 | 97789 | 73942 | 47991 | 47967 | +| 15 | 619466 | 474032 | 525778 | 372631 | 239380 | 263818 | 206290 | 296769 | 214622 | 142447 | 173344 | 90454 | 82930 | +| 16 | 1019034 | 680143 | 654750 | 405644 | 420597 | 319442 | 327876 | 460525 | 484091 | 199887 | 302305 | 130482 | 117953 | +| 17 | 1273498 | 1065531 | 1154718 | 497031 | 502334 | 539694 | 700175 | 620141 | 570807 | 268004 | 358119 | 279903 | 130041 | +| 18 | 2090089 | 1736290 | 1333553 | 602794 | 888362 | 605175 | 1254757 | 674532 | 873460 | 298071 | 406239 | 378645 | 278301 | +| 19 | 3889524 | 2883012 | 2466048 | 1697325 | 1163966 | 849949 | 1461089 | 1086024 | 1119774 | 332202 | 517102 | 492584 | 479846 | +| 20 | 6651739 | 4186926 | 3442296 | 2223954 | 2233676 | 1504284 | 2142551 | 1731345 | 1494341 | 557706 | 768773 | 898610 | 631058 | +| 21 | 8289557 | 4805680 | 5139240 | 2921191 | 2445813 | 3168396 | 2296200 | 2217575 | 2170638 | 753633 | 872083 | 1176108 | 698143 | +| 22 | 10859418 | 6827891 | 6158594 | 4887298 | 3007878 | 3898518 | 3992124 | 2939401 | 2709915 | 1197489 | 1356583 | 1504022 | 942656 | +| 23 | 15123810 | 11731363 | 9742620 | 5254745 | 4135648 | 5354837 | 5163931 | 7071762 | 3144247 | 1586189 | 2240135 | 2486833 | 1239406 | +| 24 | 20808306 | 17783159 | 12170495 | 5804128 | 5840781 | 6300391 | 8203572 | 9001254 | 5196382 | 2305729 | 3180221 | 2849481 | 2442109 | +| 25 | 29432182 | 25759382 | 16577641 | 9887365 | 8616999 | 9226135 | 9663515 | 9575262 | 7230340 | 3251912 | 3441646 | 3582655 | 2851004 | +| 26 | 38405658 | 37355659 | 29415435 | 15416329 | 12367860 | 13291389 | 13132115 | 12540800 | 10155997 | 3545947 | 5785705 | 5160652 | 3777377 | +| 27 | 61348538 | 65193345 | 35334801 | 16892508 | 14200465 | 17614230 | 17260982 | 14253616 | 11757619 | 3840811 | 7477520 | 6713587 | 4182386 | +| 28 | 91156568 | 84966056 | 52360545 | 22446945 | 20151711 | 22949203 | 23305850 | 17086667 | 14952204 | 4248920 | 7825201 | 7315094 | 4579410 | +| 29 | 152014843 | 113526990 | 56749397 | 31024928 | 24336215 | 27545670 | 32590861 | 24537585 | 16522922 | 6665787 | 9338607 | 8935544 | 5542564 | +| 30 | 208471933 | 134803005 | 80264413 | 40636168 | 41377827 | 52979471 | 37897422 | 29100645 | 17532106 | 9416686 | 11373475 | 12808942 | 6276969 | +| 31 | 240772842 | 196015388 | 120969918 | 43679803 | 49872575 | 70001618 | 43511005 | 39989064 | 21326309 | 12481255 | 14291027 | 16399268 | 7084546 | +| 32 | 305614124 | 243327159 | 176419860 | 60779582 | 77092786 | 83237024 | 66185509 | 58476165 | 35327951 | 13176152 | 14508031 | 19160671 | 9105481 | +| 33 | 460341298 | 525261279 | 262818230 | 104196553 | 99594493 | 121678252 | 70265337 | 74720366 | 42781773 | 29047503 | 21536616 | 28320792 | 12975039 | +| 34 | 627444498 | 626723336 | 349662654 | 147117787 | 134411590 | 131502991 | 117669613 | 89119274 | 51973369 | 33777300 | 24887993 | 31733455 | 17887048 | +| 35 | 877803214 | 1117294961 | 437775571 | 244005733 | 167315794 | 172011884 | 149278426 | 110476836 | 55002290 | 39953796 | 32167747 | 36703106 | 21077663 | +| 36 | 990773530 | 1824606196 | 547475749 | 296743922 | 216932118 | 226846508 | 281527921 | 161111544 | 66188561 | 52697231 | 36605915 | 45026834 | 30184087 | +| 37 | 1741868476 | 1948381278 | 625189048 | 309340804 | 255329101 | 286768418 | 327695873 | 324326807 | 89076357 | 63613297 | 39166657 | 48749981 | 36650844 | +| 38 | 2363551706 | 3608706372 | 1432246160 | 358255505 | 378948934 | 428910293 | 415235309 | 419954003 | 104499345 | 81300722 | 43595727 | 57954972 | 39310411 | +| 39 | 3148966217 | 5554166307 | 1959631791 | 418553825 | 654266100 | 578750849 | 624817445 | 485331551 | 146707304 | 109373862 | 62509514 | 80617677 | 40464757 | +| 40 | 3652327064 | 10014646570 | 2986844761 | 647206461 | 794186607 | 736676898 | 984219044 | 627202823 | 181038342 | 120243575 | 89602927 | 105004273 | 41399955 | +| 41 | 5224502250 | 11274140350 | 4543685536 | 802616934 | 1476486355 | 945454647 | 1610720260 | 702641311 | 327908422 | 127205662 | 98940749 | 145615463 | 49181743 | +| 42 | 6127078317 | 13841000586 | 5932293513 | 836227440 | 1533085761 | 1746304445 | 1953800834 | 1052213918 | 588646348 | 138853511 | 117973493 | 180607180 | 67316394 | +| 43 | 16232620003 | 17967835068 | 7783862835 | 896362311 | 1962927879 | 2372473217 | 2416294925 | 1204729923 | 704287921 | 187144599 | 143353641 | 190606039 | 70823411 | +| 44 | 17718877503 | 25273820455 | 9652527844 | 1952302791 | 2099616405 | 3372924274 | 3072158370 | 1970670881 | 872977208 | 233082949 | 156729107 | 227657402 | 101871246 | +| 45 | 25741622196 | 30584201846 | 14557217345 | 2862286068 | 2534362233 | 4495023334 | 4453379221 | 2145570435 | 1125148935 | 286366031 | 203808982 | 286695307 | 139675067 | +| 46 | 40499236349 | 40789584966 | 15067833369 | 3334440078 | 5072562082 | 4943259313 | 8612561011 | 3902062051 | 1928316855 | 357211612 | 241777135 | 443906150 | 146118621 | +| 47 | 62111873752 | 46215215573 | 19558181065 | 9230842551 | 5760986205 | 10088353213 | 8788037210 | 7455854537 | 2230430028 | 479309517 | 286733914 | 524498432 | 151232944 | +| 48 | 86847634536 | 63800375952 | 23005194627 | 10641223658 | 7116913776 | 13627712965 | 12633107125 | 8190890802 | 3212535920 | 658225438 | 367143915 | 641079230 | 158042099 | +| 49 | 111558931172 | 120597835455 | 73124504606 | 23520860602 | 21079212074 | 15273984174 | 22132085123 | 9179551077 | 3418110578 | 839590469 | 543884199 | 887068496 | 356737000 | +| 50 | | | 121532848050 | | 28477080465 | 33936548877 | 44066435831 | 11975030336 | 5444154366 | 1093863142 | 701965402 | 1119335316 | 422735554 | + +
+ +--- + +## Contempt measurements + +Older SF (around SF10) had contempt that worked rather well. This data shows the dependence of Elo difference between SFdev of October 2018 and older versions of Stockfish depending on contempt value (The SFdev used is approx. 40Elo above SF9). +Upper and lower bounds represent value with maximum error. + +| Opponent | STC | LTC | +|:--------:|:-------------------------------------------------------:|:-------------------------------------------------------:| +| 7 | | | +| 8 | | | +| 9 | | | + +Full data with values https://docs.google.com/spreadsheets/d/1R_eopD8_ujlBbt_Q0ygZMvuMsP1sc4UyO3Md4qL1z5M/edit#gid=1878521689 + +--- + +## Elo change with respect to TC + +Here is the result of some scaling tests with the 2moves book. 40000 games each (STC=10+0.1, LTC=60+0.6) + +| | SF7 -> SF8 | SF8 -> SF9 | SF9 -> SF10 | +|:-------:|:------------:|:-----------:|:-----------:| +| Elo STC | 95.91 +-2.3 | 58.28 +-2.3 | 71.03 +-2.4 | +| Elo LTC | 100.40 +-2.1 | 68.55 +-2.1 | 65.55 +-2.2 | + +So we see that the common wisdom that increased TC causes elo compression is not always true. + +See https://github.com/official-stockfish/Stockfish/issues/1859#issuecomment-449624976 + +--- + +## TC dependence of certain terms in search + +Discussed here https://github.com/official-stockfish/Stockfish/pull/2401#issuecomment-552768526 + + + +--- + +## Elo contributions from various evaluation terms + +See spreadsheet at: https://github.com/official-stockfish/Stockfish/files/3828738/Stockfish.Feature.s.Estimated.Elo.worth.1.xlsx + +_Note: The estimated elo worth for various features might be outdated, or might get outdated soon._ diff --git a/stockfish/wiki/_Footer.md b/stockfish/wiki/_Footer.md new file mode 100644 index 0000000000000000000000000000000000000000..6a372192c5974c9142f3bb9b5304c03013a40838 --- /dev/null +++ b/stockfish/wiki/_Footer.md @@ -0,0 +1 @@ +[stockfishchess.org](https://stockfishchess.org/) \ No newline at end of file