PyTket code for QSVT example
This notebook shows how to generate data for discrete quantum walk using pytket
1.34.0
Here we provide the codes for block encoding the matrix \(A\), as well as the QSVT implementation. PyTket does not have an adder by a constant function. Thus, we have modified their adder functions, which is applied between two quantum registers, to include this functionality.
# import time
# from pytket.circuit import Circuit, CircBox, OpType, QControlBox
# from pytket.extensions.qiskit import AerBackend
# from pytket.passes import DecomposeBoxes, SynthesiseTket
# backend = AerBackend()
# from pytket.circuit import Circuit, CircBox
# import numpy as np
# def build_qft_circuit(n_qubits: int, do_swaps: True) -> Circuit:
# circ = Circuit(n_qubits, name="QFT")
# for i in range(n_qubits):
# circ.H(i)
# for j in range(i + 1, n_qubits):
# circ.CU1(1 / 2 ** (j - i), j, i)
# if do_swaps:
# for k in range(0, n_qubits // 2):
# circ.SWAP(k, n_qubits - k - 1)
# return circ
# class DraperQFTAdderConstantPytket:
# def __init__(self, num_state_qubits: int, constant: int) -> Circuit:
# circuit = Circuit(num_state_qubits)
# qft_circuit = build_qft_circuit(num_state_qubits, do_swaps=False)
# circuit.add_gate(CircBox(qft_circuit),[k for k in range(num_state_qubits)])
# # Apply phase rotations for the constant
# for qubit in range(num_state_qubits):
# angle = (constant % (2 ** (qubit + 1))) * np.pi / (2 ** qubit)
# circuit.Rz(angle, qubit)
# # Apply inverse QFT
# circuit.add_gate(CircBox(qft_circuit).dagger,[k for k in range(num_state_qubits)])
# self.circuit = circuit
# def get_circuit(self) -> Circuit:
# return self.circuit
# def get_reflect_around_zero(size):
# qc = Circuit(size, name="reflection")
# qc.X(0)
# qc.H(0)
# qc.add_gate(QControlBox(CircBox(Circuit(1).X(0)), n_controls = size-1, control_state=[0]*(size-1)), [k for k in range(size)])
# qc.H(0)
# qc.X(0)
# return qc
# def get_cir_be(qc, data, block):
# qc.H(block[0])
# qc.H(block[2])
# adder_2_cir = DraperQFTAdderConstantPytket(len(data)+1, 2).get_circuit()
# adder_1_cir = DraperQFTAdderConstantPytket(len(data)+1, -1+2**(len(data)+1)).get_circuit()
# qc.add_gate(QControlBox(CircBox(adder_2_cir), n_controls = 1, control_state=0), [block[0]]+[data[k] for k in range(len(data))]+[block[1]])
# qc.add_gate(CircBox(adder_1_cir), [data[k] for k in range(len(data))]+[block[1]])
# qc.add_gate(QControlBox(CircBox(get_reflect_around_zero(len(data))),n_controls=1, control_state=0),[block[2]]+[data[k] for k in range(len(data))])
# qc.H(block[0])
# qc.H(block[2])
# return qc
# def apply_projector_controlled_phase(qc, phase, block_reg, aux_reg):
# qc.add_gate(QControlBox(CircBox(Circuit(1).X(0)), n_controls = len(block_reg), control_state=[0]*len(block_reg)),
# [block_reg[k] for k in range(len(block_reg))] + [aux_reg[0]]
# )
# qc.Rz(phase, aux_reg[0])
# qc.add_gate(QControlBox(CircBox(Circuit(1).X(0)), n_controls = len(block_reg), control_state=[0]*len(block_reg)),
# [block_reg[k] for k in range(len(block_reg))] + [aux_reg[0]]
# )
# def apply_qsvt_step(qc, phase1, phase2, u, data, block, qsvt_aux):
# qc.add_gate(CircBox(u), [data[l] for l in range(len(data))]+ [block[l] for l in range(len(block))])
# apply_projector_controlled_phase(qc, phase1, block, qsvt_aux)
# qc.add_gate(CircBox(u).dagger, [data[l] for l in range(len(data))]+ [block[l] for l in range(len(block))])
# apply_projector_controlled_phase(qc, phase2, block, qsvt_aux)
# def get_qsvt_circuit(qsvt_phases,
# size):
# qsvt_cir = Circuit()
# data = qsvt_cir.add_q_register("data", size)
# block = qsvt_cir.add_q_register("block", 3)
# cir_be = Circuit()
# data = cir_be.add_q_register("data", size)
# block = cir_be.add_q_register("block", 3)
# qsvt_aux = qsvt_cir.add_q_register("qsvt_aux", 1)
# cir_be = get_cir_be(cir_be,data, block)
# qsvt_cir.H(qsvt_aux[0])
# apply_projector_controlled_phase(qsvt_cir, qsvt_phases[0], block, qsvt_aux)
# for i in range(int(np.floor((len(qsvt_phases) - 1) / 2))):
# apply_qsvt_step(qsvt_cir,
# qsvt_phases[(2 * i) + 1], qsvt_phases[(2 * i) + 2],
# cir_be,
# data,
# block,
# qsvt_aux
# )
# qsvt_cir.add_gate(CircBox(cir_be), [data[l] for l in range(len(data))]+ [block[l] for l in range(len(block))])
# apply_projector_controlled_phase(qsvt_cir, qsvt_phases[len(qsvt_phases) - 1], block, qsvt_aux)
# qsvt_cir.H(qsvt_aux[0])
# return qsvt_cir
run an example
# SIZE = 6
# DEGREE = 3
# QSVT_PHASES = [1.280311896404252, 8.127145628464149, 1.8439603212845617, -5.002873410775335]
# start_time = time.time()
# qc_qsvt = get_qsvt_circuit(QSVT_PHASES, SIZE)
# DecomposeBoxes().apply(qc_qsvt)
# SynthesiseTket().apply(qc_qsvt)
# compiled_circ = backend.get_compiled_circuit(qc_qsvt)
# transpilation_time = time.time()-start_time
# depth = compiled_circ.depth()
# cx_counts = compiled_circ.n_gates_of_type(OpType.CX)
# width = compiled_circ.n_qubits
# print(f"==== tket for {SIZE}==== time: {transpilation_time}")