Source code for lczerolens.backends

"""Utils from the lczero executable and bindings.

Notes
----
The lczero bindings are not installed by default. You can install them by
running `pip install lczerolens[backends]`.
"""

import subprocess

import chess
import torch

from lczerolens.board import LczeroBoard

try:
    from lczero.backends import Backend, GameState
except ImportError as e:
    raise ImportError(
        "LCZero bindings are required to use the backends, install them with `pip install lczerolens[backends]`."
    ) from e


[docs] def generic_command(args, verbose=False): """ Run a generic command. """ popen = subprocess.Popen( ["lc0", *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) popen.wait() if popen.returncode != 0: if verbose: stderr = f"\n[DEBUG] stderr:\n{popen.stderr.read().decode('utf-8')}" else: stderr = "" raise RuntimeError(f"Could not run `lc0 {' '.join(args)}`." + stderr) return popen.stdout.read().decode("utf-8")
[docs] def describenet(path, verbose=False): """ Describe the net at the given path. """ return generic_command(["describenet", "-w", path], verbose=verbose)
[docs] def convert_to_onnx(in_path, out_path, verbose=False): """ Convert the net at the given path. """ return generic_command( ["leela2onnx", f"--input={in_path}", f"--output={out_path}"], verbose=verbose, )
[docs] def convert_to_leela(in_path, out_path, verbose=False): """ Convert the net at the given path. """ return generic_command( ["onnx2leela", f"--input={in_path}", f"--output={out_path}"], verbose=verbose, )
[docs] def board_from_backend(lczero_backend: Backend, lczero_game: GameState, planes: int = 112): """ Create a board from the lczero backend. """ lczero_input = lczero_game.as_input(lczero_backend) lczero_input_tensor = torch.zeros((112, 64), dtype=torch.float) for plane in range(planes): mask_str = f"{lczero_input.mask(plane):b}".zfill(64) lczero_input_tensor[plane] = torch.tensor( tuple(map(int, reversed(mask_str))), dtype=torch.float ) * lczero_input.val(plane) return lczero_input_tensor.view((112, 8, 8))
[docs] def prediction_from_backend( lczero_backend: Backend, lczero_game: GameState, softmax: bool = False, only_legal: bool = False, illegal_value: float = 0, ): """ Predicts the move. """ filtered_policy = torch.full((1858,), illegal_value, dtype=torch.float) lczero_input = lczero_game.as_input(lczero_backend) (lczero_output,) = lczero_backend.evaluate(lczero_input) if only_legal: indices = torch.tensor(lczero_game.policy_indices()) else: indices = torch.tensor(range(1858)) if softmax: policy = torch.tensor(lczero_output.p_softmax(*range(1858)), dtype=torch.float) else: policy = torch.tensor(lczero_output.p_raw(*range(1858)), dtype=torch.float) value = torch.tensor(lczero_output.q()) filtered_policy[indices] = policy[indices] return filtered_policy, value
[docs] def moves_with_castling_swap(lczero_game: GameState, board: LczeroBoard): """ Get the moves with castling swap. """ lczero_legal_moves = lczero_game.moves() lczero_policy_indices = list(lczero_game.policy_indices()) for move in board.legal_moves: uci_move = move.uci() if uci_move in lczero_legal_moves: continue if board.is_castling(move): leela_uci_move = uci_move.replace("g", "h").replace("c", "a") if leela_uci_move in lczero_legal_moves: lczero_legal_moves.remove(leela_uci_move) lczero_legal_moves.append(uci_move) lczero_policy_indices.remove( LczeroBoard.encode_move( chess.Move.from_uci(leela_uci_move), board.turn, ) ) lczero_policy_indices.append(LczeroBoard.encode_move(move, board.turn)) return lczero_legal_moves, lczero_policy_indices