def build_alternating_cnots_circuit(n_qubits, n_layers, params, skips=None): if skips is None: skips = [] c = Circuit(n_qubits) cnot_count = 0 cnots_full = 0 for i in range(n_layers): if i % 2: for q in range(n_qubits // 2): if cnots_full not in skips: c.add_operation(OpType.U3, params[2 * cnot_count], [2 * q]) c.add_operation(OpType.U3, params[2 * cnot_count + 1], [2 * q + 1]) c.CX(2 * q, 2 * q + 1) cnot_count += 1 cnots_full += 1 else: for q in range((n_qubits - 1) // 2): if cnots_full not in skips: c.add_operation(OpType.U3, params[2 * cnot_count], [2 * q + 1]) c.add_operation(OpType.U3, params[2 * cnot_count + 1], [2 * q + 2]) c.CX(2 * q + 1, 2 * q + 2) cnot_count += 1 cnots_full += 1 for q in range(n_qubits): c.add_operation(OpType.U3, params[-q], [q]) return c
def phi(qubits, alpha, n_qubits): c = Circuit(n_qubits) for i in range(len(qubits) - 1): c.CX(qubits[i], qubits[i + 1]) c.Rz(qubits[-1], alpha) for i in range(len(qubits) - 1): c.CX(qubits[-i - 2], qubits[-i - 1]) return c
def test_compilation_pass() -> None: b = MyBackend() for opt_level in range(3): c = Circuit(2) c.CX(0, 1) u = np.asarray([[0, 1], [-1j, 0]]) c.add_unitary1qbox(Unitary1qBox(u), 1) c.CX(0, 1) c.add_gate(OpType.CRz, 0.35, [1, 0]) assert not (b.valid_circuit(c)) b.compile_circuit(c, optimisation_level=opt_level) assert b.valid_circuit(c)
def test_sampler_expectation_value() -> None: c = Circuit(2) c.H(0) c.CX(0, 1) op = QubitPauliOperator({ QubitPauliString({ Qubit(0): Pauli.Z, Qubit(1): Pauli.Z }): 1.0, QubitPauliString({ Qubit(0): Pauli.X, Qubit(1): Pauli.X }): 0.3, QubitPauliString({ Qubit(0): Pauli.Z, Qubit(1): Pauli.Y }): 0.8j, QubitPauliString({Qubit(0): Pauli.Y}): -0.4j, }) b = MySampler() b.compile_circuit(c) expectation = get_operator_expectation_value(c, op, b, n_shots=2000, seed=0) assert (np.real(expectation), np.imag(expectation)) == pytest.approx( (1.3, 0.0), abs=0.1)
def test_expectation_value() -> None: c = Circuit(2) c.H(0) c.CX(0, 1) op = QubitPauliOperator({ QubitPauliString({ Qubit(0): Pauli.Z, Qubit(1): Pauli.Z }): 1.0, QubitPauliString({ Qubit(0): Pauli.X, Qubit(1): Pauli.X }): 0.3, QubitPauliString({ Qubit(0): Pauli.Z, Qubit(1): Pauli.Y }): 0.8j, QubitPauliString({Qubit(0): Pauli.Y}): -0.4j, }) b = MyBackend() b.compile_circuit(c) assert get_operator_expectation_value(c, op, b) == pytest.approx(1.3)
def test_bell() -> None: c = Circuit(2) c.H(0) c.CX(0, 1) b = MyBackend() b.compile_circuit(c) assert np.allclose(b.get_state(c), np.asarray([1, 0, 0, 1]) * 1 / np.sqrt(2))
def test_sampler_invalid_conditions() -> None: c = Circuit(2, 2) c.H(0) c.CX(0, 1, condition_bits=[0, 1], condition_value=3) c.measure_all() b = MySampler() b.compile_circuit(c) assert not (b.valid_circuit(c))
def test_sampler_bell() -> None: c = Circuit(2, 2) c.H(0) c.CX(0, 1) c.measure_all() b = MySampler() b.compile_circuit(c) assert b.get_shots(c, n_shots=10, seed=3).shape == (10, 2) assert b.get_counts(c, n_shots=10, seed=3) == {(0, 0): 5, (1, 1): 5}
def hea(params): ansatz = Circuit(4) for i in range(4): ansatz.Ry(params[i], i) for i in range(3): ansatz.CX(i, i + 1) for i in range(4): ansatz.Ry(params[4 + i], i) return ansatz
def add_operator_term(circuit: Circuit, term: QubitPauliString, angle: float): qubits = [] for q, p in term.to_dict().items(): if p != Pauli.I: qubits.append(q) if p == Pauli.X: circuit.H(q) elif p == Pauli.Y: circuit.V(q) for i in range(len(qubits) - 1): circuit.CX(i, i + 1) circuit.Rz(angle, len(qubits) - 1) for i in reversed(range(len(qubits) - 1)): circuit.CX(i, i + 1) for q, p in term.to_dict().items(): if p == Pauli.X: circuit.H(q) elif p == Pauli.Y: circuit.Vdg(q)
def cnot_unitary_to_circuit(u, n_qubits): # desc = greedy(matrix_to_function(u, n_qubits), n_qubits) # Doesn't converge, don't run this desc = decompose_unitary(u) c = Circuit(n_qubits) for d in desc: if d[0] == "N": c.X(int(d[2:])) elif d[0] == "C": c.CX(int(d.split(":")[1]), int(d.split(":")[2])) return c
def add_params_to_template(template: Circuit, params: np.ndarray): params = params.reshape((template.n_gates * 2 + template.n_qubits, 3)) c = Circuit(template.n_qubits) for i in range(template.n_gates): cnot_gate = template.get_commands()[i] c.add_operation(OpType.U3, params[2 * i], [cnot_gate.qubits[0]]) c.add_operation(OpType.U3, params[2 * i + 1], [cnot_gate.qubits[1]]) c.CX(cnot_gate.qubits[0], cnot_gate.qubits[1]) for q in range(template.n_qubits): c.add_operation(OpType.U3, params[template.n_gates * 2 + q], [q]) return c
def mnot_templates(n_controls): if n_controls == 0: return Circuit(1) c1 = Circuit(2) c1.CX(0, 1) c1.add_circuit(c1.copy(), [0, 1]) if n_controls == 1: return c1 c2 = Circuit(3) c2.CX(1, 2) c2.CX(0, 1) c2.CX(1, 2) c2.CX(0, 1) c2.CX(0, 2) circs = [c1, c2] for i in range(3, n_controls + 1): c = Circuit(i + 1) c.CX(i - 1, i) c.CX(i - 1, i) c.add_circuit(circs[-1], list(range(i))) c.CX(i - 1, i) c.CX(i - 1, i) c.add_circuit(circs[-1], list(range(i))) c.add_circuit(circs[-1], list(range(i - 1)) + [i]) c.add_circuit(circs[-1], list(range(i - 1)) + [i]) circs.append(c) return circs
def pauli_evolution(pauli: List[Tuple[int, str]], coeff: complex, circ: Circuit): """Appends the evolution circuit corresponding to a given Pauli tensor Args: pauli: coeff (complex): circ (Circuit): """ # set up the correct basis all_qbs = list(zip(*pauli))[0] for qb_idx, p in pauli: if p == 'X': circ.H(qb_idx) elif p == 'Y': # angles in half-turns circ.Rx(qb_idx, 0.5) # cnot cascade cx_qb_pairs = list(zip(sorted(all_qbs)[:-1], sorted(all_qbs)[1:])) for pair in cx_qb_pairs: circ.CX(pair[0], pair[1]) # rotation (convert angle from radians to half-turns) circ.Rz(all_qbs[-1], (2 * coeff.imag) / PI) # reverse cascade and revert basis cx_qb_pairs = list(zip(sorted(all_qbs)[:-1], sorted(all_qbs)[1:])) for pair in reversed(cx_qb_pairs): circ.CX(pair[0], pair[1]) all_qbs = list(zip(*pauli))[0] for qb_idx, p in pauli: if p == 'X': circ.H(qb_idx) elif p == 'Y': circ.Rx(qb_idx, -0.5)
def test_implicit_perm() -> None: c = Circuit(2) c.CX(0, 1) c.CX(1, 0) c.Ry(0.1, 1) c1 = c.copy() CliffordSimp().apply(c1) b = MyBackend() b.compile_circuit(c) b.compile_circuit(c1) assert c.implicit_qubit_permutation() != c1.implicit_qubit_permutation() for bo in [BasisOrder.ilo, BasisOrder.dlo]: s = b.get_state(c, bo) s1 = b.get_state(c1, bo) assert np.allclose(s, s1)
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]]
# To run every option in this example, you will need `pytket`, `pytket-qiskit`, `pytket-pyquil`, `pytket-qsharp`, `pytket-qulacs`, and `pytket-projectq`. # # With the number of simulator `Backend`s available across the `pytket` extension modules, we are often asked why to use one over another. Surely, any two simulators are equivalent if they are able to sample the circuits in the same way, right? Not quite. In this notebook we go through each of the simulators in turn and describe what sets them apart from others and how to make use of any unique features. # # But first, to demonstrate the significant overlap in functionality, we'll just give some examples of common usage for different types of backends. # ## Sampling simulator usage from pytket import Circuit from pytket.extensions.qiskit import AerBackend # Define a circuit: c = Circuit(3, 3) c.Ry(0.7, 0) c.CX(0, 1) c.X(2) c.measure_all() # Run on the backend: backend = AerBackend() backend.compile_circuit(c) handle = backend.process_circuit(c, n_shots=2000) counts = backend.get_result(handle).get_counts() print(counts) # ## Statevector simulator usage from pytket import Circuit from pytket.extensions.qiskit import AerStateBackend
) # Likewise we can retrieve multi-qubit gate information. for edge in athens_device.coupling: print("CX error rate for", edge, "is", athens_device.get_error(OpType.CX, edge)) # We've now seen how to create custom Architectures using indexing and nodes, how to use our built in Architecture generators for typical connectivity graphs, how to create custom Devices using our QubitErrorContainers, and how to automatically generate a Device object for a real quantum computer straight from IBM. # # Let's now see how we can use these objects are used for Routing circuits - we create a circuit for Routing to our original architectures and assume the only primitive constraint is the ```CX``` gate, which can only be executed on an edge in our coupling map. from pytket import Circuit example_circuit = Circuit(4) example_circuit.CX(0, 1).CX(0, 2).CX(1, 2).CX(3, 2).CX(0, 3) for gate in example_circuit: print(gate) # We can also visualise the `Circuit` using, for example, IBM Qiskit's `QuantumCircuit` printer. To do this, we must use the `pytket.extensions.qiskit` subpackage and import a method from within Qiskit. from pytket.extensions.qiskit import tk_to_qiskit print(tk_to_qiskit(example_circuit)) # This circuit can not be executed on any of our Architectures without modification. We can see this by looking at the circuits interaction graph, a graph where nodes are logical qubits and edges are some two-qubit gate. interaction_edges = [(0, 1), (0, 2), (1, 2), (3, 2), (0, 3)] draw_graph(interaction_edges) draw_graph(simple_coupling_map)
# `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]) # Correction of Bob's qubit: qtel.X(bob[0], condition_bits=[data[0], data[1]], condition_value=2) qtel.X(bob[0], condition_bits=[data[0], data[1]], condition_value=3) qtel.Z(bob[0], condition_bits=[data[0], data[1]], condition_value=1) qtel.Z(bob[0], condition_bits=[data[0], data[1]], condition_value=3)
# 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, 2.95442468e-08, 1.00000015e00, 1.00000086e00, 9.99999826e-01, 1.00000002e00,
def add_params_to_template(template: Circuit, params: np.ndarray): params = params.reshape((template.n_gates * 2 + template.n_qubits, 3)) c = Circuit(template.n_qubits) for i in range(template.n_gates): cnot_gate = template.get_commands()[i] c.add_operation(OpType.U3, params[2 * i], [cnot_gate.qubits[0]]) c.add_operation(OpType.U3, params[2 * i + 1], [cnot_gate.qubits[1]]) c.CX(cnot_gate.qubits[0], cnot_gate.qubits[1]) for q in range(template.n_qubits): c.add_operation(OpType.U3, params[template.n_gates * 2 + q], [q]) return c qft3 = Circuit(3) qft3.CX(1, 0) qft3.CX(1, 0) qft3.CX(2, 0) qft3.CX(2, 0) qft3.CX(2, 1) qft3.CX(2, 1) qft3.CX(0, 1) qft3.CX(0, 2) def approximate_via_cnots(unitary): dim = unitary.shape[0] n_qubits = int(math.log(dim, 2)) converter.n_qubits = n_qubits r = RewriteTket(Circuit(n_qubits), [], []) r.set_target_unitary(unitary)
from tket_backend_factory import get_tket_backend # Get quantum backend from command-line arguments. backend = get_tket_backend() print('*** Hello, Quantum! - Entanglement (tket) ***') repeat_count = 1000 # Set up cirbuit with 2 qubits and 2 classical bits. # Apply Hadamard gate to the control qubit 0. # Apply Controlled NOT gate to the target qubit 1 using qubit 0 as control. # Measure the qubits and save the result into classical bits. circuit = Circuit(2, 2) circuit.H(0) circuit.CX(0, 1) circuit.Measure(0, 0) circuit.Measure(1, 1) # Initialise backend and run circuit a specified number of times. backend.compile_circuit(circuit) job = backend.process_circuit(circuit, n_shots=repeat_count) # Get counts of qubit measurement results. result_counts = backend.get_result(job).get_counts() # Print results. print(f"Counts for {repeat_count} repeats:") print(f"\t00: {result_counts[(0, 0)]}") print(f"\t01: {result_counts[(0, 1)]}") print(f"\t10: {result_counts[(1, 0)]}")