diff --git a/.travis.yml b/.travis.yml index 89700c84f..fbe009436 100755 --- a/.travis.yml +++ b/.travis.yml @@ -43,7 +43,7 @@ install: - pip$PY install -e . # command to run tests -script: export OMP_NUM_THREADS=1 && pytest -W error projectq --cov projectq +script: export OMP_NUM_THREADS=1 && pytest projectq --cov projectq after_success: - coveralls diff --git a/docs/projectq.ops.rst b/docs/projectq.ops.rst index e04ca2e91..8f8d4cceb 100755 --- a/docs/projectq.ops.rst +++ b/docs/projectq.ops.rst @@ -49,6 +49,9 @@ The operations collection consists of various default gates and is a work-in-pro projectq.ops.CZ projectq.ops.Toffoli projectq.ops.TimeEvolution + projectq.ops.UniformlyControlledRy + projectq.ops.UniformlyControlledRz + projectq.ops.StatePreparation Module contents diff --git a/examples/spectral_measurement.ipynb b/examples/spectral_measurement.ipynb new file mode 100644 index 000000000..0c791ecc0 --- /dev/null +++ b/examples/spectral_measurement.ipynb @@ -0,0 +1,467 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Quantum Algorithm for Spectral Measurement with Lower Gate Count\n", + "\n", + "This tutorial shows how to implement the algorithm introduced in the following paper:\n", + "\n", + "**Quantum Algorithm for Spectral Measurement with Lower Gate Count**\n", + "by David Poulin, Alexei Kitaev, Damian S. Steiger, Matthew B. Hastings, Matthias Troyer\n", + "[Phys. Rev. Lett. 121, 010501 (2018)](https://doi.org/10.1103/PhysRevLett.121.010501)\n", + "([arXiv:1711.11025](https://arxiv.org/abs/1711.11025))\n", + "\n", + "For details please see the above paper. The implementation in ProjectQ is discussed in the PhD thesis of Damian S. Steiger (soon available online). A more detailed discussion will be uploaded soon.\n", + "Here we only show a small part of the paper, namely the implementation of W and how it can be used with iterative phase estimation to obtain eigenvalues and eigenstates." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from copy import deepcopy\n", + "import math\n", + "\n", + "import scipy.sparse.linalg as spsl\n", + "\n", + "import projectq\n", + "from projectq.backends import Simulator\n", + "from projectq.meta import Compute, Control, Dagger, Uncompute\n", + "from projectq.ops import All, H, Measure, Ph, QubitOperator, R, StatePreparation, X, Z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Let's use a simple Hamiltonian acting on 3 qubits for which we want to know the eigenvalues:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "num_qubits = 3\n", + "\n", + "hamiltonian = QubitOperator()\n", + "hamiltonian += QubitOperator(\"X0\", -1/12.)\n", + "hamiltonian += QubitOperator(\"X1\", -1/12.)\n", + "hamiltonian += QubitOperator(\"X2\", -1/12.)\n", + "hamiltonian += QubitOperator(\"Z0 Z1\", -1/12.)\n", + "hamiltonian += QubitOperator(\"Z0 Z2\", -1/12.)\n", + "hamiltonian += QubitOperator(\"\", 7/12.)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For this quantum algorithm, we need to normalize the hamiltonian:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "hamiltonian_norm = 0.\n", + "for term in hamiltonian.terms:\n", + " hamiltonian_norm += abs(hamiltonian.terms[term])\n", + "normalized_hamiltonian = deepcopy(hamiltonian)\n", + "normalized_hamiltonian /= hamiltonian_norm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**1.** We implement a short helper function which uses the ProjectQ simulator to numerically calculate some eigenvalues and eigenvectors of Hamiltonians stored in ProjectQ's `QubitOperator` in order to check our implemenation of the quantum algorithm. This function is particularly fast because it doesn't need to build the matrix of the hamiltonian but instead uses implicit matrix vector multiplication by using our simulator:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def get_eigenvalue_and_eigenvector(n_sites, hamiltonian, k, which='SA'):\n", + " \"\"\"\n", + " Returns k eigenvalues and eigenvectors of the hamiltonian.\n", + " \n", + " Args:\n", + " n_sites(int): Number of qubits/sites in the hamiltonian\n", + " hamiltonian(QubitOperator): QubitOperator representating the Hamiltonian\n", + " k: num of eigenvalue and eigenvector pairs (see spsl.eigsh k)\n", + " which: see spsl.eigsh which\n", + " \n", + " \"\"\"\n", + " def mv(v):\n", + " eng = projectq.MainEngine(backend=Simulator(), engine_list=[])\n", + " qureg = eng.allocate_qureg(n_sites)\n", + " eng.flush()\n", + " eng.backend.set_wavefunction(v, qureg)\n", + " eng.backend.apply_qubit_operator(hamiltonian, qureg)\n", + " order, output = deepcopy(eng.backend.cheat())\n", + " for i in order:\n", + " assert i == order[i]\n", + " eng.backend.set_wavefunction([1]+[0]*(2**n_sites-1), qureg)\n", + " return output\n", + "\n", + " A = spsl.LinearOperator((2**n_sites,2**n_sites), matvec=mv)\n", + "\n", + " eigenvalues, eigenvectormatrix = spsl.eigsh(A, k=k, which=which)\n", + " eigenvectors = []\n", + " for i in range(k):\n", + " eigenvectors.append(list(eigenvectormatrix[:, i]))\n", + " return eigenvalues, eigenvectors" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use this function to find the 4 lowest eigenstates of the normalized hamiltonian:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0.29217007 0.36634371 0.5 0.57417364]\n" + ] + } + ], + "source": [ + "eigenvalues, eigenvectors = get_eigenvalue_and_eigenvector(\n", + " n_sites=num_qubits,\n", + " hamiltonian=normalized_hamiltonian,\n", + " k=4)\n", + "print(eigenvalues)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that the eigenvalues are all positive as required (otherwise increase identity term in hamiltonian)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**2.** Let's define the W operator:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def W(eng, individual_terms, initial_wavefunction, ancilla_qubits, system_qubits):\n", + " \"\"\"\n", + " Applies the W operator as defined in arXiv:1711.11025.\n", + " \n", + " Args:\n", + " eng(MainEngine): compiler engine\n", + " individual_terms(list): list of individual unitary\n", + " QubitOperators. It applies\n", + " individual_terms[0] if ancilla\n", + " qubits are in state |0> where\n", + " ancilla_qubits[0] is the least\n", + " significant bit.\n", + " initial_wavefunction: Initial wavefunction of the ancilla qubits\n", + " ancilla_qubits(Qureg): ancilla quantum register in state |0>\n", + " system_qubits(Qureg): system quantum register\n", + " \"\"\"\n", + " # Apply V:\n", + " for ancilla_state in range(len(individual_terms)):\n", + " with Compute(eng):\n", + " for bit_pos in range(len(ancilla_qubits)):\n", + " if not (ancilla_state >> bit_pos) & 1:\n", + " X | ancilla_qubits[bit_pos]\n", + " with Control(eng, ancilla_qubits):\n", + " individual_terms[ancilla_state] | system_qubits\n", + " Uncompute(eng)\n", + " # Apply S: 1) Apply B^dagger\n", + " with Compute(eng):\n", + " with Dagger(eng):\n", + " StatePreparation(initial_wavefunction) | ancilla_qubits\n", + " # Apply S: 2) Apply I-2|0><0|\n", + " with Compute(eng):\n", + " All(X) | ancilla_qubits\n", + " with Control(eng, ancilla_qubits[:-1]):\n", + " Z | ancilla_qubits[-1]\n", + " Uncompute(eng)\n", + " # Apply S: 3) Apply B\n", + " Uncompute(eng)\n", + " # Could also be omitted and added when calculating the eigenvalues:\n", + " Ph(math.pi) | system_qubits[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**3.** For testing this algorithm, let's initialize the qubits in a superposition state of the lowest and second lowest eigenstate of the hamiltonian:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "eng = projectq.MainEngine()\n", + "system_qubits = eng.allocate_qureg(num_qubits)\n", + "\n", + "# Create a normalized equal superposition of the two eigenstates for numerical testing:\n", + "initial_state_norm =0.\n", + "initial_state = [i+j for i,j in zip(eigenvectors[0], eigenvectors[1])]\n", + "for amplitude in initial_state:\n", + " initial_state_norm += abs(amplitude)**2\n", + "normalized_initial_state = [amp / math.sqrt(initial_state_norm) for amp in initial_state]\n", + "\n", + "#initialize system qubits in this state:\n", + "StatePreparation(normalized_initial_state) | system_qubits" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**4.** Split the normalized_hamiltonian into individual terms and build the wavefunction for the ancilla qubits:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "individual_terms = []\n", + "initial_ancilla_wavefunction = []\n", + "for term in normalized_hamiltonian.terms:\n", + " coefficient = normalized_hamiltonian.terms[term]\n", + " initial_ancilla_wavefunction.append(math.sqrt(abs(coefficient)))\n", + " if coefficient < 0:\n", + " individual_terms.append(QubitOperator(term, -1))\n", + " else:\n", + " individual_terms.append(QubitOperator(term))\n", + "\n", + "# Calculate the number of ancilla qubits required and pad\n", + "# the ancilla wavefunction with zeros:\n", + "num_ancilla_qubits = int(math.ceil(math.log(len(individual_terms), 2)))\n", + "required_padding = 2**num_ancilla_qubits - len(initial_ancilla_wavefunction)\n", + "initial_ancilla_wavefunction.extend([0]*required_padding)\n", + "\n", + "# Initialize ancillas by applying B\n", + "ancillas = eng.allocate_qureg(num_ancilla_qubits)\n", + "StatePreparation(initial_ancilla_wavefunction) | ancillas" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**5.** Perform an iterative phase estimation of the unitary W to collapse to one of the eigenvalues of the `normalized_hamiltonian`:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "# Semiclassical iterative phase estimation\n", + "bits_of_precision = 8\n", + "pe_ancilla = eng.allocate_qubit()\n", + "\n", + "measurements = [0] * bits_of_precision\n", + "\n", + "for k in range(bits_of_precision):\n", + " H | pe_ancilla\n", + " with Control(eng, pe_ancilla):\n", + " for i in range(2**(bits_of_precision-k-1)):\n", + " W(eng=eng,\n", + " individual_terms=individual_terms,\n", + " initial_wavefunction=initial_ancilla_wavefunction,\n", + " ancilla_qubits=ancillas,\n", + " system_qubits=system_qubits)\n", + "\n", + " #inverse QFT using one qubit\n", + " for i in range(k):\n", + " if measurements[i]:\n", + " R(-math.pi/(1 << (k - i))) | pe_ancilla\n", + "\n", + " H | pe_ancilla\n", + " Measure | pe_ancilla\n", + " eng.flush()\n", + " measurements[k] = int(pe_ancilla)\n", + " # put the ancilla in state |0> again\n", + " if measurements[k]:\n", + " X | pe_ancilla\n", + "\n", + "est_phase = sum(\n", + " [(measurements[bits_of_precision - 1 - i]*1. / (1 << (i + 1)))\n", + " for i in range(bits_of_precision)])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "We measured 0.203125 corresponding to energy 0.290284677254\n" + ] + } + ], + "source": [ + "print(\"We measured {} corresponding to energy {}\".format(est_phase, math.cos(2*math.pi*est_phase)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**6.** We measured the lowest eigenstate. You can verify that this happens with 50% probability as we chose our initial state to have 50% overlap with the ground state. As the paper notes, the `system_qubits` are not in an eigenstate and one can easily test that using our simulator to get the energy of the current state:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.33236578253447085" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng.backend.get_expectation_value(normalized_hamiltonian, system_qubits)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**7.** As explained in the paper, one can change this state into an eigenstate by undoing the `StatePreparation` of the ancillas and then by measuring if the ancilla qubits in are state 0. The paper says that this should be the case with 50% probability. So let's check this (we require an ancilla to measure this):" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.5004522593645913" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "with Dagger(eng):\n", + " StatePreparation(initial_ancilla_wavefunction) | ancillas\n", + "measure_qb = eng.allocate_qubit()\n", + "with Compute(eng):\n", + " All(X) | ancillas\n", + "with Control(eng, ancillas):\n", + " X | measure_qb\n", + "Uncompute(eng)\n", + "eng.flush()\n", + "eng.backend.get_probability('1', measure_qb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, we would measure 1 (corresponding to the ancilla qubits in state 0) with probability 50% as explained in the paper. Let's assume we measure 1, then we can easily check that we are in an eigenstate of the `normalized_hamiltonian` by numerically calculating its energy:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.29263140625433537" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng.backend.collapse_wavefunction(measure_qb, [1])\n", + "eng.backend.get_expectation_value(normalized_hamiltonian, system_qubits)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indeed we are in the ground state of the `normalized_hamiltonian`. Have a look at the paper on how to recover, when the ancilla qubits are not in state 0." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/projectq/_version.py b/projectq/_version.py index e2835d3b6..02ac3660d 100755 --- a/projectq/_version.py +++ b/projectq/_version.py @@ -13,4 +13,4 @@ # limitations under the License. """Define version number here and read it from setup.py automatically""" -__version__ = "0.4" +__version__ = "0.4.1" diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 1bae69722..d14dbc27f 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -232,14 +232,16 @@ def _run(self): Send the circuit via the IBM API (JSON QASM) using the provided user data / ask for username & password. """ - if self.qasm == "": - return # finally: add measurements (no intermediate measurements are allowed) for measured_id in self._measured_ids: qb_loc = self.main_engine.mapper.current_mapping[measured_id] self.qasm += "\nmeasure q[{}] -> c[{}];".format(qb_loc, qb_loc) + # return if no operations / measurements have been performed. + if self.qasm == "": + return + max_qubit_id = max(self._allocated_qubits) qasm = ("\ninclude \"qelib1.inc\";\nqreg q[{nq}];\ncreg c[{nq}];" + self.qasm).format(nq=max_qubit_id + 1) diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 883cc5cfe..2218c3471 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -303,8 +303,8 @@ def collapse_wavefunction(self, qureg, values): Args: qureg (Qureg|list[Qubit]): Qubits to collapse. - values (list[bool]): Measurement outcome for each of the qubits - in `qureg`. + values (list[bool|int]|string[0|1]): Measurement outcome for each + of the qubits in `qureg`. Raises: RuntimeError: If an outcome has probability (approximately) 0 or @@ -321,7 +321,7 @@ def collapse_wavefunction(self, qureg, values): """ qureg = self._convert_logical_to_mapped_qureg(qureg) return self._simulator.collapse_wavefunction([qb.id for qb in qureg], - [bool(v) for v in + [bool(int(v)) for v in values]) def cheat(self): diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 19c46eb15..546ab3584 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -147,15 +147,15 @@ def test_simulator_is_available(sim): new_cmd.gate = Mock1QubitGate() assert sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 1 + assert new_cmd.gate.cnt == 4 new_cmd.gate = Mock6QubitGate() assert not sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 1 + assert new_cmd.gate.cnt == 4 new_cmd.gate = MockNoMatrixGate() assert not sim.is_available(new_cmd) - assert new_cmd.gate.cnt == 1 + assert new_cmd.gate.cnt == 7 def test_simulator_cheat(sim): diff --git a/projectq/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py index f532cd998..a27b5557c 100755 --- a/projectq/cengines/_replacer/_replacer_test.py +++ b/projectq/cengines/_replacer/_replacer_test.py @@ -178,8 +178,6 @@ def magic_filter(self, cmd): qb = eng.allocate_qubit() MagicGate() | qb eng.flush() - for cmd in backend.received_commands: - print(cmd) assert len(backend.received_commands) == 4 assert backend.received_commands[1].gate == H assert backend.received_commands[2].gate == Rx(-0.6) diff --git a/projectq/libs/revkit/_control_function_test.py b/projectq/libs/revkit/_control_function_test.py index 36164a0b7..84ee3cc41 100644 --- a/projectq/libs/revkit/_control_function_test.py +++ b/projectq/libs/revkit/_control_function_test.py @@ -41,6 +41,7 @@ def test_control_function_majority(): assert len(saving_backend.received_commands) == 7 + def test_control_function_majority_from_python(): dormouse = pytest.importorskip('dormouse') diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 56dbec862..32ff8ab54 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -33,3 +33,6 @@ from ._qubit_operator import QubitOperator from ._shortcuts import * from ._time_evolution import TimeEvolution +from ._uniformly_controlled_rotation import (UniformlyControlledRy, + UniformlyControlledRz) +from ._state_prep import StatePreparation diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 052b3abed..7f724c93e 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -34,12 +34,16 @@ import math from copy import deepcopy +import numpy as np + from projectq.types import BasicQubit from ._command import Command, apply_command ANGLE_PRECISION = 12 ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION +RTOL = 1e-10 +ATOL = 1e-12 class NotMergeable(Exception): @@ -200,8 +204,39 @@ def __or__(self, qubits): apply_command(cmd) def __eq__(self, other): - """ Return True if equal (i.e., instance of same class). """ - return isinstance(other, self.__class__) + """ Return True if equal (i.e., instance of same class). + + Unless both have a matrix attribute in which case we also check + that the matrices are identical as people might want to do the + following: + + Example: + .. code-block:: python + + gate = BasicGate() + gate.matrix = numpy.matrix([[1,0],[0, -1]]) + """ + if hasattr(self, 'matrix'): + if not hasattr(other, 'matrix'): + return False + if hasattr(other, 'matrix'): + if not hasattr(self, 'matrix'): + return False + if hasattr(self, 'matrix') and hasattr(other, 'matrix'): + if (not isinstance(self.matrix, np.matrix) or + not isinstance(other.matrix, np.matrix)): + raise TypeError("One of the gates doesn't have the correct " + "type (numpy.matrix) for the matrix " + "attribute.") + if (self.matrix.shape == other.matrix.shape and + np.allclose(self.matrix, other.matrix, + rtol=RTOL, atol=ATOL, + equal_nan=False)): + return True + else: + return False + else: + return isinstance(other, self.__class__) def __ne__(self, other): return not self.__eq__(other) diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index fead34616..61a1de767 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -14,10 +14,12 @@ """Tests for projectq.ops._basics.""" -import pytest from copy import deepcopy import math +import numpy as np +import pytest + from projectq.types import Qubit, Qureg from projectq.ops import Command from projectq import MainEngine @@ -116,6 +118,13 @@ def test_basic_gate_compare(): gate2 = _basics.BasicGate() assert gate1 == gate2 assert not (gate1 != gate2) + gate3 = _basics.BasicGate() + gate3.matrix = np.matrix([[1, 0], [0, -1]]) + assert gate1 != gate3 + gate4 = _basics.BasicGate() + gate4.matrix = [[1, 0], [0, -1]] + with pytest.raises(TypeError): + gate4 == gate3 def test_comparing_different_gates(): diff --git a/projectq/ops/_qftgate.py b/projectq/ops/_qftgate.py index 97b045072..7c1e3984b 100755 --- a/projectq/ops/_qftgate.py +++ b/projectq/ops/_qftgate.py @@ -22,5 +22,6 @@ class QFTGate(BasicGate): def __str__(self): return "QFT" + #: Shortcut (instance of) :class:`projectq.ops.QFTGate` QFT = QFTGate() diff --git a/projectq/ops/_qftgate_test.py b/projectq/ops/_qftgate_test.py index a74683006..4382d632b 100755 --- a/projectq/ops/_qftgate_test.py +++ b/projectq/ops/_qftgate_test.py @@ -20,3 +20,7 @@ def test_qft_gate_str(): gate = _qftgate.QFT assert str(gate) == "QFT" + + +def test_qft_equality(): + assert _qftgate.QFT == _qftgate.QFTGate() diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index a95e3ea9a..aa9a29f9a 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -13,11 +13,16 @@ # limitations under the License. """QubitOperator stores a sum of Pauli operators acting on qubits.""" +import cmath import copy import itertools import numpy +from ._basics import BasicGate, NotInvertible, NotMergeable +from ._command import apply_command +from ._gates import Ph, X, Y, Z + EQ_TOLERANCE = 1e-12 @@ -45,7 +50,7 @@ class QubitOperatorError(Exception): pass -class QubitOperator(object): +class QubitOperator(BasicGate): """ A sum of terms acting on qubits, e.g., 0.5 * 'X0 X5' + 0.3 * 'Z1 Z2'. @@ -68,6 +73,25 @@ class QubitOperator(object): hamiltonian = 0.5 * QubitOperator('X0 X5') + 0.3 * QubitOperator('Z0') + Our Simulator takes a hermitian QubitOperator to directly calculate the + expectation value (see Simulator.get_expectation_value) of this observable. + + A hermitian QubitOperator can also be used as input for the + TimeEvolution gate. + + If the QubitOperator is unitary, i.e., it contains only one term with a + coefficient, whose absolute value is 1, then one can apply it directly to + qubits: + + .. code-block:: python + + eng = projectq.MainEngine() + qureg = eng.allocate_qureg(6) + QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 + # with an additional global phase + # of 1.j + + Attributes: terms (dict): **key**: A term represented by a tuple containing all non-trivial local Pauli operators ('X', 'Y', or 'Z'). @@ -128,6 +152,7 @@ def __init__(self, term=None, coefficient=1.): Raises: QubitOperatorError: Invalid operators provided to QubitOperator. """ + BasicGate.__init__(self) if not isinstance(coefficient, (int, float, complex)): raise ValueError('Coefficient must be a numeric type.') self.terms = {} @@ -226,6 +251,143 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): return False return True + def __or__(self, qubits): + """ + Operator| overload which enables the following syntax: + + .. code-block:: python + + QubitOperator(...) | qureg + QubitOperator(...) | (qureg,) + QubitOperator(...) | qubit + QubitOperator(...) | (qubit,) + + Unlike other gates, this gate is only allowed to be applied to one + quantum register or one qubit and only if the QubitOperator is + unitary, i.e., consists of one term with a coefficient whose absolute + values is 1. + + Example: + + .. code-block:: python + + eng = projectq.MainEngine() + qureg = eng.allocate_qureg(6) + QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 + # with an additional global + # phase of 1.j + + While in the above example the QubitOperator gate is applied to 6 + qubits, it only acts non-trivially on the two qubits qureg[0] and + qureg[5]. Therefore, the operator| will create a new rescaled + QubitOperator, i.e, it sends the equivalent of the following new gate + to the MainEngine: + + .. code-block:: python + + QubitOperator('X0 X1', 1.j) | [qureg[0], qureg[5]] + + which is only a two qubit gate. + + Args: + qubits: one Qubit object, one list of Qubit objects, one Qureg + object, or a tuple of the former three cases. + + Raises: + TypeError: If QubitOperator is not unitary or applied to more than + one quantum register. + ValueError: If quantum register does not have enough qubits + """ + # Check that input is only one qureg or one qubit + qubits = self.make_tuple_of_qureg(qubits) + if len(qubits) != 1: + raise TypeError("Only one qubit or qureg allowed.") + # Check that operator is unitary + if not len(self.terms) == 1: + raise TypeError("Too many terms. Only QubitOperators consisting " + "of a single term (single n-qubit Pauli operator) " + "with a coefficient of unit length can be applied " + "to qubits with this function.") + (term, coefficient), = self.terms.items() + phase = cmath.phase(coefficient) + if (abs(coefficient) < 1 - EQ_TOLERANCE or + abs(coefficient) > 1 + EQ_TOLERANCE): + raise TypeError("abs(coefficient) != 1. Only QubitOperators " + "consisting of a single term (single n-qubit " + "Pauli operator) with a coefficient of unit " + "length can be applied to qubits with this " + "function.") + # Test if we need to apply only Ph + if term == (): + Ph(phase) | qubits[0][0] + return + # Check that Qureg has enough qubits: + num_qubits = len(qubits[0]) + non_trivial_qubits = set() + for index, action in term: + non_trivial_qubits.add(index) + if max(non_trivial_qubits) >= num_qubits: + raise ValueError("QubitOperator acts on more qubits than the gate " + "is applied to.") + # Apply X, Y, Z, if QubitOperator acts only on one qubit + if len(term) == 1: + if term[0][1] == "X": + X | qubits[0][term[0][0]] + elif term[0][1] == "Y": + Y | qubits[0][term[0][0]] + elif term[0][1] == "Z": + Z | qubits[0][term[0][0]] + Ph(phase) | qubits[0][term[0][0]] + return + # Create new QubitOperator gate with rescaled qubit indices in + # 0,..., len(non_trivial_qubits) - 1 + new_index = dict() + non_trivial_qubits = sorted(list(non_trivial_qubits)) + for i in range(len(non_trivial_qubits)): + new_index[non_trivial_qubits[i]] = i + new_qubitoperator = QubitOperator() + assert len(new_qubitoperator.terms) == 0 + new_term = tuple([(new_index[index], action) + for index, action in term]) + new_qubitoperator.terms[new_term] = coefficient + new_qubits = [qubits[0][i] for i in non_trivial_qubits] + # Apply new gate + cmd = new_qubitoperator.generate_command(new_qubits) + apply_command(cmd) + + def get_inverse(self): + """ + Return the inverse gate of a QubitOperator if applied as a gate. + + Raises: + NotInvertible: Not implemented for QubitOperators which have + multiple terms or a coefficient with absolute value + not equal to 1. + """ + + if len(self.terms) == 1: + (term, coefficient), = self.terms.items() + if (not abs(coefficient) < 1 - EQ_TOLERANCE and not + abs(coefficient) > 1 + EQ_TOLERANCE): + return QubitOperator(term, coefficient**(-1)) + raise NotInvertible("BasicGate: No get_inverse() implemented.") + + def get_merged(self, other): + """ + Return this gate merged with another gate. + + Standard implementation of get_merged: + + Raises: + NotMergeable: merging is not possible + """ + if (isinstance(other, self.__class__) and + len(other.terms) == 1 and + len(self.terms) == 1): + return self * other + else: + raise NotMergeable() + def __imul__(self, multiplier): """ In-place multiply (*=) terms with scalar or QubitOperator. @@ -396,7 +558,7 @@ def __iadd__(self, addend): if abs(addend.terms[term] + self.terms[term]) > 0.: self.terms[term] += addend.terms[term] else: - del self.terms[term] + self.terms.pop(term) else: self.terms[term] = addend.terms[term] else: @@ -425,7 +587,7 @@ def __isub__(self, subtrahend): if abs(self.terms[term] - subtrahend.terms[term]) > 0.: self.terms[term] -= subtrahend.terms[term] else: - del self.terms[term] + self.terms.pop(term) else: self.terms[term] = -subtrahend.terms[term] else: @@ -463,3 +625,6 @@ def __str__(self): def __repr__(self): return str(self) + + def __hash__(self): + return hash(str(self)) diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index f7f76cd61..76c832bf8 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -13,11 +13,18 @@ # limitations under the License. """Tests for _qubit_operator.py.""" +import cmath import copy +import math import numpy import pytest +from projectq import MainEngine +from projectq.cengines import DummyEngine +from ._basics import NotInvertible, NotMergeable +from ._gates import Ph, T, X, Y, Z + from projectq.ops import _qubit_operator as qo @@ -184,6 +191,98 @@ def test_isclose_different_num_terms(): assert not a.isclose(b, rel_tol=1e-12, abs_tol=0.05) +def test_get_inverse(): + qo0 = qo.QubitOperator("X1 Z2", cmath.exp(0.6j)) + qo1 = qo.QubitOperator("", 1j) + assert qo0.get_inverse().isclose( + qo.QubitOperator("X1 Z2", cmath.exp(-0.6j))) + assert qo1.get_inverse().isclose(qo.QubitOperator("", -1j)) + qo0 += qo1 + with pytest.raises(NotInvertible): + qo0.get_inverse() + + +def test_get_merged(): + qo0 = qo.QubitOperator("X1 Z2", 1j) + qo1 = qo.QubitOperator("Y3", 1j) + merged = qo0.get_merged(qo1) + assert qo0.isclose(qo.QubitOperator("X1 Z2", 1j)) + assert qo1.isclose(qo.QubitOperator("Y3", 1j)) + assert qo0.get_merged(qo1).isclose(qo.QubitOperator("X1 Z2 Y3", -1)) + with pytest.raises(NotMergeable): + qo1.get_merged(T) + qo2 = qo0 + qo1 + with pytest.raises(NotMergeable): + qo2.get_merged(qo0) + with pytest.raises(NotMergeable): + qo0.get_merged(qo2) + + +def test_or_one_qubit(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + qureg = eng.allocate_qureg(3) + eng.flush() + identity = qo.QubitOperator("", 1j) + x = qo.QubitOperator("X1", cmath.exp(0.5j)) + y = qo.QubitOperator("Y2", cmath.exp(0.6j)) + z = qo.QubitOperator("Z0", cmath.exp(4.5j)) + identity | qureg + eng.flush() + x | qureg + eng.flush() + y | qureg + eng.flush() + z | qureg + eng.flush() + assert saving_backend.received_commands[4].gate == Ph(math.pi/2.) + + assert saving_backend.received_commands[6].gate == X + assert saving_backend.received_commands[6].qubits == ([qureg[1]],) + assert saving_backend.received_commands[7].gate == Ph(0.5) + assert saving_backend.received_commands[7].qubits == ([qureg[1]],) + + assert saving_backend.received_commands[9].gate == Y + assert saving_backend.received_commands[9].qubits == ([qureg[2]],) + assert saving_backend.received_commands[10].gate == Ph(0.6) + assert saving_backend.received_commands[10].qubits == ([qureg[2]],) + + assert saving_backend.received_commands[12].gate == Z + assert saving_backend.received_commands[12].qubits == ([qureg[0]],) + assert saving_backend.received_commands[13].gate == Ph(4.5) + assert saving_backend.received_commands[13].qubits == ([qureg[0]],) + + +def test_wrong_input(): + eng = MainEngine() + qureg = eng.allocate_qureg(3) + op0 = qo.QubitOperator("X1", 0.99) + with pytest.raises(TypeError): + op0 | qureg + op1 = qo.QubitOperator("X2", 1) + with pytest.raises(ValueError): + op1 | qureg[1] + with pytest.raises(TypeError): + op0 | (qureg[1], qureg[2]) + op2 = op0 + op1 + with pytest.raises(TypeError): + op2 | qureg + + +def test_rescaling_of_indices(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + qureg = eng.allocate_qureg(4) + eng.flush() + op = qo.QubitOperator("X0 Y1 Z3", 1j) + op | qureg + eng.flush() + assert saving_backend.received_commands[5].gate.isclose( + qo.QubitOperator("X0 Y1 Z2", 1j)) + # test that gate creates a new QubitOperator + assert op.isclose(qo.QubitOperator("X0 Y1 Z3", 1j)) + + def test_imul_inplace(): qubit_op = qo.QubitOperator("X1") prev_id = id(qubit_op) @@ -417,6 +516,9 @@ def test_isub_different_term(): assert len(a.terms) == 2 assert a.terms[term_a] == pytest.approx(1.0) assert a.terms[term_b] == pytest.approx(-1.0) + b = qo.QubitOperator(term_a, 1.0) + b -= qo.QubitOperator(term_a, 1.0) + assert b.terms == {} def test_isub_bad_addend(): @@ -441,6 +543,11 @@ def test_str(): assert str(op2) == "2 I" +def test_hash(): + op = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) + assert hash(op) == hash("0.5 X1 Y3 Z8") + + def test_str_empty(): op = qo.QubitOperator() assert str(op) == '0' diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py new file mode 100644 index 000000000..455a47a52 --- /dev/null +++ b/projectq/ops/_state_prep.py @@ -0,0 +1,57 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._basics import BasicGate + + +class StatePreparation(BasicGate): + """ + Gate for transforming qubits in state |0> to any desired quantum state. + """ + def __init__(self, final_state): + """ + Initialize StatePreparation gate. + + Example: + .. code-block:: python + + qureg = eng.allocate_qureg(2) + StatePreparation([0.5, -0.5j, -0.5, 0.5]) | qureg + + Note: + The amplitude of state k is final_state[k]. When the state k is + written in binary notation, then qureg[0] denotes the qubit + whose state corresponds to the least significant bit of k. + + Args: + final_state(list[complex]): wavefunction of the desired + quantum state. len(final_state) must + be 2**len(qureg). Must be normalized! + """ + BasicGate.__init__(self) + self.final_state = list(final_state) + + def __str__(self): + return "StatePreparation" + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.final_state == other.final_state + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash("StatePreparation(" + str(self.final_state) + ")") diff --git a/projectq/ops/_state_prep_test.py b/projectq/ops/_state_prep_test.py new file mode 100644 index 000000000..161bd53a1 --- /dev/null +++ b/projectq/ops/_state_prep_test.py @@ -0,0 +1,34 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.ops._state_prep.""" + +import projectq + +from projectq.ops import _state_prep, X + + +def test_equality_and_hash(): + gate1 = _state_prep.StatePreparation([0.5, -0.5, 0.5, -0.5]) + gate2 = _state_prep.StatePreparation([0.5, -0.5, 0.5, -0.5]) + gate3 = _state_prep.StatePreparation([0.5, -0.5, 0.5, 0.5]) + assert gate1 == gate2 + assert hash(gate1) == hash(gate2) + assert gate1 != gate3 + assert gate1 != X + + +def test_str(): + gate1 = _state_prep.StatePreparation([0, 1]) + assert str(gate1) == "StatePreparation" diff --git a/projectq/ops/_uniformly_controlled_rotation.py b/projectq/ops/_uniformly_controlled_rotation.py new file mode 100644 index 000000000..d3bef04fa --- /dev/null +++ b/projectq/ops/_uniformly_controlled_rotation.py @@ -0,0 +1,147 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +from ._basics import ANGLE_PRECISION, ANGLE_TOLERANCE, BasicGate, NotMergeable + + +class UniformlyControlledRy(BasicGate): + """ + Uniformly controlled Ry gate as introduced in arXiv:quant-ph/0312218. + + This is an n-qubit gate. There are n-1 control qubits and one target qubit. + This gate applies Ry(angles(k)) to the target qubit if the n-1 control + qubits are in the classical state k. As there are 2^(n-1) classical + states for the control qubits, this gate requires 2^(n-1) (potentially + different) angle parameters. + + Example: + .. code-block:: python + + controls = eng.allocate_qureg(2) + target = eng.allocate_qubit() + UniformlyControlledRy(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target) + + Note: + The first quantum register contains the control qubits. When converting + the classical state k of the control qubits to an integer, we define + controls[0] to be the least significant (qu)bit. controls can also + be an empty list in which case the gate corresponds to an Ry. + + Args: + angles(list[float]): Rotation angles. Ry(angles[k]) is applied + conditioned on the control qubits being in state + k. + """ + def __init__(self, angles): + BasicGate.__init__(self) + rounded_angles = [] + for angle in angles: + new_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION) + if new_angle > 4 * math.pi - ANGLE_TOLERANCE: + new_angle = 0. + rounded_angles.append(new_angle) + self.angles = rounded_angles + + def get_inverse(self): + return self.__class__([-1 * angle for angle in self.angles]) + + def get_merged(self, other): + if isinstance(other, self.__class__): + new_angles = [angle1 + angle2 for (angle1, angle2) in + zip(self.angles, other.angles)] + return self.__class__(new_angles) + raise NotMergeable() + + def __str__(self): + return "UniformlyControlledRy(" + str(self.angles) + ")" + + def __eq__(self, other): + """ Return True if same class, same rotation angles.""" + if isinstance(other, self.__class__): + return self.angles == other.angles + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(str(self)) + + +class UniformlyControlledRz(BasicGate): + """ + Uniformly controlled Rz gate as introduced in arXiv:quant-ph/0312218. + + This is an n-qubit gate. There are n-1 control qubits and one target qubit. + This gate applies Rz(angles(k)) to the target qubit if the n-1 control + qubits are in the classical state k. As there are 2^(n-1) classical + states for the control qubits, this gate requires 2^(n-1) (potentially + different) angle parameters. + + Example: + .. code-block:: python + + controls = eng.allocate_qureg(2) + target = eng.allocate_qubit() + UniformlyControlledRz(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target) + + Note: + The first quantum register are the contains qubits. When converting + the classical state k of the control qubits to an integer, we define + controls[0] to be the least significant (qu)bit. controls can also + be an empty list in which case the gate corresponds to an Rz. + + Args: + angles(list[float]): Rotation angles. Rz(angles[k]) is applied + conditioned on the control qubits being in state + k. + """ + def __init__(self, angles): + BasicGate.__init__(self) + rounded_angles = [] + for angle in angles: + new_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION) + if new_angle > 4 * math.pi - ANGLE_TOLERANCE: + new_angle = 0. + rounded_angles.append(new_angle) + self.angles = rounded_angles + + def get_inverse(self): + return self.__class__([-1 * angle for angle in self.angles]) + + def get_merged(self, other): + if isinstance(other, self.__class__): + new_angles = [angle1 + angle2 for (angle1, angle2) in + zip(self.angles, other.angles)] + return self.__class__(new_angles) + raise NotMergeable() + + def __str__(self): + return "UniformlyControlledRz(" + str(self.angles) + ")" + + def __eq__(self, other): + """ Return True if same class, same rotation angles.""" + if isinstance(other, self.__class__): + return self.angles == other.angles + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(str(self)) diff --git a/projectq/ops/_uniformly_controlled_rotation_test.py b/projectq/ops/_uniformly_controlled_rotation_test.py new file mode 100644 index 000000000..f58b198f4 --- /dev/null +++ b/projectq/ops/_uniformly_controlled_rotation_test.py @@ -0,0 +1,73 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Tests for projectq.ops._uniformly_controlled_rotation.""" +import math + +import pytest + +from projectq.ops import Rx +from ._basics import NotMergeable + +from projectq.ops import _uniformly_controlled_rotation as ucr + + +@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, + ucr.UniformlyControlledRz]) +def test_init_rounding(gate_class): + gate = gate_class([0.1 + 4 * math.pi, -1e-14]) + assert gate.angles == [0.1, 0.] + + +@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, + ucr.UniformlyControlledRz]) +def test_get_inverse(gate_class): + gate = gate_class([0.1, 0.2, 0.3, 0.4]) + inverse = gate.get_inverse() + assert inverse == gate_class([-0.1, -0.2, -0.3, -0.4]) + + +@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, + ucr.UniformlyControlledRz]) +def test_get_merged(gate_class): + gate1 = gate_class([0.1, 0.2, 0.3, 0.4]) + gate2 = gate_class([0.1, 0.2, 0.3, 0.4]) + merged_gate = gate1.get_merged(gate2) + assert merged_gate == gate_class([0.2, 0.4, 0.6, 0.8]) + with pytest.raises(NotMergeable): + gate1.get_merged(Rx(0.1)) + + +def test_str_and_hash(): + gate1 = ucr.UniformlyControlledRy([0.1, 0.2, 0.3, 0.4]) + gate2 = ucr.UniformlyControlledRz([0.1, 0.2, 0.3, 0.4]) + assert str(gate1) == "UniformlyControlledRy([0.1, 0.2, 0.3, 0.4])" + assert str(gate2) == "UniformlyControlledRz([0.1, 0.2, 0.3, 0.4])" + assert hash(gate1) == hash("UniformlyControlledRy([0.1, 0.2, 0.3, 0.4])") + assert hash(gate2) == hash("UniformlyControlledRz([0.1, 0.2, 0.3, 0.4])") + + +@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, + ucr.UniformlyControlledRz]) +def test_equality(gate_class): + gate1 = gate_class([0.1, 0.2]) + gate2 = gate_class([0.1, 0.2 + 1e-14]) + assert gate1 == gate2 + gate3 = gate_class([0.1, 0.2, 0.1, 0.2]) + assert gate2 != gate3 + gate4 = ucr.UniformlyControlledRz([0.1, 0.2]) + gate5 = ucr.UniformlyControlledRy([0.1, 0.2]) + assert gate4 != gate5 + assert not gate5 == gate4 diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index e7dd915cd..aab71b28c 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -21,13 +21,17 @@ entangle, globalphase, ph2r, + qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, ry2rz, + sqrtswap2cnot, + stateprep2cnot, swap2cnot, toffoli2cnotandtgate, - time_evolution) + time_evolution, + uniformlycontrolledr2cnot) all_defined_decomposition_rules = [ rule @@ -40,12 +44,16 @@ entangle, globalphase, ph2r, + qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, ry2rz, + sqrtswap2cnot, + stateprep2cnot, swap2cnot, toffoli2cnotandtgate, - time_evolution] + time_evolution, + uniformlycontrolledr2cnot] for rule in module.all_defined_decomposition_rules ] diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py index 686c4991e..7d324e81c 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py @@ -58,8 +58,8 @@ def test_recognize_incorrect_gates(): BasicGate() | qubit # Two qubit gate: two_qubit_gate = BasicGate() - two_qubit_gate.matrix = [[1, 0, 0, 0], [0, 1, 0, 0], - [0, 0, 1, 0], [0, 0, 0, 1]] + two_qubit_gate.matrix = np.matrix([[1, 0, 0, 0], [0, 1, 0, 0], + [0, 0, 1, 0], [0, 0, 0, 1]]) two_qubit_gate | qubit with Control(eng, ctrl_qureg): # Too many Control qubits: diff --git a/projectq/setups/decompositions/cnu2toffoliandcu.py b/projectq/setups/decompositions/cnu2toffoliandcu.py index 5f7c72761..9ce2610c3 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu.py @@ -40,15 +40,21 @@ def _recognize_CnU(cmd): def _decompose_CnU(cmd): """ - Decompose a multi-controlled gate U into a single-controlled U. + Decompose a multi-controlled gate U with n control qubits into a single- + controlled U. - It uses (n-1) work qubits and 2 * (n-1) Toffoli gates. + It uses (n-1) work qubits and 2 * (n-1) Toffoli gates for general U + and (n-2) work qubits and 2n - 3 Toffoli gates if U is an X-gate. """ eng = cmd.engine qubits = cmd.qubits ctrl_qureg = cmd.control_qubits gate = cmd.gate n = get_control_count(cmd) + + # specialized for X-gate + if gate == XGate() and n > 2: + n -= 1 ancilla_qureg = eng.allocate_qureg(n-1) with Compute(eng): @@ -56,8 +62,12 @@ def _decompose_CnU(cmd): for ctrl_index in range(2, n): Toffoli | (ctrl_qureg[ctrl_index], ancilla_qureg[ctrl_index-2], ancilla_qureg[ctrl_index-1]) + ctrls = [ancilla_qureg[-1]] - with Control(eng, ancilla_qureg[-1]): + # specialized for X-gate + if gate == XGate() and get_control_count(cmd) > 2: + ctrls += [ctrl_qureg[-1]] + with Control(eng, ctrls): gate | qubits Uncompute(eng) diff --git a/projectq/setups/decompositions/cnu2toffoliandcu_test.py b/projectq/setups/decompositions/cnu2toffoliandcu_test.py index 5c815bfd2..b0e27760f 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu_test.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu_test.py @@ -105,17 +105,21 @@ def test_decomposition(): Rx(0.4) | test_qb with Control(test_eng, test_ctrl_qureg): Ry(0.6) | test_qb + with Control(test_eng, test_ctrl_qureg): + X | test_qb with Control(correct_eng, correct_ctrl_qureg[:2]): Rx(0.4) | correct_qb with Control(correct_eng, correct_ctrl_qureg): Ry(0.6) | correct_qb + with Control(correct_eng, correct_ctrl_qureg): + X | correct_qb test_eng.flush() correct_eng.flush() - assert len(correct_dummy_eng.received_commands) == 8 - assert len(test_dummy_eng.received_commands) == 20 + assert len(correct_dummy_eng.received_commands) == 9 + assert len(test_dummy_eng.received_commands) == 25 for fstate in range(16): binary_state = format(fstate, '04b') diff --git a/projectq/setups/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py new file mode 100644 index 000000000..b66f64b25 --- /dev/null +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -0,0 +1,48 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import cmath + +from projectq.cengines import DecompositionRule +from projectq.meta import Control, get_control_count +from projectq.ops import Ph, QubitOperator, X, Y, Z + + +def _recognize_qubitop(cmd): + """ For efficiency only use this if at most 1 control qubit.""" + return get_control_count(cmd) <= 1 + + +def _decompose_qubitop(cmd): + assert len(cmd.qubits) == 1 + qureg = cmd.qubits[0] + eng = cmd.engine + qubit_op = cmd.gate + with Control(eng, cmd.control_qubits): + (term, coefficient), = qubit_op.terms.items() + phase = cmath.phase(coefficient) + Ph(phase) | qureg[0] + for index, action in term: + if action == "X": + X | qureg[index] + elif action == "Y": + Y | qureg[index] + elif action == "Z": + Z | qureg[index] + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(QubitOperator, _decompose_qubitop, _recognize_qubitop) +] diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py new file mode 100644 index 000000000..3b1741cfb --- /dev/null +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -0,0 +1,100 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import cmath + +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +from projectq.meta import Control +from projectq.ops import All, Measure, Ph, QubitOperator, X, Y, Z + + +import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit + + +def test_recognize(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + ctrl_qureg = eng.allocate_qureg(2) + qureg = eng.allocate_qureg(2) + with Control(eng, ctrl_qureg): + QubitOperator("X0 Y1") | qureg + with Control(eng, ctrl_qureg[0]): + QubitOperator("X0 Y1") | qureg + eng.flush() + cmd0 = saving_backend.received_commands[4] + cmd1 = saving_backend.received_commands[5] + assert not qubitop2onequbit._recognize_qubitop(cmd0) + assert qubitop2onequbit._recognize_qubitop(cmd1) + + +def _decomp_gates(eng, cmd): + if isinstance(cmd.gate, QubitOperator): + return False + else: + return True + + +def test_qubitop2singlequbit(): + num_qubits = 4 + random_initial_state = [0.2+0.1*x*cmath.exp(0.1j+0.2j*x) + for x in range(2**(num_qubits+1))] + rule_set = DecompositionRuleSet(modules=[qubitop2onequbit]) + test_eng = MainEngine(backend=Simulator(), + engine_list=[AutoReplacer(rule_set), + InstructionFilter(_decomp_gates)]) + test_qureg = test_eng.allocate_qureg(num_qubits) + test_ctrl_qb = test_eng.allocate_qubit() + test_eng.flush() + test_eng.backend.set_wavefunction(random_initial_state, + test_qureg + test_ctrl_qb) + correct_eng = MainEngine() + correct_qureg = correct_eng.allocate_qureg(num_qubits) + correct_ctrl_qb = correct_eng.allocate_qubit() + correct_eng.flush() + correct_eng.backend.set_wavefunction(random_initial_state, + correct_qureg + correct_ctrl_qb) + + qubit_op_0 = QubitOperator("X0 Y1 Z3", -1.j) + qubit_op_1 = QubitOperator("Z0 Y1 X3", cmath.exp(0.6j)) + + qubit_op_0 | test_qureg + with Control(test_eng, test_ctrl_qb): + qubit_op_1 | test_qureg + test_eng.flush() + + correct_eng.backend.apply_qubit_operator(qubit_op_0, correct_qureg) + with Control(correct_eng, correct_ctrl_qb): + Ph(0.6) | correct_qureg[0] + Z | correct_qureg[0] + Y | correct_qureg[1] + X | correct_qureg[3] + correct_eng.flush() + + for fstate in range(2**(num_qubits+1)): + binary_state = format(fstate, '0' + str(num_qubits+1) + 'b') + test = test_eng.backend.get_amplitude(binary_state, + test_qureg + test_ctrl_qb) + correct = correct_eng.backend.get_amplitude( + binary_state, correct_qureg + correct_ctrl_qb) + assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) + + All(Measure) | correct_qureg + correct_ctrl_qb + All(Measure) | test_qureg + test_ctrl_qb + correct_eng.flush() + test_eng.flush() diff --git a/projectq/setups/decompositions/sqrtswap2cnot.py b/projectq/setups/decompositions/sqrtswap2cnot.py new file mode 100644 index 000000000..4eed87ff6 --- /dev/null +++ b/projectq/setups/decompositions/sqrtswap2cnot.py @@ -0,0 +1,44 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Registers a decomposition to achieve a SqrtSwap gate. +""" + +from projectq.cengines import DecompositionRule +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import CNOT, SqrtSwap, SqrtX + + +def _decompose_sqrtswap(cmd): + """ Decompose (controlled) swap gates.""" + assert (len(cmd.qubits) == 2 and len(cmd.qubits[0]) == 1 and + len(cmd.qubits[1]) == 1) + ctrl = cmd.control_qubits + qubit0 = cmd.qubits[0][0] + qubit1 = cmd.qubits[1][0] + eng = cmd.engine + + with Control(eng, ctrl): + with Compute(eng): + CNOT | (qubit0, qubit1) + with Control(eng, qubit1): + SqrtX | qubit0 + Uncompute(eng) + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(SqrtSwap.__class__, _decompose_sqrtswap) +] diff --git a/projectq/setups/decompositions/sqrtswap2cnot_test.py b/projectq/setups/decompositions/sqrtswap2cnot_test.py new file mode 100644 index 000000000..b804e7e81 --- /dev/null +++ b/projectq/setups/decompositions/sqrtswap2cnot_test.py @@ -0,0 +1,75 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.setups.decompositions.sqrtswap2cnot.""" + +import pytest + +import projectq +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) + +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import All, Measure, SqrtSwap + +import projectq.setups.decompositions.sqrtswap2cnot as sqrtswap2cnot + + +def _decomp_gates(eng, cmd): + if isinstance(cmd.gate, SqrtSwap.__class__): + return False + return True + + +def test_sqrtswap(): + for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], + [0, 0, 0, 1]): + correct_dummy_eng = DummyEngine(save_commands=True) + correct_eng = MainEngine(backend=Simulator(), + engine_list=[correct_dummy_eng]) + rule_set = DecompositionRuleSet(modules=[sqrtswap2cnot]) + test_dummy_eng = DummyEngine(save_commands=True) + test_eng = MainEngine(backend=Simulator(), + engine_list=[AutoReplacer(rule_set), + InstructionFilter(_decomp_gates), + test_dummy_eng]) + test_sim = test_eng.backend + correct_sim = correct_eng.backend + correct_qureg = correct_eng.allocate_qureg(2) + correct_eng.flush() + test_qureg = test_eng.allocate_qureg(2) + test_eng.flush() + + correct_sim.set_wavefunction(basis_state, correct_qureg) + test_sim.set_wavefunction(basis_state, test_qureg) + + SqrtSwap | (test_qureg[0], test_qureg[1]) + test_eng.flush() + SqrtSwap | (correct_qureg[0], correct_qureg[1]) + correct_eng.flush() + + assert (len(test_dummy_eng.received_commands) != + len(correct_dummy_eng.received_commands)) + for fstate in range(4): + binary_state = format(fstate, '02b') + test = test_sim.get_amplitude(binary_state, test_qureg) + correct = correct_sim.get_amplitude(binary_state, correct_qureg) + assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) + + All(Measure) | test_qureg + All(Measure) | correct_qureg + test_eng.flush(deallocate_qubits=True) + correct_eng.flush(deallocate_qubits=True) diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py new file mode 100644 index 000000000..aa81c2fb7 --- /dev/null +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -0,0 +1,88 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Registers decomposition for StatePreparation. +""" + +import cmath +import math + +from projectq.cengines import DecompositionRule +from projectq.meta import Control, Dagger +from projectq.ops import (StatePreparation, Ry, Rz, UniformlyControlledRy, + UniformlyControlledRz, Ph) + + +def _decompose_state_preparation(cmd): + """ + Implements state preparation based on arXiv:quant-ph/0407010v1. + """ + eng = cmd.engine + assert len(cmd.qubits) == 1 + num_qubits = len(cmd.qubits[0]) + qureg = cmd.qubits[0] + final_state = cmd.gate.final_state + if len(final_state) != 2**num_qubits: + raise ValueError("Length of final_state is invalid.") + norm = 0. + for amplitude in final_state: + norm += abs(amplitude)**2 + if norm < 1 - 1e-10 or norm > 1 + 1e-10: + raise ValueError("final_state is not normalized.") + with Control(eng, cmd.control_qubits): + # As in the paper reference, we implement the inverse: + with Dagger(eng): + # Cancel all the relative phases + phase_of_blocks = [] + for amplitude in final_state: + phase_of_blocks.append(cmath.phase(amplitude)) + for target_qubit in range(len(qureg)): + angles = [] + phase_of_next_blocks = [] + for block in range(2**(len(qureg)-target_qubit-1)): + phase0 = phase_of_blocks[2*block] + phase1 = phase_of_blocks[2*block+1] + angles.append(phase0 - phase1) + phase_of_next_blocks.append((phase0 + phase1)/2.) + UniformlyControlledRz(angles) | (qureg[(target_qubit+1):], + qureg[target_qubit]) + phase_of_blocks = phase_of_next_blocks + # Cancel global phase + Ph(-phase_of_blocks[0]) | qureg[-1] + # Remove amplitudes from states which contain a bit value 1: + abs_of_blocks = [] + for amplitude in final_state: + abs_of_blocks.append(abs(amplitude)) + for target_qubit in range(len(qureg)): + angles = [] + abs_of_next_blocks = [] + for block in range(2**(len(qureg)-target_qubit-1)): + a0 = abs_of_blocks[2*block] + a1 = abs_of_blocks[2*block+1] + if a0 == 0 and a1 == 0: + angles.append(0) + else: + angles.append( + -2. * math.acos(a0 / math.sqrt(a0**2 + a1**2))) + abs_of_next_blocks.append(math.sqrt(a0**2 + a1**2)) + UniformlyControlledRy(angles) | (qureg[(target_qubit+1):], + qureg[target_qubit]) + abs_of_blocks = abs_of_next_blocks + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(StatePreparation, _decompose_state_preparation) +] diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py new file mode 100644 index 000000000..81137b169 --- /dev/null +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -0,0 +1,71 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.setups.decompositions.stateprep2cnot.""" + +import cmath +from copy import deepcopy +import math + +import numpy as np +import pytest + +import projectq +from projectq.ops import All, Command, Measure, Ry, Rz, StatePreparation, Ph +from projectq.setups import restrictedgateset +from projectq.types import WeakQubitRef + +import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot + + +def test_wrong_final_state(): + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + cmd = Command(None, StatePreparation([0, 1j]), qubits=([qb0, qb1],)) + with pytest.raises(ValueError): + stateprep2cnot._decompose_state_preparation(cmd) + cmd2 = Command(None, StatePreparation([0, 0.999j]), qubits=([qb0],)) + with pytest.raises(ValueError): + stateprep2cnot._decompose_state_preparation(cmd2) + + +@pytest.mark.parametrize("zeros", [True, False]) +@pytest.mark.parametrize("n_qubits", [1, 2, 3, 4]) +def test_state_preparation(n_qubits, zeros): + engine_list = restrictedgateset.get_engine_list( + one_qubit_gates=(Ry, Rz, Ph)) + eng = projectq.MainEngine(engine_list=engine_list) + qureg = eng.allocate_qureg(n_qubits) + eng.flush() + + f_state = [0.2+0.1*x*cmath.exp(0.1j+0.2j*x) for x in range(2**n_qubits)] + if zeros: + for i in range(2**(n_qubits-1)): + f_state[i] = 0 + norm = 0 + for amplitude in f_state: + norm += abs(amplitude)**2 + f_state = [x / math.sqrt(norm) for x in f_state] + + StatePreparation(f_state) | qureg + eng.flush() + + wavefunction = deepcopy(eng.backend.cheat()[1]) + # Test that simulator hasn't reordered wavefunction + mapping = eng.backend.cheat()[0] + for key in mapping: + assert mapping[key] == key + All(Measure) | qureg + eng.flush() + assert np.allclose(wavefunction, f_state, rtol=1e-10, atol=1e-10) diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py new file mode 100644 index 000000000..0f27d2455 --- /dev/null +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py @@ -0,0 +1,139 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Registers decomposition for UnformlyControlledRy and UnformlyControlledRz. +""" + +from projectq.cengines import DecompositionRule +from projectq.meta import Compute, Control, Uncompute, CustomUncompute +from projectq.ops import (CNOT, Ry, Rz, + UniformlyControlledRy, + UniformlyControlledRz) + + +def _apply_ucr_n(angles, ucontrol_qubits, target_qubit, eng, gate_class, + rightmost_cnot): + if len(ucontrol_qubits) == 0: + gate = gate_class(angles[0]) + if gate != gate_class(0): + gate | target_qubit + else: + if rightmost_cnot[len(ucontrol_qubits)]: + angles1 = [] + angles2 = [] + for lower_bits in range(2**(len(ucontrol_qubits)-1)): + leading_0 = angles[lower_bits] + leading_1 = angles[lower_bits + 2**(len(ucontrol_qubits)-1)] + angles1.append((leading_0 + leading_1)/2.) + angles2.append((leading_0 - leading_1)/2.) + else: + angles1 = [] + angles2 = [] + for lower_bits in range(2**(len(ucontrol_qubits)-1)): + leading_0 = angles[lower_bits] + leading_1 = angles[lower_bits + 2**(len(ucontrol_qubits)-1)] + angles1.append((leading_0 - leading_1)/2.) + angles2.append((leading_0 + leading_1)/2.) + _apply_ucr_n(angles=angles1, + ucontrol_qubits=ucontrol_qubits[:-1], + target_qubit=target_qubit, + eng=eng, + gate_class=gate_class, + rightmost_cnot=rightmost_cnot) + # Very custom usage of Compute/CustomUncompute in the following. + if rightmost_cnot[len(ucontrol_qubits)]: + with Compute(eng): + CNOT | (ucontrol_qubits[-1], target_qubit) + else: + with CustomUncompute(eng): + CNOT | (ucontrol_qubits[-1], target_qubit) + _apply_ucr_n(angles=angles2, + ucontrol_qubits=ucontrol_qubits[:-1], + target_qubit=target_qubit, + eng=eng, + gate_class=gate_class, + rightmost_cnot=rightmost_cnot) + # Next iteration on this level do the other cnot placement + rightmost_cnot[len(ucontrol_qubits)] = ( + not rightmost_cnot[len(ucontrol_qubits)]) + + +def _decompose_ucr(cmd, gate_class): + """ + Decomposition for an uniformly controlled single qubit rotation gate. + + Follows decomposition in arXiv:quant-ph/0407010 section II and + arXiv:quant-ph/0410066v2 Fig. 9a. + + For Ry and Rz it uses 2**len(ucontrol_qubits) CNOT and also + 2**len(ucontrol_qubits) single qubit rotations. + + Args: + cmd: CommandObject to decompose. + gate_class: Ry or Rz + """ + eng = cmd.engine + with Control(eng, cmd.control_qubits): + if not (len(cmd.qubits) == 2 and len(cmd.qubits[1]) == 1): + raise TypeError("Wrong number of qubits ") + ucontrol_qubits = cmd.qubits[0] + target_qubit = cmd.qubits[1] + if not len(cmd.gate.angles) == 2**len(ucontrol_qubits): + raise ValueError("Wrong len(angles).") + if len(ucontrol_qubits) == 0: + gate_class(cmd.gate.angles[0]) | target_qubit + return + angles1 = [] + angles2 = [] + for lower_bits in range(2**(len(ucontrol_qubits)-1)): + leading_0 = cmd.gate.angles[lower_bits] + leading_1 = cmd.gate.angles[lower_bits+2**(len(ucontrol_qubits)-1)] + angles1.append((leading_0 + leading_1)/2.) + angles2.append((leading_0 - leading_1)/2.) + rightmost_cnot = {} + for i in range(len(ucontrol_qubits) + 1): + rightmost_cnot[i] = True + _apply_ucr_n(angles=angles1, + ucontrol_qubits=ucontrol_qubits[:-1], + target_qubit=target_qubit, + eng=eng, + gate_class=gate_class, + rightmost_cnot=rightmost_cnot) + # Very custom usage of Compute/CustomUncompute in the following. + with Compute(cmd.engine): + CNOT | (ucontrol_qubits[-1], target_qubit) + _apply_ucr_n(angles=angles2, + ucontrol_qubits=ucontrol_qubits[:-1], + target_qubit=target_qubit, + eng=eng, + gate_class=gate_class, + rightmost_cnot=rightmost_cnot) + with CustomUncompute(eng): + CNOT | (ucontrol_qubits[-1], target_qubit) + + +def _decompose_ucry(cmd): + return _decompose_ucr(cmd, gate_class=Ry) + + +def _decompose_ucrz(cmd): + return _decompose_ucr(cmd, gate_class=Rz) + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(UniformlyControlledRy, _decompose_ucry), + DecompositionRule(UniformlyControlledRz, _decompose_ucrz) +] diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py new file mode 100644 index 000000000..52c2555a9 --- /dev/null +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py @@ -0,0 +1,127 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for projectq.setups.decompositions.uniformlycontrolledr2cnot.""" + +import pytest + +import projectq +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) + +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import (All, Measure, Ry, Rz, UniformlyControlledRy, + UniformlyControlledRz, X) + +import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot + + +def slow_implementation(angles, control_qubits, target_qubit, eng, gate_class): + """ + Assumption is that control_qubits[0] is lowest order bit + We apply angles[0] to state |0> + """ + assert len(angles) == 2**len(control_qubits) + for index in range(2**len(control_qubits)): + with Compute(eng): + for bit_pos in range(len(control_qubits)): + if not (index >> bit_pos) & 1: + X | control_qubits[bit_pos] + with Control(eng, control_qubits): + gate_class(angles[index]) | target_qubit + Uncompute(eng) + + +def _decomp_gates(eng, cmd): + if (isinstance(cmd.gate, UniformlyControlledRy) or + isinstance(cmd.gate, UniformlyControlledRz)): + return False + return True + + +def test_no_control_qubits(): + rule_set = DecompositionRuleSet(modules=[ucr2cnot]) + eng = MainEngine(backend=DummyEngine(), + engine_list=[AutoReplacer(rule_set), + InstructionFilter(_decomp_gates)]) + qb = eng.allocate_qubit() + with pytest.raises(TypeError): + UniformlyControlledRy([0.1]) | qb + + +def test_wrong_number_of_angles(): + rule_set = DecompositionRuleSet(modules=[ucr2cnot]) + eng = MainEngine(backend=DummyEngine(), + engine_list=[AutoReplacer(rule_set), + InstructionFilter(_decomp_gates)]) + qb = eng.allocate_qubit() + with pytest.raises(ValueError): + UniformlyControlledRy([0.1, 0.2]) | ([], qb) + + +@pytest.mark.parametrize("gate_classes", [(Ry, UniformlyControlledRy), + (Rz, UniformlyControlledRz)]) +@pytest.mark.parametrize("n", [0, 1, 2, 3, 4]) +def test_uniformly_controlled_ry(n, gate_classes): + random_angles = [0.5, 0.8, 1.2, 2.5, 4.4, 2.32, 6.6, 15.12, 1, 2, 9.54, + 2.1, 3.1415, 1.1, 0.01, 0.99] + angles = random_angles[:2**n] + for basis_state_index in range(0, 2**(n+1)): + basis_state = [0] * 2**(n+1) + basis_state[basis_state_index] = 1. + correct_dummy_eng = DummyEngine(save_commands=True) + correct_eng = MainEngine(backend=Simulator(), + engine_list=[correct_dummy_eng]) + rule_set = DecompositionRuleSet(modules=[ucr2cnot]) + test_dummy_eng = DummyEngine(save_commands=True) + test_eng = MainEngine(backend=Simulator(), + engine_list=[AutoReplacer(rule_set), + InstructionFilter(_decomp_gates), + test_dummy_eng]) + test_sim = test_eng.backend + correct_sim = correct_eng.backend + correct_qb = correct_eng.allocate_qubit() + correct_ctrl_qureg = correct_eng.allocate_qureg(n) + correct_eng.flush() + test_qb = test_eng.allocate_qubit() + test_ctrl_qureg = test_eng.allocate_qureg(n) + test_eng.flush() + + correct_sim.set_wavefunction(basis_state, correct_qb + + correct_ctrl_qureg) + test_sim.set_wavefunction(basis_state, test_qb + test_ctrl_qureg) + + gate_classes[1](angles) | (test_ctrl_qureg, test_qb) + slow_implementation(angles=angles, + control_qubits=correct_ctrl_qureg, + target_qubit=correct_qb, + eng=correct_eng, + gate_class=gate_classes[0]) + test_eng.flush() + correct_eng.flush() + + for fstate in range(2**(n+1)): + binary_state = format(fstate, '0' + str(n+1) + 'b') + test = test_sim.get_amplitude(binary_state, + test_qb + test_ctrl_qureg) + correct = correct_sim.get_amplitude(binary_state, correct_qb + + correct_ctrl_qureg) + assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) + + All(Measure) | test_qb + test_ctrl_qureg + All(Measure) | correct_qb + correct_ctrl_qureg + test_eng.flush(deallocate_qubits=True) + correct_eng.flush(deallocate_qubits=True) diff --git a/pytest.ini b/pytest.ini index e5ac5982e..fab634b12 100755 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,6 @@ [pytest] testpaths = projectq + +filterwarnings = + error + ignore:the matrix subclass is not the recommended way:PendingDeprecationWarning