<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Copyright 2020 Google
Step1:  Quantum Chess REST API
<table class="tfo-notebook-buttons" align="left">
  <td>
    <a target="_blank" href="https
Step2:  It is possible to play the game in interactive mode, by applying moves to the board. Split the knight on b1 to a3 and c3.
Step3:  In Quantum Chess, a move can be uniquely defined by up to 3 squares, a type, and a variant. 
Move types can take any value from the following set
Step4:  The REST API
The Quantum Chess REST API defines an interface for REST endpoints that the Quantum Chess Engine can be directed to use when interacting with the quantum state of the game. The API declares an interface for three functions
Step5:  Implement do_move
The do_move function is used to apply a specific unitary to the qubits which correspond to the squares involved in the move. It has the following code signature
do_move( move ) 
Step6:  Notice, the circuit for the move is printed as well. This is made available in the board debug information. You can also see what happens when initializing the board using a noisy simulator with error mitigation.
Step7:  You may notice that the circuit run discarded some of the returned samples due to error mitigation and post selection.
Implement undo_last_move
The undo_last_move function is used revert the quantum state to a state immediately before the last move that was executed. It has the following code signature.
undo_last_move( ) 
Step8:  REST server implementation
With the functionality in place, you can define server endpoints and run the server. Use the flask_restful framework to create a simple server that implements these enpoints. Flask-restful allows you to encapsulate the functionality you want in classes that inherit from Resource. You will need to install flask-ngrok to give the server an accessible URL
Step9:  Define the REST endpoints for the webserver
Step10:  And start the local webserver
<ASSISTANT_TASK:>
Python Code:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Explanation: Copyright 2020 Google
End of explanation
!pip install -q git+https://github.com/quantumlib/ReCirq/
import recirq
import recirq.quantum_chess.ascii_board as ab
b = ab.AsciiBoard()
b.reset()
print(b)
Explanation: Quantum Chess REST API
<table class="tfo-notebook-buttons" align="left">
  <td>
    <a target="_blank" href="https://quantumai.google/cirq/experiments/quantum_chess/quantum_chess_rest_api"><img src="https://quantumai.google/site-assets/images/buttons/quantumai_logo_1x.png" />View on QuantumAI</a>
  </td>
  <td>
    <a target="_blank" href="https://colab.research.google.com/github/quantumlib/ReCirq/blob/master/docs/quantum_chess/quantum_chess_rest_api.ipynb"><img src="https://quantumai.google/site-assets/images/buttons/colab_logo_1x.png" />Run in Google Colab</a>
  </td>
  <td>
    <a target="_blank" href="https://github.com/quantumlib/ReCirq/blob/master/docs/quantum_chess/quantum_chess_rest_api.ipynb"><img src="https://quantumai.google/site-assets/images/buttons/github_logo_1x.png" />View source on GitHub</a>
  </td>
  <td>
    <a href="https://storage.googleapis.com/tensorflow_docs/ReCirq/docs/quantum_chess/quantum_chess_rest_api.ipynb"><img src="https://quantumai.google/site-assets/images/buttons/download_icon_1x.png" />Download notebook</a>
  </td>
</table>
Quantum Chess is a variant of chess that gives players access to extra moves which allow them to create superposition. All moves are applied to the game state via unitary evolution, allowing players to experience effects like superposition, entanglement, and interference. This project provides a limited implementation of the full Quantum Chess move set, executed on a set of qubits representing squares. The full Quantum Chess application requires an API for the chess UI to communicate with an external backend for move processing and calculation. In this notebook we will:
* Set up an ascii board representation of a Quantum Chess game running on a Cirq simualtor.
* Explore how to interact with the ascii board in interactive mode and by batching moves.
* Implement the functionality of the Quantum Chess REST API
* Start a simple server that serves REST endpoints, which could be used to hook up an instance of Quantum Chess to our Cirq implementation.
For more information on how to implement Quantum Chess moves in Cirq, including qubit mapping and error correction, check out (this other notebook)
First, install the Quantum Chess package from recirq.
End of explanation
from recirq.quantum_chess.move import Move
from recirq.quantum_chess.enums import MoveType, MoveVariant
m = Move(
    source="b1",
    target="a3",
    target2="c3",
    move_type=MoveType.SPLIT_JUMP,
    move_variant=MoveVariant.BASIC,
)
b.reset()
r = b.apply(m)
print(b)
Explanation: It is possible to play the game in interactive mode, by applying moves to the board. Split the knight on b1 to a3 and c3.
End of explanation
from recirq.quantum_chess.quantum_board import CirqBoard
from recirq.quantum_chess.bit_utils import bit_to_square, xy_to_bit
from recirq.quantum_chess.move import to_rank
global_board = CirqBoard(1)
def print_game(board):
    board.print_debug_log()
    print("\n")
    print(board)
    print("\n\n")
probs = global_board.get_probability_distribution()
print_game(global_board)
Explanation: In Quantum Chess, a move can be uniquely defined by up to 3 squares, a type, and a variant. 
Move types can take any value from the following set:
{ JUMP, SLIDE, SPLIT_JUMP, SPLIT_SLIDE, MERGE_JUMP, MERGE_SLIDE, PAWN_STEP, PAWN_TWO_STEP, PAWN_CAPTURE, PAWN_EP, KS_CASTLE, QS_CASTLE }
Jump type moves indicate there is no path to consider, like when knights move. Slide type moves must consider the squares along the sliding path. The Split versions are 3-qubit operations, that are designed to put a piece in superposition on two different targets. The Merge versions are just the inverse of a Split.
Quantum Chess introduces the concept of a move variant. The variant of a move is determined by the state of the target square, or squares. Move variants can take any value from the following set:
{BASIC, CAPTURE, EXCLUDED}
A Basic variant is a move where the target square is unoccupied. A Capture variant is a move where the target square is occupied by a piece that can be captured by the piece being moved. An Excluded variant is a move where the target is occupied by a piece that cannot be captured. This can occur if a target is occupied by a same color piece in superposition. In both capture and excluded variants, a measurement will be performed. To learn more about move types, variants, and measurements, please see this paper.
The ascii board is a convenience that can be used for testing the project imports.  The Quantum Chess REST API defines the interface, which used by the Quantum Chess Engine to assign an external resource to handle the quantum state of the game. This state encodes only the "occupancy" of each square on the board. Each square is mapped to a single qubit, where the state |1> corresponds to the square being occupied by a piece, and |0> is unoccupied. All piece type information, and rules checking, is handled classically within the Quantum Chess Engine. When implementing the API, we only neeed to use the CirqBoard. The following code shows how to initialize a CirqBoard with a single "piece" in square a1.
End of explanation
def init(board, init_basis_state):
    board.with_state(init_basis_state)
    probs = board.get_probability_distribution()
    print_game(board)
    return {
        "probabilities": probs,
        "empty": board.get_empty_squares_bitboard(),
        "full": board.get_full_squares_bitboard(),
    }
r = init(global_board, 0xFFFF00000000FFFF)
Explanation: The REST API
The Quantum Chess REST API defines an interface for REST endpoints that the Quantum Chess Engine can be directed to use when interacting with the quantum state of the game. The API declares an interface for three functions:
* init
* do_move
* undo_move
All three endpoints must return a json object with the following values:
* probabilities: an array of 64 floating point numbers representing the probability of each square being occupied. Array indices are mapped to board squares starting from a1, and increasing along rows to h8.
<center>
<img src='./images/chess_board_indices.png' width="300" >
</center>
empty_bitboard: A bitboard with bits set to 1 for all squares known to be empty, i.e. 0% chance of being occupied.
full_bitboard: A bitboard with bits set to 1 for all squares known to be occupied, i.e. 100% chance of being occupied.
A bitboard is a 64-bit integer, where each bit corresponds to a square on the chess board. The bitboard is encoded in little endian form, with the least significant bit corresponding to a1, and increasing along rows up to h8 in the most significant bit.
<center>
<img src='./images/bitboard_order.png' width="700" >
</center>
Implement init
The init function is used to initialize a quantum state to some classical starting position. It has the following code signature.
init(init_basis_state) : { probabilities, empty_bitboard, full_bitboard }
The single argument, init_basis_state, is a bitboard that represents the initial classical state of the board, i.e. which squares have a piece on them. The return value is a json object with three fields: probabilities, empty_bitboard, and full_bitboard.
The following code defines an implementation of init that prints out the probability distribution of the initialized board, and returns the appropriate json.
End of explanation
from recirq.quantum_chess.move import Move
from recirq.quantum_chess.enums import MoveType, MoveVariant
# Helper function for creating a split move from json values
def get_split_move(move_json):
    return Move(
        move_json["square1"],
        move_json["square2"],
        target2=move_json["square3"],
        move_type=MoveType(move_json["type"]),
        move_variant=MoveVariant(move_json["variant"]),
    )
# Helper function for creating a merge move from json values
def get_merge_move(move_json):
    return Move(
        move_json["square1"],
        move_json["square3"],
        source2=move_json["square2"],
        move_type=MoveType(move_json["type"]),
        move_variant=MoveVariant(move_json["variant"]),
    )
# Helper function for creating a standard move from json values
def get_standard_move(move_json):
    return Move(
        move_json["square1"],
        move_json["square2"],
        move_type=MoveType(move_json["type"]),
        move_variant=MoveVariant(move_json["variant"]),
    )
def do_move(board, move):
    board.clear_debug_log()
    r = board.do_move(move)
    probs = board.get_probability_distribution()
    print_game(board)
    return {
        "result": r,
        "probabilities": probs,
        "empty": board.get_empty_squares_bitboard(),
        "full": board.get_full_squares_bitboard(),
    }
move_json = {
    "square1": "b1",
    "square2": "a3",
    "square3": "c3",
    "type": MoveType.SPLIT_JUMP,
    "variant": MoveVariant.BASIC,
}
split_b1_a3_c3 = get_split_move(move_json)
r = init(global_board, 0xFFFF00000000FFFF)
r = do_move(global_board, split_b1_a3_c3)
Explanation: Implement do_move
The do_move function is used to apply a specific unitary to the qubits which correspond to the squares involved in the move. It has the following code signature
do_move( move ) : { probabilities, empty_bitboard, full_bitboard }
 It takes a single argument, move, which is a json object with the following fields:
* square1: integer index of the first square.
* square2: integer index of the second square.
* square3: integer index of the third square, only used for split and merge moves.
* type: enumerated type of move with the following possible values
NULL_TYPE = 0, UNSPECIFIED_STANDARD = 1, JUMP = 2, SLIDE = 3,
    SPLIT_JUMP = 4, SPLIT_SLIDE = 5, MERGE_JUMP = 6, MERGE_SLIDE = 7,
    PAWN_STEP = 8, PAWN_TWO_STEP = 9, PAWN_CAPTURE = 10, PAWN_EP = 11,
    KS_CASTLE = 12, QS_CASTLE = 13
* variant: enumerated variant of move with the following  possible values
UNSPECIFIED = 0, BASIC = 1, EXCLUDED = 2, CAPTURE = 3
The return value is a json object with three fields: probabilities, empty_bitboard, and full_bitboard.
The following code defines some helper functions to create Moves, which are used to apply specific unitaries to the qubits represented in the CirqBoard, and an implementation of do_move that will print the probability distribution after applying the move to the board.
End of explanation
from recirq.quantum_chess.enums import ErrorMitigation
from cirq import DensityMatrixSimulator, google
from cirq.contrib.noise_models import DepolarizingNoiseModel
NOISY_SAMPLER = DensityMatrixSimulator(
    noise=DepolarizingNoiseModel(depol_prob=0.004)
)
noisy_board = CirqBoard(
    0,
    sampler=NOISY_SAMPLER,
    device=google.Sycamore,
    error_mitigation=ErrorMitigation.Correct,
    noise_mitigation=0.05,
)
r = init(noisy_board, 0xFFFF00000000FFFF)
r = do_move(noisy_board, split_b1_a3_c3)
Explanation: Notice, the circuit for the move is printed as well. This is made available in the board debug information. You can also see what happens when initializing the board using a noisy simulator with error mitigation.
End of explanation
def undo_last_move(board):
    board.clear_debug_log()
    r = board.undo_last_move()
    probs = board.get_probability_distribution()
    print_game(board)
    return {
        "result": r,
        "probabilities": probs,
        "empty": board.get_empty_squares_bitboard(),
        "full": board.get_full_squares_bitboard(),
    }
r = init(global_board, 0xFFFF00000000FFFF)
r = do_move(global_board, split_b1_a3_c3)
r = undo_last_move(global_board)
Explanation: You may notice that the circuit run discarded some of the returned samples due to error mitigation and post selection.
Implement undo_last_move
The undo_last_move function is used revert the quantum state to a state immediately before the last move that was executed. It has the following code signature.
undo_last_move( ) : { probabilities, empty_bitboard, full_bitboard }
It takes no arguments, and returns the same json object as the previous endpoints. The following code is an implementation of undo_last_move() that prints the resulting probability distribution.
End of explanation
!pip install -q flask flask_restful flask-ngrok
Explanation: REST server implementation
With the functionality in place, you can define server endpoints and run the server. Use the flask_restful framework to create a simple server that implements these enpoints. Flask-restful allows you to encapsulate the functionality you want in classes that inherit from Resource. You will need to install flask-ngrok to give the server an accessible URL:
End of explanation
from flask import Flask, request, jsonify
from flask_restful import Resource, Api
from flask_ngrok import run_with_ngrok
class Init(Resource):
    def get(self):
        return {"about": "Init"}
    def post(self):
        print(request.get_json())
        n = request.get_json()["init_basis_state"]
        global_board.clear_debug_log()
        return init(global_board, int(n))
class DoMove(Resource):
    def post(self):
        move_json = request.get_json()
        t = MoveType(move_json["type"])
        # We need to convert square indices to square names.
        move_json["square1"] = bit_to_square(move_json["square1"])
        move_json["square2"] = bit_to_square(move_json["square2"])
        move_json["square3"] = bit_to_square(move_json["square3"])
        if t == MoveType.SPLIT_SLIDE or t == MoveType.SPLIT_JUMP:
            return do_move(global_board, get_split_move(move_json))
        elif t == MoveType.MERGE_JUMP or t == MoveType.MERGE_SLIDE:
            return do_move(global_board, get_merge_move(move_json))
        else:
            return do_move(global_board, get_standard_move(move_json))
class UndoLastMove(Resource):
    def post(self):
        return undo_last_move(global_board)
app = Flask(__name__)
run_with_ngrok(app)
api = Api(app)
api.add_resource(Init, "/quantumboard/init")
api.add_resource(DoMove, "/quantumboard/do_move")
api.add_resource(UndoLastMove, "/quantumboard/undo_last_move")
@app.route("/")
def home():
    return "<h1>Running Flask on Google Colab!</h1>"
Explanation: Define the REST endpoints for the webserver:
End of explanation
# docs_infra: no_execute
app.run()
Explanation: And start the local webserver:
End of explanation
<END_TASK>