{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n# Phase Tracking with the Compiler\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Phase tracking is a technique that exploits the ability of quantum control hardware to\nperform accurate and arbitrary phase rotations of pulse shapes in order to reduce the\nnumber of pulse shapes required to form a device's native quantum gate set. Moreover,\nit causes the native gate set to be continuously parametric which greatly reduces the\nsynthesis cost of arbitrary unitaries. For example, if an $R_x(90)$ gate is\ntuned up, then any single qubit unitary can be synthesized using two of these pulses\nand phase tracking.\n\nBelow, we call these phase rotation gates *virtual gates* because they are not\ndirectly used to update the quantum state. Instead, virtual gates are better thought\nof as pulse compiler directives, where the pulse compiler is a program that prepares a\nsequence of waveforms for the control electronics given a circuit of native gates. In\na multi-qubit device that employs phase tracking, the pulse compiler accumulates a\nphase on each qubit in the circuit whenever a virtual gate is encountered. Any time a\nnon-virtual gate is encountered on a given subset of qubits, the corresponding\ncummulative phases are used to modify the phases of the pulse shape.\n\nWe illustrate how phase tracking works using the following single-qubit circuit\nexpressed as rotations in matrix multiplication order. This circuit is assumed to\nbe the output of a pre-compiler that has already converted some original circuit into\n$R_x(90)$ and $R_z(\\phi)$ rotations.\n\n\\begin{align}R_z(d)\\cdot R_x(90) \\cdot R_z(c)\\cdot R_x(90)\n \\cdot R_z(b) \\cdot R_x(90) \\cdot R_z(a)\\end{align}\n\nThis can be rewritten as\n\n\\begin{align}R_z(a+b+c+d) \\cdot R_z(-(a+b+c)) \\cdot R_x(90) \\cdot R_z(a+b+c)\n \\\\ \\quad\n \\cdot R_z(-(a+b)) \\cdot R_x(90) \\cdot R_z(a+b) \\cdot R_z(-a)\n \\cdot R_x(90) \\cdot R_z(a)\\end{align}\n\nand further simplified to\n\n\\begin{align}R_z(a+b+c+d)R_{a+b+c}(90)R_{a+b}(90) R_a(90)\\end{align}\n\nwhere we have defined a nutation about some vector in the x-y plane as\n\n.. math ::\n\n R_\\phi(\\theta)=R_z(-\\phi)R_x(90)R_z(\\phi)\n =\\operatorname{exp}(-i \\theta (\\cos(\\phi)X-\\sin(\\phi)Y)/2).\n\nThus if a pulse shape for the $R_x(90)$ gate is tuned up, and our control\nelectronics are able to rotate it in quadrature by arbitrary angles thereby producing\noperations $R_\\phi(90)$, then we have sufficient control to perform the\noriginal circuit. Note further that any qubit unitary can be decomposed into an\nalternating sequence $R_z(c)\\cdot R_x(90) \\cdot R_z(b) \\cdot R_x(90) \\cdot\nR_z(a)$ where $a$, $b$, $c$ are its ZXZ Euler angles. It follows\nthat this is the *only* pulse shape that is required to perform single qubit gates.\n\nFor most systems, the final accumulated phase in the z-axis, $R_z(a+b+c+d)$,\ndoes not actually need to be implemented on the qubit. This is because the final\noperation will be a measurement of the qubit along the z-axis, whose outcome will not\nbe affected by a z-rotation.\n\nThe process of phase tracking on multi-qubit gates is similar to the single qubit\ncase, which will be seen in the examples below. As previously mentioned, a different\nphase needs to be tracked for each qubit, and pulse shapes may require a parameter for\nevery qubit they act on.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import trueq as tq\nimport numpy as np"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Using the Controlled-Z Gate\nA device whose only native two-qubit gate is the CZ gate is the simplest case because\nthis commutes with the virtual Z gate on both qubits. This means we only need to apply\naccumulated phases to the single qubit gates.\n\nFirst, we define a config object that contains all of the necessary gate factories.\nSince we will be doing phase tracking, we require not only the parametric native gate\nfactories and the virtual gate factory, but also a factory for each of the static\ngates that correspond to the parametric gates at parameter value 0.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# factories for our parametric gates. the first is a 90 degree rotation about an\n# axis in the x-y plane, equivalent to z(-a)*x(90)*z(a). the second is our cz gate\n# which happens to need no parameters because it commutes with virtual gates\nr90 = tq.config.GateFactory.from_hamiltonian(\n \"r90\", [[\"Z\", \"-theta\"], [\"X\", \"90\"], [\"Z\", \"theta\"]]\n)\ncz = tq.config.GateFactory.from_matrix(\"cz\", np.diag([1, 1, 1, -1]))\n\n# factory for our virtual gate\nz = tq.config.GateFactory.from_hamiltonian(\"z\", [[\"Z\", \"theta\"]])\n\n# factory for static gates, equal to our parametric gates at 0\nx90 = tq.config.GateFactory.from_matrix(\"x90\", r90(0))\nfactories = [x90, z, cz, r90]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we initialize our phase-tracking compiler pattern. Note that it chooses the\nvirtual gate factory by looking for a rotation about the Z-axis in units of degrees.\nIf this is not the case for you, specify any other single qubit rotation manually\nusing ``virtual=factory``.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"phase_tracker = tq.compilation.PhaseTrack(factories=factories)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next we set up our compiler and the rest of its passes. There are two essential steps:\n\n 1. Convert all abstract gates into the static gates and the virtual gate\n (:py:class:`~trueq.compilation.Native1Q` and\n :py:class:`~trueq.compilation.Native2Q`).\n 2. Use the :py:class:`~trueq.compilation.PhaseTrack` pattern to accumulate phases and\n compile them into the parametric gate parameters.\n\nThere are additionally justification, merge, and identity removal book-keeping passes\nthat can be adjusted to one's needs.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"compiler = tq.Compiler(\n [\n tq.compilation.Native2Q(factories),\n tq.compilation.Justify(),\n tq.compilation.Merge(),\n tq.compilation.RemoveId(),\n tq.compilation.Native1Q(factories),\n phase_tracker,\n tq.compilation.RemoveEmptyCycle(),\n ]\n)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We illustrate the output of this compiler using a circuit that generates the GHZ state\non $n$ qubits. Before compilation, our abstract circuit on 4 qubits is as\nfollows:\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def ghz(n):\n circuit = tq.Circuit([{0: tq.Gate.h}])\n for idx in range(n - 1):\n circuit.append({(idx, idx + 1): tq.Gate.cnot})\n circuit.measure_all()\n return circuit\n\n\nghz(4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The output bitstring distribution is as expected:\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"tq.Simulator().state(ghz(4)).to_results().plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Following compilation, our circuit contains only gates that were generated using the\n``r90`` or ``cz`` factory. The parameters the factories used to construct the gates\nare stored in the gate objects, along with the gates' matrix representations. Single\nqubit gates, under this compiler, are always decomposed into two 90 degree nutations.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"circuit = compiler.compile(ghz(4))\ncircuit"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, our output bitstring distribution is unchanged, as expected.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"tq.Simulator().state(circuit).probabilities().to_results().plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Using the Cross-Resonance Gate\nThe maximally entangling cross-resonance gate is equal to\n\n\\begin{align}CR=\\begin{pmatrix}\n 1 & -i & 0 & 0 \\\\ -i & 1 & 0 & 0 \\\\ 0 & 0 & 1 & i \\\\ 0 & 0 & i & 1\n \\end{pmatrix}/\\sqrt{2}\n =\\frac{I\\otimes I-iZ\\otimes X}{\\sqrt{2}}.\\end{align}\n\nBy adjusting the phase of the microwave control used to generate this gate, we can\nrotate action of the second qubit about the z-axis, which results in the parametric\ngate\n\n\\begin{align}CR(\\phi)\n = \\frac{I\\otimes I-i\\cos(\\phi)Z\\otimes X+i\\sin(\\phi)Z\\otimes Y}{\\sqrt{2}}.\\end{align}\n\n\nThis parametric gate is defined below as a gate factory, along with other factories\nnecessary for this example.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# define parametric gate factories\nr90 = tq.config.GateFactory.from_hamiltonian(\n \"r90\", [[\"Z\", \"-theta\"], [\"X\", \"90\"], [\"Z\", \"theta\"]]\n)\ncr = tq.config.GateFactory.from_hamiltonian(\n \"cr\", [[\"IZ\", \"-theta\"], [\"ZX\", \"90\"], [\"IZ\", \"theta\"]]\n)\n\n# factory for our virtual gate\nz = tq.config.GateFactory.from_hamiltonian(\"z\", [[\"Z\", \"theta\"]])\n\n# factory for static gates, equal to our parametric gates at 0\nx90 = tq.config.GateFactory.from_matrix(\"x90\", r90(0))\ncr0 = tq.config.GateFactory.from_matrix(\"cr0\", cr(0))\n\n# put everything into a factory list. static gates should go first\nfactories = [x90, z, cr0, r90, cr]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As in the CZ section, we define a compiler that performs phase-tracking using our\nnative gates, and test it on a circuit that generates a 4-qubit GHZ state.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"compiler = tq.Compiler(\n [\n tq.compilation.Native2Q(factories),\n tq.compilation.Justify(),\n tq.compilation.Merge(),\n tq.compilation.RemoveId(),\n tq.compilation.Native1Q(factories),\n tq.compilation.PhaseTrack(factories),\n tq.compilation.Justify(),\n ]\n)\n\ncircuit = compiler.compile(ghz(4))\ncircuit"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we verify that our bitstring output is as expected.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"tq.Simulator().state(circuit).probabilities().to_results().plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Using the Molmer-Sorensen Gate\nA maximally entangling MS gate has the form\n\n\\begin{align}MS(90)=\\begin{pmatrix}\n 1 & 0 & 0 & -i \\\\ 0 & 1 & -i & 0 \\\\ 0 & -i & 1 & 0 \\\\ -i & 0 & 0 & 1\n \\end{pmatrix}/\\sqrt{2}\n =\\frac{I\\otimes I-iX\\otimes X}{/\\sqrt{2}}\\end{align}\n\nwhich, for a trapped ion, is generated by lasing on both qubits simultaneously.\nHowever, by changing the phase of the frequency sources sent to the optical modulators\non each qubit, the azimuthal angle of the MS gate can be changed independently on both\nqubits qubits. This allows the implementation of the parametric gate\n\n\\begin{align}MS(90, \\phi_1, \\phi_2) = \\frac{II-iR_{\\phi_1}(90)\\otimes R_{\\phi_2}(90)}{\\sqrt{2}}\\end{align}\n\nwhere $R_\\phi(\\theta)$ is defined above.\n\nThis parametric gate is defined below as a gate factory, along with other factories\nnecessary for this example.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# factories for our parametric gates. the first is a 90 degree rotation about an axis in\n# the x-y plane, equivalent to z(a)*x(90)*z(-a). the second is a molmer-sorensen gate\n# with phase rotation a on the first qubit and b on the second qubit, i.e. equivalent to\n# (z(a) & z(b))*xx(90)*(z(-a) & z(-b))\nr90 = tq.config.GateFactory.from_hamiltonian(\n \"r90\", [[\"Z\", \"-theta\"], [\"X\", \"90\"], [\"Z\", \"theta\"]]\n)\nms = tq.config.GateFactory.from_hamiltonian(\n \"ms\", [[\"IZ\", \"-b\"], [\"ZI\", \"-a\"], [\"XX\", 90], [\"ZI\", \"a\"], [\"IZ\", \"b\"]]\n)\n\n# factory for our virtual gate\nz = tq.config.GateFactory.from_hamiltonian(\"z\", [[\"Z\", \"theta\"]])\n\n# factory for static gates, equal to our parametric gates at 0\nx90 = tq.config.GateFactory.from_matrix(\"x90\", r90(0))\nms0 = tq.config.GateFactory.from_matrix(\"ms0\", ms(0, 0))\n\n# put everything into a factory list\nfactories = [x90, z, ms0, r90, ms]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"phase_tracker = tq.compilation.PhaseTrack(factories)\nphase_tracker._rules"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As in the CZ section, we define a compiler that performs phase-tracking using our\nnative gates, and test it on a circuit that generates a 4-qubit GHZ state.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"compiler = tq.Compiler(\n [\n tq.compilation.Native2Q(factories),\n tq.compilation.Justify(),\n tq.compilation.Merge(),\n tq.compilation.RemoveId(),\n tq.compilation.Native1Q(factories),\n tq.compilation.PhaseTrack(factories),\n tq.compilation.Justify(),\n ]\n)\n\ncircuit = compiler.compile(ghz(4))\ncircuit"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we verify that our bitstring output is as expected.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"tq.Simulator().state(circuit).probabilities().to_results().plot()"
]
}
],
"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.7.11"
}
},
"nbformat": 4,
"nbformat_minor": 0
}