{"cells":[{"cell_type":"markdown","metadata":{},"source":["# Code portability and intro to forest"]},{"cell_type":"markdown","metadata":{},"source":["**Download this notebook - {nb-download}`Forest_portability_example.ipynb`**"]},{"cell_type":"markdown","metadata":{},"source":["The quantum hardware landscape is incredibly competitive and rapidly changing. Many full-stack quantum software platforms lock users into them in order to use the associated devices and simulators. This notebook demonstrates how `pytket` can free up your existing high-level code to be used on devices from other providers. We will take a state-preparation and evolution circuit generated using `qiskit`, and enable it to be run on several Rigetti backends.
\n","
\n","To use a real hardware device, this notebook should be run from a Rigetti QMI instance. Look [here](https://www.rigetti.com/qcs/docs/intro-to-qcs) for information on how to set this up. Otherwise, make sure you have QuilC and QVM running in server mode. You will need to have `pytket`, `pytket_pyquil`, and `pytket_qiskit` installed, which are all available from PyPI."]},{"cell_type":"markdown","metadata":{},"source":["We will start by using `qiskit` to build a random initial state over some qubits. (We remove the initial \"reset\" gates from the circuit since these are not recognized by the Forest backends, which assume an all-zero initial state.)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from qiskit import QuantumCircuit\n","from qiskit.quantum_info.states.random import random_statevector"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["n_qubits = 3\n","state = random_statevector((1 << n_qubits, 1)).data\n","state_prep_circ = QuantumCircuit(n_qubits)\n","state_prep_circ.initialize(state)\n","state_prep_circ = state_prep_circ.decompose().decompose()\n","state_prep_circ.data = [\n"," datum for datum in state_prep_circ.data if datum[0].name != \"reset\"\n","]"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["print(state_prep_circ)"]},{"cell_type":"markdown","metadata":{},"source":["We can now evolve this state under an operator for a given duration."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from qiskit.opflow import PauliTrotterEvolution\n","from qiskit.opflow.primitive_ops import PauliSumOp\n","from qiskit.quantum_info import Pauli"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["duration = 1.2\n","op = PauliSumOp.from_list([(\"XXI\", 0.3), (\"YYI\", 0.5), (\"ZZZ\", -0.4)])\n","evolved_op = (duration * op).exp_i()\n","evolution_circ = PauliTrotterEvolution(reps=1).convert(evolved_op).to_circuit()\n","print(evolution_circ)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["for op in evolution_circ:\n"," state_prep_circ.append(op)"]},{"cell_type":"markdown","metadata":{},"source":["Now that we have a circuit, `pytket` can take this and start operating on it directly. For example, we can apply some basic compilation passes to simplify it."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.qiskit import qiskit_to_tk"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["tk_circ = qiskit_to_tk(state_prep_circ)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.passes import (\n"," SequencePass,\n"," CliffordSimp,\n"," DecomposeBoxes,\n"," KAKDecomposition,\n"," SynthesiseTket,\n",")"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["DecomposeBoxes().apply(tk_circ)\n","optimise = SequencePass([KAKDecomposition(), CliffordSimp(False), SynthesiseTket()])\n","optimise.apply(tk_circ)"]},{"cell_type":"markdown","metadata":{},"source":["Display the optimised circuit:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.circuit.display import render_circuit_jupyter"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["render_circuit_jupyter(tk_circ)"]},{"cell_type":"markdown","metadata":{},"source":["The Backends in `pytket` abstract away the differences between different devices and simulators as much as possible, allowing painless switching between them. The `pytket_pyquil` package provides two Backends: `ForestBackend` encapsulates both running on physical devices via Rigetti QCS and simulating those devices on the QVM, and `ForestStateBackend` acts as a wrapper to the pyQuil Wavefunction Simulator.
\n","
\n","Both of these still have a few restrictions on the circuits that can be run. Each only supports a subset of the gate types available in `pytket`, and a real device or associated simulation will have restricted qubit connectivity. The Backend objects will contain a default compilation pass that will statisfy these constraints as much as possible, with minimal or no optimisation.
\n","
\n","The `ForestStateBackend` will allow us to view the full statevector (wavefunction) expected from a perfect execution of the circuit."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pytket.extensions.pyquil import ForestStateBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["state_backend = ForestStateBackend()\n","tk_circ = state_backend.get_compiled_circuit(tk_circ)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["handle = state_backend.process_circuit(tk_circ)\n","state = state_backend.get_result(handle).get_state()\n","print(state)"]},{"cell_type":"markdown","metadata":{},"source":["For users who are familiar with the Forest SDK, the association of qubits to indices of bitstrings (and consequently the ordering of statevectors) used by default in `pytket` Backends differs from that described in the [Forest docs](http://docs.rigetti.com/en/stable/wavefunction_simulator.html#multi-qubit-basis-enumeration). You can recover the ordering used by the Forest systems with `BackendResult.get_state(tk_circ, basis:pytket.BasisOrder.dlo)` (see our docs on the `BasisOrder` enum for more details)."]},{"cell_type":"markdown","metadata":{},"source":["Connecting to real devices works very similarly. Instead of obtaining the full statevector, we are only able to measure the quantum state and sample from the resulting distribution. Beyond that, the process is pretty much the same.
\n","
\n","The following shows how to run the circuit on the \"9q-square\" lattice. The `as_qvm` switch on the `get_qc` method will switch between connecting to the real Aspen device and the QVM, allowing you to test your code with a simulator before you reserve your slot with the device."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["tk_circ.measure_all()"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["from pyquil import get_qc\n","from pytket.extensions.pyquil import ForestBackend"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["aspen_qc = get_qc(\"9q-square\", as_qvm=True)\n","aspen_backend = ForestBackend(aspen_qc)\n","tk_circ = aspen_backend.get_compiled_circuit(tk_circ)"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["counts = aspen_backend.run_circuit(tk_circ, 2000).get_counts()\n","print(counts)"]},{"cell_type":"markdown","metadata":{},"source":["Note that attempting to connect to a live quantum device (using a `QuantumComputer` constructed with `as_qvm=False`) will fail unless it is running from a QMI instance during a reservation for the named lattice."]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.4"}},"nbformat":4,"nbformat_minor":2}