def iterated_entanglement_swap(n_iter): # Iterate the entanglement swapping protocol n_iter times it_es = Circuit() ava = it_es.add_q_register("a", 1) bella = it_es.add_q_register("b", 2) charlie = it_es.add_q_register("c", 1) data = it_es.add_c_register("d", 2) # Start with an initial Bell state it_es.H(ava[0]) it_es.CX(ava[0], bella[0]) for i in range(n_iter): if i % 2 == 0: # Teleport bella[0] to charlie[0] to give a Bell pair between ava[0] and charlier[0] tel_to_c = qtel.copy() tel_to_c.rename_units( {alice[0]: bella[0], alice[1]: bella[1], bob[0]: charlie[0]} ) it_es.append(tel_to_c) it_es.add_gate(OpType.Reset, [bella[0]]) it_es.add_gate(OpType.Reset, [bella[1]]) else: # Teleport charlie[0] to bella[0] to give a Bell pair between ava[0] and bella[0] tel_to_b = qtel.copy() tel_to_b.rename_units( {alice[0]: charlie[0], alice[1]: bella[1], bob[0]: bella[0]} ) it_es.append(tel_to_b) it_es.add_gate(OpType.Reset, [bella[1]]) it_es.add_gate(OpType.Reset, [charlie[0]]) # Return the circuit and the qubits expected to share a Bell pair if n_iter % 2 == 0: return it_es, [ava[0], bella[0]] else: return it_es, [ava[0], charlie[0]]
# # There is limited built-in functionality in `pytket` for obtaining expectation values from circuits. This is designed to encourage users to consider their needs for parallelising the processing of circuits, manipulating results (e.g. filtering, adjusting counts to mitigate errors, and other forms of data processing), or more advanced schemes for grouping the terms of the operator into measurement circuits. For this example, suppose that we want to focus on reducing the queueing time for IBM device backends, and filter our shots to eliminate some detected errors. # # This notebook makes use of the Qiskit and ProjectQ backend modules `pytket_qiskit` and `pytket_projectq`, as well as the electronic structure module `openfermion`, all three of which should first be installed via `pip`. # # We will start by generating an ansatz and Hamiltonian for the chemical of interest. Here, we are just using a simple model of $\mathrm{H}_2$ with four qubits representing the occupation of four spin orbitals. from pytket import Circuit, Qubit, Bit from pytket.utils.operators import QubitPauliOperator from sympy import symbols from openfermion import QubitOperator # Generate ansatz and Hamiltonian: ansatz = Circuit() qubits = ansatz.add_q_register("q", 4) args = symbols("a0 a1 a2 a3 a4 a5 a6 a7") for i in range(4): ansatz.Ry(args[i], qubits[i]) for i in range(3): ansatz.CX(qubits[i], qubits[i + 1]) for i in range(4): ansatz.Ry(args[4 + i], qubits[i]) for command in ansatz: print(command) # In reality, you would use an expectation value calculation as the objective function for a classical optimisation routine to determine the parameter values for the ground state. For the purposes of this notebook, we will use some predetermined values for the ansatz, already optimised for $\mathrm{H}_2$. arg_values = [ 7.17996183e-02,
class QASMParser(object): """Class for parsing OpenQASM files into CQC t|ket> Circuits.""" def __init__(self): self.circuit = Circuit() self.register_dict = dict() def parse_qasm(self, qasm): lines = qasm.splitlines() rows = [] #first, get rid of comments and whitespace lines for l in lines: i = l.find("//") if i != -1: s = l[0:i].strip() else: s = l.strip() if s: rows.append(s) #now, throw away OPENQASM descriptor etc. if not (rows[0].startswith("OPENQASM 2.0") and rows[1].startswith('include "qelib1.inc";')): raise TypeError( "File must declare OPENQASM version and its includes.") data = "\n".join(rows[2:]) #now, separate out the custom gates to deal with elsewhere while True: i = data.find("gate ") if i == -1: break j = data.find("}", i) if j == -1: raise TypeError("Custom gate definition is invalid.") self.parse_custom_gate(data[i:j + 1]) #TODO: deal with custom gate data = data[:i] + data[j + 1:] #now, parse the regular instructions instructions = [s.strip() for s in data.split(";") if s.strip()] for i in instructions: self.parse_instruction(i) return self.circuit def parse_custom_gate(self, data): raise TypeError("Cannot currently parse custom gates") def parse_instruction(self, instruction): if instruction.find("->") != -1: ###handle measure gates ###currently assumes that there is just 1 qb being read to 1 bit name_and_qbs, bits = instruction.split("->", 1) if (name_and_qbs.find("measure") == -1): raise Exception( "Error in parsing: cannot accept a non-Measure gate writing to classical register" ) name_and_qbs = name_and_qbs.replace("measure", "") name_and_qbs = name_and_qbs.replace(" ", "") name_and_qbs.strip() qregname, qbindex = name_and_qbs.split("[") qbindex = int(qbindex[0]) qubit = self.circuit.q_regs[qregname][qbindex] bits = bits.replace(" ", "") bitreg, bitindex = bits.split("[") bitindex = int(bitindex[0]) bit = self.circuit.c_regs[bitreg][bitindex] self.circuit.add_measure(qubit, bit) return if instruction.find("(") != -1: name, rest = instruction.split(") ", 1) name = name.replace(" ", "") else: name, rest = instruction.split(" ", 1) args = [s.strip() for s in rest.split(",") if s.strip()] #deal with qubit register declarations if name == "qreg" or name == "creg": regname, size = args[0].split("[", 1) regname.strip() size = int(size[:-1]) if name == "qreg": self.circuit.add_q_register(regname, size) else: self.circuit.add_c_register(regname, size) return #get qubits to append operation to qubits = [] for a in args: if "[" in a: regname, val = a.split("[", 1) val = int(val[:-1]) qubits.append(self.circuit.q_regs[regname][val]) else: raise Exception( "Unknown error in parsing: Cannot parse argument {}". format(a)) #if the gate is parameterised, get these parameters if name.find("(") != -1: name, params = name.split("(", 1) if name in PARAM_COMMANDS: angles = [s.strip() for s in params.split(",") if s.strip()] halfturn_angles = [] for ang in angles: ang = ang.replace("pi", str(math.pi)) try: halfturns = sympify(ang) / math.pi halfturn_angles.append(halfturns) except: raise TypeError("Cannot parse angle: {}".format(ang)) self.circuit.add_gate(PARAM_COMMANDS[name], halfturn_angles, qubits, []) else: raise TypeError("Cannot parse gate of type: {}".format(name)) else: if name in NOPARAM_COMMANDS: self.circuit.add_gate(NOPARAM_COMMANDS[name], [], qubits, []) else: raise TypeError("Cannot parse gate of type: {}".format(name))
# `cx a[0],a[1];` # `h a[0];` # `measure a[0] -> c[0];` # `measure a[1] -> c[1];` # `// Correction of Bob's qubit` # `if(c==1) z b[0];` # `if(c==3) z b[0];` # `if(c==2) x b[0];` # `if(c==3) x b[0];` # # This corresponds to the following `pytket` code: from pytket import Circuit qtel = Circuit() alice = qtel.add_q_register("a", 2) bob = qtel.add_q_register("b", 1) data = qtel.add_c_register("d", 2) # Bell state between Alice and Bob: qtel.H(alice[1]) qtel.CX(alice[1], bob[0]) # Bell measurement of Alice's qubits: qtel.CX(alice[0], alice[1]) qtel.H(alice[0]) qtel.Measure(alice[0], data[0]) qtel.Measure(alice[1], data[1])
# - supports mid-circuit measurement and OpenQASM-style conditional gates; # - encompasses a variety of underlying simulation methods and automatically selects the best one for each circuit (including statevector, density matrix, (extended) stabilizer and matrix product state); # - can be provided with a `qiskit.providers.Aer.noise.NoiseModel` on instantiation to perform a noisy simulation. # Useful features: # - support for fast expectation value calculations according to `QubitPauliString`s or `QubitPauliOperator`s. from pytket import Circuit from pytket.extensions.qiskit import AerBackend from itertools import combinations from qiskit.providers.aer.noise import NoiseModel, depolarizing_error # Quantum teleportation circuit: c = Circuit() alice = c.add_q_register("a", 2) bob = c.add_q_register("b", 1) data = c.add_c_register("d", 2) final = c.add_c_register("f", 1) # Start in an interesting state: c.Rx(0.3, alice[0]) # Set up a Bell state between Alice and Bob: c.H(alice[1]).CX(alice[1], bob[0]) # Measure Alice's qubits in the Bell basis: c.CX(alice[0], alice[1]).H(alice[0])