Further optimize this python chess engine, and add more weight.

```
import chess
import chess.engine
import random
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
import concurrent.futures
import time
import numpy as np
from collections import OrderedDict

class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.layers = nn.Sequential(OrderedDict([
            ('fc1', nn.Linear(768, 256)),
            ('relu1', nn.ReLU()),
            ('bn1', nn.BatchNorm1d(256)),
            ('fc2', nn.Linear(256, 128)),
            ('relu2', nn.ReLU()),
            ('bn2', nn.BatchNorm1d(128)),
            ('fc3', nn.Linear(128, 1)),
            ('tanh', nn.Tanh())
        ]))

    def forward(self, x):
        return self.layers(x)

class MCTSNode:
    def __init__(self, board, parent=None, move=None):
        self.board = board
        self.parent = parent
        self.move = move
        self.children = []
        self.visits = 0
        self.value = 0
        self.prior = 0

class UPPlusChessEngine:
    def __init__(self, stockfish_path, model_path):
        self.board = chess.Board()
        self.stockfish = chess.engine.SimpleEngine.popen_uci(stockfish_path)
        self.model = NeuralNetwork()
        self.model.load_state_dict(torch.load(model_path))
        self.model.eval()
        self.transposition_table = {}
        self.piece_values = {
            chess.PAWN: 100,
            chess.KNIGHT: 320,
            chess.BISHOP: 330,
            chess.ROOK: 500,
            chess.QUEEN: 900,
            chess.KING: 20000
        }

    def board_to_input(self, board):
        planes = np.zeros((12, 8, 8), dtype=np.float32)
        for piece_type in chess.PIECE_TYPES:
            for color in chess.COLORS:
                mask = board.pieces(piece_type, color)
                planes[piece_type - 1 + 6 * color] = np.array(list(mask)).reshape(8, 8)
        return torch.FloatTensor(planes.reshape(-1))

    @torch.no_grad()
    def evaluate_position(self, board):
        input_tensor = self.board_to_input(board)
        return self.model(input_tensor).item()

    def uct_select_child(self, node, c_param=1.4):
        best_score = float('-inf')
        best_child = None
        for child in node.children:
            exploit = child.value / (child.visits + 1e-8)
            explore = math.sqrt(math.log(node.visits + 1) / (child.visits + 1e-8))
            uct_score = exploit + c_param * explore + child.prior
            if uct_score > best_score:
                best_score = uct_score
                best_child = child
        return best_child

    def mcts_search(self, root, num_simulations=1000):
        for _ in range(num_simulations):
            node = root
            path = [node]

            while node.children:
                node = self.uct_select_child(node)
                path.append(node)

            if node.visits > 0:
                node = self.expand(node)
                path.append(node)

            value = self.simulate(node)
            self.backpropagate(path, value)

        return max(root.children, key=lambda c: c.visits)

    def expand(self, node):
        legal_moves = list(node.board.legal_moves)
        for move in legal_moves:
            new_board = node.board.copy()
            new_board.push(move)
            child = MCTSNode(new_board, parent=node, move=move)
            child.prior = self.move_priority(move, node.board)
            node.children.append(child)
        return random.choice(node.children)

    def simulate(self, node):
        board = node.board.copy()
        depth = 0
        while not board.is_game_over() and depth < 50:
            moves = list(board.legal_moves)
            move = self.select_move(board, moves)
            board.push(move)
            depth += 1
        return self.evaluate_position(board)

    def backpropagate(self, path, value):
        for node in reversed(path):
            node.visits += 1
            node.value += value
            value = 1 - value  # Flip the value for the opponent

    def get_best_move(self):
        root = MCTSNode(self.board)
        best_child = self.mcts_search(root)
        return best_child.move

    def make_move(self, move):
        try:
            self.board.push_san(move)
            return None
        except ValueError:
            return "Invalid move. Please try again."

    def play_engine_move(self):
        move = self.get_best_move()
        self.board.push(move)
        return move

    def parallel_stockfish_evaluation(self, board, depth=20):
        def evaluate_position(board):
            result = self.stockfish.analyse(board, chess.engine.Limit(depth=depth))
            return result['score'].white().score()

        with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
            future = executor.submit(evaluate_position, board)
            return future.result()

    def display_board(self):
        return str(self.board)

    def move_priority(self, move, board):
        if board.is_capture(move):
            return 0.5
        elif board.gives_check(move):
            return 0.3
        else:
            return 0.1

    def select_move(self, board, moves):
        if random.random() < 0.8:  # 80% chance of choosing the "best" move
            return max(moves, key=lambda m: self.move_priority(m, board))
        else:
            return random.choice(moves)

    def material_score(self, board):
        score = 0
        for piece_type in chess.PIECE_TYPES:
            score += len(board.pieces(piece_type, chess.WHITE)) * self.piece_values[piece_type]
            score -= len(board.pieces(piece_type, chess.BLACK)) * self.piece_values[piece_type]
        return score

    def quiescence_search(self, board, alpha, beta, depth=0):
        stand_pat = self.evaluate_position(board)
        if depth >= 4:
            return stand_pat
        if stand_pat >= beta:
            return beta
        if alpha < stand_pat:
            alpha = stand_pat

        for move in board.legal_moves:
            if board.is_capture(move):
                board.push(move)
                score = -self.quiescence_search(board, -beta, -alpha, depth + 1)
                board.pop()
                if score >= beta:
                    return beta
                if score > alpha:
                    alpha = score
        return alpha

def main():
    stockfish_path = "path/to/stockfish"
    model_path = "model_weights.pth"
    engine = UPPlusChessEngine(stockfish_path, model_path)
    print("Welcome to UP+ Chess Engine!")
    print("Enter your moves in standard algebraic notation (e.g., e2e4).")

    while not engine.board.is_game_over():
        print(engine.display_board())
        user_move = input("Your move: ")
        if user_move.lower() == 'quit':
            break
        response = engine.make_move(user_move)
        if response:
            print(response)
            continue
        if not engine.board.is_game_over():
            start_time = time.time()
            engine_move = engine.play_engine_move()
            end_time = time.time()
            if engine_move:
                print(f"Engine move: {engine_move}")
                print(f"Time taken: {end_time - start_time:.2f} seconds")
                stockfish_eval = engine.parallel_stockfish_evaluation(engine.board)
                print(f"Stockfish evaluation: {stockfish_eval}")
                material_balance = engine.material_score(engine.board)
                print(f"Material balance: {material_balance}")

    print("Game over!")
    print(engine.display_board())
    engine.stockfish.quit()

if __name__ == "__main__":
    main()
```