From f0a0c1e9687c6f0a2e4bdc894fac78b65daabe61 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Fri, 25 Jun 2021 10:33:09 +0200 Subject: [PATCH 1/6] Add checks to avoid too many compiler engines in the engine list (#405) * Add checks to avoid too many compiler engines in the engine list * Update CHANGELOG * Apply suggestions from Andreas --- CHANGELOG.md | 3 +++ projectq/cengines/_main.py | 15 +++++++++++++-- projectq/cengines/_main_test.py | 12 ++++++++++++ projectq/meta/_util.py | 8 ++++++++ projectq/meta/_util_test.py | 26 +++++++++++++++++++++++--- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c692911e1..e6c1aca4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Deprecated ### Fixed + +- Prevent infinite recursion errors when too many compiler engines are added to the MainEngine + ### Removed ### Repository diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index 2c961b9a6..ec6145ef5 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -47,6 +47,9 @@ def receive(self, command_list): # pylint: disable=unused-argument """No-op""" +_N_ENGINES_THRESHOLD = 100 + + class MainEngine(BasicEngine): # pylint: disable=too-many-instance-attributes """ The MainEngine class provides all functionality of the main compiler engine. @@ -61,10 +64,13 @@ class MainEngine(BasicEngine): # pylint: disable=too-many-instance-attributes dirty_qubits (Set): Containing all dirty qubit ids backend (BasicEngine): Access the back-end. mapper (BasicMapperEngine): Access to the mapper if there is one. - + n_engines (int): Current number of compiler engines in the engine list + n_engines_max (int): Maximum number of compiler engines allowed in the engine list. Defaults to 100. """ - def __init__(self, backend=None, engine_list=None, verbose=False): + def __init__( # pylint: disable=too-many-statements,too-many-branches + self, backend=None, engine_list=None, verbose=False + ): """ Initialize the main compiler engine and all compiler engines. @@ -118,6 +124,7 @@ def __init__(self, backend=None, engine_list=None, verbose=False): self.dirty_qubits = set() self.verbose = verbose self.main_engine = self + self.n_engines_max = _N_ENGINES_THRESHOLD if backend is None: backend = Simulator() @@ -174,6 +181,10 @@ def __init__(self, backend=None, engine_list=None, verbose=False): " twice.\n" ) + self.n_engines = len(engine_list) + if self.n_engines > self.n_engines_max: + raise ValueError('Too many compiler engines added to the MainEngine!') + self._qubit_idx = int(0) for i in range(len(engine_list) - 1): engine_list[i].next_engine = engine_list[i + 1] diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index 3178cd5c7..b8ab365c4 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -73,6 +73,18 @@ def test_main_engine_init_defaults(): assert type(engine) == type(expected) +def test_main_engine_too_many_compiler_engines(): + old = _main._N_ENGINES_THRESHOLD + _main._N_ENGINES_THRESHOLD = 3 + + _main.MainEngine(backend=DummyEngine(), engine_list=[DummyEngine(), DummyEngine()]) + + with pytest.raises(ValueError): + _main.MainEngine(backend=DummyEngine(), engine_list=[DummyEngine(), DummyEngine(), DummyEngine()]) + + _main._N_ENGINES_THRESHOLD = old + + def test_main_engine_init_mapper(): class LinearMapper(BasicMapperEngine): pass diff --git a/projectq/meta/_util.py b/projectq/meta/_util.py index 4dab11ede..4a5300eb2 100755 --- a/projectq/meta/_util.py +++ b/projectq/meta/_util.py @@ -30,6 +30,12 @@ def insert_engine(prev_engine, engine_to_insert): engine_to_insert (projectq.cengines.BasicEngine): The engine to insert at the insertion point. """ + if prev_engine.main_engine is not None: + prev_engine.main_engine.n_engines += 1 + + if prev_engine.main_engine.n_engines > prev_engine.main_engine.n_engines_max: + raise RuntimeError('Too many compiler engines added to the MainEngine!') + engine_to_insert.main_engine = prev_engine.main_engine engine_to_insert.next_engine = prev_engine.next_engine prev_engine.next_engine = engine_to_insert @@ -47,6 +53,8 @@ def drop_engine_after(prev_engine): """ dropped_engine = prev_engine.next_engine prev_engine.next_engine = dropped_engine.next_engine + if prev_engine.main_engine is not None: + prev_engine.main_engine.n_engines -= 1 dropped_engine.next_engine = None dropped_engine.main_engine = None return dropped_engine diff --git a/projectq/meta/_util_test.py b/projectq/meta/_util_test.py index 496d2f9b4..a010d1b81 100755 --- a/projectq/meta/_util_test.py +++ b/projectq/meta/_util_test.py @@ -13,9 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.meta import insert_engine, drop_engine_after + + +from . import _util def test_insert_then_drop(): @@ -30,19 +34,35 @@ def test_insert_then_drop(): assert d1.main_engine is eng assert d2.main_engine is None assert d3.main_engine is eng + assert eng.n_engines == 2 - insert_engine(d1, d2) + _util.insert_engine(d1, d2) assert d1.next_engine is d2 assert d2.next_engine is d3 assert d3.next_engine is None assert d1.main_engine is eng assert d2.main_engine is eng assert d3.main_engine is eng + assert eng.n_engines == 3 - drop_engine_after(d1) + _util.drop_engine_after(d1) assert d1.next_engine is d3 assert d2.next_engine is None assert d3.next_engine is None assert d1.main_engine is eng assert d2.main_engine is None assert d3.main_engine is eng + assert eng.n_engines == 2 + + +def test_too_many_engines(): + N = 10 + + eng = MainEngine(backend=DummyEngine(), engine_list=[]) + eng.n_engines_max = N + + for _ in range(N - 1): + _util.insert_engine(eng, DummyEngine()) + + with pytest.raises(RuntimeError): + _util.insert_engine(eng, DummyEngine()) From 10b4077acb81e8abe7a1e4220ed8eb20011e7f2e Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Fri, 25 Jun 2021 11:07:33 +0200 Subject: [PATCH 2/6] Fix some issues with automatic release publication (#407) * Only release source package... ... and leave commented out some code that would implement better testing before uploading to Pypi. * Remove unneeded comments * Update publish_release.yml * Update publish_release.yml * Update publish_release.yml --- .github/workflows/publish_release.yml | 82 ++++++++++++++------------- CHANGELOG.md | 1 + 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 96ae496d5..d0f54099a 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -1,6 +1,7 @@ name: "Publish new release" on: + workflow_dispatch: push: tags: - v[0-9]+.* @@ -14,78 +15,59 @@ jobs: packaging: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} - if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) + if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || github.event_name == 'workflow_dispatch' strategy: matrix: - os: [ubuntu-20.04, windows-2019, macos-10.15] + os: + - ubuntu-latest steps: - uses: actions/checkout@v2 + if: github.event_name != 'workflow_dispatch' + + - uses: actions/checkout@v2 + if: github.event_name == 'workflow_dispatch' + with: + ref: 'master' - name: Get history and tags for SCM versioning to work run: | git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: Extract version from tag name (Unix) - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && runner.os != 'Windows' - run: | - TAG_NAME="${GITHUB_REF/refs\/tags\//}" - VERSION=${TAG_NAME#v} - - echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + # ======================================================================== - name: Extract version from branch name (for release branches) (Unix) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#release/} - - echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - git tag v${RELEASE_VERSION} master + git tag v${VERSION} master - name: Extract version from branch name (for hotfix branches) (Unix) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os != 'Windows' run: | BRANCH_NAME="${{ github.event.pull_request.head.ref }}" VERSION=${BRANCH_NAME#hotfix/} + git tag v${VERSION} master - echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - git tag v${RELEASE_VERSION} master - - - - name: Extract version from tag name (Windows) - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && runner.os == 'Windows' - run: | - $TAG_NAME = $GITHUB_REF -replace "refs/tags/","" - $VERSION = $TAG_NAME -replace "v","" - - Write-Output "RELEASE_VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + # ------------------------------------------------------------------------ - name: Extract version from branch name (for release branches) (Windows) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "release/","" + git tag v${VERSION} master - Write-Output "RELEASE_VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - git tag v${RELEASE_VERSION} master - - - name: Extract version from branch name (for hotfix branches) (Unix) + - name: Extract version from branch name (for hotfix branches) (Windows) if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') && runner.os == 'Windows' run: | $BRANCH_NAME="${{ github.event.pull_request.head.ref }}" $VERSION = $BRANCH_NAME -replace "hotfix/","" + git tag v${VERSION} master - Write-Output "RELEASE_VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - git tag v${RELEASE_VERSION} master - - - name: Build wheels - uses: joerick/cibuildwheel@v1.11.1 - env: - CIBW_ARCHS: auto64 - CIBW_SKIP: cp27-* pp* cp35-* - CIBW_BEFORE_BUILD: python -m pip install pybind11 + # ======================================================================== - name: Build source distribution if: runner.os == 'Linux' @@ -101,11 +83,21 @@ jobs: name: packages path: ./wheelhouse/* + release: name: Publish new release runs-on: ubuntu-latest - needs: packaging + needs: + - packaging steps: + - name: Extract version from tag name (workflow_dispatch) + if: github.event_name == 'workflow_dispatch' + run: | + TAG_NAME=$(git describe --tags `git rev-list --tags --max-count=1`) + VERSION=${TAG_NAME#v} + + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + - name: Extract version from tag name if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') run: | @@ -130,8 +122,18 @@ jobs: echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + # ------------------------------------------------------------------------ + # Checkout repository to get CHANGELOG + - uses: actions/checkout@v2 + if: github.event_name != 'workflow_dispatch' + + - uses: actions/checkout@v2 + if: github.event_name == 'workflow_dispatch' + with: + ref: 'master' + # ------------------------------------------------------------------------ # Downloads all to directories matching the artifact names - uses: actions/download-artifact@v2 @@ -164,7 +166,7 @@ jobs: upload_to_pypi: name: Upload to PyPI runs-on: ubuntu-latest - needs: release # Only upload to PyPi if everything was successful + needs: release steps: - uses: actions/setup-python@v2 @@ -181,7 +183,9 @@ jobs: master_to_develop_pr: name: Merge master back into develop runs-on: ubuntu-latest - needs: release # Only create PR if everything was successful + needs: + - release + - upload_to_pypi steps: - name: Merge master into develop branch uses: thomaseizinger/create-pull-request@1.1.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index e6c1aca4b..76ae99d46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix GitHub workflow for publishing a new release + ## [0.6.0] - 2021-06-23 ### Added From 0c44fba051cff5ab68793cd1e5aaa69670a132ef Mon Sep 17 00:00:00 2001 From: XYShe <30593841+XYShe@users.noreply.github.com> Date: Thu, 1 Jul 2021 16:09:14 +0200 Subject: [PATCH 3/6] Implement a UnitarySimulator to save the unitary of a quantum circuit (#409) * Add matout() to check for matrices applied. Add check for self-control. * Move unitary logic to its own compiler engine class * Improve unitary simulator example * Added support for measurement. Added history for unitaries. Added tests * Clean up directory * Fix simulator error, add changelog * Undo unneeded space changes * Refactor example a little * Reformat some of the docstrings * Update CHANGELOG * Improve code to enable all pylint checks for the UnitarySimulator * Improve test coverage and simply parts of the code * Tweak UnitarySimulator some more - Make it so that multiple calls to flush() do not unnecessarily increase the history list of unitary matrices Co-authored-by: Damien Nguyen --- CHANGELOG.md | 3 + examples/unitary_simulator.py | 82 ++++++++ projectq/backends/__init__.py | 1 + projectq/backends/_unitary.py | 290 ++++++++++++++++++++++++++++ projectq/backends/_unitary_test.py | 292 +++++++++++++++++++++++++++++ 5 files changed, 668 insertions(+) create mode 100644 examples/unitary_simulator.py create mode 100644 projectq/backends/_unitary.py create mode 100644 projectq/backends/_unitary_test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 76ae99d46..8b8a64b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added + +- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit. + ### Changed ### Deprecated ### Fixed diff --git a/examples/unitary_simulator.py b/examples/unitary_simulator.py new file mode 100644 index 000000000..420034784 --- /dev/null +++ b/examples/unitary_simulator.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. +# pylint: skip-file + +"""Example of using the UnitarySimulator.""" + + +import numpy as np + +from projectq.backends import UnitarySimulator +from projectq.cengines import MainEngine +from projectq.meta import Control +from projectq.ops import All, X, QFT, Measure, CtrlAll + + +def run_circuit(eng, n_qubits, circuit_num, gate_after_measure=False): + """Run a quantum circuit demonstrating the capabilities of the UnitarySimulator.""" + qureg = eng.allocate_qureg(n_qubits) + + if circuit_num == 1: + All(X) | qureg + elif circuit_num == 2: + X | qureg[0] + with Control(eng, qureg[:2]): + All(X) | qureg[2:] + elif circuit_num == 3: + with Control(eng, qureg[:2], ctrl_state=CtrlAll.Zero): + All(X) | qureg[2:] + elif circuit_num == 4: + QFT | qureg + + eng.flush() + All(Measure) | qureg + + if gate_after_measure: + QFT | qureg + eng.flush() + All(Measure) | qureg + + +def main(): + """Definition of the main function of this example.""" + # Create a MainEngine with a unitary simulator backend + eng = MainEngine(backend=UnitarySimulator()) + + n_qubits = 3 + + # Run out quantum circuit + # 1 - circuit applying X on all qubits + # 2 - circuit applying an X gate followed by a controlled-X gate + # 3 - circuit applying a off-controlled-X gate + # 4 - circuit applying a QFT on all qubits (QFT will get decomposed) + run_circuit(eng, n_qubits, 3, gate_after_measure=True) + + # Output the unitary transformation of the circuit + print('The unitary of the circuit is:') + print(eng.backend.unitary) + + # Output the final state of the qubits (assuming they all start in state |0>) + print('The final state of the qubits is:') + print(eng.backend.unitary @ np.array([1] + ([0] * (2 ** n_qubits - 1)))) + print('\n') + + # Show the unitaries separated by measurement: + for history in eng.backend.history: + print('Previous unitary is: \n', history, '\n') + + +if __name__ == '__main__': + main() diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index 2b6ce5520..044b78b84 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -36,3 +36,4 @@ from ._aqt import AQTBackend from ._awsbraket import AWSBraketBackend from ._ionq import IonQBackend +from ._unitary import UnitarySimulator diff --git a/projectq/backends/_unitary.py b/projectq/backends/_unitary.py new file mode 100644 index 000000000..1ebdbc2cb --- /dev/null +++ b/projectq/backends/_unitary.py @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +"""Contain a backend that saves the unitary of a quantum circuit.""" + +from copy import deepcopy +import itertools +import math +import warnings +import random +import numpy as np + +from projectq.cengines import BasicEngine +from projectq.types import WeakQubitRef +from projectq.meta import has_negative_control, get_control_count, LogicalQubitIDTag +from projectq.ops import ( + AllocateQubitGate, + DeallocateQubitGate, + MeasureGate, + FlushGate, +) + + +def _qidmask(target_ids, control_ids, n_qubits): + """ + Calculate index masks. + + Args: + target_ids (list): list of target qubit indices + control_ids (list): list of control qubit indices + control_state (list): list of states for the control qubits (0 or 1) + n_qubits (int): number of qubits + """ + mask_list = [] + perms = np.array([x[::-1] for x in itertools.product("01", repeat=n_qubits)]).astype(int) + all_ids = np.array(range(n_qubits)) + irel_ids = np.delete(all_ids, control_ids + target_ids) + + if len(control_ids) > 0: + cmask = np.where(np.all(perms[:, control_ids] == [1] * len(control_ids), axis=1)) + else: + cmask = np.array(range(perms.shape[0])) + + if len(irel_ids) > 0: + irel_perms = np.array([x[::-1] for x in itertools.product("01", repeat=len(irel_ids))]).astype(int) + for i in range(2 ** len(irel_ids)): + irel_mask = np.where(np.all(perms[:, irel_ids] == irel_perms[i], axis=1)) + common = np.intersect1d(irel_mask, cmask) + if len(common) > 0: + mask_list.append(common) + else: + irel_mask = np.array(range(perms.shape[0])) + mask_list.append(np.intersect1d(irel_mask, cmask)) + return mask_list + + +class UnitarySimulator(BasicEngine): + """ + Simulator engine aimed at calculating the unitary transformation that represents the current quantum circuit. + + Attributes: + unitary (np.ndarray): Current unitary representing the quantum circuit being processed so far. + history (list): List of previous quantum circuit unitaries. + + Note: + The current implementation of this backend resets the unitary after the first gate that is neither a qubit + deallocation nor a measurement occurs after one of those two aforementioned gates. + + The old unitary call be accessed at anytime after such a situation occurs via the `history` property. + + .. code-block:: python + + eng = MainEngine(backend=UnitarySimulator(), engine_list=[]) + qureg = eng.allocate_qureg(3) + All(X) | qureg + + eng.flush() + All(Measure) | qureg + eng.deallocate_qubit(qureg[1]) + + X | qureg[0] # WARNING: appending gate after measurements or deallocations resets the unitary + """ + + def __init__(self): + """Initialize a UnitarySimulator object.""" + super().__init__() + self._qubit_map = dict() + self._unitary = [1] + self._num_qubits = 0 + self._is_valid = True + self._is_flushed = False + self._state = [1] + self._history = [] + + @property + def unitary(self): + """ + Access the last unitary matrix directly. + + Returns: + A numpy array which is the unitary matrix of the circuit. + """ + return deepcopy(self._unitary) + + @property + def history(self): + """ + Access all previous unitary matrices. + + The current unitary matrix is appended to this list once a gate is received after either a measurement or a + qubit deallocation has occurred. + + Returns: + A list where the elements are all previous unitary matrices representing the circuit, separated by + measurement/deallocate gates. + """ + return deepcopy(self._history) + + def is_available(self, cmd): + """ + Test whether a Command is supported by a compiler engine. + + Specialized implementation of is_available: The unitary simulator can deal with all arbitrarily-controlled gates + which provide a gate-matrix (via gate.matrix). + + Args: + cmd (Command): Command for which to check availability (single- qubit gate, arbitrary controls) + + Returns: + True if it can be simulated and False otherwise. + """ + if has_negative_control(cmd): + return False + + if isinstance(cmd.gate, (AllocateQubitGate, DeallocateQubitGate, MeasureGate)): + return True + + try: + gate_mat = cmd.gate.matrix + if len(gate_mat) > 2 ** 6: + warnings.warn("Potentially large matrix gate encountered! ({} qubits)".format(math.log2(len(gate_mat)))) + return True + except AttributeError: + return False + + def receive(self, command_list): + """ + Receive a list of commands. + + Receive a list of commands from the previous engine and handle them: + * update the unitary of the quantum circuit + * update the internal quantum state if a measurement or a qubit deallocation occurs + + prior to sending them on to the next engine. + + Args: + command_list (list): List of commands to execute on the simulator. + """ + for cmd in command_list: + self._handle(cmd) + + if not self.is_last_engine: + self.send(command_list) + + def _flush(self): + """Flush the simulator state.""" + if not self._is_flushed: + self._is_flushed = True + self._state = self._unitary @ self._state + + def _handle(self, cmd): + """ + Handle all commands. + + Args: + cmd (Command): Command to handle. + + Raises: + RuntimeError: If a measurement is performed before flush gate. + """ + if isinstance(cmd.gate, AllocateQubitGate): + self._qubit_map[cmd.qubits[0][0].id] = self._num_qubits + self._num_qubits += 1 + self._unitary = np.kron(np.identity(2), self._unitary) + self._state.extend([0] * len(self._state)) + + elif isinstance(cmd.gate, DeallocateQubitGate): + pos = self._qubit_map[cmd.qubits[0][0].id] + self._qubit_map = {key: value - 1 if value > pos else value for key, value in self._qubit_map.items()} + self._num_qubits -= 1 + self._is_valid = False + + elif isinstance(cmd.gate, MeasureGate): + self._is_valid = False + + if not self._is_flushed: + raise RuntimeError( + 'Please make sure all previous gates are flushed before measurement so the state gets updated' + ) + + if get_control_count(cmd) != 0: + raise ValueError('Cannot have control qubits with a measurement gate!') + + all_qubits = [qb for qr in cmd.qubits for qb in qr] + measurements = self.measure_qubits([qb.id for qb in all_qubits]) + + for qb, res in zip(all_qubits, measurements): + # Check if a mapper assigned a different logical id + for tag in cmd.tags: + if isinstance(tag, LogicalQubitIDTag): + qb = WeakQubitRef(qb.engine, tag.logical_qubit_id) + break + self.main_engine.set_measurement_result(qb, res) + + elif isinstance(cmd.gate, FlushGate): + self._flush() + else: + if not self._is_valid: + self._flush() + + warnings.warn( + "Processing of other gates after a qubit deallocation or measurement will reset the unitary," + "previous unitary can be accessed in history" + ) + self._history.append(self._unitary) + self._unitary = np.identity(2 ** self._num_qubits, dtype=complex) + self._state = np.array([1] + ([0] * (2 ** self._num_qubits - 1)), dtype=complex) + self._is_valid = True + + self._is_flushed = False + mask_list = _qidmask( + [self._qubit_map[qb.id] for qr in cmd.qubits for qb in qr], + [self._qubit_map[qb.id] for qb in cmd.control_qubits], + self._num_qubits, + ) + for mask in mask_list: + cache = np.identity(2 ** self._num_qubits, dtype=complex) + cache[np.ix_(mask, mask)] = cmd.gate.matrix + self._unitary = cache @ self._unitary + + def measure_qubits(self, ids): + """ + Measure the qubits with IDs ids and return a list of measurement outcomes (True/False). + + Args: + ids (list): List of qubit IDs to measure. + + Returns: + List of measurement results (containing either True or False). + """ + random_outcome = random.random() + val = 0.0 + i_picked = 0 + while val < random_outcome and i_picked < len(self._state): + val += np.abs(self._state[i_picked]) ** 2 + i_picked += 1 + + i_picked -= 1 + + pos = [self._qubit_map[ID] for ID in ids] + res = [False] * len(pos) + + mask = 0 + val = 0 + for i, _pos in enumerate(pos): + res[i] = ((i_picked >> _pos) & 1) == 1 + mask |= 1 << _pos + val |= (res[i] & 1) << _pos + + nrm = 0.0 + for i, _state in enumerate(self._state): + if (mask & i) != val: + self._state[i] = 0.0 + else: + nrm += np.abs(_state) ** 2 + + self._state *= 1.0 / np.sqrt(nrm) + return res diff --git a/projectq/backends/_unitary_test.py b/projectq/backends/_unitary_test.py new file mode 100644 index 000000000..e082305e8 --- /dev/null +++ b/projectq/backends/_unitary_test.py @@ -0,0 +1,292 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +""" +Contains the tests for the UnitarySimulator +""" + +import itertools +import numpy as np +import pytest +from scipy.stats import unitary_group + +from projectq.cengines import MainEngine, DummyEngine, NotYetMeasuredError +from projectq.ops import ( + BasicGate, + MatrixGate, + All, + Measure, + Allocate, + Deallocate, + Command, + X, + Y, + Rx, + Rxx, + H, + CNOT, +) +from projectq.meta import Control, LogicalQubitIDTag +from projectq.types import WeakQubitRef + +from ._unitary import UnitarySimulator + + +def test_unitary_is_available(): + sim = UnitarySimulator() + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + qb2 = WeakQubitRef(engine=None, idx=2) + qb3 = WeakQubitRef(engine=None, idx=2) + qb4 = WeakQubitRef(engine=None, idx=2) + qb5 = WeakQubitRef(engine=None, idx=2) + qb6 = WeakQubitRef(engine=None, idx=2) + + assert sim.is_available(Command(None, Allocate, qubits=([qb0],))) + assert sim.is_available(Command(None, Deallocate, qubits=([qb0],))) + assert sim.is_available(Command(None, Measure, qubits=([qb0],))) + assert sim.is_available(Command(None, X, qubits=([qb0],))) + assert sim.is_available(Command(None, Rx(1.2), qubits=([qb0],))) + assert sim.is_available(Command(None, Rxx(1.2), qubits=([qb0, qb1],))) + assert sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1])) + assert sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='1')) + + assert not sim.is_available(Command(None, BasicGate(), qubits=([qb0],))) + assert not sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='0')) + + with pytest.warns(UserWarning): + assert sim.is_available( + Command( + None, + MatrixGate(np.identity(2 ** 7)), + qubits=([qb0, qb1, qb2, qb3, qb4, qb5, qb6],), + ) + ) + + +def test_unitary_warnings(): + eng = MainEngine(backend=DummyEngine(save_commands=True), engine_list=[UnitarySimulator()]) + qubit = eng.allocate_qubit() + X | qubit + + with pytest.raises(RuntimeError): + Measure | qubit + + +def test_unitary_not_last_engine(): + eng = MainEngine(backend=DummyEngine(save_commands=True), engine_list=[UnitarySimulator()]) + qubit = eng.allocate_qubit() + X | qubit + eng.flush() + Measure | qubit + assert len(eng.backend.received_commands) == 4 + + +def test_unitary_flush_does_not_invalidate(): + eng = MainEngine(backend=UnitarySimulator(), engine_list=[]) + qureg = eng.allocate_qureg(2) + + X | qureg[0] + eng.flush() + + Y | qureg[1] + eng.flush() + + # Make sure that calling flush() multiple time is ok (before measurements) + eng.flush() + eng.flush() + + # Nothing should be added to the history here since no measurements or qubit deallocation happened + assert not eng.backend.history + assert np.allclose(eng.backend.unitary, np.kron(Y.matrix, X.matrix)) + + All(Measure) | qureg + + # Make sure that calling flush() multiple time is ok (after measurement) + eng.flush() + eng.flush() + + # Nothing should be added to the history here since no gate since measurements or qubit deallocation happened + assert not eng.backend.history + assert np.allclose(eng.backend.unitary, np.kron(Y.matrix, X.matrix)) + + +def test_unitary_after_deallocation_or_measurement(): + eng = MainEngine(backend=UnitarySimulator(), engine_list=[]) + qubit = eng.allocate_qubit() + X | qubit + + assert not eng.backend.history + + eng.flush() + Measure | qubit + + # FlushGate and MeasureGate do not append to the history + assert not eng.backend.history + assert np.allclose(eng.backend.unitary, X.matrix) + + with pytest.warns(UserWarning): + Y | qubit + + # YGate after FlushGate and MeasureGate does not append current unitary (identity) to the history + assert len(eng.backend.history) == 1 + assert np.allclose(eng.backend.unitary, Y.matrix) # Reset of unitary when applying Y above + assert np.allclose(eng.backend.history[0], X.matrix) + + # Still ok + eng.flush() + Measure | qubit + + # FlushGate and MeasureGate do not append to the history + assert len(eng.backend.history) == 1 + assert np.allclose(eng.backend.unitary, Y.matrix) + assert np.allclose(eng.backend.history[0], X.matrix) + + # Make sure that the new gate will trigger appending to the history and modify the current unitary + with pytest.warns(UserWarning): + Rx(1) | qubit + assert len(eng.backend.history) == 2 + assert np.allclose(eng.backend.unitary, Rx(1).matrix) + assert np.allclose(eng.backend.history[0], X.matrix) + assert np.allclose(eng.backend.history[1], Y.matrix) + + # -------------------------------------------------------------------------- + + eng = MainEngine(backend=UnitarySimulator(), engine_list=[]) + qureg = eng.allocate_qureg(2) + All(X) | qureg + + XX_matrix = np.kron(X.matrix, X.matrix) + assert not eng.backend.history + assert np.allclose(eng.backend.unitary, XX_matrix) + + eng.deallocate_qubit(qureg[0]) + + assert not eng.backend.history + + with pytest.warns(UserWarning): + Y | qureg[1] + + # An internal call to flush() happens automatically since the X + # gate occurs as the simulator is in an invalid state (after qubit + # deallocation) + assert len(eng.backend.history) == 1 + assert np.allclose(eng.backend.history[0], XX_matrix) + assert np.allclose(eng.backend.unitary, Y.matrix) + + # Still ok + eng.flush() + Measure | qureg[1] + + # Nothing should have changed + assert len(eng.backend.history) == 1 + assert np.allclose(eng.backend.history[0], XX_matrix) + assert np.allclose(eng.backend.unitary, Y.matrix) + + +def test_unitary_simulator(): + def create_random_unitary(n): + return unitary_group.rvs(2 ** n) + + mat1 = create_random_unitary(1) + mat2 = create_random_unitary(2) + mat3 = create_random_unitary(3) + mat4 = create_random_unitary(1) + + n_qubits = 3 + + def apply_gates(eng, qureg): + MatrixGate(mat1) | qureg[0] + MatrixGate(mat2) | qureg[1:] + MatrixGate(mat3) | qureg + + with Control(eng, qureg[1]): + MatrixGate(mat2) | (qureg[0], qureg[2]) + MatrixGate(mat4) | qureg[0] + + with Control(eng, qureg[1], ctrl_state='0'): + MatrixGate(mat1) | qureg[0] + with Control(eng, qureg[2], ctrl_state='0'): + MatrixGate(mat1) | qureg[0] + + for basis_state in [list(x[::-1]) for x in itertools.product([0, 1], repeat=2 ** n_qubits)][1:]: + ref_eng = MainEngine(engine_list=[], verbose=True) + ref_qureg = ref_eng.allocate_qureg(n_qubits) + ref_eng.backend.set_wavefunction(basis_state, ref_qureg) + apply_gates(ref_eng, ref_qureg) + + test_eng = MainEngine(backend=UnitarySimulator(), engine_list=[], verbose=True) + test_qureg = test_eng.allocate_qureg(n_qubits) + + assert np.allclose(test_eng.backend.unitary, np.identity(2 ** n_qubits)) + + apply_gates(test_eng, test_qureg) + + qubit_map, ref_state = ref_eng.backend.cheat() + assert qubit_map == {i: i for i in range(n_qubits)} + + test_state = test_eng.backend.unitary @ np.array(basis_state) + + assert np.allclose(ref_eng.backend.cheat()[1], test_state) + + ref_eng.flush() + test_eng.flush() + All(Measure) | ref_qureg + All(Measure) | test_qureg + + +def test_unitary_functional_measurement(): + eng = MainEngine(UnitarySimulator()) + qubits = eng.allocate_qureg(5) + # entangle all qubits: + H | qubits[0] + for qb in qubits[1:]: + CNOT | (qubits[0], qb) + eng.flush() + All(Measure) | qubits + + bit_value_sum = sum([int(qubit) for qubit in qubits]) + assert bit_value_sum == 0 or bit_value_sum == 5 + + qb1 = WeakQubitRef(engine=eng, idx=qubits[0].id) + qb2 = WeakQubitRef(engine=eng, idx=qubits[1].id) + with pytest.raises(ValueError): + eng.backend._handle(Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[qb2])) + + +def test_unitary_measure_mapped_qubit(): + eng = MainEngine(UnitarySimulator()) + qb1 = WeakQubitRef(engine=eng, idx=1) + qb2 = WeakQubitRef(engine=eng, idx=2) + cmd0 = Command(engine=eng, gate=Allocate, qubits=([qb1],)) + cmd1 = Command(engine=eng, gate=X, qubits=([qb1],)) + cmd2 = Command( + engine=eng, + gate=Measure, + qubits=([qb1],), + controls=[], + tags=[LogicalQubitIDTag(2)], + ) + with pytest.raises(NotYetMeasuredError): + int(qb1) + with pytest.raises(NotYetMeasuredError): + int(qb2) + + eng.send([cmd0, cmd1]) + eng.flush() + eng.send([cmd2]) + with pytest.raises(NotYetMeasuredError): + int(qb1) + assert int(qb2) == 1 From 1c07475ad9dbc30baedddf78fddafa96112ffcdc Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Wed, 14 Jul 2021 19:12:09 +0200 Subject: [PATCH 4/6] Modernize ProjectQ (isort, PEP 257 docstrings, drop Python 2 code, more flake8 plugins) and fix phase estimation unit tests (#408) * Fix tests for the phase estimation decomposition * Fix docstrings according to PEP257 * Modernize code by removing some Python 2 compatibility code * Update CHANGELOG * Add isort to pre-commit-config.yaml and run it on the project * Move common exception classes to their own files * Fix changes from the latest on develop * Update CHANGELOG * Update Python code in documentation folder * Add `meta` repository to pre-commit configuration * Re-indent and cleanup .pre-commit-config.yaml * Add some more flake8 plugins to pre-commit config file - flake8-breakpoint - flake8-comprehensions - flake8-eradicate - flake8-mutable * Fix small bug with drawing using matplotlib * Address review comments * Better comment in docs/conf.py --- .pre-commit-config.yaml | 111 ++++++----- CHANGELOG.md | 8 + docs/conf.py | 146 +++++++------- docs/package_description.py | 50 +++-- examples/aqt.py | 11 +- examples/bellpair_circuit.py | 7 +- examples/control_tester.py | 4 +- examples/gate_zoo.py | 46 ++--- examples/grover.py | 14 +- examples/hws4.py | 7 +- examples/hws6.py | 11 +- examples/ibm.py | 11 +- examples/ionq.py | 2 +- examples/ionq_bv.py | 2 +- examples/ionq_half_adder.py | 3 +- examples/quantum_random_numbers.py | 4 +- examples/quantum_random_numbers_ibm.py | 4 +- examples/shor.py | 9 +- examples/teleport.py | 28 +-- examples/teleport_circuit.py | 8 +- examples/unitary_simulator.py | 2 +- projectq/backends/__init__.py | 11 +- projectq/backends/_aqt/_aqt.py | 85 ++++---- projectq/backends/_aqt/_aqt_http_client.py | 56 +++--- projectq/backends/_aqt/_aqt_test.py | 13 +- projectq/backends/_awsbraket/_awsbraket.py | 67 ++++--- .../_awsbraket/_awsbraket_boto3_client.py | 95 ++++----- .../_awsbraket_boto3_client_test.py | 1 + .../_awsbraket_boto3_client_test_fixtures.py | 28 ++- .../backends/_awsbraket/_awsbraket_test.py | 47 +++-- .../_awsbraket/_awsbraket_test_fixtures.py | 21 +- projectq/backends/_circuits/__init__.py | 5 +- projectq/backends/_circuits/_drawer.py | 119 +++++------ .../backends/_circuits/_drawer_matplotlib.py | 85 ++++---- .../_circuits/_drawer_matplotlib_test.py | 4 +- projectq/backends/_circuits/_drawer_test.py | 7 +- projectq/backends/_circuits/_plot.py | 105 +++++----- projectq/backends/_circuits/_plot_test.py | 4 +- projectq/backends/_circuits/_to_latex.py | 28 +-- projectq/backends/_circuits/_to_latex_test.py | 19 +- .../{_ionq/_ionq_exc.py => _exceptions.py} | 22 +-- projectq/backends/_ibm/_ibm.py | 68 ++++--- projectq/backends/_ibm/_ibm_http_client.py | 67 +++---- .../backends/_ibm/_ibm_http_client_test.py | 84 ++++---- projectq/backends/_ibm/_ibm_test.py | 20 +- projectq/backends/_ionq/_ionq.py | 62 +++--- projectq/backends/_ionq/_ionq_http_client.py | 17 +- .../backends/_ionq/_ionq_http_client_test.py | 2 +- projectq/backends/_ionq/_ionq_mapper.py | 15 +- projectq/backends/_ionq/_ionq_mapper_test.py | 2 +- projectq/backends/_ionq/_ionq_test.py | 7 +- projectq/backends/_printer.py | 20 +- projectq/backends/_printer_test.py | 5 +- projectq/backends/_resource.py | 28 +-- projectq/backends/_resource_test.py | 5 +- projectq/backends/_sim/__init__.py | 2 +- .../backends/_sim/_classical_simulator.py | 51 +++-- .../_sim/_classical_simulator_test.py | 16 +- projectq/backends/_sim/_pysim.py | 42 ++-- projectq/backends/_sim/_simulator.py | 159 +++++++-------- projectq/backends/_sim/_simulator_test.py | 26 ++- .../backends/_sim/_simulator_test_fixtures.py | 11 +- projectq/backends/_unitary.py | 16 +- projectq/backends/_unitary_test.py | 21 +- projectq/cengines/__init__.py | 17 +- projectq/cengines/_basicmapper.py | 20 +- projectq/cengines/_basicmapper_test.py | 4 +- projectq/cengines/_basics.py | 66 +++---- projectq/cengines/_basics_test.py | 35 ++-- projectq/cengines/_cmdmodifier.py | 8 +- projectq/cengines/_cmdmodifier_test.py | 6 +- projectq/cengines/_ibm5qubitmapper.py | 90 ++++----- projectq/cengines/_ibm5qubitmapper_test.py | 18 +- projectq/cengines/_linearmapper.py | 40 ++-- projectq/cengines/_linearmapper_test.py | 125 ++++++------ projectq/cengines/_main.py | 28 +-- projectq/cengines/_main_test.py | 4 +- projectq/cengines/_manualmapper.py | 13 +- projectq/cengines/_manualmapper_test.py | 6 +- projectq/cengines/_optimize.py | 44 ++--- projectq/cengines/_optimize_test.py | 10 +- .../cengines/_replacer/_decomposition_rule.py | 38 ++-- .../_replacer/_decomposition_rule_set.py | 76 +++----- .../_replacer/_decomposition_rule_test.py | 1 + projectq/cengines/_replacer/_replacer.py | 40 ++-- projectq/cengines/_replacer/_replacer_test.py | 6 +- projectq/cengines/_swapandcnotflipper.py | 33 ++-- projectq/cengines/_swapandcnotflipper_test.py | 14 +- projectq/cengines/_tagremover.py | 12 +- projectq/cengines/_tagremover_test.py | 4 +- projectq/cengines/_testengine.py | 27 ++- projectq/cengines/_testengine_test.py | 6 +- projectq/cengines/_twodmapper.py | 50 +++-- projectq/cengines/_twodmapper_test.py | 31 ++- projectq/libs/hist/_histogram.py | 3 +- projectq/libs/hist/_histogram_test.py | 6 +- projectq/libs/math/__init__.py | 11 +- projectq/libs/math/_constantmath.py | 31 ++- projectq/libs/math/_constantmath_test.py | 7 +- projectq/libs/math/_default_rules.py | 33 ++-- projectq/libs/math/_gates.py | 184 +++++++++--------- projectq/libs/math/_gates_math_test.py | 19 +- projectq/libs/math/_gates_test.py | 14 +- projectq/libs/math/_quantummath.py | 43 ++-- projectq/libs/math/_quantummath_test.py | 15 +- projectq/libs/revkit/__init__.py | 2 +- projectq/libs/revkit/_control_function.py | 13 +- .../libs/revkit/_control_function_test.py | 1 - projectq/libs/revkit/_permutation.py | 24 +-- projectq/libs/revkit/_permutation_test.py | 1 - projectq/libs/revkit/_phase.py | 42 ++-- projectq/libs/revkit/_phase_test.py | 6 +- projectq/libs/revkit/_utils.py | 2 +- projectq/meta/__init__.py | 15 +- projectq/meta/_compute.py | 65 +++---- projectq/meta/_compute_test.py | 11 +- projectq/meta/_control.py | 22 +-- projectq/meta/_control_test.py | 13 +- projectq/meta/_dagger.py | 20 +- projectq/meta/_dagger_test.py | 9 +- projectq/meta/_dirtyqubit.py | 15 +- projectq/meta/_dirtyqubit_test.py | 4 +- projectq/meta/_exceptions.py | 24 +++ projectq/meta/_logicalqubit.py | 12 +- projectq/meta/_loop.py | 30 ++- projectq/meta/_loop_test.py | 11 +- projectq/meta/_util.py | 18 +- projectq/meta/_util_test.py | 1 - projectq/ops/__init__.py | 30 +-- projectq/ops/_basics.py | 107 +++++----- projectq/ops/_basics_test.py | 9 +- projectq/ops/_command.py | 85 ++++---- projectq/ops/_command_test.py | 9 +- projectq/ops/_gates.py | 151 ++++++++------ projectq/ops/_gates_test.py | 12 +- projectq/ops/_metagates.py | 57 ++---- projectq/ops/_metagates_test.py | 23 ++- projectq/ops/_qaagate.py | 6 +- projectq/ops/_qaagate_test.py | 2 +- projectq/ops/_qftgate.py | 7 +- projectq/ops/_qpegate.py | 6 +- projectq/ops/_qpegate_test.py | 2 +- projectq/ops/_qubit_operator.py | 35 ++-- projectq/ops/_qubit_operator_test.py | 6 +- projectq/ops/_shortcuts.py | 10 +- projectq/ops/_shortcuts_test.py | 4 +- projectq/ops/_state_prep.py | 26 ++- projectq/ops/_state_prep_test.py | 2 +- projectq/ops/_time_evolution.py | 25 +-- projectq/ops/_time_evolution_test.py | 5 +- .../ops/_uniformly_controlled_rotation.py | 25 +-- .../_uniformly_controlled_rotation_test.py | 4 +- projectq/setups/_utils.py | 30 +-- projectq/setups/aqt.py | 25 +-- projectq/setups/aqt_test.py | 56 +++--- projectq/setups/awsbraket.py | 26 ++- projectq/setups/awsbraket_test.py | 5 +- projectq/setups/decompositions/__init__.py | 12 +- projectq/setups/decompositions/_gates_test.py | 10 +- .../decompositions/amplitudeamplification.py | 6 +- .../amplitudeamplification_test.py | 8 +- .../decompositions/arb1qubit2rzandry.py | 13 +- .../decompositions/arb1qubit2rzandry_test.py | 4 +- .../decompositions/carb1qubit2cnotrzandry.py | 11 +- projectq/setups/decompositions/cnot2cz.py | 7 +- .../setups/decompositions/cnot2cz_test.py | 3 +- projectq/setups/decompositions/cnot2rxx.py | 9 +- .../setups/decompositions/cnot2rxx_test.py | 2 +- .../setups/decompositions/cnu2toffoliandcu.py | 17 +- .../decompositions/cnu2toffoliandcu_test.py | 2 +- .../setups/decompositions/controlstate.py | 11 +- .../decompositions/controlstate_test.py | 9 +- projectq/setups/decompositions/crz2cxandrz.py | 2 +- projectq/setups/decompositions/entangle.py | 2 +- projectq/setups/decompositions/h2rx.py | 7 +- projectq/setups/decompositions/h2rx_test.py | 2 +- .../setups/decompositions/phaseestimation.py | 4 +- .../decompositions/phaseestimation_test.py | 45 +++-- .../decompositions/qft2crandhadamard.py | 2 +- .../setups/decompositions/qubitop2onequbit.py | 5 +- .../decompositions/qubitop2onequbit_test.py | 5 +- projectq/setups/decompositions/r2rzandph.py | 2 +- projectq/setups/decompositions/rx2rz.py | 9 +- projectq/setups/decompositions/ry2rz.py | 7 +- projectq/setups/decompositions/rz2rx.py | 8 +- projectq/setups/decompositions/rz2rx_test.py | 1 + .../setups/decompositions/sqrtswap2cnot.py | 6 +- .../decompositions/sqrtswap2cnot_test.py | 5 +- .../setups/decompositions/stateprep2cnot.py | 11 +- .../decompositions/stateprep2cnot_test.py | 7 +- projectq/setups/decompositions/swap2cnot.py | 4 +- .../setups/decompositions/time_evolution.py | 12 +- .../decompositions/time_evolution_test.py | 20 +- .../decompositions/toffoli2cnotandtgate.py | 2 +- .../uniformlycontrolledr2cnot.py | 5 +- .../uniformlycontrolledr2cnot_test.py | 4 +- projectq/setups/default.py | 17 +- projectq/setups/grid.py | 4 +- projectq/setups/grid_test.py | 5 +- projectq/setups/ibm.py | 46 ++--- projectq/setups/ibm_test.py | 56 +++--- projectq/setups/ionq.py | 8 +- projectq/setups/ionq_test.py | 2 +- projectq/setups/linear.py | 4 +- projectq/setups/linear_test.py | 5 +- projectq/setups/restrictedgateset.py | 71 +++---- projectq/setups/restrictedgateset_test.py | 9 +- projectq/setups/trapped_ion_decomposer.py | 35 ++-- .../setups/trapped_ion_decomposer_test.py | 12 +- projectq/tests/_factoring_test.py | 4 +- projectq/types/_qubit.py | 33 +--- projectq/types/_qubit_test.py | 2 +- pyproject.toml | 6 +- setup.cfg | 2 +- setup.py | 68 ++++--- 215 files changed, 2535 insertions(+), 2600 deletions(-) rename projectq/backends/{_ionq/_ionq_exc.py => _exceptions.py} (70%) create mode 100644 projectq/meta/_exceptions.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 03afd8fe9..1d179c97e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,56 +12,77 @@ # # See https://github.com/pre-commit/pre-commit +--- + ci: - skip: [check-manifest] + skip: [check-manifest] repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 - hooks: - - id: check-added-large-files - - id: check-case-conflict - - id: check-merge-conflict - - id: check-symlinks - - id: check-yaml - - id: check-toml - - id: debug-statements - - id: end-of-file-fixer - - id: mixed-line-ending - - id: trailing-whitespace - - id: fix-encoding-pragma + - repo: meta + hooks: + - id: check-useless-excludes + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: check-toml + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + - id: fix-encoding-pragma + + # Changes tabs to spaces + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.1.10 + hooks: + - id: remove-tabs -# Changes tabs to spaces -- repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.10 - hooks: - - id: remove-tabs + - repo: https://github.com/PyCQA/isort + rev: 5.9.1 + hooks: + - id: isort + name: isort (python) -- repo: https://github.com/psf/black - rev: 21.5b1 - hooks: - - id: black - language_version: python3 - # This is a slow hook, so only run this if --hook-stage manual is passed - stages: [manual] + - repo: https://github.com/psf/black + rev: 21.5b1 + hooks: + - id: black + language_version: python3 + # This is a slow hook, so only run this if --hook-stage manual is passed + stages: [manual] -- repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - exclude: ^(docs/.*|tools/.*)$ + - repo: https://gitlab.com/PyCQA/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + name: flake8-strict + exclude: ^(.*_test\.py)$ + additional_dependencies: [flake8-comprehensions, flake8-breakpoint, + flake8-eradicate, flake8-mutable, + flake8-docstrings] + - id: flake8 + name: flake8-test-files + additional_dependencies: [flake8-comprehensions, flake8-breakpoint, + flake8-eradicate, flake8-mutable] + files: ^(.*_test\.py)$ -- repo: https://github.com/pre-commit/mirrors-pylint - rev: 'v3.0.0a3' - hooks: - - id: pylint - args: ['--score=n'] - # This is a slow hook, so only run this if --hook-stage manual is passed - stages: [manual] - additional_dependencies: ['pybind11>=2.6', 'numpy', 'requests', 'boto3', 'matplotlib', 'networkx'] + - repo: https://github.com/pre-commit/mirrors-pylint + rev: 'v3.0.0a3' + hooks: + - id: pylint + args: ['--score=n'] + # This is a slow hook, so only run this if --hook-stage manual is passed + stages: [manual] + additional_dependencies: [pybind11>=2.6, numpy, requests, boto3, matplotlib, networkx, sympy] -- repo: https://github.com/mgedmin/check-manifest - rev: "0.46" - hooks: - - id: check-manifest - additional_dependencies: ['setuptools-scm', 'pybind11>=2.6'] + - repo: https://github.com/mgedmin/check-manifest + rev: '0.46' + hooks: + - id: check-manifest + additional_dependencies: ['setuptools-scm', 'pybind11>=2.6'] diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b8a64b87..5adc9dbbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Prevent infinite recursion errors when too many compiler engines are added to the MainEngine +- Error in testing the decomposition for the phase estimation gate +- Make all docstrings PEP257 compliant ### Removed + +- Some compatibility code for Python 2.x + ### Repository +- Added `isort` to the list of pre-commit hooks +- Added `flake8-docstrings` to the flake8 checks to ensure PEP257 compliance for docstrings + ## [0.6.1] - 2021-06-23 ### Repository diff --git a/docs/conf.py b/docs/conf.py index 46527b4fa..4b2b99fa2 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,26 +16,20 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# pylint: skip-file -import os -import sys +"""Configuration file for generating the documentation for ProjectQ.""" -sys.path.insert(0, os.path.abspath('..')) +# pylint: disable=invalid-name +import functools +import importlib +import inspect +import os +import sys from importlib.metadata import version -import projectq - -# Also import all the modules that are not automatically imported -import projectq.libs.math -import projectq.libs.revkit -import projectq.setups.default -import projectq.setups.grid -import projectq.setups.ibm -import projectq.setups.linear -import projectq.setups.restrictedgateset -import projectq.setups.decompositions +sys.path.insert(0, os.path.abspath('..')) # for projectq +sys.path.append(os.path.abspath('.')) # for package_description # -- General configuration ------------------------------------------------ @@ -43,10 +37,6 @@ # # needs_sphinx = '1.0' -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -import sphinx_rtd_theme extensions = [ 'sphinx.ext.autodoc', @@ -76,7 +66,7 @@ # General information about the project. project = 'ProjectQ' -copyright = '2017, ProjectQ' +copyright = '2017-2021, ProjectQ' # pylint: disable=redefined-builtin author = 'ProjectQ' # The version info for the project you're documenting, acts as replacement for @@ -85,8 +75,8 @@ # # The short X.Y version. -release = version('projectq') -version = '.'.join(release.split('.')[:2]) +release = version('projectq') # Full version string +version = '.'.join(release.split('.')[:3]) # X.Y.Z # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -361,13 +351,23 @@ # texinfo_no_detailmenu = False # -- Options for sphinx.ext.linkcode -------------------------------------- -import inspect + + +def recursive_getattr(obj, attr, *args): + """Recursively get the attributes of a Python object.""" + + def _getattr(obj, attr): + return getattr(obj, attr, *args) + + return functools.reduce(_getattr, [obj] + attr.split('.')) def linkcode_resolve(domain, info): + """Change URLs in documentation on the fly.""" # Copyright 2018 ProjectQ (www.projectq.ch), all rights reserved. on_rtd = os.environ.get('READTHEDOCS') == 'True' github_url = "https://github.com/ProjectQ-Framework/ProjectQ/tree/" + github_tag = 'v' + version if on_rtd: rtd_tag = os.environ.get('READTHEDOCS_VERSION') if rtd_tag == 'latest': @@ -385,53 +385,53 @@ def linkcode_resolve(domain, info): github_tag = ''.join(github_tag) else: github_tag = rtd_tag - else: - github_tag = 'v' + version + if domain != 'py': return None - else: + try: + module = importlib.import_module(info['module']) + obj = recursive_getattr(module, info['fullname']) + except (AttributeError, ValueError): + # AttributeError: + # Object might be a non-static attribute of a class, e.g., self.num_qubits, which would only exist after init + # was called. + # For the moment we don't need a link for that as there is a link for the class already + # + # ValueError: + # info['module'] is empty + return None + try: + filepath = inspect.getsourcefile(obj) + line_number = inspect.getsourcelines(obj)[1] + except TypeError: + # obj might be a property or a static class variable, e.g., + # loop_tag_id in which case obj is an int and inspect will fail try: - if 'module' in info and 'fullname' in info and info['module'] and info['fullname']: - obj = eval(info['module'] + '.' + info['fullname']) + # load obj one hierarchy higher (either class or module) + new_higher_name = info['fullname'].split('.') + module = importlib.import_module(info['module']) + if len(new_higher_name) > 1: + obj = module else: - return None - except AttributeError: - # Object might be a non-static attribute of a class, e.g., - # self.num_qubits, which would only exist after init was called. - # For the moment we don't need a link for that as there is a link - # for the class already - return None - try: + obj = recursive_getattr(module, '.' + '.'.join(new_higher_name[:-1])) + filepath = inspect.getsourcefile(obj) line_number = inspect.getsourcelines(obj)[1] - except: - # obj might be a property or a static class variable, e.g., - # loop_tag_id in which case obj is an int and inspect will fail - try: - # load obj one hierarchy higher (either class or module) - new_higher_name = info['fullname'].split('.') - if len(new_higher_name) <= 1: - obj = eval(info['module']) - else: - obj = eval(info['module'] + '.' + '.'.join(new_higher_name[:-1])) - filepath = inspect.getsourcefile(obj) - line_number = inspect.getsourcelines(obj)[1] - except: - return None - # Only require relative path projectq/relative_path - projectq_path = inspect.getsourcefile(projectq)[:-11] - relative_path = os.path.relpath(filepath, projectq_path) - url = github_url + github_tag + "/projectq/" + relative_path + "#L" + str(line_number) - return url + except AttributeError: + return None + # Calculate the relative path of the object with respect to the root directory (ie. projectq/some/path/to/a/file.py) + projectq_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) + os.path.sep + idx = len(projectq_path) + relative_path = filepath[idx:] -# ------------------------------------------------------------------------------ + url = github_url + github_tag + "/" + relative_path + "#L" + str(line_number) + return url -import importlib -sys.path.append(os.path.abspath('.')) -desc = importlib.import_module('package_description') +# ------------------------------------------------------------------------------ +desc = importlib.import_module('package_description') PackageDescription = desc.PackageDescription # ------------------------------------------------------------------------------ @@ -458,7 +458,8 @@ def linkcode_resolve(domain, info): PackageDescription( 'libs.math', desc=''' -A tiny math library which will be extended thoughout the next weeks. Right now, it only contains the math functions necessary to run Beauregard's implementation of Shor's algorithm. +A tiny math library which will be extended thoughout the next weeks. Right now, it only contains the math functions +necessary to run Beauregard's implementation of Shor's algorithm. ''', ), PackageDescription( @@ -486,40 +487,46 @@ def linkcode_resolve(domain, info): The integration of RevKit into ProjectQ and other quantum programming languages is described in the paper - * Mathias Soeken, Thomas Haener, and Martin Roetteler "Programming Quantum Computers Using Design Automation," in: Design Automation and Test in Europe (2018) [`arXiv:1803.01022 `_] + * Mathias Soeken, Thomas Haener, and Martin Roetteler "Programming Quantum Computers Using Design Automation," + in: Design Automation and Test in Europe (2018) [`arXiv:1803.01022 `_] ''', module_special_members='__init__,__or__', ), PackageDescription( 'libs', desc=''' -The library collection of ProjectQ which, for now, consists of a tiny math library and an interface library to RevKit. Soon, more libraries will be added. +The library collection of ProjectQ which, for now, consists of a tiny math library and an interface library to RevKit. +Soon, more libraries will be added. ''', ), PackageDescription( 'meta', desc=''' Contains meta statements which allow more optimal code while making it easier for users to write their code. -Examples are `with Compute`, followed by an automatic uncompute or `with Control`, which allows the user to condition an entire code block upon the state of a qubit. +Examples are `with Compute`, followed by an automatic uncompute or `with Control`, which allows the user to condition +an entire code block upon the state of a qubit. ''', ), PackageDescription( 'ops', desc=''' -The operations collection consists of various default gates and is a work-in-progress, as users start to work with ProjectQ. +The operations collection consists of various default gates and is a work-in-progress, as users start to work with +ProjectQ. ''', module_special_members='__init__,__or__', ), PackageDescription( 'setups.decompositions', desc=''' -The decomposition package is a collection of gate decomposition / replacement rules which can be used by, e.g., the AutoReplacer engine. +The decomposition package is a collection of gate decomposition / replacement rules which can be used by, +e.g., the AutoReplacer engine. ''', ), PackageDescription( 'setups', desc=''' -The setups package contains a collection of setups which can be loaded by the `MainEngine`. Each setup contains a `get_engine_list` function which returns a list of compiler engines: +The setups package contains a collection of setups which can be loaded by the `MainEngine`. +Each setup contains a `get_engine_list` function which returns a list of compiler engines: Example: .. code-block:: python @@ -539,9 +546,10 @@ def linkcode_resolve(domain, info): ), PackageDescription( 'types', - ''' -The types package contains quantum types such as Qubit, Qureg, and WeakQubitRef. With further development of the math library, also quantum integers, quantum fixed point numbers etc. will be added. -''', + ( + 'The types package contains quantum types such as Qubit, Qureg, and WeakQubitRef. With further development ' + 'of the math library, also quantum integers, quantum fixed point numbers etc. will be added.' + ), ), ] # ------------------------------------------------------------------------------ diff --git a/docs/package_description.py b/docs/package_description.py index 6beeff848..fcfbb5b21 100644 --- a/docs/package_description.py +++ b/docs/package_description.py @@ -13,15 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing some helper classes for generating the documentation""" +"""Module containing some helper classes for generating the documentation.""" +import importlib import inspect -import sys -import os +import pkgutil class PackageDescription: # pylint: disable=too-many-instance-attributes,too-few-public-methods - """Class representing a package description""" + """A package description class.""" package_list = [] @@ -35,27 +35,24 @@ def __init__( # pylint: disable=too-many-arguments helper_submodules=None, ): """ + Initialize a PackageDescription object. + Args: name (str): Name of ProjectQ module desc (str): (optional) Description of module - module_special_members (str): (optional) Special members to include - in the documentation of the module - submodule_special_members (str): (optional) Special members to - include in the documentation of submodules - submodules_desc (str): (optional) Description to print out before - the list of submodules - helper_submodules (list): (optional) List of tuples for helper - sub-modules to include in the documentation. - Tuples are (section_title, submodukle_name, - automodule_properties) + module_special_members (str): (optional) Special members to include in the documentation of the module + submodule_special_members (str): (optional) Special members to include in the documentation of submodules + submodules_desc (str): (optional) Description to print out before the list of submodules + helper_submodules (list): (optional) List of tuples for helper sub-modules to include in the + documentation. + Tuples are (section_title, submodukle_name, automodule_properties) """ - self.name = pkg_name self.desc = desc if pkg_name not in PackageDescription.package_list: PackageDescription.package_list.append(pkg_name) - self.module = sys.modules['projectq.{}'.format(self.name)] + self.module = importlib.import_module('projectq.{}'.format(self.name)) self.module_special_members = module_special_members self.submodule_special_members = submodule_special_members @@ -63,15 +60,14 @@ def __init__( # pylint: disable=too-many-arguments self.helper_submodules = helper_submodules - module_root = os.path.dirname(self.module.__file__) - sub = [ - (name, obj) - for name, obj in inspect.getmembers( - self.module, - lambda obj: inspect.ismodule(obj) and hasattr(obj, '__file__') and module_root in obj.__file__, - ) - if pkg_name[0] != '_' - ] + sub = [] + for _, module_name, _ in pkgutil.iter_modules(self.module.__path__, self.module.__name__ + '.'): + if not module_name.endswith('_test'): + try: + idx = len(self.module.__name__) + 1 + sub.append((module_name[idx:], importlib.import_module(module_name))) + except ImportError: + pass self.subpackages = [] self.submodules = [] @@ -99,9 +95,7 @@ def __init__( # pylint: disable=too-many-arguments self.members.sort(key=lambda x: x[0].lower()) def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-statements - """ - Conversion to ReST formatted string. - """ + """Conversion to ReST formatted string.""" new_lines = [] new_lines.append(self.name) new_lines.append('=' * len(self.name)) diff --git a/examples/aqt.py b/examples/aqt.py index e5fb6f658..68da13606 100644 --- a/examples/aqt.py +++ b/examples/aqt.py @@ -1,19 +1,22 @@ # -*- coding: utf-8 -*- # pylint: skip-file -import matplotlib.pyplot as plt +"""Example of running a quantum circuit using the AQT APIs.""" + import getpass +import matplotlib.pyplot as plt + +import projectq.setups.aqt from projectq import MainEngine from projectq.backends import AQTBackend from projectq.libs.hist import histogram -from projectq.ops import Measure, Entangle, All -import projectq.setups.aqt +from projectq.ops import All, Entangle, Measure def run_entangle(eng, num_qubits=3): """ - Runs an entangling operation on the provided compiler engine. + Run an entangling operation on the provided compiler engine. Args: eng (MainEngine): Main compiler engine to use. diff --git a/examples/bellpair_circuit.py b/examples/bellpair_circuit.py index d652ebc33..c4343f801 100755 --- a/examples/bellpair_circuit.py +++ b/examples/bellpair_circuit.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Example implementation of a quantum circuit generating a Bell pair state.""" + import matplotlib.pyplot as plt +from teleport import create_bell_pair from projectq import MainEngine from projectq.backends import CircuitDrawer -from projectq.setups.default import get_engine_list from projectq.libs.hist import histogram - -from teleport import create_bell_pair +from projectq.setups.default import get_engine_list # create a main compiler engine drawing_engine = CircuitDrawer() diff --git a/examples/control_tester.py b/examples/control_tester.py index 5e671fafe..8f9463735 100755 --- a/examples/control_tester.py +++ b/examples/control_tester.py @@ -14,13 +14,15 @@ # limitations under the License. # pylint: skip-file +"""Example of using the control state for control qubits.""" from projectq.cengines import MainEngine from projectq.meta import Control -from projectq.ops import All, X, Measure, CtrlAll +from projectq.ops import All, CtrlAll, Measure, X def run_circuit(eng, circuit_num): + """Run the quantum circuit.""" qubit = eng.allocate_qureg(2) ctrl_fail = eng.allocate_qureg(3) ctrl_success = eng.allocate_qureg(3) diff --git a/examples/gate_zoo.py b/examples/gate_zoo.py index 404d9822b..c8e97ed97 100644 --- a/examples/gate_zoo.py +++ b/examples/gate_zoo.py @@ -1,45 +1,45 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Showcase most of the quantum gates available in ProjectQ.""" + import os import sys from projectq import MainEngine from projectq.backends import CircuitDrawer from projectq.ops import ( - X, - Y, - Z, + CNOT, + QFT, + All, + Barrier, + BasicMathGate, + C, + Entangle, + H, + Measure, + Ph, + QubitOperator, Rx, Ry, Rz, - Ph, S, - T, - H, - Toffoli, - Barrier, - Swap, SqrtSwap, SqrtX, - C, - CNOT, - Entangle, - QFT, - TimeEvolution, - QubitOperator, - BasicMathGate, - Measure, - All, + Swap, + T, Tensor, + TimeEvolution, + Toffoli, + X, + Y, + Z, get_inverse, ) def zoo_profile(): - ''' - Generate and display the zoo of quantum gates. - ''' + """Generate and display the zoo of quantum gates.""" # create a main compiler engine with a drawing backend drawing_engine = CircuitDrawer() locations = {0: 1, 1: 2, 2: 0, 3: 3} @@ -104,7 +104,7 @@ def add(x, y): def openfile(filename): - ''' + """ Open a file. Args: @@ -112,7 +112,7 @@ def openfile(filename): Return: bool: succeed if True. - ''' + """ platform = sys.platform if platform == "linux" or platform == "linux2": os.system('xdg-open %s' % filename) diff --git a/examples/grover.py b/examples/grover.py index feff9ef58..539fc525a 100755 --- a/examples/grover.py +++ b/examples/grover.py @@ -1,16 +1,18 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Example implementation of Grover's algorithm.""" + import math from projectq import MainEngine -from projectq.ops import H, Z, X, Measure, All -from projectq.meta import Loop, Compute, Uncompute, Control +from projectq.meta import Compute, Control, Loop, Uncompute +from projectq.ops import All, H, Measure, X, Z def run_grover(eng, n, oracle): """ - Runs Grover's algorithm on n qubit using the provided quantum oracle. + Run Grover's algorithm on n qubit using the provided quantum oracle. Args: eng (MainEngine): Main compiler engine to run Grover on. @@ -62,8 +64,10 @@ def run_grover(eng, n, oracle): def alternating_bits_oracle(eng, qubits, output): """ - Marks the solution string 1,0,1,0,...,0,1 by flipping the output qubit, - conditioned on qubits being equal to the alternating bit-string. + Alternating bit oracle. + + Mark the solution string 1,0,1,0,...,0,1 by flipping the output qubit, conditioned on qubits being equal to the + alternating bit-string. Args: eng (MainEngine): Main compiler engine the algorithm is being run on. diff --git a/examples/hws4.py b/examples/hws4.py index 14a4fe551..8ba5b6174 100644 --- a/examples/hws4.py +++ b/examples/hws4.py @@ -1,14 +1,17 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Example of a 4-qubit phase function.""" + from projectq.cengines import MainEngine -from projectq.ops import All, H, X, Measure -from projectq.meta import Compute, Uncompute from projectq.libs.revkit import PhaseOracle +from projectq.meta import Compute, Uncompute +from projectq.ops import All, H, Measure, X # phase function def f(a, b, c, d): + """Phase function.""" return (a and b) ^ (c and d) diff --git a/examples/hws6.py b/examples/hws6.py index 38892f371..bf5540d07 100644 --- a/examples/hws6.py +++ b/examples/hws6.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from projectq.cengines import MainEngine -from projectq.ops import All, H, X, Measure -from projectq.meta import Compute, Uncompute, Dagger -from projectq.libs.revkit import PhaseOracle, PermutationOracle +"""Example of a 6-qubit phase function.""" import revkit +from projectq.cengines import MainEngine +from projectq.libs.revkit import PermutationOracle, PhaseOracle +from projectq.meta import Compute, Dagger, Uncompute +from projectq.ops import All, H, Measure, X + # phase function def f(a, b, c, d, e, f): + """Phase function.""" return (a and b) ^ (c and d) ^ (e and f) diff --git a/examples/ibm.py b/examples/ibm.py index 6914d051f..24bd0c097 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -1,19 +1,22 @@ # -*- coding: utf-8 -*- # pylint: skip-file -import matplotlib.pyplot as plt +"""Example of running a quantum circuit using the IBM QE APIs.""" + import getpass +import matplotlib.pyplot as plt + +import projectq.setups.ibm from projectq import MainEngine from projectq.backends import IBMBackend from projectq.libs.hist import histogram -from projectq.ops import Measure, Entangle, All -import projectq.setups.ibm +from projectq.ops import All, Entangle, Measure def run_entangle(eng, num_qubits=3): """ - Runs an entangling operation on the provided compiler engine. + Run an entangling operation on the provided compiler engine. Args: eng (MainEngine): Main compiler engine to use. diff --git a/examples/ionq.py b/examples/ionq.py index 8ca8cc66b..71f0c6831 100644 --- a/examples/ionq.py +++ b/examples/ionq.py @@ -29,7 +29,7 @@ def run_entangle(eng, num_qubits=3): """ - Runs an entangling operation on the provided compiler engine. + Run an entangling operation on the provided compiler engine. Args: eng (MainEngine): Main compiler engine to use. diff --git a/examples/ionq_bv.py b/examples/ionq_bv.py index 61aa3c16e..afaf48c78 100644 --- a/examples/ionq_bv.py +++ b/examples/ionq_bv.py @@ -30,13 +30,13 @@ def oracle(qureg, input_size, s): """Apply the 'oracle'.""" - for bit in range(input_size): if s[input_size - 1 - bit] == '1': CX | (qureg[bit], qureg[input_size]) def run_bv_circuit(eng, input_size, s_int): + """Run the quantum circuit.""" s = ('{0:0' + str(input_size) + 'b}').format(s_int) print("Secret string: ", s) print("Number of qubits: ", str(input_size + 1)) diff --git a/examples/ionq_half_adder.py b/examples/ionq_half_adder.py index 798ed4ac4..905fa38e9 100644 --- a/examples/ionq_half_adder.py +++ b/examples/ionq_half_adder.py @@ -14,7 +14,7 @@ # limitations under the License. # pylint: skip-file -"""Example of a basic 'half-adder' circuit using an IonQBackend""" +"""Example of a basic 'half-adder' circuit using an IonQBackend.""" import getpass import random @@ -30,6 +30,7 @@ def run_half_adder(eng): + """Run the half-adder circuit.""" # allocate the quantum register to entangle circuit = eng.allocate_qureg(4) qubit1, qubit2, qubit3, qubit4 = circuit diff --git a/examples/quantum_random_numbers.py b/examples/quantum_random_numbers.py index 796603d37..afa09dc4f 100755 --- a/examples/quantum_random_numbers.py +++ b/examples/quantum_random_numbers.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from projectq.ops import H, Measure +"""Example of a simple quantum random number generator.""" + from projectq import MainEngine +from projectq.ops import H, Measure # create a main compiler engine eng = MainEngine() diff --git a/examples/quantum_random_numbers_ibm.py b/examples/quantum_random_numbers_ibm.py index 77e427434..adfcb7101 100755 --- a/examples/quantum_random_numbers_ibm.py +++ b/examples/quantum_random_numbers_ibm.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- # pylint: skip-file +"""Example of a simple quantum random number generator using IBM's API.""" + import projectq.setups.ibm -from projectq.ops import H, Measure from projectq import MainEngine from projectq.backends import IBMBackend +from projectq.ops import H, Measure # create a main compiler engine eng = MainEngine(IBMBackend(), engine_list=projectq.setups.ibm.get_engine_list()) diff --git a/examples/shor.py b/examples/shor.py index f604abb25..39dce16dc 100755 --- a/examples/shor.py +++ b/examples/shor.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from __future__ import print_function +"""Example implementation of Shor's algorithm.""" import math import random @@ -17,7 +17,7 @@ import projectq.libs.math import projectq.setups.decompositions -from projectq.backends import Simulator, ResourceCounter +from projectq.backends import ResourceCounter, Simulator from projectq.cengines import ( AutoReplacer, DecompositionRuleSet, @@ -28,12 +28,12 @@ ) from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN from projectq.meta import Control -from projectq.ops import All, BasicMathGate, get_inverse, H, Measure, QFT, R, Swap, X +from projectq.ops import QFT, All, BasicMathGate, H, Measure, R, Swap, X, get_inverse def run_shor(eng, N, a, verbose=False): """ - Runs the quantum subroutine of Shor's algorithm for factoring. + Run the quantum subroutine of Shor's algorithm for factoring. Args: eng (MainEngine): Main compiler engine to use. @@ -92,6 +92,7 @@ def run_shor(eng, N, a, verbose=False): # Filter function, which defines the gate set for the first optimization # (don't decompose QFTs and iQFTs to make cancellation easier) def high_level_gates(eng, cmd): + """Filter high-level gates.""" g = cmd.gate if g == QFT or get_inverse(g) == QFT or g == Swap: return True diff --git a/examples/teleport.py b/examples/teleport.py index 2a6b964da..4d2e684aa 100755 --- a/examples/teleport.py +++ b/examples/teleport.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from projectq.ops import CNOT, H, Measure, Rz, X, Z +"""Example of a quantum teleportation circuit.""" + from projectq import MainEngine -from projectq.meta import Dagger, Control +from projectq.meta import Control, Dagger +from projectq.ops import CNOT, H, Measure, Rz, X, Z def create_bell_pair(eng): r""" - Returns a Bell-pair (two qubits in state :math:`|A\rangle \otimes |B - \rangle = \frac 1{\sqrt 2} \left( |0\rangle\otimes|0\rangle + |1\rangle - \otimes|1\rangle \right)`). + Create a Bell pair state with two qubits. + + Returns a Bell-pair (two qubits in state :math:`|A\rangle \otimes |B \rangle = \frac 1{\sqrt 2} \left( + |0\rangle\otimes|0\rangle + |1\rangle \otimes|1\rangle \right)`). Args: eng (MainEngine): MainEngine from which to allocate the qubits. @@ -29,18 +32,16 @@ def create_bell_pair(eng): def run_teleport(eng, state_creation_function, verbose=False): """ - Runs quantum teleportation on the provided main compiler engine. + Run quantum teleportation on the provided main compiler engine. - Creates a state from |0> using the state_creation_function, teleports this - state to Bob who then tries to uncompute his qubit using the inverse of - the state_creation_function. If successful, deleting the qubit won't raise - an error in the underlying Simulator back-end (else it will). + Creates a state from |0> using the state_creation_function, teleports this state to Bob who then tries to + uncompute his qubit using the inverse of the state_creation_function. If successful, deleting the qubit won't + raise an error in the underlying Simulator back-end (else it will). Args: eng (MainEngine): Main compiler engine to run the circuit on. - state_creation_function (function): Function which accepts the main - engine and a qubit in state |0>, which it then transforms to the - state that Alice would like to send to Bob. + state_creation_function (function): Function which accepts the main engine and a qubit in state |0>, which it + then transforms to the state that Alice would like to send to Bob. verbose (bool): If True, info messages will be printed. """ @@ -96,6 +97,7 @@ def run_teleport(eng, state_creation_function, verbose=False): # we would like to send. Bob can then try to uncompute it and, if he # arrives back at |0>, we know that the teleportation worked. def create_state(eng, qb): + """Create a quantum state.""" H | qb Rz(1.21) | qb diff --git a/examples/teleport_circuit.py b/examples/teleport_circuit.py index 1f002b915..1dfd3a485 100755 --- a/examples/teleport_circuit.py +++ b/examples/teleport_circuit.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- # pylint: skip-file -from projectq import MainEngine -from projectq.backends import CircuitDrawer +"""Example if drawing of a quantum teleportation circuit.""" import teleport +from projectq import MainEngine +from projectq.backends import CircuitDrawer + if __name__ == "__main__": # create a main compiler engine with a simulator backend: drawing_engine = CircuitDrawer() @@ -15,7 +17,7 @@ # we just want to draw the teleportation circuit def create_state(eng, qb): - pass + """Create a quantum state.""" # run the teleport and then, let Bob try to uncompute his qubit: teleport.run_teleport(eng, create_state, verbose=False) diff --git a/examples/unitary_simulator.py b/examples/unitary_simulator.py index 420034784..4a840dc70 100644 --- a/examples/unitary_simulator.py +++ b/examples/unitary_simulator.py @@ -22,7 +22,7 @@ from projectq.backends import UnitarySimulator from projectq.cengines import MainEngine from projectq.meta import Control -from projectq.ops import All, X, QFT, Measure, CtrlAll +from projectq.ops import QFT, All, CtrlAll, Measure, X def run_circuit(eng, n_qubits, circuit_num, gate_after_measure=False): diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index 044b78b84..8a8a50646 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -28,12 +28,13 @@ * an interface to the AWS Braket service decives (and simulators) * an interface to the IonQ trapped ionq hardware (and simulator). """ -from ._printer import CommandPrinter -from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib -from ._sim import Simulator, ClassicalSimulator -from ._resource import ResourceCounter -from ._ibm import IBMBackend from ._aqt import AQTBackend from ._awsbraket import AWSBraketBackend +from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib +from ._exceptions import DeviceNotHandledError, DeviceOfflineError, DeviceTooSmall +from ._ibm import IBMBackend from ._ionq import IonQBackend +from ._printer import CommandPrinter +from ._resource import ResourceCounter +from ._sim import ClassicalSimulator, Simulator from ._unitary import UnitarySimulator diff --git a/projectq/backends/_aqt/_aqt.py b/projectq/backends/_aqt/_aqt.py index e250e239c..245b074f6 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -12,17 +12,19 @@ # 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. -""" Back-end to run quantum program on AQT's API.""" + +"""Back-end to run quantum program on AQT's API.""" import math import random from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag -from projectq.ops import Rx, Ry, Rxx, Measure, Allocate, Barrier, Deallocate, FlushGate +from projectq.meta import LogicalQubitIDTag, get_control_count +from projectq.ops import Allocate, Barrier, Deallocate, FlushGate, Measure, Rx, Rxx, Ry from projectq.types import WeakQubitRef -from ._aqt_http_client import send, retrieve +from .._exceptions import InvalidCommandError +from ._aqt_http_client import retrieve, send # _rearrange_result & _format_counts imported and modified from qiskit @@ -44,8 +46,10 @@ def _format_counts(samples, length): class AQTBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """ - The AQT Backend class, which stores the circuit, transforms it to the - appropriate data format, and sends the circuit through the AQT API. + Backend for building circuits and submitting them to the AQT API. + + The AQT Backend class, which stores the circuit, transforms it to the appropriate data format, and sends the + circuit through the AQT API. """ def __init__( @@ -63,24 +67,19 @@ def __init__( Initialize the Backend object. Args: - use_hardware (bool): If True, the code is run on the AQT quantum - chip (instead of using the AQT simulator) - num_runs (int): Number of runs to collect statistics. - (default is 100, max is usually around 200) - verbose (bool): If True, statistics are printed, in addition to - the measurement result being registered (at the end of the - circuit). + use_hardware (bool): If True, the code is run on the AQT quantum chip (instead of using the AQT simulator) + num_runs (int): Number of runs to collect statistics. (default is 100, max is usually around 200) + verbose (bool): If True, statistics are printed, in addition to the measurement result being registered + (at the end of the circuit). token (str): AQT user API token. device (str): name of the AQT device to use. simulator By default - num_retries (int): Number of times to retry to obtain - results from the AQT API. (default is 3000) - interval (float, int): Number of seconds between successive - attempts to obtain results from the AQT API. + num_retries (int): Number of times to retry to obtain results from the AQT API. (default is 3000) + interval (float, int): Number of seconds between successive attempts to obtain results from the AQT API. (default is 1) - retrieve_execution (int): Job ID to retrieve instead of re- - running the circuit (e.g., if previous run timed out). + retrieve_execution (int): Job ID to retrieve instead of re- running the circuit (e.g., if previous run + timed out). """ - BasicEngine.__init__(self) + super().__init__() self._reset() if use_hardware: self.device = device @@ -92,7 +91,7 @@ def __init__( self._token = token self._num_retries = num_retries self._interval = interval - self._probabilities = dict() + self._probabilities = {} self._circuit = [] self._mapper = [] self._measured_ids = [] @@ -130,7 +129,7 @@ def _store(self, cmd): cmd: Command to store """ if self._clear: - self._probabilities = dict() + self._probabilities = {} self._clear = False self._circuit = [] self._allocated_qubits = set() @@ -168,16 +167,16 @@ def _store(self, cmd): return if gate == Barrier: return - raise Exception('Invalid command: ' + str(cmd)) + raise InvalidCommandError('Invalid command: ' + str(cmd)) def _logical_to_physical(self, qb_id): """ Return the physical location of the qubit with the given logical id. + If no mapper is present then simply returns the qubit ID. Args: - qb_id (int): ID of the logical qubit whose position should be - returned. + qb_id (int): ID of the logical qubit whose position should be returned. """ try: mapping = self.main_engine.mapper.current_mapping @@ -199,29 +198,30 @@ def _logical_to_physical(self, qb_id): def get_probabilities(self, qureg): """ - Return the list of basis states with corresponding probabilities. - If input qureg is a subset of the register used for the experiment, - then returns the projected probabilities over the other states. - The measured bits are ordered according to the supplied quantum - register, i.e., the left-most bit in the state-string corresponds to - the first qubit in the supplied quantum register. + Return the probability of the outcome `bit_string` when measuring the quantum register `qureg`. + + Return the list of basis states with corresponding probabilities. If input qureg is a subset of the register + used for the experiment, then returns the projected probabilities over the other states. The measured bits + are ordered according to the supplied quantum register, i.e., the left-most bit in the state-string + corresponds to the first qubit in the supplied quantum register. + Warning: Only call this function after the circuit has been executed! + Args: - qureg (list): Quantum register determining the order of the - qubits. + qureg (list): Quantum register determining the order of the qubits. + Returns: - probability_dict (dict): Dictionary mapping n-bit strings to - probabilities. + probability_dict (dict): Dictionary mapping n-bit strings to probabilities. + Raises: - RuntimeError: If no data is available (i.e., if the circuit has - not been executed). Or if a qubit was supplied which was not - present in the circuit (might have gotten optimized away). + RuntimeError: If no data is available (i.e., if the circuit has not been executed). Or if a qubit was + supplied which was not present in the circuit (might have gotten optimized away). """ if len(self._probabilities) == 0: raise RuntimeError("Please, run the circuit first!") - probability_dict = dict() + probability_dict = {} for state in self._probabilities: mapped_state = ['0'] * len(qureg) for i, qubit in enumerate(qureg): @@ -236,8 +236,7 @@ def _run(self): """ Run the circuit. - Send the circuit via the AQT API using the provided user - token / ask for the user token. + Send the circuit via the AQT API using the provided user token / ask for the user token. """ # finally: measurements # NOTE AQT DOESN'T SEEM TO HAVE MEASUREMENT INSTRUCTIONS (no @@ -303,7 +302,9 @@ def _run(self): def receive(self, command_list): """ - Receives a command list and, for each command, stores it until completion. Upon flush, send the data to the + Receive a list of commands. + + Receive a command list and, for each command, stores it until completion. Upon flush, send the data to the AQT API. Args: diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py index a38b69329..7f869d254 100644 --- a/projectq/backends/_aqt/_aqt_http_client.py +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -12,41 +12,49 @@ # 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. -""" Back-end to run quantum program on AQT cloud platform""" + +"""Back-end to run quantum program on AQT cloud platform.""" import getpass import signal import time import requests -from requests.compat import urljoin from requests import Session +from requests.compat import urljoin + +from .._exceptions import DeviceOfflineError, DeviceTooSmall, RequestTimeoutError # An AQT token can be requested at: # https://gateway-portal.aqt.eu/ + _API_URL = 'https://gateway.aqt.eu/marmot/' class AQT(Session): - """Class managing the session to AQT's APIs""" + """Class managing the session to AQT's APIs.""" def __init__(self): + """Initialize an AQT session with AQT's APIs.""" super().__init__() - self.backends = dict() + self.backends = {} self.timeout = 5.0 self.token = None def update_devices_list(self, verbose=False): """ + Update the internal device list. + Returns: (list): list of available devices - Up to my knowledge there is no proper API call for online devices, - so we just assume that the list from AQT portal always up to date + Note: + Up to my knowledge there is no proper API call for online devices, so we just assume that the list from + AQT portal always up to date """ # TODO: update once the API for getting online devices is available - self.backends = dict() + self.backends = {} self.backends['aqt_simulator'] = {'nq': 11, 'version': '0.0.1', 'url': 'sim/'} self.backends['aqt_simulator_noise'] = { 'nq': 11, @@ -60,7 +68,7 @@ def update_devices_list(self, verbose=False): def is_online(self, device): """ - Check whether a device is currently online + Check whether a device is currently online. Args: device (str): name of the aqt device to use @@ -72,7 +80,7 @@ def is_online(self, device): def can_run_experiment(self, info, device): """ - check if the device is big enough to run the code + Check if the device is big enough to run the code. Args: info (dict): dictionary sent by the backend containing the code to @@ -87,6 +95,8 @@ def can_run_experiment(self, info, device): def authenticate(self, token=None): """ + Authenticate with the AQT Web API. + Args: token (str): AQT user API token. """ @@ -96,7 +106,7 @@ def authenticate(self, token=None): self.token = token def run(self, info, device): - """Run a quantum circuit""" + """Run a quantum circuit.""" argument = { 'data': info['circuit'], 'access_token': self.token, @@ -114,9 +124,7 @@ def run(self, info, device): def get_result( # pylint: disable=too-many-arguments self, device, execution_id, num_retries=3000, interval=1, verbose=False ): - """ - Get the result of an execution - """ + """Get the result of an execution.""" if verbose: print("Waiting for results. [Job ID: {}]".format(execution_id)) @@ -153,21 +161,12 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover if original_sigint_handler is not None: signal.signal(signal.SIGINT, original_sigint_handler) - raise Exception("Timeout. The ID of your submitted job is {}.".format(execution_id)) - - -class DeviceTooSmall(Exception): - """Exception raised if the device is too small to run the circuit""" - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" + raise RequestTimeoutError("Timeout. The ID of your submitted job is {}.".format(execution_id)) def show_devices(verbose=False): """ - Access the list of available devices and their properties (ex: for setup - configuration) + Access the list of available devices and their properties (ex: for setup configuration). Args: verbose (bool): If True, additional information is printed @@ -182,7 +181,7 @@ def show_devices(verbose=False): def retrieve(device, token, jobid, num_retries=3000, interval=1, verbose=False): # pylint: disable=too-many-arguments """ - Retrieves a previously run job by its ID. + Retrieve a previously run job by its ID. Args: device (str): Device on which the code was run / is running. @@ -208,15 +207,14 @@ def send( verbose=False, ): # pylint: disable=too-many-arguments """ - Sends cicruit through the AQT API and runs the quantum circuit. + Send cicruit through the AQT API and runs the quantum circuit. Args: info(dict): Contains representation of the circuit to run. device (str): name of the aqt device. Simulator chosen by default token (str): AQT user API token. - verbose (bool): If True, additional information is printed, such as - measurement statistics. Otherwise, the backend simply registers - one measurement result (same behavior as the projectq Simulator). + verbose (bool): If True, additional information is printed, such as measurement statistics. Otherwise, the + backend simply registers one measurement result (same behavior as the projectq Simulator). Returns: (list) samples form the AQT server diff --git a/projectq/backends/_aqt/_aqt_test.py b/projectq/backends/_aqt/_aqt_test.py index a11293853..397f6ffd9 100644 --- a/projectq/backends/_aqt/_aqt_test.py +++ b/projectq/backends/_aqt/_aqt_test.py @@ -14,14 +14,15 @@ # limitations under the License. """Tests for projectq.backends._aqt._aqt.py.""" -import pytest import math +import pytest + from projectq import MainEngine from projectq.backends._aqt import _aqt -from projectq.types import WeakQubitRef -from projectq.cengines import DummyEngine, BasicMapperEngine +from projectq.cengines import BasicMapperEngine, DummyEngine from projectq.ops import ( + NOT, All, Allocate, Barrier, @@ -29,11 +30,10 @@ Deallocate, Entangle, Measure, - NOT, Rx, + Rxx, Ry, Rz, - Rxx, S, Sdag, T, @@ -42,6 +42,7 @@ Y, Z, ) +from projectq.types import WeakQubitRef # Insure that no HTTP request can be made in all tests in this module @@ -202,7 +203,7 @@ def mock_send(*args, **kwargs): backend.get_probabilities([]) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index 9580c36f0..ba27e64f7 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -12,44 +12,47 @@ # 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. -""" Back-end to run quantum program on AWS Braket provided devices.""" -import random +"""Back-end to run quantum program on AWS Braket provided devices.""" + import json +import random from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control -from projectq.types import WeakQubitRef +from projectq.meta import LogicalQubitIDTag, get_control_count, has_negative_control from projectq.ops import ( - R, - SwapGate, + Allocate, + Barrier, + DaggeredGate, + Deallocate, + FlushGate, HGate, + Measure, + R, Rx, Ry, Rz, - SGate, Sdag, - TGate, + SGate, + SqrtXGate, + SwapGate, Tdag, + TGate, XGate, YGate, ZGate, - SqrtXGate, - Measure, - Allocate, - Deallocate, - Barrier, - FlushGate, - DaggeredGate, ) +from projectq.types import WeakQubitRef -# TODO: Add MatrixGate to cover the unitary operation in the SV1 simulator +from ._awsbraket_boto3_client import retrieve, send -from ._awsbraket_boto3_client import send, retrieve +# TODO: Add MatrixGate to cover the unitary operation in the SV1 simulator class AWSBraketBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """ + Compiler engine class implementing support for the AWS Braket framework. + The AWS Braket Backend class, which stores the circuit, transforms it to Braket compatible, and sends the circuit through the Boto3 and Amazon Braket SDK. """ @@ -85,7 +88,7 @@ def __init__( timed out). The TaskArns have the form: "arn:aws:braket:us-east-1:123456789012:quantum-task/5766032b-2b47-4bf9-cg00-f11851g4015b" """ - BasicEngine.__init__(self) + super().__init__() self._reset() if use_hardware: self.device = device @@ -98,7 +101,7 @@ def __init__( self._s3_folder = s3_folder self._num_retries = num_retries self._interval = interval - self._probabilities = dict() + self._probabilities = {} self._circuit = "" self._measured_ids = [] self._allocated_qubits = set() @@ -156,7 +159,6 @@ def is_available(self, cmd): # pylint: disable=too-many-return-statements,too-m Args: cmd (Command): Command for which to check availability """ - gate = cmd.gate if gate in (Measure, Allocate, Deallocate, Barrier): return True @@ -270,7 +272,7 @@ def _store(self, cmd): # pylint: disable=too-many-branches ) if self._clear: - self._probabilities = dict() + self._probabilities = {} self._clear = False self._circuit = "" self._allocated_qubits = set() @@ -321,8 +323,7 @@ def _logical_to_physical(self, qb_id): Return the physical location of the qubit with the given logical id. Args: - qb_id (int): ID of the logical qubit whose position should be - returned. + qb_id (int): ID of the logical qubit whose position should be returned. """ if self.main_engine.mapper is not None: mapping = self.main_engine.mapper.current_mapping @@ -338,24 +339,23 @@ def _logical_to_physical(self, qb_id): def get_probabilities(self, qureg): """ - Return the list of basis states with corresponding probabilities. If input qureg is a subset of the register - used for the experiment, then returns the projected probabilities over the other states. + Return the list of basis states with corresponding probabilities. + + If input qureg is a subset of the register used for the experiment, then returns the projected probabilities + over the other states. The measured bits are ordered according to the supplied quantum register, i.e., the left-most bit in the state-string corresponds to the first qubit in the supplied quantum register. Args: - qureg (list): Quantum register determining the order of the - qubits. + qureg (list): Quantum register determining the order of the qubits. Returns: - probability_dict (dict): Dictionary mapping n-bit strings to - probabilities. + probability_dict (dict): Dictionary mapping n-bit strings to probabilities. Raises: - RuntimeError: If no data is available (i.e., if the circuit has not - been executed). Or if a qubit was supplied which was not - present in the circuit (might have gotten optimized away). + RuntimeError: If no data is available (i.e., if the circuit has not been executed). Or if a qubit was + supplied which was not present in the circuit (might have gotten optimized away). Warning: Only call this function after the circuit has been executed! @@ -367,12 +367,11 @@ def get_probabilities(self, qureg): circuit has already been executed. In order to obtain the probabilities of a previous job you have to get the TaskArn and remember the qubits and ordering used in the original job. - """ if len(self._probabilities) == 0: raise RuntimeError("Please, run the circuit first!") - probability_dict = dict() + probability_dict = {} for state in self._probabilities: mapped_state = ['0'] * len(qureg) for i, qubit in enumerate(qureg): diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py index 392d1af0e..39ad3b265 100755 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py @@ -29,23 +29,25 @@ import boto3 import botocore +from .._exceptions import DeviceOfflineError, DeviceTooSmall, RequestTimeoutError + class AWSBraket: - """ - Manage a session between ProjectQ and AWS Braket service. - """ + """Manage a session between ProjectQ and AWS Braket service.""" def __init__(self): - self.backends = dict() + """Initialize a session with the AWS Braket Web APIs.""" + self.backends = {} self.timeout = 5.0 - self._credentials = dict() + self._credentials = {} self._s3_folder = [] def authenticate(self, credentials=None): """ + Authenticate with AWSBraket Web APIs. + Args: - credentials (dict): mapping the AWS key credentials as the - AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + credentials (dict): mapping the AWS key credentials as the AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. """ if credentials is None: # pragma: no cover credentials['AWS_ACCESS_KEY_ID'] = getpass.getpass(prompt="Enter AWS_ACCESS_KEY_ID: ") @@ -55,9 +57,10 @@ def authenticate(self, credentials=None): def get_s3_folder(self, s3_folder=None): """ + Get the S3 bucket that contains the results. + Args: - s3_folder (list): contains the S3 bucket and directory to store the - results. + s3_folder (list): contains the S3 bucket and directory to store the results. """ if s3_folder is None: # pragma: no cover s3_bucket = input("Enter the S3 Bucket configured in Braket: ") @@ -68,20 +71,18 @@ def get_s3_folder(self, s3_folder=None): def get_list_devices(self, verbose=False): """ - Get the list of available devices with their basic properties + Get the list of available devices with their basic properties. Args: verbose (bool): print the returned dictionnary if True Returns: - (dict) backends dictionary by deviceName, containing the qubit size - 'nq', the coupling map 'coupling_map' if applicable (IonQ - Device as an ion device is having full connectivity) and the - Schema Header version 'version', because it seems that no - device version is available by now + (dict) backends dictionary by deviceName, containing the qubit size 'nq', the coupling map 'coupling_map' + if applicable (IonQ Device as an ion device is having full connectivity) and the Schema Header + version 'version', because it seems that no device version is available by now """ # TODO: refresh region_names if more regions get devices available - self.backends = dict() + self.backends = {} region_names = ['us-west-1', 'us-east-1'] for region in region_names: client = boto3.client( @@ -156,15 +157,13 @@ def can_run_experiment(self, info, device): Check if the device is big enough to run the code. Args: - info (dict): dictionary sent by the backend containing the code to - run + info (dict): dictionary sent by the backend containing the code to run device (str): name of the device to use Returns: (tuple): (bool) True if device is big enough, False otherwise (int) maximum number of qubit available on the device (int) number of qubit needed for the circuit - """ nb_qubit_max = self.backends[device]['nq'] nb_qubit_needed = info['nq'] @@ -175,14 +174,11 @@ def run(self, info, device): Run the quantum code to the AWS Braket selected device. Args: - info (dict): dictionary sent by the backend containing the code to - run + info (dict): dictionary sent by the backend containing the code to run device (str): name of the device to use Returns: task_arn (str): The Arn of the task - - """ argument = { 'circ': info['circuit'], @@ -220,9 +216,7 @@ def run(self, info, device): return response['quantumTaskArn'] def get_result(self, execution_id, num_retries=30, interval=1, verbose=False): # pylint: disable=too-many-locals - """ - Get the result of an execution - """ + """Get the result of an execution.""" if verbose: print("Waiting for results. [Job Arn: {}]".format(execution_id)) @@ -233,18 +227,19 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover def _calculate_measurement_probs(measurements): """ - Calculate the measurement probabilities based on the - list of measurements for a job sent to a SV1 Braket simulator + Calculate the measurement probabilities . + + Calculate the measurement probabilities based on the list of measurements for a job sent to a SV1 Braket + simulator. Args: measurements (list): list of measurements Returns: - measurementsProbabilities (dict): The measurements - with their probabilities + measurementsProbabilities (dict): The measurements with their probabilities """ total_mes = len(measurements) - unique_mes = [list(x) for x in set(tuple(x) for x in measurements)] + unique_mes = [list(x) for x in {tuple(x) for x in measurements}] total_unique_mes = len(unique_mes) len_qubits = len(unique_mes[0]) measurements_probabilities = {} @@ -315,29 +310,19 @@ def _calculate_measurement_probs(measurements): if original_sigint_handler is not None: signal.signal(signal.SIGINT, original_sigint_handler) - raise Exception( + raise RequestTimeoutError( "Timeout. " "The Arn of your submitted job is {} and the status " "of the job is {}.".format(execution_id, status) ) -class DeviceTooSmall(Exception): - """Exception raised if the device is too small to run the circuit""" - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" - - def show_devices(credentials=None, verbose=False): """ - Access the list of available devices and their properties (ex: for setup - configuration) + Access the list of available devices and their properties (ex: for setup configuration). Args: - credentials (dict): Dictionary storing the AWS credentials with - keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + credentials (dict): Dictionary storing the AWS credentials with keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. verbose (bool): If True, additional information is printed Returns: @@ -353,16 +338,14 @@ def show_devices(credentials=None, verbose=False): def retrieve(credentials, task_arn, num_retries=30, interval=1, verbose=False): """ - Retrieves a job/task by its Arn. + Retrieve a job/task by its Arn. Args: - credentials (dict): Dictionary storing the AWS credentials with - keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + credentials (dict): Dictionary storing the AWS credentials with keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. task_arn (str): The Arn of the task to retreive Returns: - (dict) measurement probabilities from the result - stored in the S3 folder + (dict) measurement probabilities from the result stored in the S3 folder """ try: awsbraket_session = AWSBraket() @@ -385,22 +368,18 @@ def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-local info, device, credentials, s3_folder, num_retries=30, interval=1, verbose=False ): """ - Sends cicruit through the Boto3 SDK and runs the quantum circuit. + Send cicruit through the Boto3 SDK and runs the quantum circuit. Args: info(dict): Contains representation of the circuit to run. device (str): name of the AWS Braket device. - credentials (dict): Dictionary storing the AWS credentials with keys - AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. - s3_folder (list): Contains the S3 bucket and directory to store the - results. - verbose (bool): If True, additional information is printed, such as - measurement statistics. Otherwise, the backend simply registers one - measurement result (same behavior as the projectq Simulator). + credentials (dict): Dictionary storing the AWS credentials with keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + s3_folder (list): Contains the S3 bucket and directory to store the results. + verbose (bool): If True, additional information is printed, such as measurement statistics. Otherwise, the + backend simply registers one measurement result (same behavior as the projectq Simulator). Returns: (list) samples from the AWS Braket device - """ try: awsbraket_session = AWSBraket() diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py index 267e982ca..795d9e308 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -23,6 +23,7 @@ _has_boto3 = True try: import botocore + from projectq.backends._awsbraket import _awsbraket_boto3_client except ImportError: _has_boto3 = False diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py index 8092a4a45..0fa5cb2b9 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py @@ -35,8 +35,11 @@ # * real_device_online_setup # ============================================================================== -from io import StringIO +"""Define test fixtures for the AWSBraket HTTP client.""" + import json +from io import StringIO + import pytest try: @@ -44,8 +47,10 @@ except ImportError: class StreamingBody: + """Dummy implementation of a StreamingBody.""" + def __init__(self, raw_stream, content_length): - pass + """Initialize a dummy StreamingBody.""" # ============================================================================== @@ -53,11 +58,13 @@ def __init__(self, raw_stream, content_length): @pytest.fixture def arntask(): + """Define an ARNTask test setup.""" return 'arn:aws:braket:us-east-1:id:taskuuid' @pytest.fixture def creds(): + """Credentials test setup.""" return { 'AWS_ACCESS_KEY_ID': 'aws_access_key_id', 'AWS_SECRET_KEY': 'aws_secret_key', @@ -66,11 +73,13 @@ def creds(): @pytest.fixture def s3_folder(): - return ['S3Bucket', "S3Directory"] + """S3 folder value test setup.""" + return ['S3Bucket', 'S3Directory'] @pytest.fixture def info(): + """Info value test setup.""" return { 'circuit': '{"braketSchemaHeader":' '{"name": "braket.ir.jaqcd.program", "version": "1"}, ' @@ -86,6 +95,7 @@ def info(): @pytest.fixture def results_json(): + """Results test setup.""" return json.dumps( { "braketSchemaHeader": { @@ -106,6 +116,7 @@ def results_json(): @pytest.fixture def results_dict(results_json): + """Results dict test setup.""" body = StreamingBody(StringIO(results_json), len(results_json)) return { 'ResponseMetadata': { @@ -118,11 +129,13 @@ def results_dict(results_json): @pytest.fixture def res_completed(): + """Completed results test setup.""" return {"000": 0.1, "010": 0.4, "110": 0.1, "001": 0.1, "111": 0.3} @pytest.fixture def search_value(): + """Search value test setup.""" return { "devices": [ { @@ -159,6 +172,7 @@ def search_value(): @pytest.fixture def device_value_devicecapabilities(): + """Device capabilities value test setup.""" return json.dumps( { "braketSchemaHeader": { @@ -219,6 +233,7 @@ def device_value_devicecapabilities(): @pytest.fixture def device_value(device_value_devicecapabilities): + """Device value test setup.""" return { "deviceName": "Aspen-8", "deviceType": "QPU", @@ -230,6 +245,7 @@ def device_value(device_value_devicecapabilities): @pytest.fixture def devicelist_result(): + """Device list value test setup.""" return { 'name1': { 'coupling_map': {}, @@ -284,16 +300,19 @@ def devicelist_result(): @pytest.fixture def show_devices_setup(creds, search_value, device_value, devicelist_result): + """Show devices value test setup.""" return creds, search_value, device_value, devicelist_result @pytest.fixture def retrieve_setup(arntask, creds, device_value, res_completed, results_dict): + """Retrieve value test setup.""" return arntask, creds, device_value, res_completed, results_dict @pytest.fixture(params=["qpu", "sim"]) def retrieve_devicetypes_setup(request, arntask, creds, results_json, device_value_devicecapabilities): + """Retrieve device types value test setup.""" if request.param == "qpu": body_qpu = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -358,6 +377,7 @@ def retrieve_devicetypes_setup(request, arntask, creds, results_json, device_val @pytest.fixture def send_too_many_setup(creds, s3_folder, search_value, device_value): + """Send too many value test setup.""" info_too_much = { 'circuit': '{"braketSchemaHeader":' '{"name": "braket.ir.jaqcd.program", "version": "1"}, ' @@ -383,6 +403,7 @@ def real_device_online_setup( res_completed, results_json, ): + """Real device online value test setup.""" qtarntask = {'quantumTaskArn': arntask} body = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -407,4 +428,5 @@ def real_device_online_setup( @pytest.fixture def send_that_error_setup(creds, s3_folder, info, search_value, device_value): + """Send error value test setup.""" return creds, s3_folder, info, search_value, device_value diff --git a/projectq/backends/_awsbraket/_awsbraket_test.py b/projectq/backends/_awsbraket/_awsbraket_test.py index dc51283ab..22205a2f1 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_test.py @@ -14,50 +14,48 @@ # limitations under the License. """ Test for projectq.backends._awsbraket._awsbraket.py""" -import pytest - import copy import math -from projectq import MainEngine +import pytest -from projectq.types import WeakQubitRef +from projectq import MainEngine from projectq.cengines import ( - BasicMapperEngine, - DummyEngine, AutoReplacer, + BasicMapperEngine, DecompositionRuleSet, + DummyEngine, ) from projectq.cengines._replacer import NoGateDecompositionError - from projectq.ops import ( - R, - Swap, + CNOT, + NOT, + All, + Allocate, + Barrier, + C, + Command, + Deallocate, + Entangle, H, + MatrixGate, + Measure, + Ph, + R, Rx, Ry, Rz, S, Sdag, + SqrtX, + Swap, T, Tdag, X, Y, Z, - CNOT, - SqrtX, - MatrixGate, - Entangle, - Ph, - NOT, - C, - Measure, - Allocate, - Deallocate, - Barrier, - All, - Command, ) +from projectq.types import WeakQubitRef from ._awsbraket_test_fixtures import * # noqa: F401,F403 @@ -66,6 +64,7 @@ _has_boto3 = True try: import botocore + from projectq.backends._awsbraket import _awsbraket except ImportError: _has_boto3 = False @@ -85,7 +84,7 @@ def mapper(request): class TrivialMapper(BasicMapperEngine): def __init__(self): super().__init__() - self.current_mapping = dict() + self.current_mapping = {} def receive(self, command_list): for cmd in command_list: @@ -489,7 +488,7 @@ def test_awsbraket_retrieve(mocker, retrieve_setup): backend = _awsbraket.AWSBraketBackend(retrieve_execution=arntask, credentials=creds, num_retries=2, verbose=True) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res diff --git a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py index 894e4dea6..71968697d 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py @@ -29,8 +29,11 @@ # * functional_setup # ============================================================================== -from io import StringIO +"""Define test fixtures for the AWSBraket backend.""" + import json +from io import StringIO + import pytest try: @@ -38,8 +41,10 @@ except ImportError: class StreamingBody: + """Dummy implementation of a StreamingBody.""" + def __init__(self, raw_stream, content_length): - pass + """Initialize a dummy StreamingBody.""" # ============================================================================== @@ -47,11 +52,13 @@ def __init__(self, raw_stream, content_length): @pytest.fixture def arntask(): + """Define an ARNTask test setup.""" return 'arn:aws:braket:us-east-1:id:retrieve_execution' @pytest.fixture def creds(): + """Credentials test setup.""" return { 'AWS_ACCESS_KEY_ID': 'aws_access_key_id', 'AWS_SECRET_KEY': 'aws_secret_key', @@ -60,11 +67,13 @@ def creds(): @pytest.fixture def s3_folder(): + """S3 folder value test setup.""" return ['S3Bucket', 'S3Directory'] @pytest.fixture def device_value(): + """Device value test setup.""" device_value_devicecapabilities = json.dumps( { "braketSchemaHeader": { @@ -133,6 +142,7 @@ def device_value(): @pytest.fixture def search_value(): + """Search value test setup.""" return { "devices": [ { @@ -162,6 +172,7 @@ def search_value(): @pytest.fixture def completed_value(): + """Completed value test setup.""" return { 'deviceArn': 'arndevice', 'deviceParameters': 'parameters', @@ -179,12 +190,13 @@ def completed_value(): @pytest.fixture def sent_error_setup(creds, s3_folder, device_value, search_value): - + """Send error test setup.""" return creds, s3_folder, search_value, device_value @pytest.fixture def results_json(): + """Results test setup.""" return json.dumps( { "braketSchemaHeader": { @@ -205,7 +217,7 @@ def results_json(): @pytest.fixture def retrieve_setup(arntask, creds, device_value, completed_value, results_json): - + """Retrieve test setup.""" body = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -221,6 +233,7 @@ def retrieve_setup(arntask, creds, device_value, completed_value, results_json): @pytest.fixture def functional_setup(arntask, creds, s3_folder, search_value, device_value, completed_value, results_json): + """Functional test setup.""" qtarntask = {'quantumTaskArn': arntask} body2 = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { diff --git a/projectq/backends/_circuits/__init__.py b/projectq/backends/_circuits/__init__.py index 8985f0fe0..638ddfbbc 100755 --- a/projectq/backends/_circuits/__init__.py +++ b/projectq/backends/_circuits/__init__.py @@ -15,8 +15,7 @@ """ProjectQ module for exporting/printing quantum circuits""" -from ._to_latex import to_latex -from ._plot import to_draw - from ._drawer import CircuitDrawer from ._drawer_matplotlib import CircuitDrawerMatplotlib +from ._plot import to_draw +from ._to_latex import to_latex diff --git a/projectq/backends/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index bea56f40b..bb19408fa 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -12,20 +12,20 @@ # 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. -""" -Contains a compiler engine which generates TikZ Latex code describing the -circuit. -""" + +"""Contain a compiler engine which generates TikZ Latex code describing the circuit.""" + from builtins import input -from projectq.cengines import LastEngineException, BasicEngine -from projectq.ops import FlushGate, Measure, Allocate, Deallocate +from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import get_control_count +from projectq.ops import Allocate, Deallocate, FlushGate, Measure + from ._to_latex import to_latex -class CircuitItem: - """Item of a quantum circuit to draw""" +class CircuitItem: # pylint: disable=too-few-public-methods + """Item of a quantum circuit to draw.""" def __init__(self, gate, lines, ctrl_lines): """ @@ -42,6 +42,7 @@ def __init__(self, gate, lines, ctrl_lines): self.id = -1 def __eq__(self, other): + """Equal operator.""" return ( self.gate == other.gate and self.lines == other.lines @@ -49,22 +50,16 @@ def __eq__(self, other): and self.id == other.id ) - def __ne__(self, other): - return not self.__eq__(other) - class CircuitDrawer(BasicEngine): """ - CircuitDrawer is a compiler engine which generates TikZ code for drawing - quantum circuits. + CircuitDrawer is a compiler engine which generates TikZ code for drawing quantum circuits. - The circuit can be modified by editing the settings.json file which is - generated upon first execution. This includes adjusting the gate width, - height, shadowing, line thickness, and many more options. + The circuit can be modified by editing the settings.json file which is generated upon first execution. This + includes adjusting the gate width, height, shadowing, line thickness, and many more options. - After initializing the CircuitDrawer, it can also be given the mapping - from qubit IDs to wire location (via the :meth:`set_qubit_locations` - function): + After initializing the CircuitDrawer, it can also be given the mapping from qubit IDs to wire location (via the + :meth:`set_qubit_locations` function): .. code-block:: python @@ -76,9 +71,8 @@ class CircuitDrawer(BasicEngine): print(circuit_backend.get_latex()) # prints LaTeX code - To see the qubit IDs in the generated circuit, simply set the `draw_id` - option in the settings.json file under "gates":"AllocateQubitGate" to - True: + To see the qubit IDs in the generated circuit, simply set the `draw_id` option in the settings.json file under + "gates":"AllocateQubitGate" to True: .. code-block:: python @@ -119,8 +113,7 @@ class CircuitDrawer(BasicEngine): } } - All gates (except for the ones requiring special treatment) support the - following properties: + All gates (except for the ones requiring special treatment) support the following properties: .. code-block:: python @@ -137,40 +130,38 @@ def __init__(self, accept_input=False, default_measure=0): """ Initialize a circuit drawing engine. - The TikZ code generator uses a settings file (settings.json), which - can be altered by the user. It contains gate widths, heights, offsets, - etc. + The TikZ code generator uses a settings file (settings.json), which can be altered by the user. It contains + gate widths, heights, offsets, etc. Args: - accept_input (bool): If accept_input is true, the printer queries - the user to input measurement results if the CircuitDrawer is - the last engine. Otherwise, all measurements yield the result - default_measure (0 or 1). - default_measure (bool): Default value to use as measurement - results if accept_input is False and there is no underlying - backend to register real measurement results. + accept_input (bool): If accept_input is true, the printer queries the user to input measurement results if + the CircuitDrawer is the last engine. Otherwise, all measurements yield the result default_measure (0 + or 1). + default_measure (bool): Default value to use as measurement results if accept_input is False and there is + no underlying backend to register real measurement results. """ - BasicEngine.__init__(self) + super().__init__() self._accept_input = accept_input self._default_measure = default_measure - self._qubit_lines = dict() + self._qubit_lines = {} self._free_lines = [] - self._map = dict() + self._map = {} # Order in which qubit lines are drawn self._drawing_order = [] def is_available(self, cmd): """ - Specialized implementation of is_available: Returns True if the - CircuitDrawer is the last engine (since it can print any command). + Test whether a Command is supported by a compiler engine. + + Specialized implementation of is_available: Returns True if the CircuitDrawer is the last engine (since it can + print any command). Args: - cmd (Command): Command for which to check availability (all - Commands can be printed). + cmd (Command): Command for which to check availability (all Commands can be printed). + Returns: - availability (bool): True, unless the next engine cannot handle - the Command (if there is a next engine). + availability (bool): True, unless the next engine cannot handle the Command (if there is a next engine). """ try: return BasicEngine.is_available(self, cmd) @@ -179,19 +170,17 @@ def is_available(self, cmd): def set_qubit_locations(self, id_to_loc): """ - Sets the qubit lines to use for the qubits explicitly. + Set the qubit lines to use for the qubits explicitly. - To figure out the qubit IDs, simply use the setting `draw_id` in the - settings file. It is located in "gates":"AllocateQubitGate". - If draw_id is True, the qubit IDs are drawn in red. + To figure out the qubit IDs, simply use the setting `draw_id` in the settings file. It is located in + "gates":"AllocateQubitGate". If draw_id is True, the qubit IDs are drawn in red. Args: - id_to_loc (dict): Dictionary mapping qubit ids to qubit line - numbers. + id_to_loc (dict): Dictionary mapping qubit ids to qubit line numbers. Raises: - RuntimeError: If the mapping has already begun (this function - needs be called before any gates have been received). + RuntimeError: If the mapping has already begun (this function needs be called before any gates have been + received). """ if len(self._map) > 0: raise RuntimeError("set_qubit_locations() has to be called before applying gates!") @@ -208,12 +197,13 @@ def set_qubit_locations(self, id_to_loc): def _print_cmd(self, cmd): """ - Add the command cmd to the circuit diagram, taking care of potential - measurements as specified in the __init__ function. + Add a command to the list of commands to be printed. - Queries the user for measurement input if a measurement command - arrives if accept_input was set to True. Otherwise, it uses the - default_measure parameter to register the measurement outcome. + Add the command cmd to the circuit diagram, taking care of potential measurements as specified in the __init__ + function. + + Queries the user for measurement input if a measurement command arrives if accept_input was set to + True. Otherwise, it uses the default_measure parameter to register the measurement outcome. Args: cmd (Command): Command to add to the circuit diagram. @@ -270,12 +260,10 @@ def get_latex(self, ordered=False, draw_gates_in_parallel=True): where my_circuit.py calls this function and prints it to the terminal. Args: - ordered(bool): flag if the gates should be drawn in the order they - were added to the circuit - draw_gates_in_parallel(bool): flag if parallel gates should be drawn - parallel (True), or not (False) + ordered(bool): flag if the gates should be drawn in the order they were added to the circuit + draw_gates_in_parallel(bool): flag if parallel gates should be drawn parallel (True), or not (False) """ - qubit_lines = dict() + qubit_lines = {} for line in range(len(self._qubit_lines)): new_line = self._map[line] @@ -301,12 +289,13 @@ def get_latex(self, ordered=False, draw_gates_in_parallel=True): def receive(self, command_list): """ - Receive a list of commands from the previous engine, print the - commands, and then send them on to the next engine. + Receive a list of commands. + + Receive a list of commands from the previous engine, print the commands, and then send them on to the next + engine. Args: - command_list (list): List of Commands to print (and - potentially send on to the next engine). + command_list (list): List of Commands to print (and potentially send on to the next engine). """ for cmd in command_list: if not cmd.gate == FlushGate(): diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index ee83c6023..37d42f43a 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -12,18 +12,17 @@ # 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. -""" -Contains a compiler engine which generates matplotlib figures describing the -circuit. -""" -from builtins import input -import re +"""Contain a compiler engine which generates matplotlib figures describing the circuit.""" + import itertools +import re +from builtins import input -from projectq.cengines import LastEngineException, BasicEngine -from projectq.ops import FlushGate, Measure, Allocate, Deallocate +from projectq.cengines import BasicEngine, LastEngineException from projectq.meta import get_control_count +from projectq.ops import Allocate, Deallocate, FlushGate, Measure + from ._plot import to_draw # ============================================================================== @@ -53,43 +52,37 @@ def _format_gate_str(cmd): class CircuitDrawerMatplotlib(BasicEngine): - """ - CircuitDrawerMatplotlib is a compiler engine which using Matplotlib library - for drawing quantum circuits - """ + """CircuitDrawerMatplotlib is a compiler engine which using Matplotlib library for drawing quantum circuits.""" def __init__(self, accept_input=False, default_measure=0): """ - Initialize a circuit drawing engine(mpl) + Initialize a circuit drawing engine(mpl). Args: - accept_input (bool): If accept_input is true, the printer queries - the user to input measurement results if the CircuitDrawerMPL - is the last engine. Otherwise, all measurements yield the - result default_measure (0 or 1). - default_measure (bool): Default value to use as measurement - results if accept_input is False and there is no underlying - backend to register real measurement results. + accept_input (bool): If accept_input is true, the printer queries the user to input measurement results if + the CircuitDrawerMPL is the last engine. Otherwise, all measurements yield the result default_measure + (0 or 1). + default_measure (bool): Default value to use as measurement results if accept_input is False and there is + no underlying backend to register real measurement results. """ - BasicEngine.__init__(self) + super().__init__() self._accept_input = accept_input self._default_measure = default_measure - self._map = dict() + self._map = {} self._qubit_lines = {} def is_available(self, cmd): """ - Specialized implementation of is_available: Returns True if the - CircuitDrawerMatplotlib is the last engine + Test whether a Command is supported by a compiler engine. + + Specialized implementation of is_available: Returns True if the CircuitDrawerMatplotlib is the last engine (since it can print any command). Args: - cmd (Command): Command for which to check availability (all - Commands can be printed). + cmd (Command): Command for which to check availability (all Commands can be printed). Returns: - availability (bool): True, unless the next engine cannot handle - the Command (if there is a next engine). + availability (bool): True, unless the next engine cannot handle the Command (if there is a next engine). """ try: # Multi-qubit gates may fail at drawing time if the target qubits @@ -100,11 +93,10 @@ def is_available(self, cmd): def _process(self, cmd): # pylint: disable=too-many-branches """ - Process the command cmd and stores it in the internal storage + Process the command cmd and stores it in the internal storage. - Queries the user for measurement input if a measurement command - arrives if accept_input was set to True. Otherwise, it uses the - default_measure parameter to register the measurement outcome. + Queries the user for measurement input if a measurement command arrives if accept_input was set to + True. Otherwise, it uses the default_measure parameter to register the measurement outcome. Args: cmd (Command): Command to add to the circuit diagram. @@ -165,12 +157,13 @@ def _process(self, cmd): # pylint: disable=too-many-branches def receive(self, command_list): """ - Receive a list of commands from the previous engine, print the - commands, and then send them on to the next engine. + Receive a list of commands. + + Receive a list of commands from the previous engine, print the commands, and then send them on to the next + engine. Args: - command_list (list): List of Commands to print (and - potentially send on to the next engine). + command_list (list): List of Commands to print (and potentially send on to the next engine). """ for cmd in command_list: if not isinstance(cmd.gate, FlushGate): @@ -181,25 +174,21 @@ def receive(self, command_list): def draw(self, qubit_labels=None, drawing_order=None, **kwargs): """ - Generates and returns the plot of the quantum circuit stored so far + Generate and returns the plot of the quantum circuit stored so far. Args: - qubit_labels (dict): label for each wire in the output figure. - Keys: qubit IDs, Values: string to print out as label for - that particular qubit wire. - drawing_order (dict): position of each qubit in the output - graphic. Keys: qubit IDs, Values: position of qubit on the - qubit line in the graphic. - **kwargs (dict): additional parameters are used to update - the default plot parameters + qubit_labels (dict): label for each wire in the output figure. Keys: qubit IDs, Values: string to print + out as label for that particular qubit wire. + drawing_order (dict): position of each qubit in the output graphic. Keys: qubit IDs, Values: position of + qubit on the qubit line in the graphic. + **kwargs (dict): additional parameters are used to update the default plot parameters Returns: A tuple containing the matplotlib figure and axes objects Note: - Additional keyword arguments can be passed to this - function in order to further customize the figure output - by matplotlib (default value in parentheses): + Additional keyword arguments can be passed to this function in order to further customize the figure + output by matplotlib (default value in parentheses): - fontsize (14): Font size in pt - column_spacing (.5): Vertical spacing between two diff --git a/projectq/backends/_circuits/_drawer_matplotlib_test.py b/projectq/backends/_circuits/_drawer_matplotlib_test.py index 9c52ad34f..885d835fa 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib_test.py +++ b/projectq/backends/_circuits/_drawer_matplotlib_test.py @@ -20,7 +20,7 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import H, X, Rx, CNOT, Swap, Measure, Command, BasicGate +from projectq.ops import CNOT, BasicGate, Command, H, Measure, Rx, Swap, X from projectq.types import WeakQubitRef from . import _drawer_matplotlib as _drawer @@ -108,7 +108,7 @@ def _draw_subst(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): class MyGate(BasicGate): def __init__(self, *args): - BasicGate.__init__(self) + super().__init__() self.params = args def __str__(self): diff --git a/projectq/backends/_circuits/_drawer_test.py b/projectq/backends/_circuits/_drawer_test.py index 26ef4974b..d9b94b42c 100755 --- a/projectq/backends/_circuits/_drawer_test.py +++ b/projectq/backends/_circuits/_drawer_test.py @@ -18,13 +18,12 @@ import pytest +import projectq.backends._circuits._drawer as _drawer from projectq import MainEngine -from projectq.ops import H, X, CNOT, Measure, Command +from projectq.backends._circuits._drawer import CircuitDrawer, CircuitItem +from projectq.ops import CNOT, Command, H, Measure, X from projectq.types import WeakQubitRef -import projectq.backends._circuits._drawer as _drawer -from projectq.backends._circuits._drawer import CircuitItem, CircuitDrawer - @pytest.mark.parametrize("ordered", [False, True]) def test_drawer_getlatex(ordered): diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index 0d3673fc6..7be85711d 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -13,23 +13,22 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This module provides the basic functionality required to plot a quantum -circuit in a matplotlib figure. +This module provides the basic functionality required to plot a quantum circuit in a matplotlib figure. + It is mainly used by the CircuitDrawerMatplotlib compiler engine. -Currently, it supports all single-qubit gates, including their controlled -versions to an arbitrary number of control qubits. It also supports -multi-target qubit gates under some restrictions. Namely that the target -qubits must be neighbours in the output figure (which cannot be determined -durinng compilation at this time). +Currently, it supports all single-qubit gates, including their controlled versions to an arbitrary number of control +qubits. It also supports multi-target qubit gates under some restrictions. Namely that the target qubits must be +neighbours in the output figure (which cannot be determined durinng compilation at this time). """ from copy import deepcopy -import numpy as np + import matplotlib.pyplot as plt -from matplotlib.collections import PatchCollection, LineCollection +import numpy as np +from matplotlib.collections import LineCollection, PatchCollection from matplotlib.lines import Line2D -from matplotlib.patches import Circle, Arc, Rectangle +from matplotlib.patches import Arc, Circle, Rectangle # Important note on units for the plot parameters. # The following entries are in inches: @@ -46,46 +45,42 @@ # - x_offset # # The rest have misc. units (as defined by matplotlib) -_DEFAULT_PLOT_PARAMS = dict( - fontsize=14.0, - column_spacing=0.5, - control_radius=0.015, - labels_margin=1, - linewidth=1.0, - not_radius=0.03, - gate_offset=0.05, - mgate_width=0.1, - swap_delta=0.02, - x_offset=0.05, - wire_height=1, -) +_DEFAULT_PLOT_PARAMS = { + 'fontsize': 14.0, + 'column_spacing': 0.5, + 'control_radius': 0.015, + 'labels_margin': 1, + 'linewidth': 1.0, + 'not_radius': 0.03, + 'gate_offset': 0.05, + 'mgate_width': 0.1, + 'swap_delta': 0.02, + 'x_offset': 0.05, + 'wire_height': 1, +} # ============================================================================== def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): """ - Translates a given circuit to a matplotlib figure. + Translate a given circuit to a matplotlib figure. Args: qubit_lines (dict): list of gates for each qubit axis - qubit_labels (dict): label to print in front of the qubit wire for - each qubit ID + qubit_labels (dict): label to print in front of the qubit wire for each qubit ID drawing_order (dict): index of the wire for each qubit ID to be drawn. - **kwargs (dict): additional parameters are used to update the default - plot parameters + **kwargs (dict): additional parameters are used to update the default plot parameters Returns: A tuple with (figure, axes) Note: - Numbering of qubit wires starts at 0 at the bottom and increases - vertically. + Numbering of qubit wires starts at 0 at the bottom and increases vertically. Note: - Additional keyword arguments can be passed to this - function in order to further customize the figure output - by matplotlib (default value in parentheses): + Additional keyword arguments can be passed to this function in order to further customize the figure output by + matplotlib (default value in parentheses): - fontsize (14): Font size in pt - column_spacing (.5): Vertical spacing between two @@ -113,9 +108,9 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): n_qubits = len(qubit_lines) drawing_order = {qubit_id: n_qubits - qubit_id - 1 for qubit_id in list(qubit_lines)} else: - if list(drawing_order) != list(qubit_lines): + if set(drawing_order) != set(qubit_lines): raise RuntimeError('Qubit IDs in drawing_order do not match ' + 'qubit IDs in qubit_lines!') - if list(sorted(drawing_order.values())) != list(range(len(drawing_order))): + if set(drawing_order.values()) != set(range(len(drawing_order))): raise RuntimeError( 'Indices of qubit wires in drawing_order must be between 0 and {}!'.format(len(drawing_order)) ) @@ -184,7 +179,7 @@ def gate_width(axes, gate_str, plot_params): 0, gate_str, visible=True, - bbox=dict(edgecolor='k', facecolor='w', fill=True, lw=1.0), + bbox={'edgecolor': 'k', 'facecolor': 'w', 'fill': True, 'lw': 1.0}, fontsize=14, ) obj.figure.canvas.draw() @@ -231,7 +226,7 @@ def calculate_gate_grid(axes, qubit_lines, plot_params): def text(axes, gate_pos, wire_pos, textstr, plot_params): """ - Draws a text box on the figure. + Draw a text box on the figure. Args: axes (matplotlib.axes.Axes): axes object @@ -258,7 +253,7 @@ def text(axes, gate_pos, wire_pos, textstr, plot_params): def create_figure(plot_params): """ - Create a new figure as well as a new axes instance + Create a new figure as well as a new axes instance. Args: plot_params (dict): plot parameters @@ -276,8 +271,9 @@ def create_figure(plot_params): def resize_figure(fig, axes, width, height, plot_params): """ - Resizes a figure and adjust the limits of the axes instance to make sure - that the distances in data coordinates on the screen stay constant. + Resize a figure and adjust the limits of the axes instance. + + This functions makes sure that the distances in data coordinates on the screen stay constant. Args: fig (matplotlib.figure.Figure): figure object @@ -300,7 +296,7 @@ def draw_gates( # pylint: disable=too-many-arguments axes, qubit_lines, drawing_order, gate_grid, wire_grid, plot_params ): """ - Draws the gates. + Draw the gates. Args: qubit_lines (dict): list of gates for each qubit axis @@ -332,15 +328,14 @@ def draw_gate( axes, gate_str, gate_pos, target_wires, targets_order, control_wires, plot_params ): # pylint: disable=too-many-arguments """ - Draws a single gate at a given location. + Draw a single gate at a given location. Args: axes (AxesSubplot): axes object gate_str (str): string representation of a gate gate_pos (float): x coordinate of the gate [data units] target_wires (list): y coordinates of the target qubits - targets_order (list): index of the wires corresponding to the target - qubit IDs + targets_order (list): index of the wires corresponding to the target qubit IDs control_wires (list): y coordinates of the control qubits plot_params (dict): plot parameters @@ -403,7 +398,7 @@ def draw_gate( def draw_generic_gate(axes, gate_pos, wire_pos, gate_str, plot_params): """ - Draws a measurement gate. + Draw a measurement gate. Args: axes (AxesSubplot): axes object @@ -438,7 +433,7 @@ def draw_generic_gate(axes, gate_pos, wire_pos, gate_str, plot_params): def draw_measure_gate(axes, gate_pos, wire_pos, plot_params): """ - Draws a measurement gate. + Draw a measurement gate. Args: axes (AxesSubplot): axes object @@ -490,7 +485,7 @@ def multi_qubit_gate( # pylint: disable=too-many-arguments axes, gate_str, gate_pos, wire_pos_min, wire_pos_max, plot_params ): """ - Draws a multi-target qubit gate. + Draw a multi-target qubit gate. Args: axes (matplotlib.axes.Axes): axes object @@ -531,7 +526,7 @@ def multi_qubit_gate( # pylint: disable=too-many-arguments def draw_x_gate(axes, gate_pos, wire_pos, plot_params): """ - Draws the symbol for a X/NOT gate. + Draw the symbol for a X/NOT gate. Args: axes (matplotlib.axes.Axes): axes object @@ -556,7 +551,7 @@ def draw_x_gate(axes, gate_pos, wire_pos, plot_params): def draw_control_z_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): """ - Draws the symbol for a controlled-Z gate. + Draw the symbol for a controlled-Z gate. Args: axes (matplotlib.axes.Axes): axes object @@ -581,7 +576,7 @@ def draw_control_z_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): def draw_swap_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): """ - Draws the symbol for a SWAP gate. + Draw the symbol for a SWAP gate. Args: axes (matplotlib.axes.Axes): axes object @@ -605,14 +600,13 @@ def draw_swap_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): def draw_wires(axes, n_labels, gate_grid, wire_grid, plot_params): """ - Draws all the circuit qubit wires. + Draw all the circuit qubit wires. Args: axes (matplotlib.axes.Axes): axes object n_labels (int): number of qubit gate_grid (ndarray): array with the ref. x positions of the gates - wire_grid (ndarray): array with the ref. y positions of the qubit - wires + wire_grid (ndarray): array with the ref. y positions of the qubit wires plot_params (dict): plot parameters """ # pylint: disable=invalid-name @@ -632,15 +626,14 @@ def draw_wires(axes, n_labels, gate_grid, wire_grid, plot_params): def draw_labels(axes, qubit_labels, drawing_order, wire_grid, plot_params): """ - Draws the labels at the start of each qubit wire + Draw the labels at the start of each qubit wire. Args: axes (matplotlib.axes.Axes): axes object qubit_labels (list): labels of the qubit to be drawn drawing_order (dict): Mapping between wire indices and qubit IDs gate_grid (ndarray): array with the ref. x positions of the gates - wire_grid (ndarray): array with the ref. y positions of the qubit - wires + wire_grid (ndarray): array with the ref. y positions of the qubit wires plot_params (dict): plot parameters """ for qubit_id in qubit_labels: diff --git a/projectq/backends/_circuits/_plot_test.py b/projectq/backends/_circuits/_plot_test.py index 85a2f8d4c..d5f3f4f64 100644 --- a/projectq/backends/_circuits/_plot_test.py +++ b/projectq/backends/_circuits/_plot_test.py @@ -20,8 +20,10 @@ Then run the tests simply with '--mpl' """ -import pytest from copy import deepcopy + +import pytest + import projectq.backends._circuits._plot as _plot # ============================================================================== diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index 4b1a568fa..ca64cf407 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -13,19 +13,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module for exporting quantum circuits to LaTeX code""" +"""ProjectQ module for exporting quantum circuits to LaTeX code.""" import json + from projectq.ops import ( Allocate, - Deallocate, DaggeredGate, - get_inverse, + Deallocate, Measure, SqrtSwap, Swap, X, Z, + get_inverse, ) @@ -50,7 +51,7 @@ def _gate_name(gate): def to_latex(circuit, drawing_order=None, draw_gates_in_parallel=True): """ - Translates a given circuit to a TikZ picture in a Latex document. + Translate a given circuit to a TikZ picture in a Latex document. It uses a json-configuration file which (if it does not exist) is created automatically upon running this function for the first time. The config file can be used to determine custom gate sizes, offsets, etc. @@ -108,7 +109,7 @@ def get_default_settings(): Returns: settings (dict): Default circuit settings """ - settings = dict() + settings = {} settings['gate_shadow'] = True settings['lines'] = { 'style': 'very thin', @@ -149,7 +150,7 @@ def get_default_settings(): def _header(settings): """ - Writes the Latex header using the settings file. + Write the Latex header using the settings file. The header includes all packages and defines all tikz styles. @@ -660,12 +661,12 @@ def _gate_pre_offset(self, gate): def _gate_offset(self, gate): """ - Return the offset to use after placing this gate and, if no pre_offset - is defined, the same offset is used in front of the gate. + Return the offset to use after placing this gate. + + If no pre_offset is defined, the same offset is used in front of the gate. Returns: - gate_offset (float): Offset. - (settings['gates'][gate_class_name]['offset']) + gate_offset (float): Offset. (settings['gates'][gate_class_name]['offset']) """ if isinstance(gate, DaggeredGate): gate = gate._gate # pylint: disable=protected-access @@ -709,7 +710,7 @@ def _phase(self, line, pos): def _op(self, line, op=None, offset=0): """ - Returns the gate name for placing a gate on a line. + Return the gate name for placing a gate on a line. Args: line (int): Line number. @@ -725,6 +726,8 @@ def _op(self, line, op=None, offset=0): def _line(self, point1, point2, double=False, line=None): # pylint: disable=too-many-locals,unused-argument """ + Create a line that connects two points. + Connects point1 and point2, where point1 and point2 are either to qubit line indices, in which case the two most recent gates are connected, or two gate indices, in which case line denotes the line number and the two gates are connected on the given line. @@ -733,8 +736,7 @@ def _line(self, point1, point2, double=False, line=None): # pylint: disable=too p1 (int): Index of the first object to connect. p2 (int): Index of the second object to connect. double (bool): Draws double lines if True. - line (int or None): Line index - if provided, p1 and p2 are gate - indices. + line (int or None): Line index - if provided, p1 and p2 are gate indices. Returns: tex_str (string): Latex code to draw this / these line(s). diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index 2d2246114..0ebdc1054 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -20,24 +20,23 @@ import pytest +import projectq.backends._circuits._drawer as _drawer +import projectq.backends._circuits._to_latex as _to_latex from projectq import MainEngine +from projectq.meta import Control from projectq.ops import ( + CNOT, BasicGate, + C, H, - X, - CNOT, Measure, - Z, - Swap, - SqrtX, SqrtSwap, - C, + SqrtX, + Swap, + X, + Z, get_inverse, ) -from projectq.meta import Control - -import projectq.backends._circuits._to_latex as _to_latex -import projectq.backends._circuits._drawer as _drawer def test_tolatex(): diff --git a/projectq/backends/_ionq/_ionq_exc.py b/projectq/backends/_exceptions.py similarity index 70% rename from projectq/backends/_ionq/_ionq_exc.py rename to projectq/backends/_exceptions.py index ad7b52e9e..8626df65c 100644 --- a/projectq/backends/_ionq/_ionq_exc.py +++ b/projectq/backends/_exceptions.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Error classes used by the IonQBackend and IonQ http client.""" +"""Exception classes for projectq.backends.""" class DeviceTooSmall(Exception): @@ -24,27 +24,21 @@ class DeviceOfflineError(Exception): """Raised when a device is required but is currently offline.""" +class DeviceNotHandledError(Exception): + """Exception raised if a selected device cannot handle the circuit or is not supported by ProjectQ.""" + + class RequestTimeoutError(Exception): - """Raised if a request to IonQ's Job creation API times out.""" + """Raised if a request to the job creation API times out.""" class JobSubmissionError(Exception): - """Raised when the IonQ Job creation API contains an error of some kind.""" + """Raised when the job creation API contains an error of some kind.""" class InvalidCommandError(Exception): - """Raised if the IonQBackend engine encounters an invalid command.""" + """Raised if the backend encounters an invalid command.""" class MidCircuitMeasurementError(Exception): """Raised when a mid-circuit measurement is detected on a qubit.""" - - -__all__ = [ - 'JobSubmissionError', - 'DeviceOfflineError', - 'DeviceTooSmall', - 'RequestTimeoutError', - 'InvalidCommandError', - 'MidCircuitMeasurementError', -] diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index a020d2a80..ad070b875 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -12,22 +12,37 @@ # 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. -""" Back-end to run quantum program on IBM's Quantum Experience.""" + +"""Back-end to run quantum program on IBM's Quantum Experience.""" + import math import random from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control -from projectq.ops import NOT, H, Rx, Ry, Rz, Measure, Allocate, Deallocate, Barrier, FlushGate +from projectq.meta import LogicalQubitIDTag, get_control_count, has_negative_control +from projectq.ops import ( + NOT, + Allocate, + Barrier, + Deallocate, + FlushGate, + H, + Measure, + Rx, + Ry, + Rz, +) from projectq.types import WeakQubitRef -from ._ibm_http_client import send, retrieve +from .._exceptions import InvalidCommandError +from ._ibm_http_client import retrieve, send class IBMBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """ - The IBM Backend class, which stores the circuit, transforms it to JSON, - and sends the circuit through the IBM API. + Define the compiler engine class that handles interactions with the IBM API. + + The IBM Backend class, which stores the circuit, transforms it to JSON, and sends the circuit through the IBM API. """ def __init__( @@ -74,7 +89,7 @@ def __init__( self._token = token self._num_retries = num_retries self._interval = interval - self._probabilities = dict() + self._probabilities = {} self.qasm = "" self._json = [] self._measured_ids = [] @@ -103,8 +118,11 @@ def is_available(self, cmd): return False def get_qasm(self): - """Return the QASM representation of the circuit sent to the backend. - Should be called AFTER calling the ibm device""" + """ + Return the QASM representation of the circuit sent to the backend. + + Should be called AFTER calling the ibm device. + """ return self.qasm def _reset(self): @@ -125,7 +143,7 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements raise RuntimeError('No mapper is present in the compiler engine list!') if self._clear: - self._probabilities = dict() + self._probabilities = {} self._clear = False self.qasm = "" self._json = [] @@ -180,7 +198,9 @@ def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos) self._json.append({'qubits': [qb_pos], 'name': 'u2', 'params': [0, 3.141592653589793]}) else: - raise Exception('Command not authorized. You should run the circuit with the appropriate ibm setup.') + raise InvalidCommandError( + 'Command not authorized. You should run the circuit with the appropriate ibm setup.' + ) def _logical_to_physical(self, qb_id): """ @@ -201,13 +221,13 @@ def _logical_to_physical(self, qb_id): def get_probabilities(self, qureg): """ - Return the list of basis states with corresponding probabilities. - If input qureg is a subset of the register used for the experiment, - then returns the projected probabilities over the other states. + Return the probability of the outcome `bit_string` when measuring the quantum register `qureg`. - The measured bits are ordered according to the supplied quantum - register, i.e., the left-most bit in the state-string corresponds to - the first qubit in the supplied quantum register. + Return the list of basis states with corresponding probabilities. If input qureg is a subset of the register + used for the experiment, then returns the projected probabilities over the other states. + + The measured bits are ordered according to the supplied quantum register, i.e., the left-most bit in the + state-string corresponds to the first qubit in the supplied quantum register. Warning: Only call this function after the circuit has been executed! @@ -217,18 +237,16 @@ def get_probabilities(self, qureg): qubits. Returns: - probability_dict (dict): Dictionary mapping n-bit strings to - probabilities. + probability_dict (dict): Dictionary mapping n-bit strings to probabilities. Raises: - RuntimeError: If no data is available (i.e., if the circuit has - not been executed). Or if a qubit was supplied which was not - present in the circuit (might have gotten optimized away). + RuntimeError: If no data is available (i.e., if the circuit has not been executed). Or if a qubit was + supplied which was not present in the circuit (might have gotten optimized away). """ if len(self._probabilities) == 0: raise RuntimeError("Please, run the circuit first!") - probability_dict = dict() + probability_dict = {} for state in self._probabilities: mapped_state = ['0'] * len(qureg) for i, val in enumerate(qureg): @@ -314,7 +332,9 @@ def _run(self): # pylint: disable=too-many-locals def receive(self, command_list): """ - Receives a command list and, for each command, stores it until completion. Upon flush, send the data to the + Receive a list of commands. + + Receive a command list and, for each command, stores it until completion. Upon flush, send the data to the IBM QE API. Args: diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 323256de2..a30233b44 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" Back-end to run quantum program on IBM QE cloud platform""" +"""Back-end to run quantum program on IBM QE cloud platform.""" # helpers to run the jsonified gate sequence on ibm quantum experience server @@ -21,13 +21,15 @@ # source at: https://github.com/Qiskit/qiskit-ibmq-provider import getpass -import time import signal +import time import uuid import requests -from requests.compat import urljoin from requests import Session +from requests.compat import urljoin + +from .._exceptions import DeviceOfflineError, DeviceTooSmall _AUTH_API_URL = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' _API_URL = 'https://api.quantum-computing.ibm.com/api/' @@ -37,33 +39,31 @@ class IBMQ(Session): - """ - Manage a session between ProjectQ and the IBMQ web API. - """ + """Manage a session between ProjectQ and the IBMQ web API.""" def __init__(self, **kwargs): + """Initialize a session with the IBM QE's APIs.""" super().__init__(**kwargs) - self.backends = dict() + self.backends = {} self.timeout = 5.0 def get_list_devices(self, verbose=False): """ - Get the list of available IBM backends with their properties + Get the list of available IBM backends with their properties. Args: verbose (bool): print the returned dictionnary if True Returns: - (dict) backends dictionary by name device, containing the qubit - size 'nq', the coupling map 'coupling_map' as well as the - device version 'version' + (dict) backends dictionary by name device, containing the qubit size 'nq', the coupling map 'coupling_map' + as well as the device version 'version' """ list_device_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' argument = {'allow_redirects': True, 'timeout': (self.timeout, None)} request = super().get(urljoin(_API_URL, list_device_url), **argument) request.raise_for_status() r_json = request.json() - self.backends = dict() + self.backends = {} for obj in r_json: self.backends[obj['backend_name']] = { 'nq': obj['n_qubits'], @@ -93,8 +93,7 @@ def can_run_experiment(self, info, device): Check if the device is big enough to run the code. Args: - info (dict): dictionary sent by the backend containing the code to - run + info (dict): dictionary sent by the backend containing the code to run device (str): name of the ibm device to use Returns: @@ -109,6 +108,8 @@ def can_run_experiment(self, info, device): def authenticate(self, token=None): """ + Authenticate with IBM's Web API. + Args: token (str): IBM quantum experience user API token. """ @@ -130,21 +131,18 @@ def authenticate(self, token=None): def run(self, info, device): # pylint: disable=too-many-locals """ Run the quantum code to the IBMQ machine. - Update since September 2020: only protocol available is what they call - 'object storage' where a job request via the POST method gets in - return a url link to which send the json data. A final http validates - the data communication. + + Update since September 2020: only protocol available is what they call 'object storage' where a job request + via the POST method gets in return a url link to which send the json data. A final http validates the data + communication. Args: - info (dict): dictionary sent by the backend containing the code to - run + info (dict): dictionary sent by the backend containing the code to run device (str): name of the ibm device to use Returns: (tuple): (str) Execution Id - """ - # STEP1: Obtain most of the URLs for handling communication with # quantum device json_step1 = { @@ -217,10 +215,7 @@ def run(self, info, device): # pylint: disable=too-many-locals def get_result( self, device, execution_id, num_retries=3000, interval=1, verbose=False ): # pylint: disable=too-many-arguments,too-many-locals - """ - Get the result of an execution - """ - + """Get the result of an execution.""" job_status_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/' + execution_id if verbose: @@ -292,18 +287,9 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover raise Exception("Timeout. The ID of your submitted job is {}.".format(execution_id)) -class DeviceTooSmall(Exception): - """Exception raised if the device is too small to run the circuit""" - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" - - def show_devices(token=None, verbose=False): """ - Access the list of available devices and their properties (ex: for setup - configuration) + Access the list of available devices and their properties (ex: for setup configuration). Args: token (str): IBM quantum experience user API token. @@ -319,7 +305,7 @@ def show_devices(token=None, verbose=False): def retrieve(device, token, jobid, num_retries=3000, interval=1, verbose=False): # pylint: disable=too-many-arguments """ - Retrieves a previously run job by its ID. + Retrieve a previously run job by its ID. Args: device (str): Device on which the code was run / is running. @@ -346,16 +332,15 @@ def send( verbose=False, ): # pylint: disable=too-many-arguments """ - Sends QASM through the IBM API and runs the quantum circuit. + Send QASM through the IBM API and runs the quantum circuit. Args: info(dict): Contains representation of the circuit to run. device (str): name of the ibm device. Simulator chosen by default token (str): IBM quantum experience user API token. shots (int): Number of runs of the same circuit to collect statistics. - verbose (bool): If True, additional information is printed, such as - measurement statistics. Otherwise, the backend simply registers - one measurement result (same behavior as the projectq Simulator). + verbose (bool): If True, additional information is printed, such as measurement statistics. Otherwise, the + backend simply registers one measurement result (same behavior as the projectq Simulator). Returns: (dict) result form the IBMQ server diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 278017c4e..69b067723 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -71,21 +71,19 @@ def raise_for_status(self): status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[1] == urljoin(_API_URL, status_url) and (request_num[0] == 1 or request_num[0] == 6): request_num[0] += 1 - connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return MockResponse( [ { @@ -334,21 +332,19 @@ def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[1] == urljoin(_API_URL, status_url): - connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return MockResponse( [ { @@ -532,21 +528,19 @@ def raise_for_status(self): # Accessing status of device. Return device info. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[1] == urljoin(_API_URL, status_url): - connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return MockResponse( [ { diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index c16dbe461..8d744222f 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -14,20 +14,24 @@ # limitations under the License. """Tests for projectq.backends._ibm._ibm.py.""" -import pytest import math + +import pytest + from projectq.backends._ibm import _ibm -from projectq.cengines import MainEngine, BasicMapperEngine, DummyEngine +from projectq.cengines import BasicMapperEngine, DummyEngine, MainEngine from projectq.meta import LogicalQubitIDTag from projectq.ops import ( + CNOT, + NOT, All, Allocate, Barrier, Command, Deallocate, Entangle, + H, Measure, - NOT, Rx, Ry, Rz, @@ -38,8 +42,6 @@ X, Y, Z, - H, - CNOT, ) from projectq.setups import restrictedgateset from projectq.types import WeakQubitRef @@ -121,7 +123,7 @@ def mock_send(*args, **kwargs): monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res @@ -140,7 +142,7 @@ def mock_send(*args, **kwargs): def test_ibm_sent_error_2(monkeypatch): backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res @@ -218,7 +220,7 @@ def mock_retrieve(*args, **kwargs): monkeypatch.setattr(_ibm, "retrieve", mock_retrieve) backend = _ibm.IBMBackend(retrieve_execution="ab1s2", num_runs=1000) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res @@ -339,7 +341,7 @@ def mock_send(*args, **kwargs): with pytest.raises(RuntimeError): backend.get_probabilities([]) mapper = BasicMapperEngine() - res = dict() + res = {} for i in range(4): res[i] = i mapper.current_mapping = res diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index 191d3bd9b..e6480ba74 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -13,7 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" Back-end to run quantum programs using IonQ hardware.""" +"""Back-end to run quantum programs using IonQ hardware.""" + import random from projectq.cengines import BasicEngine @@ -45,8 +46,8 @@ ) from projectq.types import WeakQubitRef +from .._exceptions import InvalidCommandError, MidCircuitMeasurementError from . import _ionq_http_client as http_client -from ._ionq_exc import InvalidCommandError, MidCircuitMeasurementError GATE_MAP = { XGate: 'x', @@ -95,27 +96,25 @@ def __init__( interval=1, retrieve_execution=None, ): # pylint: disable=too-many-arguments - """Constructor for the IonQBackend. + """ + Initialize an IonQBackend object. Args: - use_hardware (bool, optional): Whether or not to use real IonQ - hardware or just a simulator. If False, the ionq_simulator is - used regardless of the value of ``device``. Defaults to False. + use_hardware (bool, optional): Whether or not to use real IonQ hardware or just a simulator. If False, the + ionq_simulator is used regardless of the value of ``device``. Defaults to False. num_runs (int, optional): Number of times to run circuits. Defaults to 100. - verbose (bool, optional): If True, print statistics after job - results have been collected. Defaults to False. + verbose (bool, optional): If True, print statistics after job results have been collected. Defaults to + False. token (str, optional): An IonQ API token. Defaults to None. - device (str, optional): Device to run jobs on. - Supported devices are ``'ionq_qpu'`` or ``'ionq_simulator'``. - Defaults to ``'ionq_simulator'``. - num_retries (int, optional): Number of times to retry fetching a - job after it has been submitted. Defaults to 3000. - interval (int, optional): Number of seconds to wait inbetween - result fetch retries. Defaults to 1. - retrieve_execution (str, optional): An IonQ API Job ID. - If provided, a job with this ID will be fetched. Defaults to None. + device (str, optional): Device to run jobs on. Supported devices are ``'ionq_qpu'`` or + ``'ionq_simulator'``. Defaults to ``'ionq_simulator'``. + num_retries (int, optional): Number of times to retry fetching a job after it has been submitted. Defaults + to 3000. + interval (int, optional): Number of seconds to wait inbetween result fetch retries. Defaults to 1. + retrieve_execution (str, optional): An IonQ API Job ID. If provided, a job with this ID will be + fetched. Defaults to None. """ - BasicEngine.__init__(self) + super().__init__() self.device = device if use_hardware else 'ionq_simulator' self._num_runs = num_runs self._verbose = verbose @@ -124,12 +123,13 @@ def __init__( self._interval = interval self._circuit = [] self._measured_ids = [] - self._probabilities = dict() + self._probabilities = {} self._retrieve_execution = retrieve_execution self._clear = True def is_available(self, cmd): - """Test if this backend is available to process the provided command. + """ + Test if this backend is available to process the provided command. Args: cmd (Command): A command to process. @@ -160,14 +160,12 @@ def is_available(self, cmd): return False def _reset(self): - """Reset this backend. - - .. NOTE:: - - This sets ``_clear = True``, which will trigger state cleanup - on the next call to ``_store``. """ + Reset this backend. + Note: + This sets ``_clear = True``, which will trigger state cleanup on the next call to ``_store``. + """ # Lastly, reset internal state for measured IDs and circuit body. self._circuit = [] self._clear = True @@ -185,7 +183,7 @@ def _store(self, cmd): """ if self._clear: self._measured_ids = [] - self._probabilities = dict() + self._probabilities = {} self._clear = False # No-op/Meta gates. @@ -269,13 +267,11 @@ def get_probability(self, state, qureg): return probs[state] def get_probabilities(self, qureg): - """Given the provided qubit register, determine the probability of - each possible outcome. - - .. NOTE:: + """ + Given the provided qubit register, determine the probability of each possible outcome. - This method should only be called *after* a circuit has been - run and its results are available. + Note: + This method should only be called *after* a circuit has been run and its results are available. Args: qureg (Qureg): A ProjectQ Qureg object. diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index 8f45389b9..2cb181158 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" HTTP Client for the IonQ API. """ +"""HTTP Client for the IonQ API.""" import getpass import json @@ -24,7 +24,7 @@ from requests import Session from requests.compat import urljoin -from ._ionq_exc import ( +from .._exceptions import ( DeviceOfflineError, DeviceTooSmall, JobSubmissionError, @@ -38,8 +38,9 @@ class IonQ(Session): """A requests.Session based HTTP client for the IonQ API.""" def __init__(self, verbose=False): + """Initialize an session with IonQ's APIs.""" super().__init__() - self.backends = dict() + self.backends = {} self.timeout = 5.0 self.token = None self._verbose = verbose @@ -61,7 +62,8 @@ def update_devices_list(self): print(self.backends) def is_online(self, device): - """Check if a given device is online. + """ + Check if a given device is online. Args: device (str): An IonQ device name. @@ -73,8 +75,7 @@ def is_online(self, device): def can_run_experiment(self, info, device): """ - Determine whether or not the desired device has enough allocatable - qubits to run something. + Determine whether or not the desired device has enough allocatable qubits to run something. This returns a three-element tuple with whether or not the experiment can be run, the max number of qubits possible, and the number of qubits @@ -165,7 +166,8 @@ def run(self, info, device): ) def get_result(self, device, execution_id, num_retries=3000, interval=1): - """Given a backend and ID, fetch the results for this job's execution. + """ + Given a backend and ID, fetch the results for this job's execution. The return dictionary should have at least: @@ -190,7 +192,6 @@ def get_result(self, device, execution_id, num_retries=3000, interval=1): Returns: dict: A dict of job data for an engine to consume. """ - if self._verbose: # pragma: no cover print("Waiting for results. [Job ID: {}]".format(execution_id)) diff --git a/projectq/backends/_ionq/_ionq_http_client_test.py b/projectq/backends/_ionq/_ionq_http_client_test.py index c1586569d..777c5d7f0 100644 --- a/projectq/backends/_ionq/_ionq_http_client_test.py +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -20,8 +20,8 @@ import requests from requests.compat import urljoin +from projectq.backends._exceptions import JobSubmissionError, RequestTimeoutError from projectq.backends._ionq import _ionq_http_client -from projectq.backends._ionq._ionq_exc import JobSubmissionError, RequestTimeoutError # Insure that no HTTP request can be made in all tests in this module diff --git a/projectq/backends/_ionq/_ionq_mapper.py b/projectq/backends/_ionq/_ionq_mapper.py index 24c330f0f..3a5eb5a57 100644 --- a/projectq/backends/_ionq/_ionq_mapper.py +++ b/projectq/backends/_ionq/_ionq_mapper.py @@ -12,7 +12,9 @@ # 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. -"""Mapper that has a max number of allocatable qubits.""" + +"""Mapper that has a maximum number of allocatable qubits.""" + from projectq.cengines import BasicMapperEngine from projectq.meta import LogicalQubitIDTag from projectq.ops import AllocateQubitGate, Command, DeallocateQubitGate, FlushGate @@ -20,9 +22,10 @@ class BoundedQubitMapper(BasicMapperEngine): - """Maps logical qubits to a fixed number of hardware qubits""" + """Map logical qubits to a fixed number of hardware qubits.""" def __init__(self, max_qubits): + """Initialize a BoundedQubitMapper object.""" super().__init__() self._qubit_idx = 0 self.max_qubits = max_qubits @@ -34,7 +37,7 @@ def _reset(self): def _process_cmd(self, cmd): current_mapping = self.current_mapping if current_mapping is None: - current_mapping = dict() + current_mapping = {} if isinstance(cmd.gate, AllocateQubitGate): qubit_id = cmd.qubits[0][0].id @@ -74,6 +77,12 @@ def _process_cmd(self, cmd): self._send_cmd_with_mapped_ids(cmd) def receive(self, command_list): + """ + Receive a list of commands. + + Args: + command_list (list): List of commands to receive. + """ for cmd in command_list: if isinstance(cmd.gate, FlushGate): self._reset() diff --git a/projectq/backends/_ionq/_ionq_mapper_test.py b/projectq/backends/_ionq/_ionq_mapper_test.py index 4f7e70605..f86fb10d9 100644 --- a/projectq/backends/_ionq/_ionq_mapper_test.py +++ b/projectq/backends/_ionq/_ionq_mapper_test.py @@ -16,7 +16,7 @@ from projectq.backends import Simulator from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper -from projectq.cengines import MainEngine, DummyEngine +from projectq.cengines import DummyEngine, MainEngine from projectq.meta import LogicalQubitIDTag from projectq.ops import AllocateQubitGate, Command, DeallocateQubitGate from projectq.types import WeakQubitRef diff --git a/projectq/backends/_ionq/_ionq_test.py b/projectq/backends/_ionq/_ionq_test.py index 4156edb63..b23450353 100644 --- a/projectq/backends/_ionq/_ionq_test.py +++ b/projectq/backends/_ionq/_ionq_test.py @@ -21,12 +21,11 @@ import pytest from projectq import MainEngine -from projectq.backends._ionq import _ionq, _ionq_http_client -from projectq.backends._ionq._ionq_exc import ( +from projectq.backends._exceptions import ( InvalidCommandError, MidCircuitMeasurementError, ) -from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper +from projectq.backends._ionq import _ionq, _ionq_http_client from projectq.cengines import DummyEngine from projectq.ops import ( CNOT, @@ -56,6 +55,8 @@ ) from projectq.types import WeakQubitRef +from ._ionq_mapper import BoundedQubitMapper + @pytest.fixture(scope='function') def mapper_factory(): diff --git a/projectq/backends/_printer.py b/projectq/backends/_printer.py index fc91ae839..9016b4753 100755 --- a/projectq/backends/_printer.py +++ b/projectq/backends/_printer.py @@ -12,22 +12,22 @@ # 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. -""" -Contains a compiler engine which prints commands to stdout prior to sending them on to the next engines (see -CommandPrinter). -""" -import sys +"""Contains a compiler engine which prints commands to stdout prior to sending them on to the next engines.""" + +import sys from builtins import input from projectq.cengines import BasicEngine, LastEngineException +from projectq.meta import LogicalQubitIDTag, get_control_count from projectq.ops import FlushGate, Measure -from projectq.meta import get_control_count, LogicalQubitIDTag from projectq.types import WeakQubitRef class CommandPrinter(BasicEngine): """ + Compiler engine that prints command to the standard output. + CommandPrinter is a compiler engine which prints commands to stdout prior to sending them on to the next compiler engine. """ @@ -42,13 +42,15 @@ def __init__(self, accept_input=True, default_measure=False, in_place=False): default_measure (bool): Default measurement result (if accept_input is False). in_place (bool): If in_place is true, all output is written on the same line of the terminal. """ - BasicEngine.__init__(self) + super().__init__() self._accept_input = accept_input self._default_measure = default_measure self._in_place = in_place def is_available(self, cmd): """ + Test whether a Command is supported by a compiler engine. + Specialized implementation of is_available: Returns True if the CommandPrinter is the last engine (since it can print any command). @@ -64,6 +66,8 @@ def is_available(self, cmd): def _print_cmd(self, cmd): """ + Print a command. + Print a command or, if the command is a measurement instruction and the CommandPrinter is the last engine in the engine pipeline: Query the user for the measurement result (if accept_input = True) / Set the result to 0 (if it's False). @@ -102,6 +106,8 @@ def _print_cmd(self, cmd): def receive(self, command_list): """ + Receive a list of commands. + Receive a list of commands from the previous engine, print the commands, and then send them on to the next engine. diff --git a/projectq/backends/_printer_test.py b/projectq/backends/_printer_test.py index 3c06b3ed1..8d81ffc1b 100755 --- a/projectq/backends/_printer_test.py +++ b/projectq/backends/_printer_test.py @@ -19,13 +19,12 @@ import pytest from projectq import MainEngine +from projectq.backends import _printer from projectq.cengines import DummyEngine, InstructionFilter, NotYetMeasuredError from projectq.meta import LogicalQubitIDTag -from projectq.ops import Allocate, Command, H, Measure, NOT, T +from projectq.ops import NOT, Allocate, Command, H, Measure, T from projectq.types import WeakQubitRef -from projectq.backends import _printer - def test_command_printer_is_available(): inline_cmd_printer = _printer.CommandPrinter() diff --git a/projectq/backends/_resource.py b/projectq/backends/_resource.py index 558b298bf..e279d579f 100755 --- a/projectq/backends/_resource.py +++ b/projectq/backends/_resource.py @@ -13,13 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Contains a compiler engine which counts the number of calls for each type of gate used in a circuit, in addition to +Contain a compiler engine to calculate resource count used by a quantum circuit. + +A resrouce counter compiler engine counts the number of calls for each type of gate used in a circuit, in addition to the max. number of active qubits. """ from projectq.cengines import BasicEngine, LastEngineException -from projectq.meta import get_control_count, LogicalQubitIDTag -from projectq.ops import FlushGate, Deallocate, Allocate, Measure +from projectq.meta import LogicalQubitIDTag, get_control_count +from projectq.ops import Allocate, Deallocate, FlushGate, Measure from projectq.types import WeakQubitRef @@ -43,17 +45,19 @@ def __init__(self): Sets all statistics to zero. """ - BasicEngine.__init__(self) + super().__init__() self.gate_counts = {} self.gate_class_counts = {} self._active_qubits = 0 self.max_width = 0 # key: qubit id, depth of this qubit - self._depth_of_qubit = dict() + self._depth_of_qubit = {} self._previous_max_depth = 0 def is_available(self, cmd): """ + Test whether a Command is supported by a compiler engine. + Specialized implementation of is_available: Returns True if the ResourceCounter is the last engine (since it can count any command). @@ -70,18 +74,14 @@ def is_available(self, cmd): @property def depth_of_dag(self): - """ - Return the depth of the DAG. - """ + """Return the depth of the DAG.""" if self._depth_of_qubit: current_max = max(self._depth_of_qubit.values()) return max(current_max, self._previous_max_depth) return self._previous_max_depth def _add_cmd(self, cmd): # pylint: disable=too-many-branches - """ - Add a gate to the count. - """ + """Add a gate to the count.""" if cmd.gate == Allocate: self._active_qubits += 1 self._depth_of_qubit[cmd.qubits[0][0].id] = 0 @@ -155,9 +155,9 @@ def __str__(self): return ( "Gate class counts:\n " - + "\n ".join(list(sorted(gate_class_list))) + + "\n ".join(sorted(gate_class_list)) + "\n\nGate counts:\n " - + "\n ".join(list(sorted(gate_list))) + + "\n ".join(sorted(gate_list)) + "\n\nMax. width (number of qubits) : " + str(self.max_width) + "." @@ -166,6 +166,8 @@ def __str__(self): def receive(self, command_list): """ + Receive a list of commands. + Receive a list of commands from the previous engine, increases the counters of the received commands, and then send them on to the next engine. diff --git a/projectq/backends/_resource_test.py b/projectq/backends/_resource_test.py index 9031d9cc9..89ebeef0f 100755 --- a/projectq/backends/_resource_test.py +++ b/projectq/backends/_resource_test.py @@ -18,13 +18,12 @@ import pytest +from projectq.backends import ResourceCounter from projectq.cengines import DummyEngine, MainEngine, NotYetMeasuredError from projectq.meta import LogicalQubitIDTag -from projectq.ops import All, Allocate, CNOT, Command, H, Measure, QFT, Rz, Rzz, X +from projectq.ops import CNOT, QFT, All, Allocate, Command, H, Measure, Rz, Rzz, X from projectq.types import WeakQubitRef -from projectq.backends import ResourceCounter - class MockEngine(object): def is_available(self, cmd): diff --git a/projectq/backends/_sim/__init__.py b/projectq/backends/_sim/__init__.py index c0d0d5d3f..1557d03a1 100755 --- a/projectq/backends/_sim/__init__.py +++ b/projectq/backends/_sim/__init__.py @@ -15,5 +15,5 @@ """ProjectQ module dedicated to simulation""" -from ._simulator import Simulator from ._classical_simulator import ClassicalSimulator +from ._simulator import Simulator diff --git a/projectq/backends/_sim/_classical_simulator.py b/projectq/backends/_sim/_classical_simulator.py index 308987ca8..eb270e219 100755 --- a/projectq/backends/_sim/_classical_simulator.py +++ b/projectq/backends/_sim/_classical_simulator.py @@ -12,13 +12,12 @@ # 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. -""" -A simulator that only permits classical operations, for faster/easier testing. -""" + +"""A simulator that only permits classical operations, for faster/easier testing.""" from projectq.cengines import BasicEngine from projectq.meta import LogicalQubitIDTag -from projectq.ops import XGate, BasicMathGate, Measure, FlushGate, Allocate, Deallocate +from projectq.ops import Allocate, BasicMathGate, Deallocate, FlushGate, Measure, XGate from projectq.types import WeakQubitRef @@ -31,13 +30,14 @@ class ClassicalSimulator(BasicEngine): """ def __init__(self): - BasicEngine.__init__(self) + """Initialize a ClassicalSimulator object.""" + super().__init__() self._state = 0 self._bit_positions = {} def _convert_logical_to_mapped_qubit(self, qubit): """ - Converts a qubit from a logical to a mapped qubit if there is a mapper. + Convert a qubit from a logical to a mapped qubit if there is a mapper. Args: qubit (projectq.types.Qubit): Logical quantum bit @@ -51,7 +51,7 @@ def _convert_logical_to_mapped_qubit(self, qubit): def read_bit(self, qubit): """ - Reads a bit. + Read a bit. Note: If there is a mapper present in the compiler, this function automatically converts from logical qubits to @@ -67,7 +67,11 @@ def read_bit(self, qubit): return self._read_mapped_bit(qubit) def _read_mapped_bit(self, mapped_qubit): - """Internal use only. Does not change logical to mapped qubits.""" + """ + Read a mapped bit value. + + For internal use only. Does not change logical to mapped qubits. + """ return (self._state >> self._bit_positions[mapped_qubit.id]) & 1 def write_bit(self, qubit, value): @@ -86,7 +90,11 @@ def write_bit(self, qubit, value): self._write_mapped_bit(qubit, value) def _write_mapped_bit(self, mapped_qubit, value): - """Internal use only. Does not change logical to mapped qubits.""" + """ + Write a mapped bit value. + + For internal use only. Does not change logical to mapped qubits. + """ pos = self._bit_positions[mapped_qubit.id] if value: self._state |= 1 << pos @@ -95,7 +103,7 @@ def _write_mapped_bit(self, mapped_qubit, value): def _mask(self, qureg): """ - Returns a mask, to compare against the state, with bits from the register set to 1 and other bits set to 0. + Return a mask, to compare against the state, with bits from the register set to 1 and other bits set to 0. Args: qureg (projectq.types.Qureg): The bits whose positions should be set. @@ -110,7 +118,7 @@ def _mask(self, qureg): def read_register(self, qureg): """ - Reads a group of bits as a little-endian integer. + Read a group of bits as a little-endian integer. Note: If there is a mapper present in the compiler, this function automatically converts from logical qubits to @@ -129,7 +137,11 @@ def read_register(self, qureg): return self._read_mapped_register(new_qureg) def _read_mapped_register(self, mapped_qureg): - """Internal use only. Does not change logical to mapped qubits.""" + """ + Read a value to some mapped quantum register. + + For internal use only. Does not change logical to mapped qubits. + """ mask = 0 for i, qubit in enumerate(mapped_qureg): mask |= self._read_mapped_bit(qubit) << i @@ -137,7 +149,7 @@ def _read_mapped_register(self, mapped_qureg): def write_register(self, qureg, value): """ - Sets a group of bits to store a little-endian integer value. + Set a group of bits to store a little-endian integer value. Note: If there is a mapper present in the compiler, this function automatically converts from logical qubits to @@ -153,13 +165,18 @@ def write_register(self, qureg, value): self._write_mapped_register(new_qureg, value) def _write_mapped_register(self, mapped_qureg, value): - """Internal use only. Does not change logical to mapped qubits.""" + """ + Write a value to some mapped quantum register. + + For internal use only. Does not change logical to mapped qubits. + """ if value < 0 or value >= 1 << len(mapped_qureg): raise ValueError("Value won't fit in register.") for i, mapped_qubit in enumerate(mapped_qureg): self._write_mapped_bit(mapped_qubit, (value >> i) & 1) def is_available(self, cmd): + """Test whether a Command is supported by a compiler engine.""" return ( cmd.gate == Measure or cmd.gate == Allocate @@ -168,7 +185,11 @@ def is_available(self, cmd): ) def receive(self, command_list): - """Forward all commands to the next engine.""" + """ + Receive a list of commands. + + This implementation simply forwards all commands to the next engine. + """ for cmd in command_list: self._handle(cmd) if not self.is_last_engine: diff --git a/projectq/backends/_sim/_classical_simulator_test.py b/projectq/backends/_sim/_classical_simulator_test.py index 8a35d2159..f8e9121bb 100755 --- a/projectq/backends/_sim/_classical_simulator_test.py +++ b/projectq/backends/_sim/_classical_simulator_test.py @@ -16,25 +16,17 @@ import pytest from projectq import MainEngine -from projectq.ops import ( - All, - BasicMathGate, - C, - Measure, - NOT, - X, - Y, -) from projectq.cengines import ( AutoReplacer, BasicMapperEngine, DecompositionRuleSet, DummyEngine, ) -from ._simulator_test import mapper # noqa: F401 +from projectq.ops import NOT, All, BasicMathGate, C, Measure, X, Y from projectq.types import WeakQubitRef from ._classical_simulator import ClassicalSimulator +from ._simulator_test import mapper # noqa: F401 def test_simulator_read_write(mapper): # noqa: F811 @@ -103,11 +95,11 @@ def test_simulator_bit_repositioning(mapper): # noqa: F811 def test_simulator_arithmetic(mapper): # noqa: F811 class Offset(BasicMathGate): def __init__(self, amount): - BasicMathGate.__init__(self, lambda x: (x + amount,)) + super().__init__(lambda x: (x + amount,)) class Sub(BasicMathGate): def __init__(self): - BasicMathGate.__init__(self, lambda x, y: (x, y - x)) + super().__init__(lambda x, y: (x, y - x)) engine_list = [] if mapper is not None: diff --git a/projectq/backends/_sim/_pysim.py b/projectq/backends/_sim/_pysim.py index 54860cafd..dc4687dbc 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -12,14 +12,16 @@ # 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. + """ -Contains a (slow) Python simulator. +A (slow) Python simulator. Please compile the c++ simulator for large-scale simulations. """ -import random import os +import random + import numpy as _np _USE_REFCHECK = True @@ -46,7 +48,7 @@ def __init__(self, rnd_seed, *args, **kwargs): # pylint: disable=unused-argumen """ random.seed(rnd_seed) self._state = _np.ones(1, dtype=_np.complex128) - self._map = dict() + self._map = {} self._num_qubits = 0 print("(Note: This is the (slow) Python simulator.)") @@ -64,8 +66,7 @@ def cheat(self): def measure_qubits(self, ids): """ - Measure the qubits with IDs ids and return a list of measurement - outcomes (True/False). + Measure the qubits with IDs ids and return a list of measurement outcomes (True/False). Args: ids (list): List of qubit IDs to measure. @@ -162,7 +163,7 @@ def deallocate_qubit(self, qubit_id): newstate[k : k + (1 << pos)] = self._state[i : i + (1 << pos)] # noqa: E203 k += 1 << pos - newmap = dict() + newmap = {} for key, value in self._map.items(): if value > pos: newmap[key] = value - 1 @@ -290,7 +291,9 @@ def get_probability(self, bit_string, ids): def get_amplitude(self, bit_string, ids): """ - Return the probability amplitude of the supplied `bit_string`. The ordering is given by the list of qubit ids. + Return the probability amplitude of the supplied `bit_string`. + + The ordering is given by the list of qubit ids. Args: bit_string (list[bool|int]): Computational basis state @@ -314,8 +317,9 @@ def get_amplitude(self, bit_string, ids): def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): # pylint: disable=too-many-locals """ - Applies exp(-i*time*H) to the wave function, i.e., evolves under the Hamiltonian H for a given time. The terms - in the Hamiltonian are not required to commute. + Apply exp(-i*time*H) to the wave function, i.e., evolves under the Hamiltonian H for a given time. + + The terms in the Hamiltonian are not required to commute. This function computes the action of the matrix exponential using ideas from Al-Mohy and Higham, 2011. TODO: Implement better estimates for s. @@ -361,7 +365,7 @@ def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): # pylint: dis def apply_controlled_gate(self, matrix, ids, ctrlids): """ - Applies the k-qubit gate matrix m to the qubits with indices ids, using ctrlids as control qubits. + Apply the k-qubit gate matrix m to the qubits with indices ids, using ctrlids as control qubits. Args: matrix (list[list]): 2^k x 2^k complex matrix describing the k-qubit gate. @@ -378,7 +382,7 @@ def apply_controlled_gate(self, matrix, ids, ctrlids): def _single_qubit_gate(self, matrix, pos, mask): """ - Applies the single qubit gate matrix m to the qubit at position `pos` using `mask` to identify control qubits. + Apply the single qubit gate matrix m to the qubit at position `pos` using `mask` to identify control qubits. Args: matrix (list[list]): 2x2 complex matrix describing the single-qubit gate. @@ -398,7 +402,7 @@ def kernel(u, d, m): # pylint: disable=invalid-name def _multi_qubit_gate(self, matrix, pos, mask): # pylint: disable=too-many-locals """ - Applies the k-qubit gate matrix m to the qubits at `pos` using `mask` to identify control qubits. + Apply the k-qubit gate matrix m to the qubits at `pos` using `mask` to identify control qubits. Args: matrix (list[list]): 2^k x 2^k complex matrix describing the k-qubit gate. @@ -458,11 +462,10 @@ def collapse_wavefunction(self, ids, values): Args: ids (list[int]): Qubit IDs to collapse. - values (list[bool]): Measurement outcome for each of the qubit IDs - in `ids`. + values (list[bool]): Measurement outcome for each of the qubit IDs in `ids`. + Raises: - RuntimeError: If probability of outcome is ~0 or unknown qubits - are provided. + RuntimeError: If probability of outcome is ~0 or unknown qubits are provided. """ if len(ids) != len(values): raise ValueError('The number of ids and values do not match!') @@ -493,12 +496,15 @@ def collapse_wavefunction(self, ids, values): def run(self): """ - Dummy function to implement the same interface as the c++ simulator. + Provide a dummy implementation for running a quantum circuit. + + Only defined to provide the same interface as the c++ simulator. """ def _apply_term(self, term, ids, ctrlids=None): """ - Applies a QubitOperator term to the state vector. + Apply a QubitOperator term to the state vector. + (Helper function for time evolution & expectation) Args: diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index cfc2b56ec..f7b6f8144 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -12,17 +12,27 @@ # 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. + """ -Contains the projectq interface to a C++-based simulator, which has to be -built first. If the c++ simulator is not exported to python, a (slow) python +The ProjectQ interface to a C++-based simulator. + +The C++ simulator has to be built first. If the C++ simulator is not exported to python, a (slow) python implementation is used as an alternative. """ import math import random + from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control -from projectq.ops import Measure, FlushGate, Allocate, Deallocate, BasicMathGate, TimeEvolution +from projectq.meta import LogicalQubitIDTag, get_control_count, has_negative_control +from projectq.ops import ( + Allocate, + BasicMathGate, + Deallocate, + FlushGate, + Measure, + TimeEvolution, +) from projectq.types import WeakQubitRef FALLBACK_TO_PYSIM = False @@ -36,11 +46,9 @@ class Simulator(BasicEngine): """ - Simulator is a compiler engine which simulates a quantum computer using - C++-based kernels. + Simulator is a compiler engine which simulates a quantum computer using C++-based kernels. - OpenMP is enabled and the number of threads can be controlled using the - OMP_NUM_THREADS environment variable, i.e. + OpenMP is enabled and the number of threads can be controlled using the OMP_NUM_THREADS environment variable, i.e. .. code-block:: bash @@ -50,49 +58,41 @@ class Simulator(BasicEngine): def __init__(self, gate_fusion=False, rnd_seed=None): """ - Construct the C++/Python-simulator object and initialize it with a - random seed. + Construct the C++/Python-simulator object and initialize it with a random seed. Args: - gate_fusion (bool): If True, gates are cached and only executed - once a certain gate-size has been reached (only has an effect - for the c++ simulator). - rnd_seed (int): Random seed (uses random.randint(0, 4294967295) by - default). - - Example of gate_fusion: Instead of applying a Hadamard gate to 5 - qubits, the simulator calculates the kronecker product of the 1-qubit - gate matrices and then applies one 5-qubit gate. This increases - operational intensity and keeps the simulator from having to iterate - through the state vector multiple times. Depending on the system (and, - especially, number of threads), this may or may not be beneficial. + gate_fusion (bool): If True, gates are cached and only executed once a certain gate-size has been reached + (only has an effect for the c++ simulator). + rnd_seed (int): Random seed (uses random.randint(0, 4294967295) by default). + + Example of gate_fusion: Instead of applying a Hadamard gate to 5 qubits, the simulator calculates the + kronecker product of the 1-qubit gate matrices and then applies one 5-qubit gate. This increases operational + intensity and keeps the simulator from having to iterate through the state vector multiple times. Depending on + the system (and, especially, number of threads), this may or may not be beneficial. Note: - If the C++ Simulator extension was not built or cannot be found, - the Simulator defaults to a Python implementation of the kernels. - While this is much slower, it is still good enough to run basic - quantum algorithms. - - If you need to run large simulations, check out the tutorial in - the docs which gives futher hints on how to build the C++ - extension. + If the C++ Simulator extension was not built or cannot be found, the Simulator defaults to a Python + implementation of the kernels. While this is much slower, it is still good enough to run basic quantum + algorithms. + + If you need to run large simulations, check out the tutorial in the docs which gives futher hints on how + to build the C++ extension. """ if rnd_seed is None: rnd_seed = random.randint(0, 4294967295) - BasicEngine.__init__(self) + super().__init__() self._simulator = SimulatorBackend(rnd_seed) self._gate_fusion = gate_fusion def is_available(self, cmd): """ - Specialized implementation of is_available: The simulator can deal - with all arbitrarily-controlled gates which provide a - gate-matrix (via gate.matrix) and acts on 5 or less qubits (not - counting the control qubits). + Test whether a Command is supported by a compiler engine. + + Specialized implementation of is_available: The simulator can deal with all arbitrarily-controlled gates which + provide a gate-matrix (via gate.matrix) and acts on 5 or less qubits (not counting the control qubits). Args: - cmd (Command): Command for which to check availability (single- - qubit gate, arbitrary controls) + cmd (Command): Command for which to check availability (single- qubit gate, arbitrary controls) Returns: True if it can be simulated and False otherwise. @@ -118,7 +118,7 @@ def is_available(self, cmd): def _convert_logical_to_mapped_qureg(self, qureg): """ - Converts a qureg from logical to mapped qubits if there is a mapper. + Convert a qureg from logical to mapped qubits if there is a mapper. Args: qureg (list[Qubit],Qureg): Logical quantum bits @@ -136,6 +136,8 @@ def _convert_logical_to_mapped_qureg(self, qureg): def get_expectation_value(self, qubit_operator, qureg): """ + Return the expectation value of a qubit operator. + Get the expectation value of qubit_operator w.r.t. the current wave function represented by the supplied quantum register. @@ -147,18 +149,15 @@ def get_expectation_value(self, qubit_operator, qureg): Expectation value Note: - Make sure all previous commands (especially allocations) have - passed through the compilation chain (call main_engine.flush() to - make sure). + Make sure all previous commands (especially allocations) have passed through the compilation chain (call + main_engine.flush() to make sure). Note: - If there is a mapper present in the compiler, this function - automatically converts from logical qubits to mapped qubits for - the qureg argument. + If there is a mapper present in the compiler, this function automatically converts from logical qubits to + mapped qubits for the qureg argument. Raises: - Exception: If `qubit_operator` acts on more qubits than present in - the `qureg` argument. + Exception: If `qubit_operator` acts on more qubits than present in the `qureg` argument. """ qureg = self._convert_logical_to_mapped_qureg(qureg) num_qubits = len(qureg) @@ -170,32 +169,27 @@ def get_expectation_value(self, qubit_operator, qureg): def apply_qubit_operator(self, qubit_operator, qureg): """ - Apply a (possibly non-unitary) qubit_operator to the current wave - function represented by the supplied quantum register. + Apply a (possibly non-unitary) qubit_operator to the current wave function represented by a quantum register. Args: qubit_operator (projectq.ops.QubitOperator): Operator to apply. - qureg (list[Qubit],Qureg): Quantum bits to which to apply the - operator. + qureg (list[Qubit],Qureg): Quantum bits to which to apply the operator. Raises: Exception: If `qubit_operator` acts on more qubits than present in the `qureg` argument. Warning: - This function allows applying non-unitary gates and it will not - re-normalize the wave function! It is for numerical experiments - only and should not be used for other purposes. + This function allows applying non-unitary gates and it will not re-normalize the wave function! It is for + numerical experiments only and should not be used for other purposes. Note: - Make sure all previous commands (especially allocations) have - passed through the compilation chain (call main_engine.flush() to - make sure). + Make sure all previous commands (especially allocations) have passed through the compilation chain (call + main_engine.flush() to make sure). Note: - If there is a mapper present in the compiler, this function - automatically converts from logical qubits to mapped qubits for - the qureg argument. + If there is a mapper present in the compiler, this function automatically converts from logical qubits to + mapped qubits for the qureg argument. """ qureg = self._convert_logical_to_mapped_qureg(qureg) num_qubits = len(qureg) @@ -207,8 +201,7 @@ def apply_qubit_operator(self, qubit_operator, qureg): def get_probability(self, bit_string, qureg): """ - Return the probability of the outcome `bit_string` when measuring - the quantum register `qureg`. + Return the probability of the outcome `bit_string` when measuring the quantum register `qureg`. Args: bit_string (list[bool|int]|string[0|1]): Measurement outcome. @@ -218,14 +211,12 @@ def get_probability(self, bit_string, qureg): Probability of measuring the provided bit string. Note: - Make sure all previous commands (especially allocations) have - passed through the compilation chain (call main_engine.flush() to - make sure). + Make sure all previous commands (especially allocations) have passed through the compilation chain (call + main_engine.flush() to make sure). Note: - If there is a mapper present in the compiler, this function - automatically converts from logical qubits to mapped qubits for - the qureg argument. + If there is a mapper present in the compiler, this function automatically converts from logical qubits to + mapped qubits for the qureg argument. """ qureg = self._convert_logical_to_mapped_qureg(qureg) bit_string = [bool(int(b)) for b in bit_string] @@ -234,26 +225,24 @@ def get_probability(self, bit_string, qureg): def get_amplitude(self, bit_string, qureg): """ Return the probability amplitude of the supplied `bit_string`. + The ordering is given by the quantum register `qureg`, which must contain all allocated qubits. Args: bit_string (list[bool|int]|string[0|1]): Computational basis state - qureg (Qureg|list[Qubit]): Quantum register determining the - ordering. Must contain all allocated qubits. + qureg (Qureg|list[Qubit]): Quantum register determining the ordering. Must contain all allocated qubits. Returns: Probability amplitude of the provided bit string. Note: - Make sure all previous commands (especially allocations) have - passed through the compilation chain (call main_engine.flush() to - make sure). + Make sure all previous commands (especially allocations) have passed through the compilation chain (call + main_engine.flush() to make sure). Note: - If there is a mapper present in the compiler, this function - automatically converts from logical qubits to mapped qubits for - the qureg argument. + If there is a mapper present in the compiler, this function automatically converts from logical qubits to + mapped qubits for the qureg argument. """ qureg = self._convert_logical_to_mapped_qureg(qureg) bit_string = [bool(int(b)) for b in bit_string] @@ -335,18 +324,18 @@ def cheat(self): def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too-many-statements """ - Handle all commands, i.e., call the member functions of the C++- - simulator object corresponding to measurement, allocation/ + Handle all commands. + + i.e., call the member functions of the C++- simulator object corresponding to measurement, allocation/ deallocation, and (controlled) single-qubit gate. Args: cmd (Command): Command to handle. Raises: - Exception: If a non-single-qubit gate needs to be processed - (which should never happen due to is_available). + Exception: If a non-single-qubit gate needs to be processed (which should never happen due to + is_available). """ - if cmd.gate == Measure: if get_control_count(cmd) != 0: raise ValueError('Cannot have control qubits with a measurement gate!') @@ -436,13 +425,13 @@ def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too def receive(self, command_list): """ - Receive a list of commands from the previous engine and handle them - (simulate them classically) prior to sending them on to the next - engine. + Receive a list of commands. + + Receive a list of commands from the previous engine and handle them (simulate them classically) prior to + sending them on to the next engine. Args: - command_list (list): List of commands to execute on the - simulator. + command_list (list): List of commands to execute on the simulator. """ for cmd in command_list: if not cmd.gate == FlushGate(): diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 4e6001e35..b386ec48d 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -19,27 +19,29 @@ import copy import math +import random + import numpy import pytest -import random import scipy import scipy.sparse import scipy.sparse.linalg from projectq import MainEngine +from projectq.backends import Simulator from projectq.cengines import ( - BasicEngine, BasicMapperEngine, DummyEngine, LocalOptimizer, NotYetMeasuredError, ) +from projectq.meta import Control, Dagger, LogicalQubitIDTag from projectq.ops import ( + CNOT, All, Allocate, BasicGate, BasicMathGate, - CNOT, Command, H, MatrixGate, @@ -55,11 +57,8 @@ Y, Z, ) -from projectq.meta import Control, Dagger, LogicalQubitIDTag from projectq.types import WeakQubitRef -from projectq.backends import Simulator - def test_is_cpp_simulator_present(): import projectq.backends._sim._cppsim @@ -104,8 +103,8 @@ def mapper(request): class TrivialMapper(BasicMapperEngine): def __init__(self): - BasicEngine.__init__(self) - self.current_mapping = dict() + super().__init__() + self.current_mapping = {} def receive(self, command_list): for cmd in command_list: @@ -126,7 +125,7 @@ def receive(self, command_list): class Mock1QubitGate(MatrixGate): def __init__(self): - MatrixGate.__init__(self) + super().__init__() self.cnt = 0 @property @@ -137,7 +136,7 @@ def matrix(self): class Mock6QubitGate(MatrixGate): def __init__(self): - MatrixGate.__init__(self) + super().__init__() self.cnt = 0 @property @@ -148,7 +147,7 @@ def matrix(self): class MockNoMatrixGate(BasicGate): def __init__(self): - BasicGate.__init__(self) + super().__init__() self.cnt = 0 @@ -264,7 +263,7 @@ def test_simulator_measure_mapped_qubit(sim): class Plus2Gate(BasicMathGate): def __init__(self): - BasicMathGate.__init__(self, lambda x: (x + 2,)) + super().__init__(lambda x: (x + 2,)) def test_simulator_emulation(sim): @@ -746,8 +745,8 @@ def test_simulator_constant_math_emulation(): results = [[[1, 1, 0, 0, 0]], [[0, 1, 0, 0, 0]], [[0, 1, 1, 1, 0]]] import projectq.backends._sim._simulator as _sim - from projectq.backends._sim._pysim import Simulator as PySim from projectq.backends._sim._cppsim import Simulator as CppSim + from projectq.backends._sim._pysim import Simulator as PySim from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN def gate_filter(eng, cmd): @@ -781,7 +780,6 @@ def run_simulation(sim): _sim.FALLBACK_TO_PYSIM = True pysim = Simulator() pysim._simulator = PySim(1) - # run_simulation(pysim) for result in results: ref = result[0] diff --git a/projectq/backends/_sim/_simulator_test_fixtures.py b/projectq/backends/_sim/_simulator_test_fixtures.py index 28cb00f5c..8d256fbc2 100644 --- a/projectq/backends/_sim/_simulator_test_fixtures.py +++ b/projectq/backends/_sim/_simulator_test_fixtures.py @@ -14,20 +14,19 @@ # limitations under the License. import pytest -from projectq.cengines import BasicEngine, BasicMapperEngine + +from projectq.cengines import BasicMapperEngine @pytest.fixture(params=["mapper", "no_mapper"]) def mapper(request): - """ - Adds a mapper which changes qubit ids by adding 1 - """ + """Add a mapper which changes qubit ids by adding 1.""" if request.param == "mapper": class TrivialMapper(BasicMapperEngine): def __init__(self): - BasicEngine.__init__(self) - self.current_mapping = dict() + super().__init__() + self.current_mapping = {} def receive(self, command_list): for cmd in command_list: diff --git a/projectq/backends/_unitary.py b/projectq/backends/_unitary.py index 1ebdbc2cb..625fc50c2 100644 --- a/projectq/backends/_unitary.py +++ b/projectq/backends/_unitary.py @@ -15,22 +15,18 @@ """Contain a backend that saves the unitary of a quantum circuit.""" -from copy import deepcopy import itertools import math -import warnings import random +import warnings +from copy import deepcopy + import numpy as np from projectq.cengines import BasicEngine +from projectq.meta import LogicalQubitIDTag, get_control_count, has_negative_control +from projectq.ops import AllocateQubitGate, DeallocateQubitGate, FlushGate, MeasureGate from projectq.types import WeakQubitRef -from projectq.meta import has_negative_control, get_control_count, LogicalQubitIDTag -from projectq.ops import ( - AllocateQubitGate, - DeallocateQubitGate, - MeasureGate, - FlushGate, -) def _qidmask(target_ids, control_ids, n_qubits): @@ -96,7 +92,7 @@ class UnitarySimulator(BasicEngine): def __init__(self): """Initialize a UnitarySimulator object.""" super().__init__() - self._qubit_map = dict() + self._qubit_map = {} self._unitary = [1] self._num_qubits = 0 self._is_valid = True diff --git a/projectq/backends/_unitary_test.py b/projectq/backends/_unitary_test.py index e082305e8..27c3fc850 100644 --- a/projectq/backends/_unitary_test.py +++ b/projectq/backends/_unitary_test.py @@ -18,27 +18,28 @@ """ import itertools + import numpy as np import pytest from scipy.stats import unitary_group -from projectq.cengines import MainEngine, DummyEngine, NotYetMeasuredError +from projectq.cengines import DummyEngine, MainEngine, NotYetMeasuredError +from projectq.meta import Control, LogicalQubitIDTag from projectq.ops import ( - BasicGate, - MatrixGate, + CNOT, All, - Measure, Allocate, - Deallocate, + BasicGate, Command, - X, - Y, + Deallocate, + H, + MatrixGate, + Measure, Rx, Rxx, - H, - CNOT, + X, + Y, ) -from projectq.meta import Control, LogicalQubitIDTag from projectq.types import WeakQubitRef from ._unitary import UnitarySimulator diff --git a/projectq/cengines/__init__.py b/projectq/cengines/__init__.py index 9b25fde78..e9ca6e132 100755 --- a/projectq/cengines/__init__.py +++ b/projectq/cengines/__init__.py @@ -13,23 +13,24 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""ProjectQ module containing all compiler engines""" +"""ProjectQ module containing all compiler engines.""" + +from ._basics import BasicEngine, ForwarderEngine, LastEngineException # isort:skip +from ._cmdmodifier import CommandModifier # isort:skip +from ._basicmapper import BasicMapperEngine # isort:skip -from ._basics import BasicEngine, LastEngineException, ForwarderEngine -from ._cmdmodifier import CommandModifier -from ._basicmapper import BasicMapperEngine from ._ibm5qubitmapper import IBM5QubitMapper -from ._swapandcnotflipper import SwapAndCNOTFlipper from ._linearmapper import LinearMapper, return_swap_depth -from ._manualmapper import ManualMapper from ._main import MainEngine, NotYetMeasuredError, UnsupportedEngineError +from ._manualmapper import ManualMapper from ._optimize import LocalOptimizer from ._replacer import ( AutoReplacer, - InstructionFilter, - DecompositionRuleSet, DecompositionRule, + DecompositionRuleSet, + InstructionFilter, ) +from ._swapandcnotflipper import SwapAndCNOTFlipper from ._tagremover import TagRemover from ._testengine import CompareEngine, DummyEngine from ._twodmapper import GridMapper diff --git a/projectq/cengines/_basicmapper.py b/projectq/cengines/_basicmapper.py index 876717138..e81a96c19 100644 --- a/projectq/cengines/_basicmapper.py +++ b/projectq/cengines/_basicmapper.py @@ -13,14 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Defines the parent class from which all mappers should be derived. +The parent class from which all mappers should be derived. There is only one engine currently allowed to be derived from BasicMapperEngine. This allows the simulator to automatically translate logical qubit ids to mapped ids. """ from copy import deepcopy -from projectq.meta import drop_engine_after, insert_engine, LogicalQubitIDTag +from projectq.meta import LogicalQubitIDTag, drop_engine_after, insert_engine from projectq.ops import MeasureGate from ._basics import BasicEngine @@ -37,21 +37,18 @@ class BasicMapperEngine(BasicEngine): """ def __init__(self): + """Initialize a BasicMapperEngine object.""" super().__init__() self._current_mapping = None @property def current_mapping(self): - """ - Access the current mapping - """ + """Access the current mapping.""" return deepcopy(self._current_mapping) @current_mapping.setter def current_mapping(self, current_mapping): - """ - Set the current mapping - """ + """Set the current mapping.""" self._current_mapping = current_mapping def _send_cmd_with_mapped_ids(self, cmd): @@ -86,6 +83,11 @@ def add_logical_id(command, old_tags=deepcopy(cmd.tags)): self.send([new_cmd]) def receive(self, command_list): - """Forward all commands to the next engine.""" + """ + Receive a list of commands. + + This implementation simply forwards all commands to the next compiler engine while adjusting the qubit IDs of + measurement gates. + """ for cmd in command_list: self._send_cmd_with_mapped_ids(cmd) diff --git a/projectq/cengines/_basicmapper_test.py b/projectq/cengines/_basicmapper_test.py index 9a7089e60..42c6e3cbf 100644 --- a/projectq/cengines/_basicmapper_test.py +++ b/projectq/cengines/_basicmapper_test.py @@ -14,13 +14,11 @@ # limitations under the License. """Tests for projectq.cengines._basicmapper.py.""" -from projectq.cengines import DummyEngine +from projectq.cengines import DummyEngine, _basicmapper from projectq.meta import LogicalQubitIDTag from projectq.ops import Allocate, BasicGate, Command, Deallocate, FlushGate, Measure from projectq.types import WeakQubitRef -from projectq.cengines import _basicmapper - def test_basic_mapper_engine_send_cmd_with_mapped_ids(): mapper = _basicmapper.BasicMapperEngine() diff --git a/projectq/cengines/_basics.py b/projectq/cengines/_basics.py index 72ddcfaab..a8f393a81 100755 --- a/projectq/cengines/_basics.py +++ b/projectq/cengines/_basics.py @@ -13,22 +13,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing the basic definition of a compiler engine""" +"""Module containing the basic definition of a compiler engine.""" -from projectq.ops import Allocate, Deallocate +from projectq.ops import Allocate, Command, Deallocate from projectq.types import Qubit, Qureg, WeakQubitRef -from projectq.ops import Command class LastEngineException(Exception): """ - Exception thrown when the last engine tries to access the next one. (Next engine does not exist) + Exception thrown when the last engine tries to access the next one. (Next engine does not exist). The default implementation of isAvailable simply asks the next engine whether the command is available. An engine which legally may be the last engine, this behavior needs to be adapted (see BasicEngine.isAvailable). """ def __init__(self, engine): + """Initialize the exception.""" super().__init__( ( "\nERROR: Sending to next engine failed. {} as last engine?\nIf this is legal, please override" @@ -39,9 +39,10 @@ def __init__(self, engine): class BasicEngine: """ - Basic compiler engine: All compiler engines are derived from this class. It provides basic functionality such as - qubit allocation/deallocation and functions that provide information about the engine's position (e.g., next - engine). + Basic compiler engine: All compiler engines are derived from this class. + + It provides basic functionality such as qubit allocation/deallocation and functions that provide information about + the engine's position (e.g., next engine). This information is provided by the MainEngine, which initializes all further engines. @@ -63,8 +64,10 @@ def __init__(self): def is_available(self, cmd): """ - Default implementation of is_available: Ask the next engine whether a command is available, i.e., whether it - can be executed by the next engine(s). + Test whether a Command is supported by a compiler engine. + + Default implementation of is_available: Ask the next engine whether a command is available, i.e., whether it can + be executed by the next engine(s). Args: cmd (Command): Command for which to check availability. @@ -81,21 +84,16 @@ def is_available(self, cmd): def allocate_qubit(self, dirty=False): """ - Return a new qubit as a list containing 1 qubit object (quantum - register of size 1). - - Allocates a new qubit by getting a (new) qubit id from the MainEngine, - creating the qubit object, and then sending an AllocateQubit command - down the pipeline. If dirty=True, the fresh qubit can be replaced by - a pre-allocated one (in an unknown, dirty, initial state). Dirty qubits - must be returned to their initial states before they are deallocated / - freed. - - All allocated qubits are added to the MainEngine's set of active - qubits as weak references. This allows proper clean-up at the end of - the Python program (using atexit), deallocating all qubits which are - still alive. Qubit ids of dirty qubits are registered in MainEngine's - dirty_qubits set. + Return a new qubit as a list containing 1 qubit object (quantum register of size 1). + + Allocates a new qubit by getting a (new) qubit id from the MainEngine, creating the qubit object, and then + sending an AllocateQubit command down the pipeline. If dirty=True, the fresh qubit can be replaced by a + pre-allocated one (in an unknown, dirty, initial state). Dirty qubits must be returned to their initial states + before they are deallocated / freed. + + All allocated qubits are added to the MainEngine's set of active qubits as weak references. This allows proper + clean-up at the end of the Python program (using atexit), deallocating all qubits which are still alive. Qubit + ids of dirty qubits are registered in MainEngine's dirty_qubits set. Args: dirty (bool): If True, indicates that the allocated qubit may be @@ -108,7 +106,9 @@ def allocate_qubit(self, dirty=False): qb = Qureg([Qubit(self, new_id)]) cmd = Command(self, Allocate, (qb,)) if dirty: - from projectq.meta import DirtyQubitTag # pylint: disable=import-outside-toplevel + from projectq.meta import ( # pylint: disable=import-outside-toplevel + DirtyQubitTag, + ) if self.is_meta_tag_supported(DirtyQubitTag): cmd.tags += [DirtyQubitTag()] @@ -130,8 +130,9 @@ def allocate_qureg(self, n_qubits): def deallocate_qubit(self, qubit): """ - Deallocate a qubit (and sends the deallocation command down the pipeline). If the qubit was allocated as a - dirty qubit, add DirtyQubitTag() to Deallocate command. + Deallocate a qubit (and sends the deallocation command down the pipeline). + + If the qubit was allocated as a dirty qubit, add DirtyQubitTag() to Deallocate command. Args: qubit (BasicQubit): Qubit to deallocate. @@ -141,7 +142,9 @@ def deallocate_qubit(self, qubit): if qubit.id == -1: raise ValueError("Already deallocated.") - from projectq.meta import DirtyQubitTag # pylint: disable=import-outside-toplevel + from projectq.meta import ( # pylint: disable=import-outside-toplevel + DirtyQubitTag, + ) is_dirty = qubit.id in self.main_engine.dirty_qubits self.send( @@ -159,7 +162,7 @@ def deallocate_qubit(self, qubit): def is_meta_tag_supported(self, meta_tag): """ - Check if there is a compiler engine handling the meta tag + Check if there is a compiler engine handling the meta tag. Args: engine: First engine to check (then iteratively calls getNextEngine) @@ -180,10 +183,7 @@ def is_meta_tag_supported(self, meta_tag): return False def send(self, command_list): - """ - Forward the list of commands to the next engine in the pipeline. - """ - + """Forward the list of commands to the next engine in the pipeline.""" self.next_engine.receive(command_list) diff --git a/projectq/cengines/_basics_test.py b/projectq/cengines/_basics_test.py index e76b94b7f..cb4b2e0b2 100755 --- a/projectq/cengines/_basics_test.py +++ b/projectq/cengines/_basics_test.py @@ -15,26 +15,25 @@ """Tests for projectq.cengines._basics.py.""" import types -import pytest -# try: -# import mock -# except ImportError: -# from unittest import mock +import pytest from projectq import MainEngine -from projectq.types import Qubit -from projectq.cengines import DummyEngine, InstructionFilter +from projectq.cengines import DummyEngine, InstructionFilter, _basics from projectq.meta import DirtyQubitTag from projectq.ops import ( AllocateQubitGate, + ClassicalInstructionGate, DeallocateQubitGate, - H, FastForwardingGate, - ClassicalInstructionGate, + H, ) +from projectq.types import Qubit -from projectq.cengines import _basics +# try: +# import mock +# except ImportError: +# from unittest import mock def test_basic_engine_init(): @@ -112,15 +111,13 @@ def allow_dirty_qubits(self, meta_tag): # Test uniqueness of ids assert ( len( - set( - [ - qubit[0].id, - not_dirty_qubit[0].id, - dirty_qubit[0].id, - qureg[0].id, - qureg[1].id, - ] - ) + { + qubit[0].id, + not_dirty_qubit[0].id, + dirty_qubit[0].id, + qureg[0].id, + qureg[1].id, + } ) == 5 ) diff --git a/projectq/cengines/_cmdmodifier.py b/projectq/cengines/_cmdmodifier.py index c988cccd6..f5deae94a 100755 --- a/projectq/cengines/_cmdmodifier.py +++ b/projectq/cengines/_cmdmodifier.py @@ -13,7 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Contains a CommandModifier engine, which can be used to, e.g., modify the tags of all commands which pass by (see the +A CommandModifier engine that can be used to apply a user-defined transformation to all incoming commands. + +A CommandModifier engine can be used to, e.g., modify the tags of all commands which pass by (see the AutoReplacer for an example). """ @@ -22,6 +24,8 @@ class CommandModifier(BasicEngine): """ + Compiler engine applying a user-defined transformation to all incoming commands. + CommandModifier is a compiler engine which applies a function to all incoming commands, sending on the resulting command instead of the original one. """ @@ -46,6 +50,8 @@ def cmd_mod_fun(cmd): def receive(self, command_list): """ + Receive a list of commands. + Receive a list of commands from the previous engine, modify all commands, and send them on to the next engine. Args: diff --git a/projectq/cengines/_cmdmodifier_test.py b/projectq/cengines/_cmdmodifier_test.py index 79f8d858e..5b7ed9fb6 100755 --- a/projectq/cengines/_cmdmodifier_test.py +++ b/projectq/cengines/_cmdmodifier_test.py @@ -15,10 +15,8 @@ """Tests for projectq.cengines._cmdmodifier.py.""" from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import H, FastForwardingGate, ClassicalInstructionGate - -from projectq.cengines import _cmdmodifier +from projectq.cengines import DummyEngine, _cmdmodifier +from projectq.ops import ClassicalInstructionGate, FastForwardingGate, H def test_command_modifier(): diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 4f9d093e5..6e44f5117 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -12,15 +12,14 @@ # 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. -""" -Contains a compiler engine to map to the 5-qubit IBM chip -""" + +"""Contains a compiler engine to map to the 5-qubit IBM chip.""" + import itertools -from projectq.ops import FlushGate, NOT, Allocate -from projectq.meta import get_control_count from projectq.backends import IBMBackend - +from projectq.meta import get_control_count +from projectq.ops import NOT, Allocate, FlushGate from ._basicmapper import BasicMapperEngine @@ -35,9 +34,8 @@ class IBM5QubitMapper(BasicMapperEngine): The mapper has to be run once on the entire circuit. Warning: - If the provided circuit cannot be mapped to the hardware layout - without performing Swaps, the mapping procedure - **raises an Exception**. + If the provided circuit cannot be mapped to the hardware layout without performing Swaps, the mapping + procedure **raises an Exception**. """ def __init__(self, connections=None): @@ -47,35 +45,32 @@ def __init__(self, connections=None): Resets the mapping. """ super().__init__() - self.current_mapping = dict() + self.current_mapping = {} self._reset() self._cmds = [] - self._interactions = dict() + self._interactions = {} if connections is None: # general connectivity easier for testing functions - self.connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + self.connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } else: self.connections = connections def is_available(self, cmd): """ - Check if the IBM backend can perform the Command cmd and return True - if so. + Check if the IBM backend can perform the Command cmd and return True if so. Args: cmd (Command): The command to check @@ -83,25 +78,21 @@ def is_available(self, cmd): return IBMBackend().is_available(cmd) def _reset(self): - """ - Reset the mapping parameters so the next circuit can be mapped. - """ + """Reset the mapping parameters so the next circuit can be mapped.""" self._cmds = [] - self._interactions = dict() + self._interactions = {} def _determine_cost(self, mapping): """ - Determines the cost of the circuit with the given mapping. + Determine the cost of the circuit with the given mapping. Args: - mapping (dict): Dictionary with key, value pairs where keys are - logical qubit ids and the corresponding value is the physical - location on the IBM Q chip. + mapping (dict): Dictionary with key, value pairs where keys are logical qubit ids and the corresponding + value is the physical location on the IBM Q chip. Returns: - Cost measure taking into account CNOT directionality or None - if the circuit cannot be executed given the mapping. + Cost measure taking into account CNOT directionality or None if the circuit cannot be executed given the + mapping. """ - cost = 0 for tpl in self._interactions: ctrl_id = tpl[0] @@ -117,13 +108,12 @@ def _determine_cost(self, mapping): def _run(self): """ - Runs all stored gates. + Run all stored gates. Raises: Exception: - If the mapping to the IBM backend cannot be performed or if - the mapping was already determined but more CNOTs get sent - down the pipeline. + If the mapping to the IBM backend cannot be performed or if the mapping was already determined but + more CNOTs get sent down the pipeline. """ if len(self.current_mapping) > 0 and max(self.current_mapping.values()) > 4: raise RuntimeError( @@ -145,7 +135,7 @@ def _run(self): best_mapping = mapping if best_cost is None: raise RuntimeError("Circuit cannot be mapped without using Swaps. Mapping failed.") - self._interactions = dict() + self._interactions = {} self.current_mapping = best_mapping for cmd in self._cmds: @@ -178,18 +168,18 @@ def _store(self, cmd): def receive(self, command_list): """ - Receives a command list and, for each command, stores it until - completion. + Receive a list of commands. + + Receive a command list and, for each command, stores it until completion. Args: command_list (list of Command objects): list of commands to receive. Raises: - Exception: If mapping the CNOT gates to 1 qubit would require - Swaps. The current version only supports remapping of CNOT - gates without performing any Swaps due to the large costs - associated with Swapping given the CNOT constraints. + Exception: If mapping the CNOT gates to 1 qubit would require Swaps. The current version only supports + remapping of CNOT gates without performing any Swaps due to the large costs associated with Swapping + given the CNOT constraints. """ for cmd in command_list: self._store(cmd) diff --git a/projectq/cengines/_ibm5qubitmapper_test.py b/projectq/cengines/_ibm5qubitmapper_test.py index 29ed59092..d56e9e7ed 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -17,11 +17,9 @@ import pytest from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import H, CNOT, All - -from projectq.cengines import _ibm5qubitmapper, SwapAndCNOTFlipper from projectq.backends import IBMBackend +from projectq.cengines import DummyEngine, SwapAndCNOTFlipper, _ibm5qubitmapper +from projectq.ops import CNOT, All, H def test_ibm5qubitmapper_is_available(monkeypatch): @@ -35,7 +33,7 @@ def mock_send(*args, **kwargs): def test_ibm5qubitmapper_invalid_circuit(): - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, @@ -55,7 +53,7 @@ def test_ibm5qubitmapper_invalid_circuit(): def test_ibm5qubitmapper_valid_circuit1(): - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, @@ -77,7 +75,7 @@ def test_ibm5qubitmapper_valid_circuit1(): def test_ibm5qubitmapper_valid_circuit2(): - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, @@ -99,7 +97,7 @@ def test_ibm5qubitmapper_valid_circuit2(): def test_ibm5qubitmapper_valid_circuit2_ibmqx4(): - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} backend = DummyEngine(save_commands=True) class FakeIBMBackend(IBMBackend): @@ -131,7 +129,7 @@ class FakeIBMBackend(IBMBackend): def test_ibm5qubitmapper_optimizeifpossible(): backend = DummyEngine(save_commands=True) - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} eng = MainEngine( backend=backend, engine_list=[ @@ -173,7 +171,7 @@ def test_ibm5qubitmapper_optimizeifpossible(): def test_ibm5qubitmapper_toomanyqubits(): backend = DummyEngine(save_commands=True) - connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + connectivity = {(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)} eng = MainEngine( backend=backend, engine_list=[ diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index 911b5e975..8546e3774 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -27,9 +27,9 @@ from projectq.ops import ( Allocate, AllocateQubitGate, + Command, Deallocate, DeallocateQubitGate, - Command, FlushGate, Swap, ) @@ -40,7 +40,7 @@ def return_swap_depth(swaps): """ - Returns the circuit depth to execute these swaps. + Return the circuit depth to execute these swaps. Args: swaps(list of tuples): Each tuple contains two integers representing the two IDs of the qubits involved in the @@ -48,7 +48,7 @@ def return_swap_depth(swaps): Returns: Circuit depth to execute these swaps. """ - depth_of_qubits = dict() + depth_of_qubits = {} for qb0_id, qb1_id in swaps: if qb0_id not in depth_of_qubits: depth_of_qubits[qb0_id] = 0 @@ -62,7 +62,7 @@ def return_swap_depth(swaps): class LinearMapper(BasicMapperEngine): # pylint: disable=too-many-instance-attributes """ - Maps a quantum circuit to a linear chain of nearest neighbour interactions. + Map a quantum circuit to a linear chain of nearest neighbour interactions. Maps a quantum circuit to a linear chain of qubits with nearest neighbour interactions using Swap gates. It supports open or cyclic boundary conditions. @@ -99,20 +99,18 @@ def __init__(self, num_qubits, cyclic=False, storage=1000): self.cyclic = cyclic self.storage = storage # Storing commands - self._stored_commands = list() + self._stored_commands = [] # Logical qubit ids for which the Allocate gate has already been # processed and sent to the next engine but which are not yet # deallocated: self._currently_allocated_ids = set() # Statistics: self.num_mappings = 0 - self.depth_of_swaps = dict() - self.num_of_swaps_per_mapping = dict() + self.depth_of_swaps = {} + self.num_of_swaps_per_mapping = {} def is_available(self, cmd): - """ - Only allows 1 or two qubit gates. - """ + """Only allows 1 or two qubit gates.""" num_qubits = 0 for qureg in cmd.all_qubits: num_qubits += len(qureg) @@ -121,7 +119,7 @@ def is_available(self, cmd): @staticmethod def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_commands, current_mapping): """ - Builds a mapping of qubits to a linear chain. + Build a mapping of qubits to a linear chain. It goes through stored_commands and tries to find a mapping to apply these gates on a first come first served basis. More compilicated scheme could try to optimize to apply as many gates as possible between the Swaps. @@ -150,7 +148,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_comma segments = [] # neighbour_ids only used to speedup the lookup process if qubits # are already connected. key: qubit_id, value: set of neighbour ids - neighbour_ids = dict() + neighbour_ids = {} for qubit_id in active_qubits: neighbour_ids[qubit_id] = set() @@ -208,7 +206,7 @@ def _process_two_qubit_gate( # pylint: disable=too-many-arguments,too-many-bran num_qubits, cyclic, qubit0, qubit1, active_qubits, segments, neighbour_ids ): """ - Processes a two qubit gate. + Process a two qubit gate. It either removes the two qubits from active_qubits if the gate is not possible or updates the segements such that the gate is possible. @@ -316,7 +314,7 @@ def _return_new_mapping_from_segments( # pylint: disable=too-many-locals,too-ma num_qubits, segments, allocated_qubits, current_mapping ): """ - Combines the individual segments into a new mapping. + Combine the individual segments into a new mapping. It tries to minimize the number of swaps to go from the old mapping in self.current_mapping to the new mapping which it returns. The strategy is to map a segment to the same region where most of the qubits are @@ -393,7 +391,7 @@ def _return_new_mapping_from_segments( # pylint: disable=too-many-locals,too-ma current_position_to_fill += best_padding + len(best_segment) num_unused_qubits -= best_padding # Create mapping - new_mapping = dict() + new_mapping = {} for pos, logical_id in enumerate(new_chain): if logical_id is not None: new_mapping[logical_id] = pos @@ -401,7 +399,7 @@ def _return_new_mapping_from_segments( # pylint: disable=too-many-locals,too-ma def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): """ - Returns the swap operation for an odd-even transposition sort. + Return the swap operation for an odd-even transposition sort. See https://en.wikipedia.org/wiki/Odd-even_sort for more info. @@ -451,7 +449,7 @@ def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): def _send_possible_commands(self): # pylint: disable=too-many-branches """ - Sends the stored commands possible without changing the mapping. + Send the stored commands possible without changing the mapping. Note: self.current_mapping must exist already """ @@ -523,7 +521,7 @@ def _send_possible_commands(self): # pylint: disable=too-many-branches def _run(self): # pylint: disable=too-many-locals,too-many-branches """ - Creates a new mapping and executes possible gates. + Create a new mapping and executes possible gates. It first allocates all 0, ..., self.num_qubits-1 mapped qubit ids, if they are not already used because we might need them all for the swaps. Then it creates a new map, swaps all the qubits to the new map, executes @@ -531,7 +529,7 @@ def _run(self): # pylint: disable=too-many-locals,too-many-branches """ num_of_stored_commands_before = len(self._stored_commands) if not self.current_mapping: - self.current_mapping = dict() + self.current_mapping = {} else: self._send_possible_commands() if len(self._stored_commands) == 0: @@ -595,7 +593,9 @@ def _run(self): # pylint: disable=too-many-locals,too-many-branches def receive(self, command_list): """ - Receives a command list and, for each command, stores it until we do a mapping (FlushGate or Cache of stored + Receive a list of commands. + + Receive a command list and, for each command, stores it until we do a mapping (FlushGate or Cache of stored commands is full). Args: diff --git a/projectq/cengines/_linearmapper_test.py b/projectq/cengines/_linearmapper_test.py index efe6b68d2..0b414fd12 100644 --- a/projectq/cengines/_linearmapper_test.py +++ b/projectq/cengines/_linearmapper_test.py @@ -18,21 +18,20 @@ import pytest from projectq.cengines import DummyEngine +from projectq.cengines import _linearmapper as lm from projectq.meta import LogicalQubitIDTag from projectq.ops import ( + CNOT, + QFT, Allocate, BasicGate, - CNOT, Command, Deallocate, FlushGate, - QFT, X, ) from projectq.types import WeakQubitRef -from projectq.cengines import _linearmapper as lm - def test_return_swap_depth(): swaps = [] @@ -89,7 +88,7 @@ def test_return_new_mapping_allocate_qubits(): mapper = lm.LinearMapper(num_qubits=2, cyclic=False) qb0 = WeakQubitRef(engine=None, idx=0) qb1 = WeakQubitRef(engine=None, idx=1) - mapper._currently_allocated_ids = set([4]) + mapper._currently_allocated_ids = {4} cmd0 = Command(None, Allocate, ([qb0],)) cmd1 = Command(None, Allocate, ([qb1],)) mapper._stored_commands = [cmd0, cmd1] @@ -100,7 +99,7 @@ def test_return_new_mapping_allocate_qubits(): stored_commands=mapper._stored_commands, current_mapping=mapper.current_mapping, ) - assert mapper._currently_allocated_ids == set([4]) + assert mapper._currently_allocated_ids == {4} assert mapper._stored_commands == [cmd0, cmd1] assert len(new_mapping) == 2 assert 4 in new_mapping and 0 in new_mapping @@ -172,8 +171,8 @@ def test_return_new_mapping_previous_error(): def test_process_two_qubit_gate_not_in_segments_test0(): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) segments = [[0, 1]] - active_qubits = set([0, 1, 4, 6]) - neighbour_ids = {0: set([1]), 1: set([0]), 4: set(), 6: set()} + active_qubits = {0, 1, 4, 6} + neighbour_ids = {0: {1}, 1: {0}, 4: set(), 6: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -186,15 +185,15 @@ def test_process_two_qubit_gate_not_in_segments_test0(): assert len(segments) == 2 assert segments[0] == [0, 1] assert segments[1] == [4, 6] - assert neighbour_ids[4] == set([6]) - assert neighbour_ids[6] == set([4]) - assert active_qubits == set([0, 1, 4, 6]) + assert neighbour_ids[4] == {6} + assert neighbour_ids[6] == {4} + assert active_qubits == {0, 1, 4, 6} def test_process_two_qubit_gate_not_in_segments_test1(): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) segments = [] - active_qubits = set([4, 6]) + active_qubits = {4, 6} neighbour_ids = {4: set(), 6: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, @@ -206,7 +205,7 @@ def test_process_two_qubit_gate_not_in_segments_test1(): neighbour_ids=neighbour_ids, ) assert len(segments) == 0 - assert active_qubits == set([4]) + assert active_qubits == {4} @pytest.mark.parametrize("qb0, qb1", [(1, 2), (2, 1)]) @@ -214,8 +213,8 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment(qb0, qb1): # add on the right to segment mapper = lm.LinearMapper(num_qubits=3, cyclic=False) segments = [[0, 1]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0}, 2: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -226,9 +225,9 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment(qb0, qb1): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) - assert neighbour_ids[1] == set([0, 2]) - assert neighbour_ids[2] == set([1]) + assert active_qubits == {0, 1, 2} + assert neighbour_ids[1] == {0, 2} + assert neighbour_ids[2] == {1} @pytest.mark.parametrize("qb0, qb1", [(0, 1), (1, 0)]) @@ -236,8 +235,8 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment2(qb0, qb1): # add on the left to segment mapper = lm.LinearMapper(num_qubits=3, cyclic=False) segments = [[1, 2]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([]), 1: set([2]), 2: set([1])} + active_qubits = {0, 1, 2} + neighbour_ids = {0: set(), 1: {2}, 2: {1}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -248,17 +247,17 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment2(qb0, qb1): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) - assert neighbour_ids[1] == set([0, 2]) - assert neighbour_ids[0] == set([1]) + assert active_qubits == {0, 1, 2} + assert neighbour_ids[1] == {0, 2} + assert neighbour_ids[0] == {1} @pytest.mark.parametrize("qb0, qb1", [(1, 2), (2, 1)]) def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment_cycle(qb0, qb1): mapper = lm.LinearMapper(num_qubits=3, cyclic=True) segments = [[0, 1]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0}, 2: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -269,9 +268,9 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment_cycle(qb0, qb1): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) - assert neighbour_ids[1] == set([0, 2]) - assert neighbour_ids[2] == set([1, 0]) + assert active_qubits == {0, 1, 2} + assert neighbour_ids[1] == {0, 2} + assert neighbour_ids[2] == {1, 0} @pytest.mark.parametrize("qb0, qb1", [(1, 2), (2, 1)]) @@ -279,8 +278,8 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_seg_cycle2(qb0, qb1): # not yet long enough segment for cycle mapper = lm.LinearMapper(num_qubits=4, cyclic=True) segments = [[0, 1]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0}, 2: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -291,16 +290,16 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_seg_cycle2(qb0, qb1): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) - assert neighbour_ids[1] == set([0, 2]) - assert neighbour_ids[2] == set([1]) + assert active_qubits == {0, 1, 2} + assert neighbour_ids[1] == {0, 2} + assert neighbour_ids[2] == {1} def test_process_two_qubit_gate_one_qubit_in_middle_of_segment(): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) segments = [] - active_qubits = set([0, 1, 2, 3]) - neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1]), 3: set()} + active_qubits = {0, 1, 2, 3} + neighbour_ids = {0: {1}, 1: {0, 2}, 2: {1}, 3: set()} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -311,14 +310,14 @@ def test_process_two_qubit_gate_one_qubit_in_middle_of_segment(): neighbour_ids=neighbour_ids, ) assert len(segments) == 0 - assert active_qubits == set([0, 2]) + assert active_qubits == {0, 2} def test_process_two_qubit_gate_both_in_same_segment(): mapper = lm.LinearMapper(num_qubits=3, cyclic=False) segments = [[0, 1, 2]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1])} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0, 2}, 2: {1}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -329,14 +328,14 @@ def test_process_two_qubit_gate_both_in_same_segment(): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([1]) + assert active_qubits == {1} def test_process_two_qubit_gate_already_connected(): mapper = lm.LinearMapper(num_qubits=3, cyclic=False) segments = [[0, 1, 2]] - active_qubits = set([0, 1, 2]) - neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1])} + active_qubits = {0, 1, 2} + neighbour_ids = {0: {1}, 1: {0, 2}, 2: {1}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -347,7 +346,7 @@ def test_process_two_qubit_gate_already_connected(): neighbour_ids=neighbour_ids, ) assert segments == [[0, 1, 2]] - assert active_qubits == set([0, 1, 2]) + assert active_qubits == {0, 1, 2} @pytest.mark.parametrize( @@ -362,8 +361,8 @@ def test_process_two_qubit_gate_already_connected(): def test_process_two_qubit_gate_combine_segments(qb0, qb1, result_seg): mapper = lm.LinearMapper(num_qubits=4, cyclic=False) segments = [[0, 1], [2, 3]] - active_qubits = set([0, 1, 2, 3, 4]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set([3]), 3: set([2])} + active_qubits = {0, 1, 2, 3, 4} + neighbour_ids = {0: {1}, 1: {0}, 2: {3}, 3: {2}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -390,8 +389,8 @@ def test_process_two_qubit_gate_combine_segments(qb0, qb1, result_seg): def test_process_two_qubit_gate_combine_segments_cycle(qb0, qb1, result_seg): mapper = lm.LinearMapper(num_qubits=4, cyclic=True) segments = [[0, 1], [2, 3]] - active_qubits = set([0, 1, 2, 3, 4]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set([3]), 3: set([2])} + active_qubits = {0, 1, 2, 3, 4} + neighbour_ids = {0: {1}, 1: {0}, 2: {3}, 3: {2}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -421,8 +420,8 @@ def test_process_two_qubit_gate_combine_segments_cycle2(qb0, qb1, result_seg): # Not long enough segment for cyclic mapper = lm.LinearMapper(num_qubits=5, cyclic=True) segments = [[0, 1], [2, 3]] - active_qubits = set([0, 1, 2, 3, 4]) - neighbour_ids = {0: set([1]), 1: set([0]), 2: set([3]), 3: set([2])} + active_qubits = {0, 1, 2, 3, 4} + neighbour_ids = {0: {1}, 1: {0}, 2: {3}, 3: {2}} mapper._process_two_qubit_gate( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, @@ -450,7 +449,7 @@ def test_process_two_qubit_gate_combine_segments_cycle2(qb0, qb1, result_seg): ) def test_return_new_mapping_from_segments(segments, current_chain, correct_chain, allocated_qubits): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) - current_mapping = dict() + current_mapping = {} for pos, logical_id in enumerate(current_chain): current_mapping[logical_id] = pos mapper.current_mapping = current_mapping @@ -460,7 +459,7 @@ def test_return_new_mapping_from_segments(segments, current_chain, correct_chain allocated_qubits=allocated_qubits, current_mapping=mapper.current_mapping, ) - correct_mapping = dict() + correct_mapping = {} for pos, logical_id in enumerate(correct_chain): if logical_id is not None: correct_mapping[logical_id] = pos @@ -478,8 +477,8 @@ def test_return_new_mapping_from_segments(segments, current_chain, correct_chain ) def test_odd_even_transposition_sort_swaps(old_chain, new_chain): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) - old_map = dict() - new_map = dict() + old_map = {} + new_map = {} for pos, logical_id in enumerate(old_chain): if logical_id is not None: old_map[logical_id] = pos @@ -510,9 +509,9 @@ def test_send_possible_commands_allocate(): qb0 = WeakQubitRef(engine=None, idx=0) cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] - mapper._currently_allocated_ids = set([10]) + mapper._currently_allocated_ids = {10} # not in mapping: - mapper.current_mapping = dict() + mapper.current_mapping = {} assert len(backend.received_commands) == 0 mapper._send_possible_commands() assert len(backend.received_commands) == 0 @@ -525,7 +524,7 @@ def test_send_possible_commands_allocate(): assert backend.received_commands[0].gate == Allocate assert backend.received_commands[0].qubits[0][0].id == 3 assert backend.received_commands[0].tags == [LogicalQubitIDTag(0)] - assert mapper._currently_allocated_ids == set([10, 0]) + assert mapper._currently_allocated_ids == {10, 0} def test_send_possible_commands_deallocate(): @@ -536,8 +535,8 @@ def test_send_possible_commands_deallocate(): qb0 = WeakQubitRef(engine=None, idx=0) cmd0 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] - mapper.current_mapping = dict() - mapper._currently_allocated_ids = set([10]) + mapper.current_mapping = {} + mapper._currently_allocated_ids = {10} # not yet allocated: mapper._send_possible_commands() assert len(backend.received_commands) == 0 @@ -551,8 +550,8 @@ def test_send_possible_commands_deallocate(): assert backend.received_commands[0].qubits[0][0].id == 3 assert backend.received_commands[0].tags == [LogicalQubitIDTag(0)] assert len(mapper._stored_commands) == 0 - assert mapper.current_mapping == dict() - assert mapper._currently_allocated_ids == set([10]) + assert mapper.current_mapping == {} + assert mapper._currently_allocated_ids == {10} def test_send_possible_commands_keep_remaining_gates(): @@ -581,7 +580,7 @@ def test_send_possible_commands_not_cyclic(): qb1 = WeakQubitRef(engine=None, idx=1) qb2 = WeakQubitRef(engine=None, idx=2) qb3 = WeakQubitRef(engine=None, idx=3) - mapper._currently_allocated_ids = set([0, 1, 2, 3]) + mapper._currently_allocated_ids = {0, 1, 2, 3} cmd0 = Command(None, CNOT, qubits=([qb0],), controls=[qb2]) cmd1 = Command(None, CNOT, qubits=([qb1],), controls=[qb2]) cmd2 = Command(None, CNOT, qubits=([qb1],), controls=[qb3]) @@ -609,7 +608,7 @@ def test_send_possible_commands_cyclic(): qb1 = WeakQubitRef(engine=None, idx=1) qb2 = WeakQubitRef(engine=None, idx=2) qb3 = WeakQubitRef(engine=None, idx=3) - mapper._currently_allocated_ids = set([0, 1, 2, 3]) + mapper._currently_allocated_ids = {0, 1, 2, 3} cmd0 = Command(None, CNOT, qubits=([qb0],), controls=[qb1]) cmd1 = Command(None, CNOT, qubits=([qb1],), controls=[qb2]) cmd2 = Command(None, CNOT, qubits=([qb1],), controls=[qb3]) @@ -649,7 +648,7 @@ def test_run_and_receive(): mapper.receive([cmd_flush]) assert mapper._stored_commands == [] assert len(backend.received_commands) == 7 - assert mapper._currently_allocated_ids == set([0, 2]) + assert mapper._currently_allocated_ids == {0, 2} assert mapper.current_mapping == {0: 2, 2: 0} or mapper.current_mapping == { 0: 0, 2: 2, @@ -657,7 +656,7 @@ def test_run_and_receive(): cmd6 = Command(None, X, qubits=([qb0],), controls=[qb2]) mapper.storage = 1 mapper.receive([cmd6]) - assert mapper._currently_allocated_ids == set([0, 2]) + assert mapper._currently_allocated_ids == {0, 2} assert mapper._stored_commands == [] assert len(mapper.current_mapping) == 2 assert 0 in mapper.current_mapping diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index ec6145ef5..6f13e25e0 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -12,21 +12,20 @@ # 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. -""" -Contains the main engine of every compiler engine pipeline, called MainEngine. -""" + +"""The main engine of every compiler engine pipeline, called MainEngine.""" import atexit import sys import traceback import weakref +from projectq.backends import Simulator from projectq.ops import Command, FlushGate from projectq.types import WeakQubitRef -from projectq.backends import Simulator -from ._basics import BasicEngine from ._basicmapper import BasicMapperEngine +from ._basics import BasicEngine class NotYetMeasuredError(Exception): @@ -34,17 +33,19 @@ class NotYetMeasuredError(Exception): class UnsupportedEngineError(Exception): - """Exception raised when a non-supported compiler engine is encountered""" + """Exception raised when a non-supported compiler engine is encountered.""" class _ErrorEngine: # pylint: disable=too-few-public-methods """ + Fake compiler engine class. + Fake compiler engine class only used to ensure gracious failure when an exception occurs in the MainEngine constructor. """ def receive(self, command_list): # pylint: disable=unused-argument - """No-op""" + """No-op.""" _N_ENGINES_THRESHOLD = 100 @@ -120,7 +121,7 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches """ super().__init__() self.active_qubits = weakref.WeakSet() - self._measurements = dict() + self._measurements = {} self.dirty_qubits = set() self.verbose = verbose self.main_engine = self @@ -171,7 +172,7 @@ def __init__( # pylint: disable=too-many-statements,too-many-branches engine_list = engine_list + [backend] # Test that user did not supply twice the same engine instance - num_different_engines = len(set(id(item) for item in engine_list)) + num_different_engines = len({id(item) for item in engine_list}) if len(engine_list) != num_different_engines: self.next_engine = _ErrorEngine() raise UnsupportedEngineError( @@ -224,7 +225,7 @@ def __del__(self): def set_measurement_result(self, qubit, value): """ - Register a measurement result + Register a measurement result. The engine being responsible for measurement results needs to register these results with the master engine such that they are available when the user calls an int() or bool() conversion operator on a measured qubit. @@ -237,8 +238,9 @@ def set_measurement_result(self, qubit, value): def get_measurement_result(self, qubit): """ - Return the classical value of a measured qubit, given that an engine registered this result previously (see - setMeasurementResult). + Return the classical value of a measured qubit, given that an engine registered this result previously. + + See also setMeasurementResult. Args: qubit (BasicQubit): Qubit of which to get the measurement result. @@ -266,7 +268,7 @@ def get_measurement_result(self, qubit): def get_new_qubit_id(self): """ - Returns a unique qubit id to be used for the next qubit allocation. + Return a unique qubit id to be used for the next qubit allocation. Returns: new_qubit_id (int): New unique qubit id. diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index b8ab365c4..6600fd865 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -18,12 +18,10 @@ import pytest -from projectq.cengines import DummyEngine, BasicMapperEngine, LocalOptimizer from projectq.backends import Simulator +from projectq.cengines import BasicMapperEngine, DummyEngine, LocalOptimizer, _main from projectq.ops import AllocateQubitGate, DeallocateQubitGate, FlushGate, H -from projectq.cengines import _main - def test_main_engine_init(): ceng1 = DummyEngine() diff --git a/projectq/cengines/_manualmapper.py b/projectq/cengines/_manualmapper.py index 4af0122ac..8699feb88 100755 --- a/projectq/cengines/_manualmapper.py +++ b/projectq/cengines/_manualmapper.py @@ -12,9 +12,9 @@ # 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. -""" -Contains a compiler engine to add mapping information -""" + +"""A compiler engine to add mapping information.""" + from ._basicmapper import BasicMapperEngine @@ -29,8 +29,9 @@ class ManualMapper(BasicMapperEngine): def __init__(self, map_fun=lambda x: x): """ - Initialize the mapper to a given mapping. If no mapping function is provided, the qubit id is used as the - location. + Initialize the mapper to a given mapping. + + If no mapping function is provided, the qubit id is used as the location. Args: map_fun (function): Function which, given the qubit id, returns an integer describing the physical @@ -38,7 +39,7 @@ def __init__(self, map_fun=lambda x: x): """ super().__init__() self.map = map_fun - self.current_mapping = dict() + self.current_mapping = {} def receive(self, command_list): """ diff --git a/projectq/cengines/_manualmapper_test.py b/projectq/cengines/_manualmapper_test.py index 64a04dfd6..c84301742 100755 --- a/projectq/cengines/_manualmapper_test.py +++ b/projectq/cengines/_manualmapper_test.py @@ -15,11 +15,9 @@ """Tests for projectq.cengines._manualmapper.py.""" from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import H, Measure, All +from projectq.cengines import DummyEngine, ManualMapper from projectq.meta import LogicalQubitIDTag - -from projectq.cengines import ManualMapper +from projectq.ops import All, H, Measure def test_manualmapper_mapping(): diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 39762bd6a..d8a63b2c4 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -12,19 +12,20 @@ # 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. -""" -Contains a local optimizer engine. -""" + +"""A local optimizer engine.""" import warnings -from projectq.ops import FlushGate, FastForwardingGate, NotMergeable +from projectq.ops import FastForwardingGate, FlushGate, NotMergeable from ._basics import BasicEngine class LocalOptimizer(BasicEngine): """ + Circuit optimization compiler engine. + LocalOptimizer is a compiler engine which optimizes locally (merging rotations, cancelling gates with their inverse) in a local window of user- defined size. @@ -42,7 +43,7 @@ def __init__(self, cache_size=5, m=None): # pylint: disable=invalid-name cache_size (int): Number of gates to cache per qubit, before sending on the first gate. """ super().__init__() - self._l = dict() # dict of lists containing operations for each qubit + self._l = {} # dict of lists containing operations for each qubit if m: warnings.warn( @@ -55,9 +56,7 @@ def __init__(self, cache_size=5, m=None): # pylint: disable=invalid-name # sends n gate operations of the qubit with index idx def _send_qubit_pipeline(self, idx, n_gates): - """ - Send n gate operations of the qubit with index idx to the next engine. - """ + """Send n gate operations of the qubit with index idx to the next engine.""" il = self._l[idx] # pylint: disable=invalid-name for i in range(min(n_gates, len(il))): # loop over first n operations # send all gates before n-qubit gate for other qubits involved @@ -89,8 +88,9 @@ def _send_qubit_pipeline(self, idx, n_gates): def _get_gate_indices(self, idx, i, qubit_ids): """ - Return all indices of a command, each index corresponding to the command's index in one of the qubits' command - lists. + Return all indices of a command. + + Each index corresponding to the command's index in one of the qubits' command lists. Args: idx (int): qubit index @@ -116,6 +116,8 @@ def _get_gate_indices(self, idx, i, qubit_ids): def _optimize(self, idx, lim=None): """ + Gate cancellation routine. + Try to remove identity gates using the is_identity function, then merge or even cancel successive gates using the get_merged and get_inverse functions of the gate (see, e.g., BasicRotationGate). @@ -197,10 +199,7 @@ def _optimize(self, idx, lim=None): return limit def _check_and_send(self): - """ - Check whether a qubit pipeline must be sent on and, if so, - optimize the pipeline and then send it on. - """ + """Check whether a qubit pipeline must be sent on and, if so, optimize the pipeline and then send it on.""" for i in self._l: if ( len(self._l[i]) >= self._cache_size @@ -212,17 +211,14 @@ def _check_and_send(self): self._send_qubit_pipeline(i, len(self._l[i]) - self._cache_size + 1) elif len(self._l[i]) > 0 and isinstance(self._l[i][-1].gate, FastForwardingGate): self._send_qubit_pipeline(i, len(self._l[i])) - new_dict = dict() + new_dict = {} for idx in self._l: if len(self._l[idx]) > 0: new_dict[idx] = self._l[idx] self._l = new_dict def _cache_cmd(self, cmd): - """ - Cache a command, i.e., inserts it into the command lists of all qubits - involved. - """ + """Cache a command, i.e., inserts it into the command lists of all qubits involved.""" # are there qubit ids that haven't been added to the list? idlist = [qubit.id for sublist in cmd.all_qubits for qubit in sublist] @@ -236,20 +232,22 @@ def _cache_cmd(self, cmd): def receive(self, command_list): """ - Receive commands from the previous engine and cache them. - If a flush gate arrives, the entire buffer is sent on. + Receive a list of commands. + + Receive commands from the previous engine and cache them. If a flush gate arrives, the entire buffer is sent + on. """ for cmd in command_list: if cmd.gate == FlushGate(): # flush gate --> optimize and flush for idx in self._l: self._optimize(idx) self._send_qubit_pipeline(idx, len(self._l[idx])) - new_dict = dict() + new_dict = {} for idx in self._l: if len(self._l[idx]) > 0: # pragma: no cover new_dict[idx] = self._l[idx] self._l = new_dict - if self._l != dict(): # pragma: no cover + if self._l != {}: # pragma: no cover raise RuntimeError('Internal compiler error: qubits remaining in LocalOptimizer after a flush!') self.send([cmd]) else: diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index fc2ac96c0..104dfcef3 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -19,20 +19,18 @@ import pytest from projectq import MainEngine -from projectq.cengines import DummyEngine +from projectq.cengines import DummyEngine, _optimize from projectq.ops import ( CNOT, + AllocateQubitGate, + ClassicalInstructionGate, + FastForwardingGate, H, Rx, Ry, - AllocateQubitGate, X, - FastForwardingGate, - ClassicalInstructionGate, ) -from projectq.cengines import _optimize - def test_local_optimizer_init_api_change(): with pytest.warns(DeprecationWarning): diff --git a/projectq/cengines/_replacer/_decomposition_rule.py b/projectq/cengines/_replacer/_decomposition_rule.py index f7bee3d67..812ca1e81 100755 --- a/projectq/cengines/_replacer/_decomposition_rule.py +++ b/projectq/cengines/_replacer/_decomposition_rule.py @@ -13,49 +13,41 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing the definition of a decomposition rule""" +"""Module containing the definition of a decomposition rule.""" from projectq.ops import BasicGate class ThisIsNotAGateClassError(TypeError): - """Exception raised when a gate instance is encountered instead of a gate class in a decomposition rule""" + """Exception raised when a gate instance is encountered instead of a gate class in a decomposition rule.""" class DecompositionRule: # pylint: disable=too-few-public-methods - """ - A rule for breaking down specific gates into sequences of simpler gates. - """ + """A rule for breaking down specific gates into sequences of simpler gates.""" def __init__(self, gate_class, gate_decomposer, gate_recognizer=lambda cmd: True): """ + Initialize a DecompositionRule object. + Args: gate_class (type): The type of gate that this rule decomposes. - The gate class is redundant information used to make lookups - faster when iterating over a circuit and deciding "which rules - apply to this gate?" again and again. + The gate class is redundant information used to make lookups faster when iterating over a circuit and + deciding "which rules apply to this gate?" again and again. - Note that this parameter is a gate type, not a gate instance. - You supply gate_class=MyGate or gate_class=MyGate().__class__, - not gate_class=MyGate(). + Note that this parameter is a gate type, not a gate instance. You supply gate_class=MyGate or + gate_class=MyGate().__class__, not gate_class=MyGate(). - gate_decomposer (function[projectq.ops.Command]): Function which, - given the command to decompose, applies a sequence of gates - corresponding to the high-level function of a gate of type - gate_class. + gate_decomposer (function[projectq.ops.Command]): Function which, given the command to decompose, applies + a sequence of gates corresponding to the high-level function of a gate of type gate_class. - gate_recognizer (function[projectq.ops.Command] : boolean): A - predicate that determines if the decomposition applies to the - given command (on top of the filtering by gate_class). + gate_recognizer (function[projectq.ops.Command] : boolean): A predicate that determines if the + decomposition applies to the given command (on top of the filtering by gate_class). - For example, a decomposition rule may only to apply rotation - gates that rotate by a specific angle. + For example, a decomposition rule may only to apply rotation gates that rotate by a specific angle. - If no gate_recognizer is given, the decomposition applies to - all gates matching the gate_class. + If no gate_recognizer is given, the decomposition applies to all gates matching the gate_class. """ - # Check for common gate_class type mistakes. if isinstance(gate_class, BasicGate): raise ThisIsNotAGateClassError( diff --git a/projectq/cengines/_replacer/_decomposition_rule_set.py b/projectq/cengines/_replacer/_decomposition_rule_set.py index aba4eba6b..738ea0420 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_set.py +++ b/projectq/cengines/_replacer/_decomposition_rule_set.py @@ -13,25 +13,24 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing the definition of a decomposition rule set""" +"""Module containing the definition of a decomposition rule set.""" from projectq.meta import Dagger class DecompositionRuleSet: - """ - A collection of indexed decomposition rules. - """ + """A collection of indexed decomposition rules.""" def __init__(self, rules=None, modules=None): """ + Initialize a DecompositionRuleSet object. + Args: rules list[DecompositionRule]: Initial decomposition rules. - modules (iterable[ModuleWithDecompositionRuleSet]): A list of - things with an "all_defined_decomposition_rules" property - containing decomposition rules to add to the rule set. + modules (iterable[ModuleWithDecompositionRuleSet]): A list of things with an + "all_defined_decomposition_rules" property containing decomposition rules to add to the rule set. """ - self.decompositions = dict() + self.decompositions = {} if rules: self.add_decomposition_rules(rules) @@ -42,9 +41,7 @@ def __init__(self, rules=None, modules=None): ) def add_decomposition_rules(self, rules): - """ - Add some decomposition rules to a decomposition rule set - """ + """Add some decomposition rules to a decomposition rule set.""" for rule in rules: self.add_decomposition_rule(rule) @@ -63,47 +60,37 @@ def add_decomposition_rule(self, rule): class ModuleWithDecompositionRuleSet: # pragma: no cover # pylint: disable=too-few-public-methods - """ - Interface type for explaining one of the parameters that can be given to - DecompositionRuleSet. - """ + """Interface type for explaining one of the parameters that can be given to DecompositionRuleSet.""" def __init__(self, all_defined_decomposition_rules): """ + Initialize a ModuleWithDecompositionRuleSet object. + Args: - all_defined_decomposition_rules (list[DecompositionRule]): - A list of decomposition rules. + all_defined_decomposition_rules (list[DecompositionRule]): A list of decomposition rules. """ self.all_defined_decomposition_rules = all_defined_decomposition_rules class _Decomposition: # pylint: disable=too-few-public-methods - """ - The Decomposition class can be used to register a decomposition rule (by - calling register_decomposition) - """ + """The Decomposition class can be used to register a decomposition rule (by calling register_decomposition).""" def __init__(self, replacement_fun, recogn_fun): """ - Construct the Decomposition object. + Initialize a Decomposition object. Args: - replacement_fun: Function that, when called with a `Command` - object, decomposes this command. - recogn_fun: Function that, when called with a `Command` object, - returns True if and only if the replacement rule can handle - this command. - - Every Decomposition is registered with the gate class. The - Decomposition rule is then potentially valid for all objects which are - an instance of that same class - (i.e., instance of gate_object.__class__). All other parameters have - to be checked by the recogn_fun, i.e., it has to decide whether the - decomposition rule can indeed be applied to replace the given Command. - - As an example, consider recognizing the Toffoli gate, which is a - Pauli-X gate with 2 control qubits. The recognizer function would then - be: + replacement_fun: Function that, when called with a `Command` object, decomposes this command. + recogn_fun: Function that, when called with a `Command` object, returns True if and only if the + replacement rule can handle this command. + + Every Decomposition is registered with the gate class. The Decomposition rule is then potentially valid for + all objects which are an instance of that same class (i.e., instance of gate_object.__class__). All other + parameters have to be checked by the recogn_fun, i.e., it has to decide whether the decomposition rule can + indeed be applied to replace the given Command. + + As an example, consider recognizing the Toffoli gate, which is a Pauli-X gate with 2 control qubits. The + recognizer function would then be: .. code-block:: python @@ -128,14 +115,11 @@ def recogn_toffoli(cmd): def get_inverse_decomposition(self): """ - Return the Decomposition object which handles the inverse of the - original command. - - This simulates the user having added a decomposition rule for the - inverse as well. Since decomposing the inverse of a command can be - achieved by running the original decomposition inside a - `with Dagger(engine):` statement, this is not necessary - (and will be done automatically by the framework). + Return the Decomposition object which handles the inverse of the original command. + + This simulates the user having added a decomposition rule for the inverse as well. Since decomposing the + inverse of a command can be achieved by running the original decomposition inside a `with Dagger(engine):` + statement, this is not necessary (and will be done automatically by the framework). Returns: Decomposition handling the inverse of the original command. diff --git a/projectq/cengines/_replacer/_decomposition_rule_test.py b/projectq/cengines/_replacer/_decomposition_rule_test.py index c2ac1f75e..a67759dc7 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_test.py +++ b/projectq/cengines/_replacer/_decomposition_rule_test.py @@ -17,6 +17,7 @@ import pytest from projectq.ops import BasicRotationGate + from . import DecompositionRule, ThisIsNotAGateClassError diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index 0a9a4d684..a0a942196 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -12,25 +12,28 @@ # 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. + """ -Contains an AutoReplacer compiler engine which uses engine.is_available to -determine whether a command can be executed. If not, it uses the loaded setup -(e.g., default) to find an appropriate decomposition. +Definitions of a few compiler engines that handle command filtering and replacement. + +Contains an AutoReplacer compiler engine which uses engine.is_available to determine whether a command can be +executed. If not, it uses the loaded setup (e.g., default) to find an appropriate decomposition. -The InstructionFilter can be used to further specify which gates to -replace/keep. +The InstructionFilter can be used to further specify which gates to replace/keep. """ -from projectq.cengines import BasicEngine, ForwarderEngine, CommandModifier +from projectq.cengines import BasicEngine, CommandModifier, ForwarderEngine from projectq.ops import FlushGate, get_inverse class NoGateDecompositionError(Exception): - """Exception raised when no gate decomposition rule can be found""" + """Exception raised when no gate decomposition rule can be found.""" class InstructionFilter(BasicEngine): """ + A compiler engine that implements a user-defined is_available() method. + The InstructionFilter is a compiler engine which changes the behavior of is_available according to a filter function. All commands are passed to this function, which then returns whether this command can be executed (True) or needs replacement (False). @@ -38,6 +41,8 @@ class InstructionFilter(BasicEngine): def __init__(self, filterfun): """ + Initialize an InstructionFilter object. + Initializer: The provided filterfun returns True for all commands which do not need replacement and False for commands that do. @@ -45,11 +50,13 @@ def __init__(self, filterfun): filterfun (function): Filter function which returns True for available commands, and False otherwise. filterfun will be called as filterfun(self, cmd). """ - BasicEngine.__init__(self) + super().__init__() self._filterfun = filterfun def is_available(self, cmd): """ + Test whether a Command is supported by a compiler engine. + Specialized implementation of BasicBackend.is_available: Forwards this call to the filter function given to the constructor. @@ -60,7 +67,9 @@ def is_available(self, cmd): def receive(self, command_list): """ - Forward all commands to the next engine. + Receive a list of commands. + + This implementation simply forwards all commands to the next engine. Args: command_list (list): List of commands to receive. @@ -70,6 +79,8 @@ def receive(self, command_list): class AutoReplacer(BasicEngine): """ + A compiler engine to automatically replace certain commands. + The AutoReplacer is a compiler engine which uses engine.is_available in order to determine which commands need to be replaced/decomposed/compiled further. The loaded setup is used to find decomposition rules appropriate for each command (e.g., setups.default). @@ -104,12 +115,14 @@ def decomposition_chooser(cmd, decomp_list): return decomp_list[0] repl = AutoReplacer(decomposition_chooser) """ - BasicEngine.__init__(self) + super().__init__() self._decomp_chooser = decomposition_chooser self.decomposition_rule_set = decomposition_rule_se def _process_command(self, cmd): # pylint: disable=too-many-locals,too-many-branches """ + Process a command. + Check whether a command cmd can be handled by further engines and, if not, replace it using the decomposition rules loaded with the setup (e.g., setups.default). @@ -205,9 +218,10 @@ def cmd_mod_fun(cmd): # Adds the tags def receive(self, command_list): """ - Receive a list of commands from the previous compiler engine and, if - necessary, replace/decompose the gates according to the decomposition - rules in the loaded setup. + Receive a list of commands. + + Receive a list of commands from the previous compiler engine and, if necessary, replace/decompose the gates + according to the decomposition rules in the loaded setup. Args: command_list (list): List of commands to handle. diff --git a/projectq/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py index b2675f191..dbf68ea2e 100755 --- a/projectq/cengines/_replacer/_replacer_test.py +++ b/projectq/cengines/_replacer/_replacer_test.py @@ -17,18 +17,18 @@ import pytest from projectq import MainEngine -from projectq.cengines import DummyEngine, DecompositionRuleSet, DecompositionRule +from projectq.cengines import DecompositionRule, DecompositionRuleSet, DummyEngine +from projectq.cengines._replacer import _replacer from projectq.ops import ( BasicGate, ClassicalInstructionGate, Command, H, - S, NotInvertible, Rx, + S, X, ) -from projectq.cengines._replacer import _replacer def test_filter_engine(): diff --git a/projectq/cengines/_swapandcnotflipper.py b/projectq/cengines/_swapandcnotflipper.py index dfb87b7a3..2d7943abb 100755 --- a/projectq/cengines/_swapandcnotflipper.py +++ b/projectq/cengines/_swapandcnotflipper.py @@ -13,14 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Contains a compiler engine which flips the directionality of CNOTs according -to the given connectivity graph. It also translates Swap gates to CNOTs if -necessary. +A compiler engine which flips the directionality of CNOTs according to the given connectivity graph. + +It also translates Swap gates to CNOTs if necessary. """ from copy import deepcopy from projectq.meta import get_control_count -from projectq.ops import All, NOT, CNOT, H, Swap +from projectq.ops import CNOT, NOT, All, H, Swap from ._basics import BasicEngine, ForwarderEngine from ._cmdmodifier import CommandModifier @@ -28,11 +28,10 @@ class SwapAndCNOTFlipper(BasicEngine): """ - Flips CNOTs and translates Swaps to CNOTs where necessary. + Flip CNOTs and translates Swaps to CNOTs where necessary. Warning: - This engine assumes that CNOT and Hadamard gates are supported by - the following engines. + This engine assumes that CNOT and Hadamard gates are supported by the following engines. Warning: This engine cannot be used as a backend. @@ -43,18 +42,15 @@ def __init__(self, connectivity): Initialize the engine. Args: - connectivity (set): Set of tuples (c, t) where if (c, t) is an - element of the set means that a CNOT can be performed between - the physical ids (c, t) with c being the control and t being - the target qubit. + connectivity (set): Set of tuples (c, t) where if (c, t) is an element of the set means that a CNOT can be + performed between the physical ids (c, t) with c being the control and t being the target qubit. """ super().__init__() self.connectivity = connectivity def is_available(self, cmd): """ - Check if the IBM backend can perform the Command cmd and return True - if so. + Check if the IBM backend can perform the Command cmd and return True if so. Args: cmd (Command): The command to check @@ -120,14 +116,13 @@ def cmd_mod(command): def receive(self, command_list): """ - Receives a command list and if the command is a CNOT gate, it flips - it using Hadamard gates if necessary; if it is a Swap gate, it - decomposes it using 3 CNOTs. All other gates are simply sent to the - next engine. + Receive a list of commands. + + Receive a command list and if the command is a CNOT gate, it flips it using Hadamard gates if necessary; if it + is a Swap gate, it decomposes it using 3 CNOTs. All other gates are simply sent to the next engine. Args: - command_list (list of Command objects): list of commands to - receive. + command_list (list of Command objects): list of commands to receive. """ for cmd in command_list: if self._needs_flipping(cmd): diff --git a/projectq/cengines/_swapandcnotflipper_test.py b/projectq/cengines/_swapandcnotflipper_test.py index 61256aa52..dc9611a48 100755 --- a/projectq/cengines/_swapandcnotflipper_test.py +++ b/projectq/cengines/_swapandcnotflipper_test.py @@ -18,8 +18,8 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import All, H, CNOT, X, Swap, Command -from projectq.meta import Control, Compute, Uncompute, ComputeTag, UncomputeTag +from projectq.meta import Compute, ComputeTag, Control, Uncompute, UncomputeTag +from projectq.ops import CNOT, All, Command, H, Swap, X from projectq.types import WeakQubitRef from . import _swapandcnotflipper @@ -72,7 +72,7 @@ def test_swapandcnotflipper_is_available(): def test_swapandcnotflipper_flips_cnot(): backend = DummyEngine(save_commands=True) - connectivity = set([(0, 1)]) + connectivity = {(0, 1)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() @@ -91,7 +91,7 @@ def test_swapandcnotflipper_flips_cnot(): def test_swapandcnotflipper_invalid_circuit(): backend = DummyEngine(save_commands=True) - connectivity = set([(0, 2)]) + connectivity = {(0, 2)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() @@ -107,7 +107,7 @@ def test_swapandcnotflipper_invalid_circuit(): def test_swapandcnotflipper_optimize_swaps(): backend = DummyEngine(save_commands=True) - connectivity = set([(1, 0)]) + connectivity = {(1, 0)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() @@ -123,7 +123,7 @@ def test_swapandcnotflipper_optimize_swaps(): assert hgates == 4 backend = DummyEngine(save_commands=True) - connectivity = set([(0, 1)]) + connectivity = {(0, 1)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() @@ -141,7 +141,7 @@ def test_swapandcnotflipper_optimize_swaps(): def test_swapandcnotflipper_keeps_tags(): backend = DummyEngine(save_commands=True) - connectivity = set([(1, 0)]) + connectivity = {(1, 0)} flipper = _swapandcnotflipper.SwapAndCNOTFlipper(connectivity) eng = MainEngine(backend=backend, engine_list=[flipper]) qb0 = eng.allocate_qubit() diff --git a/projectq/cengines/_tagremover.py b/projectq/cengines/_tagremover.py index 60da9b0d8..2c6497ac5 100755 --- a/projectq/cengines/_tagremover.py +++ b/projectq/cengines/_tagremover.py @@ -13,8 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Contains a TagRemover engine, which removes temporary command tags (such as Compute/Uncompute), thus enabling -optimization across meta statements (loops after unrolling, compute/uncompute, ...) +The TagRemover compiler engine. + +A TagRemover engine removes temporary command tags (such as Compute/Uncompute), thus enabling optimization across meta +statements (loops after unrolling, compute/uncompute, ...) """ from projectq.meta import ComputeTag, UncomputeTag @@ -23,6 +25,8 @@ class TagRemover(BasicEngine): """ + Compiler engine that remove temporary command tags. + TagRemover is a compiler engine which removes temporary command tags (see the tag classes such as LoopTag in projectq.meta._loop). @@ -32,7 +36,7 @@ class TagRemover(BasicEngine): def __init__(self, tags=None): """ - Construct the TagRemover. + Initialize a TagRemover object. Args: tags: A list of meta tag classes (e.g., [ComputeTag, UncomputeTag]) @@ -48,6 +52,8 @@ def __init__(self, tags=None): def receive(self, command_list): """ + Receive a list of commands. + Receive a list of commands from the previous engine, remove all tags which are an instance of at least one of the meta tags provided in the constructor, and then send them on to the next compiler engine. diff --git a/projectq/cengines/_tagremover_test.py b/projectq/cengines/_tagremover_test.py index d22369762..c9679dcd0 100755 --- a/projectq/cengines/_tagremover_test.py +++ b/projectq/cengines/_tagremover_test.py @@ -17,11 +17,9 @@ import pytest from projectq import MainEngine +from projectq.cengines import DummyEngine, _tagremover from projectq.meta import ComputeTag, UncomputeTag from projectq.ops import Command, H -from projectq.cengines import DummyEngine - -from projectq.cengines import _tagremover def test_tagremover_default(): diff --git a/projectq/cengines/_testengine.py b/projectq/cengines/_testengine.py index eace5744b..1e2d6f934 100755 --- a/projectq/cengines/_testengine.py +++ b/projectq/cengines/_testengine.py @@ -15,13 +15,14 @@ """TestEngine and DummyEngine.""" from copy import deepcopy + from projectq.ops import FlushGate from ._basics import BasicEngine def _compare_cmds(cmd1, cmd2): - """Compare two command objects""" + """Compare two command objects.""" cmd2 = deepcopy(cmd2) cmd2.engine = cmd1.engine return cmd1 == cmd2 @@ -29,20 +30,23 @@ def _compare_cmds(cmd1, cmd2): class CompareEngine(BasicEngine): """ + Command list comparison compiler engine for testing purposes. + CompareEngine is an engine which saves all commands. It is only intended for testing purposes. Two CompareEngine backends can be compared and return True if they contain the same commmands. """ def __init__(self): + """Initialize a CompareEngine object.""" super().__init__() self._l = [[]] def is_available(self, cmd): - """All commands are accepted by this compiler engine""" + """All commands are accepted by this compiler engine.""" return True def cache_cmd(self, cmd): - """Cache a command""" + """Cache a command.""" # are there qubit ids that haven't been added to the list? all_qubit_id_list = [qubit.id for qureg in cmd.all_qubits for qubit in qureg] maxidx = int(0) @@ -61,7 +65,9 @@ def cache_cmd(self, cmd): def receive(self, command_list): """ - Receives a command list and, for each command, stores it inside the cache before sending it to the next + Receive a list of commands. + + Receive a command list and, for each command, stores it inside the cache before sending it to the next compiler engine. Args: @@ -74,6 +80,7 @@ def receive(self, command_list): self.send(command_list) def __eq__(self, other): + """Equal operator.""" if not isinstance(other, CompareEngine) or len(self._l) != len(other._l): return False for i in range(len(self._l)): @@ -84,10 +91,8 @@ def __eq__(self, other): return False return True - def __ne__(self, other): - return not self.__eq__(other) - def __str__(self): + """Return a string representation of the object.""" string = "" for qubit_id in range(len(self._l)): string += "Qubit {0} : ".format(qubit_id) @@ -109,7 +114,7 @@ class DummyEngine(BasicEngine): def __init__(self, save_commands=False): """ - Initialize DummyEngine + Initialize a DummyEngine. Args: save_commands (default = False): If True, commands are saved in @@ -120,12 +125,14 @@ def __init__(self, save_commands=False): self.received_commands = [] def is_available(self, cmd): - """All commands are accepted by this compiler engine""" + """All commands are accepted by this compiler engine.""" return True def receive(self, command_list): """ - Receives a command list and, for each command, stores it internally if requested before sending it to the next + Receive a list of commands. + + Receive a command list and, for each command, stores it internally if requested before sending it to the next compiler engine. Args: diff --git a/projectq/cengines/_testengine_test.py b/projectq/cengines/_testengine_test.py index baf4010de..fee0866e5 100755 --- a/projectq/cengines/_testengine_test.py +++ b/projectq/cengines/_testengine_test.py @@ -15,10 +15,8 @@ """Tests for projectq.cengines._testengine.py.""" from projectq import MainEngine -from projectq.cengines import DummyEngine -from projectq.ops import CNOT, H, Rx, Allocate, FlushGate - -from projectq.cengines import _testengine +from projectq.cengines import DummyEngine, _testengine +from projectq.ops import CNOT, Allocate, FlushGate, H, Rx def test_compare_engine_str(): diff --git a/projectq/cengines/_twodmapper.py b/projectq/cengines/_twodmapper.py index f3bf4b5d3..4b4bb18b9 100644 --- a/projectq/cengines/_twodmapper.py +++ b/projectq/cengines/_twodmapper.py @@ -20,10 +20,10 @@ Output: Quantum circuit in which qubits are placed in 2-D square grid in which only nearest neighbour qubits can perform a 2 qubit gate. The mapper uses Swap gates in order to move qubits next to each other. """ -from copy import deepcopy import itertools import math import random +from copy import deepcopy import networkx as nx @@ -37,7 +37,6 @@ ) from projectq.types import WeakQubitRef - from ._basicmapper import BasicMapperEngine from ._linearmapper import LinearMapper, return_swap_depth @@ -109,14 +108,14 @@ def __init__( # pylint: disable=too-many-arguments # Before sending we use this map to translate to backend ids: self._mapped_ids_to_backend_ids = mapped_ids_to_backend_ids if self._mapped_ids_to_backend_ids is None: - self._mapped_ids_to_backend_ids = dict() + self._mapped_ids_to_backend_ids = {} for i in range(self.num_qubits): self._mapped_ids_to_backend_ids[i] = i if not (set(self._mapped_ids_to_backend_ids.keys()) == set(range(self.num_qubits))) or not ( len(set(self._mapped_ids_to_backend_ids.values())) == self.num_qubits ): raise RuntimeError("Incorrect mapped_ids_to_backend_ids parameter") - self._backend_ids_to_mapped_ids = dict() + self._backend_ids_to_mapped_ids = {} for mapped_id, backend_id in self._mapped_ids_to_backend_ids.items(): self._backend_ids_to_mapped_ids[backend_id] = mapped_id # As we use internally the mapped ids which are in row-major order, @@ -132,7 +131,7 @@ def __init__( # pylint: disable=too-many-arguments # places. self._rng = random.Random(11) # Storing commands - self._stored_commands = list() + self._stored_commands = [] # Logical qubit ids for which the Allocate gate has already been # processed and sent to the next engine but which are not yet # deallocated: @@ -140,8 +139,8 @@ def __init__( # pylint: disable=too-many-arguments # Change between 2D and 1D mappings (2D is a snake like 1D chain) # Note it translates to our mapped ids in row major order and not # backend ids which might be different. - self._map_2d_to_1d = dict() - self._map_1d_to_2d = dict() + self._map_2d_to_1d = {} + self._map_1d_to_2d = {} for row_index in range(self.num_rows): for column_index in range(self.num_columns): if row_index % 2 == 0: @@ -155,11 +154,12 @@ def __init__( # pylint: disable=too-many-arguments self._map_1d_to_2d[mapped_id_1d] = mapped_id_2d # Statistics: self.num_mappings = 0 - self.depth_of_swaps = dict() - self.num_of_swaps_per_mapping = dict() + self.depth_of_swaps = {} + self.num_of_swaps_per_mapping = {} @property def current_mapping(self): + """Access to the mapping stored inside the mapper engine.""" return deepcopy(self._current_mapping) @current_mapping.setter @@ -168,14 +168,12 @@ def current_mapping(self, current_mapping): if current_mapping is None: self._current_row_major_mapping = None else: - self._current_row_major_mapping = dict() + self._current_row_major_mapping = {} for logical_id, backend_id in current_mapping.items(): self._current_row_major_mapping[logical_id] = self._backend_ids_to_mapped_ids[backend_id] def is_available(self, cmd): - """ - Only allows 1 or two qubit gates. - """ + """Only allow 1 or two qubit gates.""" num_qubits = 0 for qureg in cmd.all_qubits: num_qubits += len(qureg) @@ -183,7 +181,7 @@ def is_available(self, cmd): def _return_new_mapping(self): """ - Returns a new mapping of the qubits. + Return a new mapping of the qubits. It goes through self._saved_commands and tries to find a mapping to apply these gates on a first come first served basis. It reuses the function of a 1D mapper and creates a mapping for a 1D linear chain and then @@ -195,7 +193,7 @@ def _return_new_mapping(self): """ # Change old mapping to 1D in order to use LinearChain heuristic if self._current_row_major_mapping: - old_mapping_1d = dict() + old_mapping_1d = {} for logical_id, mapped_id in self._current_row_major_mapping.items(): old_mapping_1d[logical_id] = self._map_2d_to_1d[mapped_id] else: @@ -209,15 +207,13 @@ def _return_new_mapping(self): current_mapping=old_mapping_1d, ) - new_mapping_2d = dict() + new_mapping_2d = {} for logical_id, mapped_id in new_mapping_1d.items(): new_mapping_2d[logical_id] = self._map_1d_to_2d[mapped_id] return new_mapping_2d def _compare_and_swap(self, element0, element1, key): - """ - If swapped (inplace), then return swap operation so that key(element0) < key(element1) - """ + """If swapped (inplace), then return swap operation so that key(element0) < key(element1).""" if key(element0) > key(element1): mapped_id0 = element0.current_column + element0.current_row * self.num_columns mapped_id1 = element1.current_column + element1.current_row * self.num_columns @@ -283,7 +279,7 @@ def return_swaps( # pylint: disable=too-many-locals,too-many-branches,too-many- self, old_mapping, new_mapping, permutation=None ): """ - Returns the swap operation to change mapping + Return the swap operation to change mapping. Args: old_mapping: dict: keys are logical ids and values are mapped qubit ids @@ -417,7 +413,7 @@ def __init__( # pylint: disable=too-many-arguments def _send_possible_commands(self): # pylint: disable=too-many-branches """ - Sends the stored commands possible without changing the mapping. + Send the stored commands possible without changing the mapping. Note: self._current_row_major_mapping (hence also self.current_mapping) must exist already """ @@ -475,7 +471,7 @@ def _send_possible_commands(self): # pylint: disable=too-many-branches mapped_ids.add(self._current_row_major_mapping[qubit.id]) # Check that mapped ids are nearest neighbour on 2D grid if len(mapped_ids) == 2: - qb0, qb1 = sorted(list(mapped_ids)) + qb0, qb1 = sorted(mapped_ids) send_gate = False if qb1 - qb0 == self.num_columns: send_gate = True @@ -494,7 +490,7 @@ def _send_possible_commands(self): # pylint: disable=too-many-branches def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-statements """ - Creates a new mapping and executes possible gates. + Create a new mapping and executes possible gates. It first allocates all 0, ..., self.num_qubits-1 mapped qubit ids, if they are not already used because we might need them all for the swaps. Then it creates a new map, swaps all the qubits to the new map, executes @@ -502,7 +498,7 @@ def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-st """ num_of_stored_commands_before = len(self._stored_commands) if not self.current_mapping: - self.current_mapping = dict() + self.current_mapping = {} else: self._send_possible_commands() if len(self._stored_commands) == 0: @@ -570,7 +566,7 @@ def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-st self.send([cmd]) # Change to new map: self._current_row_major_mapping = new_row_major_mapping - new_mapping = dict() + new_mapping = {} for logical_id, mapped_id in new_row_major_mapping.items(): new_mapping[logical_id] = self._mapped_ids_to_backend_ids[mapped_id] self.current_mapping = new_mapping @@ -585,7 +581,9 @@ def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-st def receive(self, command_list): """ - Receives a command list and, for each command, stores it until we do a mapping (FlushGate or Cache of stored + Receive a list of commands. + + Receive a command list and, for each command, stores it until we do a mapping (FlushGate or Cache of stored commands is full). Args: diff --git a/projectq/cengines/_twodmapper_test.py b/projectq/cengines/_twodmapper_test.py index a21969a74..f86c4ee02 100644 --- a/projectq/cengines/_twodmapper_test.py +++ b/projectq/cengines/_twodmapper_test.py @@ -14,20 +14,19 @@ # limitations under the License. """Tests for projectq.cengines._2dmapper.py.""" -from copy import deepcopy import itertools import random +from copy import deepcopy import pytest import projectq from projectq.cengines import DummyEngine, LocalOptimizer +from projectq.cengines import _twodmapper as two_d from projectq.meta import LogicalQubitIDTag from projectq.ops import Allocate, BasicGate, Command, Deallocate, FlushGate, X from projectq.types import WeakQubitRef -from projectq.cengines import _twodmapper as two_d - def test_is_available(): mapper = two_d.GridMapper(num_rows=2, num_columns=2) @@ -125,10 +124,10 @@ def test_return_new_mapping(different_backend_ids): assert new_mapping == possible_solution_1 or new_mapping == possible_solution_2 eng.flush() if different_backend_ids: - transformed_sol1 = dict() + transformed_sol1 = {} for logical_id, mapped_id in possible_solution_1.items(): transformed_sol1[logical_id] = map_to_backend_ids[mapped_id] - transformed_sol2 = dict() + transformed_sol2 = {} for logical_id, mapped_id in possible_solution_2.items(): transformed_sol2[logical_id] = map_to_backend_ids[mapped_id] assert mapper.current_mapping == transformed_sol1 or mapper.current_mapping == transformed_sol2 @@ -153,8 +152,8 @@ def test_return_swaps_random(num_rows, num_columns, seed, none_old, none_new): num_qubits = num_rows * num_columns old_chain = random.sample(range(num_qubits), num_qubits) new_chain = random.sample(range(num_qubits), num_qubits) - old_mapping = dict() - new_mapping = dict() + old_mapping = {} + new_mapping = {} for i in range(num_qubits): old_mapping[old_chain[i]] = i new_mapping[new_chain[i]] = i @@ -252,9 +251,9 @@ def test_send_possible_commands_allocate(different_backend_ids): qb0 = WeakQubitRef(engine=None, idx=0) cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] - mapper._currently_allocated_ids = set([10]) + mapper._currently_allocated_ids = {10} # not in mapping: - mapper.current_mapping = dict() + mapper.current_mapping = {} assert len(backend.received_commands) == 0 mapper._send_possible_commands() assert len(backend.received_commands) == 0 @@ -273,7 +272,7 @@ def test_send_possible_commands_allocate(different_backend_ids): tags=[LogicalQubitIDTag(0)], ) assert backend.received_commands[0] == received_cmd - assert mapper._currently_allocated_ids == set([10, 0]) + assert mapper._currently_allocated_ids == {10, 0} @pytest.mark.parametrize("different_backend_ids", [False, True]) @@ -289,8 +288,8 @@ def test_send_possible_commands_deallocate(different_backend_ids): qb0 = WeakQubitRef(engine=None, idx=0) cmd0 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] - mapper.current_mapping = dict() - mapper._currently_allocated_ids = set([10]) + mapper.current_mapping = {} + mapper._currently_allocated_ids = {10} # not yet allocated: mapper._send_possible_commands() assert len(backend.received_commands) == 0 @@ -301,8 +300,8 @@ def test_send_possible_commands_deallocate(different_backend_ids): mapper._send_possible_commands() assert len(backend.received_commands) == 1 assert len(mapper._stored_commands) == 0 - assert mapper.current_mapping == dict() - assert mapper._currently_allocated_ids == set([10]) + assert mapper.current_mapping == {} + assert mapper._currently_allocated_ids == {10} @pytest.mark.parametrize("different_backend_ids", [False, True]) @@ -391,7 +390,7 @@ def choose_last_permutation(swaps): mapper.receive([cmd_flush]) assert mapper._stored_commands == [] assert len(backend.received_commands) == 10 - assert mapper._currently_allocated_ids == set([0, 2, 3]) + assert mapper._currently_allocated_ids == {0, 2, 3} if different_backend_ids: assert ( mapper.current_mapping == {0: 21, 2: 3, 3: 0} @@ -409,7 +408,7 @@ def choose_last_permutation(swaps): cmd9 = Command(None, X, qubits=([qb0],), controls=[qb3]) mapper.storage = 1 mapper.receive([cmd9]) - assert mapper._currently_allocated_ids == set([0, 2, 3]) + assert mapper._currently_allocated_ids == {0, 2, 3} assert mapper._stored_commands == [] assert len(mapper.current_mapping) == 3 assert 0 in mapper.current_mapping diff --git a/projectq/libs/hist/_histogram.py b/projectq/libs/hist/_histogram.py index 85c0be1b3..18a674e23 100644 --- a/projectq/libs/hist/_histogram.py +++ b/projectq/libs/hist/_histogram.py @@ -13,9 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Functions to plot a histogram of measured data""" +"""Functions to plot a histogram of measured data.""" -from __future__ import print_function import matplotlib.pyplot as plt from projectq.backends import Simulator diff --git a/projectq/libs/hist/_histogram_test.py b/projectq/libs/hist/_histogram_test.py index c6cb78a95..6181a0161 100644 --- a/projectq/libs/hist/_histogram_test.py +++ b/projectq/libs/hist/_histogram_test.py @@ -13,15 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest import matplotlib import matplotlib.pyplot as plt # noqa: F401 +import pytest from projectq import MainEngine -from projectq.ops import H, C, X, Measure, All, AllocateQubitGate, FlushGate -from projectq.cengines import DummyEngine, BasicEngine from projectq.backends import Simulator +from projectq.cengines import BasicEngine, DummyEngine from projectq.libs.hist import histogram +from projectq.ops import All, AllocateQubitGate, C, FlushGate, H, Measure, X @pytest.fixture(scope="module") diff --git a/projectq/libs/math/__init__.py b/projectq/libs/math/__init__.py index f950ab3cf..06dc384c7 100755 --- a/projectq/libs/math/__init__.py +++ b/projectq/libs/math/__init__.py @@ -13,17 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from ._default_rules import all_defined_decomposition_rules from ._gates import ( AddConstant, - SubConstant, AddConstantModN, - SubConstantModN, - MultiplyByConstantModN, AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, + MultiplyByConstantModN, MultiplyQuantum, + SubConstant, + SubConstantModN, + SubtractQuantum, ) - -from ._default_rules import all_defined_decomposition_rules diff --git a/projectq/libs/math/_constantmath.py b/projectq/libs/math/_constantmath.py index 25eb6def7..1b502aabc 100755 --- a/projectq/libs/math/_constantmath.py +++ b/projectq/libs/math/_constantmath.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Module containing constant math quantum operations""" +"""Module containing constant math quantum operations.""" import math @@ -22,21 +22,20 @@ except ImportError: # pragma: no cover from fractions import gcd -from projectq.ops import R, X, Swap, CNOT, QFT -from projectq.meta import Control, Compute, Uncompute, CustomUncompute -from ._gates import AddConstant, SubConstant, AddConstantModN, SubConstantModN +from projectq.meta import Compute, Control, CustomUncompute, Uncompute +from projectq.ops import CNOT, QFT, R, Swap, X + +from ._gates import AddConstant, AddConstantModN, SubConstant, SubConstantModN # Draper's addition by constant https://arxiv.org/abs/quant-ph/0008033 def add_constant(eng, constant, quint): """ - Adds a classical constant c to the quantum integer (qureg) quint using - Draper addition. + Add a classical constant c to the quantum integer (qureg) quint using Draper addition. - Note: Uses the Fourier-transform adder from - https://arxiv.org/abs/quant-ph/0008033. + Note: + Uses the Fourier-transform adder from https://arxiv.org/abs/quant-ph/0008033. """ - with Compute(eng): QFT | quint @@ -51,9 +50,9 @@ def add_constant(eng, constant, quint): # Modular adder by Beauregard https://arxiv.org/abs/quant-ph/0205095 def add_constant_modN(eng, constant, N, quint): # pylint: disable=invalid-name """ - Adds a classical constant c to a quantum integer (qureg) quint modulo N - using Draper addition and the construction from - https://arxiv.org/abs/quant-ph/0205095. + Add a classical constant c to a quantum integer (qureg) quint modulo N using Draper addition. + + This function uses Draper addition and the construction from https://arxiv.org/abs/quant-ph/0205095. """ if constant < 0 or constant > N: raise ValueError('Pre-condition failed: 0 <= constant < N') @@ -82,7 +81,9 @@ def add_constant_modN(eng, constant, N, quint): # pylint: disable=invalid-name # from https://arxiv.org/abs/quant-ph/0205095 def mul_by_constant_modN(eng, constant, N, quint_in): # pylint: disable=invalid-name """ - Multiplies a quantum integer by a classical number a modulo N, i.e., + Multiply a quantum integer by a classical number a modulo N. + + i.e., |x> -> |a*x mod N> @@ -113,9 +114,7 @@ def mul_by_constant_modN(eng, constant, N, quint_in): # pylint: disable=invalid def inv_mod_N(a, N): # pylint: disable=invalid-name - """ - Calculate the inverse of a modulo N - """ + """Calculate the inverse of a modulo N.""" # pylint: disable=invalid-name s = 0 old_s = 1 diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index 83eaf060e..aafcff127 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -16,14 +16,13 @@ import pytest +import projectq.libs.math from projectq import MainEngine -from projectq.cengines import InstructionFilter, AutoReplacer, DecompositionRuleSet from projectq.backends import Simulator +from projectq.cengines import AutoReplacer, DecompositionRuleSet, InstructionFilter +from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN from projectq.ops import All, BasicMathGate, ClassicalInstructionGate, Measure, X - -import projectq.libs.math from projectq.setups.decompositions import qft2crandhadamard, swap2cnot -from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN def init(engine, quint, value): diff --git a/projectq/libs/math/_default_rules.py b/projectq/libs/math/_default_rules.py index 60e25dd8b..ca4ad4a4f 100755 --- a/projectq/libs/math/_default_rules.py +++ b/projectq/libs/math/_default_rules.py @@ -12,48 +12,37 @@ # 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 few default replacement rules for Shor's algorithm to work -(see Examples). -""" -from projectq.meta import Control +"""Registers a few default replacement rules for Shor's algorithm to work (see Examples).""" + from projectq.cengines import DecompositionRule +from projectq.meta import Control +from ._constantmath import add_constant, add_constant_modN, mul_by_constant_modN from ._gates import ( AddConstant, AddConstantModN, - MultiplyByConstantModN, AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, + MultiplyByConstantModN, MultiplyQuantum, -) - -from ._gates import ( + SubtractQuantum, _InverseAddQuantumGate, _InverseDivideQuantumGate, _InverseMultiplyQuantumGate, ) - -from ._constantmath import ( - add_constant, - add_constant_modN, - mul_by_constant_modN, -) - from ._quantummath import ( add_quantum, - subtract_quantum, - inverse_add_quantum_carry, comparator, - quantum_conditional_add, - quantum_division, + inverse_add_quantum_carry, inverse_quantum_division, + inverse_quantum_multiplication, + quantum_conditional_add, quantum_conditional_add_carry, + quantum_division, quantum_multiplication, - inverse_quantum_multiplication, + subtract_quantum, ) diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index 45b2bc1fe..35777da05 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -12,15 +12,15 @@ # 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. -"""Math gates for ProjectQ""" + +"""Quantum number math gates for ProjectQ.""" from projectq.ops import BasicMathGate class AddConstant(BasicMathGate): """ - Add a constant to a quantum number represented by a quantum register, - stored from low- to high-bit. + Add a constant to a quantum number represented by a quantum register, stored from low- to high-bit. Example: .. code-block:: python @@ -35,7 +35,7 @@ class AddConstant(BasicMathGate): def __init__(self, a): # pylint: disable=invalid-name """ - Initializes the gate to the number to add. + Initialize the gate to the number to add. Args: a (int): Number to add to a quantum register. @@ -43,32 +43,29 @@ def __init__(self, a): # pylint: disable=invalid-name It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((x + a),)) + super().__init__(lambda x: ((x + a),)) self.a = a # pylint: disable=invalid-name def get_inverse(self): - """ - Return the inverse gate (subtraction of the same constant). - """ + """Return the inverse gate (subtraction of the same constant).""" return SubConstant(self.a) def __str__(self): + """Return a string representation of the object.""" return "AddConstant({})".format(self.a) def __eq__(self, other): + """Equal operator.""" return isinstance(other, AddConstant) and self.a == other.a def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def SubConstant(a): # pylint: disable=invalid-name """ - Subtract a constant from a quantum number represented by a quantum - register, stored from low- to high-bit. + Subtract a constant from a quantum number represented by a quantum register, stored from low- to high-bit. Args: a (int): Constant to subtract @@ -85,8 +82,7 @@ def SubConstant(a): # pylint: disable=invalid-name class AddConstantModN(BasicMathGate): """ - Add a constant to a quantum number represented by a quantum register - modulo N. + Add a constant to a quantum number represented by a quantum register modulo N. The number is stored from low- to high-bit, i.e., qunum[0] is the LSB. @@ -108,50 +104,45 @@ class AddConstantModN(BasicMathGate): def __init__(self, a, N): """ - Initializes the gate to the number to add modulo N. + Initialize the gate to the number to add modulo N. Args: a (int): Number to add to a quantum register (0 <= a < N). N (int): Number modulo which the addition is carried out. - It also initializes its base class, BasicMathGate, with the - corresponding function, so it can be emulated efficiently. + It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated + efficiently. """ - BasicMathGate.__init__(self, lambda x: ((x + a) % N,)) + super().__init__(lambda x: ((x + a) % N,)) self.a = a # pylint: disable=invalid-name self.N = N def __str__(self): + """Return a string representation of the object.""" return "AddConstantModN({}, {})".format(self.a, self.N) def get_inverse(self): - """ - Return the inverse gate (subtraction of the same number a modulo the - same number N). - """ + """Return the inverse gate (subtraction of the same number a modulo the same number N).""" return SubConstantModN(self.a, self.N) def __eq__(self, other): + """Equal operator.""" return isinstance(other, AddConstantModN) and self.a == other.a and self.N == other.N def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def SubConstantModN(a, N): # pylint: disable=invalid-name """ - Subtract a constant from a quantum number represented by a quantum - register modulo N. + Subtract a constant from a quantum number represented by a quantum register modulo N. The number is stored from low- to high-bit, i.e., qunum[0] is the LSB. Args: a (int): Constant to add - N (int): Constant modulo which the addition of a should be carried - out. + N (int): Constant modulo which the addition of a should be carried out. Example: .. code-block:: python @@ -173,8 +164,7 @@ def SubConstantModN(a, N): # pylint: disable=invalid-name class MultiplyByConstantModN(BasicMathGate): """ - Multiply a quantum number represented by a quantum register by a constant - modulo N. + Multiply a quantum number represented by a quantum register by a constant modulo N. The number is stored from low- to high-bit, i.e., qunum[0] is the LSB. @@ -197,7 +187,7 @@ class MultiplyByConstantModN(BasicMathGate): def __init__(self, a, N): # pylint: disable=invalid-name """ - Initializes the gate to the number to multiply with modulo N. + Initialize the gate to the number to multiply with modulo N. Args: a (int): Number by which to multiply a quantum register @@ -207,26 +197,27 @@ def __init__(self, a, N): # pylint: disable=invalid-name It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((a * x) % N,)) + super().__init__(lambda x: ((a * x) % N,)) self.a = a # pylint: disable=invalid-name self.N = N def __str__(self): + """Return a string representation of the object.""" return "MultiplyByConstantModN({}, {})".format(self.a, self.N) def __eq__(self, other): + """Equal operator.""" return isinstance(other, MultiplyByConstantModN) and self.a == other.a and self.N == other.N def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - class AddQuantumGate(BasicMathGate): """ - Adds up two quantum numbers represented by quantum registers. + Add up two quantum numbers represented by quantum registers. + The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. Example: @@ -243,21 +234,23 @@ class AddQuantumGate(BasicMathGate): """ def __init__(self): - BasicMathGate.__init__(self, None) + """Initialize an AddQuantumGate object.""" + super().__init__(None) def __str__(self): + """Return a string representation of the object.""" return "AddQuantum" def __eq__(self, other): + """Equal operator.""" return isinstance(other, AddQuantumGate) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def get_math_function(self, qubits): + """Get the math function associated with an AddQuantumGate.""" n_qubits = len(qubits[0]) def math_fun(a): # pylint: disable=invalid-name @@ -273,10 +266,7 @@ def math_fun(a): # pylint: disable=invalid-name return math_fun def get_inverse(self): - """ - Return the inverse gate (subtraction of the same number a modulo the - same number N). - """ + """Return the inverse gate (subtraction of the same number a modulo the same number N).""" return _InverseAddQuantumGate() @@ -284,15 +274,14 @@ def get_inverse(self): class _InverseAddQuantumGate(BasicMathGate): - """ - Internal gate glass to support emulation for inverse - addition. - """ + """Internal gate glass to support emulation for inverse addition.""" def __init__(self): - BasicMathGate.__init__(self, None) + """Initialize an _InverseAddQuantumGate object.""" + super().__init__(None) def __str__(self): + """Return a string representation of the object.""" return "_InverseAddQuantum" def get_math_function(self, qubits): @@ -309,8 +298,7 @@ def math_fun(a): # pylint: disable=invalid-name class SubtractQuantumGate(BasicMathGate): """ - Subtract one quantum number represented by a quantum register from - another quantum number represented by a quantum register. + Subtract one quantum number from another quantum number both represented by quantum registers. Example: .. code-block:: python @@ -326,6 +314,8 @@ class SubtractQuantumGate(BasicMathGate): def __init__(self): """ + Initialize a SubtractQuantumGate object. + Initializes the gate to its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ @@ -333,24 +323,22 @@ def __init__(self): def subtract(a, b): # pylint: disable=invalid-name return (a, b - a) - BasicMathGate.__init__(self, subtract) + super().__init__(subtract) def __str__(self): + """Return a string representation of the object.""" return "SubtractQuantum" def __eq__(self, other): + """Equal operator.""" return isinstance(other, SubtractQuantumGate) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def get_inverse(self): - """ - Return the inverse gate (subtraction of the same number a modulo the same number N). - """ + """Return the inverse gate (subtraction of the same number a modulo the same number N).""" return AddQuantum @@ -359,7 +347,8 @@ def get_inverse(self): class ComparatorQuantumGate(BasicMathGate): """ - Flips a compare qubit if the binary value of first imput is higher than the second input. + Flip a compare qubit if the binary value of first imput is higher than the second input. + The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. Example: .. code-block:: python @@ -377,7 +366,9 @@ class ComparatorQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + Initilize a ComparatorQuantumGate object. + + Initialize the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ @@ -390,24 +381,22 @@ def compare(a, b, c): # pylint: disable=invalid-name c = 0 return (a, b, c) - BasicMathGate.__init__(self, compare) + super().__init__(compare) def __str__(self): + """Return a string representation of the object.""" return "Comparator" def __eq__(self, other): + """Equal operator.""" return isinstance(other, ComparatorQuantumGate) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def get_inverse(self): - """ - Return the inverse gate - """ + """Return the inverse of this gate.""" return AddQuantum @@ -416,7 +405,9 @@ def get_inverse(self): class DivideQuantumGate(BasicMathGate): """ - Divides one quantum number from another. Takes three inputs which should be quantum registers of equal size; a + Divide one quantum number from another. + + Takes three inputs which should be quantum registers of equal size; a dividend, a remainder and a divisor. The remainder should be in the state |0...0> and the dividend should be bigger than the divisor.The gate returns (in this order): the remainder, the quotient and the divisor. @@ -441,7 +432,9 @@ class DivideQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + Initialize a DivideQuantumGate object. + + Initialize the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ @@ -452,33 +445,34 @@ def division(dividend, remainder, divisor): quotient = remainder + dividend // divisor return ((dividend - (quotient * divisor)), quotient, divisor) - BasicMathGate.__init__(self, division) + super().__init__(division) def get_inverse(self): + """Return the inverse of this gate.""" return _InverseDivideQuantumGate() def __str__(self): + """Return a string representation of the object.""" return "DivideQuantum" def __eq__(self, other): + """Equal operator.""" return isinstance(other, DivideQuantumGate) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - DivideQuantum = DivideQuantumGate() class _InverseDivideQuantumGate(BasicMathGate): - """ - Internal gate glass to support emulation for inverse division. - """ + """Internal gate glass to support emulation for inverse division.""" def __init__(self): + """Initialize an _InverseDivideQuantumGate object.""" + def inverse_division(remainder, quotient, divisor): if divisor == 0: return (quotient, remainder, divisor) @@ -487,18 +481,20 @@ def inverse_division(remainder, quotient, divisor): remainder = 0 return (dividend, remainder, divisor) - BasicMathGate.__init__(self, inverse_division) + super().__init__(inverse_division) def __str__(self): + """Return a string representation of the object.""" return "_InverseDivideQuantum" class MultiplyQuantumGate(BasicMathGate): """ - Multiplies two quantum numbers represented by a quantum registers. Requires three quantum registers as inputs, - the first two are the numbers to be multiplied and should have the same size (n qubits). The third register will - hold the product and should be of size 2n+1. The numbers are stored from low- to high-bit, i.e., qunum[0] is the - LSB. + Multiply two quantum numbers represented by a quantum registers. + + Requires three quantum registers as inputs, the first two are the numbers to be multiplied and should have the + same size (n qubits). The third register will hold the product and should be of size 2n+1. The numbers are stored + from low- to high-bit, i.e., qunum[0] is the LSB. Example: .. code-block:: python @@ -514,28 +510,31 @@ class MultiplyQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + Initialize a MultiplyQuantumGate object. + + Initialize the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ def multiply(a, b, c): # pylint: disable=invalid-name return (a, b, c + a * b) - BasicMathGate.__init__(self, multiply) + super().__init__(multiply) def __str__(self): + """Return a string representation of the object.""" return "MultiplyQuantum" def __eq__(self, other): + """Equal operator.""" return isinstance(other, MultiplyQuantumGate) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) - def __ne__(self, other): - return not self.__eq__(other) - def get_inverse(self): + """Return the inverse of this gate.""" return _InverseMultiplyQuantumGate() @@ -543,15 +542,16 @@ def get_inverse(self): class _InverseMultiplyQuantumGate(BasicMathGate): - """ - Internal gate glass to support emulation for inverse multiplication. - """ + """Internal gate glass to support emulation for inverse multiplication.""" def __init__(self): + """Initialize an _InverseMultiplyQuantumGate object.""" + def inverse_multiplication(a, b, c): # pylint: disable=invalid-name return (a, b, c - a * b) - BasicMathGate.__init__(self, inverse_multiplication) + super().__init__(inverse_multiplication) def __str__(self): + """Return a string representation of the object.""" return "_InverseMultiplyQuantum" diff --git a/projectq/libs/math/_gates_math_test.py b/projectq/libs/math/_gates_math_test.py index 35b265c8e..5640e7426 100644 --- a/projectq/libs/math/_gates_math_test.py +++ b/projectq/libs/math/_gates_math_test.py @@ -16,29 +16,28 @@ import pytest +import projectq.libs.math +import projectq.setups.decompositions +from projectq.backends import CommandPrinter from projectq.cengines import ( - MainEngine, - TagRemover, AutoReplacer, - InstructionFilter, DecompositionRuleSet, + InstructionFilter, + MainEngine, + TagRemover, ) -from projectq.meta import Control, Compute, Uncompute -from projectq.ops import All, Measure, X, BasicMathGate, ClassicalInstructionGate -import projectq.setups.decompositions +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import All, BasicMathGate, ClassicalInstructionGate, Measure, X -import projectq.libs.math from . import ( AddConstant, AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, MultiplyQuantum, + SubtractQuantum, ) -from projectq.backends import CommandPrinter - def print_all_probabilities(eng, qureg): i = 0 diff --git a/projectq/libs/math/_gates_test.py b/projectq/libs/math/_gates_test.py index 2bc50a816..aa78ebf8e 100755 --- a/projectq/libs/math/_gates_test.py +++ b/projectq/libs/math/_gates_test.py @@ -17,22 +17,22 @@ from projectq.libs.math import ( AddConstant, AddConstantModN, - MultiplyByConstantModN, - SubConstant, - SubConstantModN, AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, + MultiplyByConstantModN, MultiplyQuantum, + SubConstant, + SubConstantModN, + SubtractQuantum, ) from ._gates import ( AddQuantumGate, - SubtractQuantumGate, - MultiplyQuantumGate, - DivideQuantumGate, ComparatorQuantumGate, + DivideQuantumGate, + MultiplyQuantumGate, + SubtractQuantumGate, ) diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py index bb86329fd..8b66fbb69 100644 --- a/projectq/libs/math/_quantummath.py +++ b/projectq/libs/math/_quantummath.py @@ -13,16 +13,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Definition of some mathematical quantum operations""" +"""Definition of some mathematical quantum operations.""" -from projectq.ops import All, X, CNOT from projectq.meta import Control +from projectq.ops import CNOT, All, X + from ._gates import AddQuantum, SubtractQuantum def add_quantum(eng, quint_a, quint_b, carry=None): """ - Adds two quantum integers, i.e., + Add two quantum integers. + + i.e., |a0...a(n-1)>|b(0)...b(n-1)>|c> -> |a0...a(n-1)>|b+a(0)...b+a(n)> @@ -82,7 +85,9 @@ def add_quantum(eng, quint_a, quint_b, carry=None): def subtract_quantum(eng, quint_a, quint_b): """ - Subtracts two quantum integers, i.e., + Subtract two quantum integers. + + i.e., |a>|b> -> |a>|b-a> @@ -140,7 +145,7 @@ def subtract_quantum(eng, quint_a, quint_b): def inverse_add_quantum_carry(eng, quint_a, quint_b): """ - Inverse of quantum addition with carry + Inverse of quantum addition with carry. Args: eng (MainEngine): ProjectQ MainEngine @@ -164,7 +169,9 @@ def inverse_add_quantum_carry(eng, quint_a, quint_b): def comparator(eng, quint_a, quint_b, comp): """ - Compares the size of two quantum integers, i.e, + Compare the size of two quantum integers. + + i.e, if a>b: |a>|b>|c> -> |a>|b>|c+1> @@ -227,7 +234,9 @@ def comparator(eng, quint_a, quint_b, comp): def quantum_conditional_add(eng, quint_a, quint_b, conditional): """ - Adds up two quantum integers if conditional is high, i.e., + Add up two quantum integers if conditional is high. + + i.e., |a>|b>|c> -> |a>|b+a>|c> (without a carry out qubit) @@ -285,7 +294,9 @@ def quantum_conditional_add(eng, quint_a, quint_b, conditional): def quantum_division(eng, dividend, remainder, divisor): """ - Performs restoring integer division, i.e., + Perform restoring integer division. + + i.e., |dividend>|remainder>|divisor> -> |remainder>|quotient>|divisor> @@ -340,7 +351,9 @@ def quantum_division(eng, dividend, remainder, divisor): def inverse_quantum_division(eng, remainder, quotient, divisor): """ - Performs the inverse of a restoring integer division, i.e., + Perform the inverse of a restoring integer division. + + i.e., |remainder>|quotient>|divisor> -> |dividend>|remainder(0)>|divisor> @@ -373,7 +386,9 @@ def inverse_quantum_division(eng, remainder, quotient, divisor): def quantum_conditional_add_carry(eng, quint_a, quint_b, ctrl, z): # pylint: disable=invalid-name """ - Adds up two quantum integers if the control qubit is |1>, i.e., + Add up two quantum integers if the control qubit is |1>. + + i.e., |a>|b>|ctrl>|z(0)z(1)> -> |a>|s(0)...s(n-1)>|ctrl>|s(n)z(1)> (where s denotes the sum of a and b) @@ -448,7 +463,9 @@ def quantum_conditional_add_carry(eng, quint_a, quint_b, ctrl, z): # pylint: di def quantum_multiplication(eng, quint_a, quint_b, product): """ - Multiplies two quantum integers, i.e, + Multiplies two quantum integers. + + i.e, |a>|b>|0> -> |a>|b>|a*b> @@ -498,7 +515,9 @@ def quantum_multiplication(eng, quint_a, quint_b, product): def inverse_quantum_multiplication(eng, quint_a, quint_b, product): """ - Inverse of the multiplication of two quantum integers, i.e, + Inverse of the multiplication of two quantum integers. + + i.e, |a>|b>|a*b> -> |a>|b>|0> diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py index c51fd81b6..bd520f668 100644 --- a/projectq/libs/math/_quantummath_test.py +++ b/projectq/libs/math/_quantummath_test.py @@ -15,23 +15,20 @@ import pytest +import projectq.libs.math from projectq import MainEngine -from projectq.cengines import InstructionFilter, AutoReplacer, DecompositionRuleSet from projectq.backends import Simulator -from projectq.ops import All, BasicMathGate, ClassicalInstructionGate, Measure, X - -from projectq.setups.decompositions import swap2cnot - -import projectq.libs.math +from projectq.cengines import AutoReplacer, DecompositionRuleSet, InstructionFilter from projectq.libs.math import ( AddQuantum, - SubtractQuantum, ComparatorQuantum, DivideQuantum, MultiplyQuantum, + SubtractQuantum, ) - -from projectq.meta import Control, Compute, Uncompute, Dagger +from projectq.meta import Compute, Control, Dagger, Uncompute +from projectq.ops import All, BasicMathGate, ClassicalInstructionGate, Measure, X +from projectq.setups.decompositions import swap2cnot def print_all_probabilities(eng, qureg): diff --git a/projectq/libs/revkit/__init__.py b/projectq/libs/revkit/__init__.py index 8cbf8bc17..a411ab55d 100644 --- a/projectq/libs/revkit/__init__.py +++ b/projectq/libs/revkit/__init__.py @@ -15,6 +15,6 @@ """Module containing code to interface with RevKit""" -from ._permutation import PermutationOracle from ._control_function import ControlFunctionOracle +from ._permutation import PermutationOracle from ._phase import PhaseOracle diff --git a/projectq/libs/revkit/_control_function.py b/projectq/libs/revkit/_control_function.py index d80613cf8..341cb5f9f 100644 --- a/projectq/libs/revkit/_control_function.py +++ b/projectq/libs/revkit/_control_function.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""RevKit support for control function oracles""" +"""RevKit support for control function oracles.""" from projectq.ops import BasicGate @@ -23,7 +23,7 @@ class ControlFunctionOracle: # pylint: disable=too-few-public-methods """ - Synthesizes a negation controlled by an arbitrary control function. + Synthesize a negation controlled by an arbitrary control function. This creates a circuit for a NOT gate which is controlled by an arbitrary Boolean control function. The control function is provided as integer @@ -32,7 +32,6 @@ class ControlFunctionOracle: # pylint: disable=too-few-public-methods the value for function can be, e.g., ``0b11101000``, ``0xe8``, or ``232``. Example: - This example creates a circuit that causes to invert qubit ``d``, the majority-of-three function evaluates to true for the control qubits ``a``, ``b``, and ``c``. @@ -44,7 +43,7 @@ class ControlFunctionOracle: # pylint: disable=too-few-public-methods def __init__(self, function, **kwargs): """ - Initializes a control function oracle. + Initialize a control function oracle. Args: function (int): Function truth table. @@ -75,7 +74,7 @@ def __init__(self, function, **kwargs): def __or__(self, qubits): """ - Applies control function to qubits (and synthesizes circuit). + Apply control function to qubits (and synthesizes circuit). Args: qubits (tuple): Qubits to which the control function is @@ -117,9 +116,7 @@ def __or__(self, qubits): _exec(revkit.to_projectq(mct=True), qs) def _check_function(self): - """ - Checks whether function is valid. - """ + """Check whether function is valid.""" # function must be positive. We check in __or__ whether function is # too large if self.function < 0: diff --git a/projectq/libs/revkit/_control_function_test.py b/projectq/libs/revkit/_control_function_test.py index 9c547a583..3ea58c58a 100644 --- a/projectq/libs/revkit/_control_function_test.py +++ b/projectq/libs/revkit/_control_function_test.py @@ -18,7 +18,6 @@ from projectq import MainEngine from projectq.cengines import DummyEngine - from projectq.libs.revkit import ControlFunctionOracle # run this test only if RevKit Python module can be loaded diff --git a/projectq/libs/revkit/_permutation.py b/projectq/libs/revkit/_permutation.py index 71384f828..b5bc522b1 100644 --- a/projectq/libs/revkit/_permutation.py +++ b/projectq/libs/revkit/_permutation.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""RevKit support for permutation oracles""" +"""RevKit support for permutation oracles.""" from projectq.ops import BasicGate @@ -22,7 +22,7 @@ class PermutationOracle: # pylint: disable=too-few-public-methods """ - Synthesizes a permutation using RevKit. + Synthesize a permutation using RevKit. Given a permutation over `2**q` elements (starting from 0), this class helps to automatically find a reversible circuit over `q` qubits that @@ -36,16 +36,15 @@ class PermutationOracle: # pylint: disable=too-few-public-methods def __init__(self, permutation, **kwargs): """ - Initializes a permutation oracle. + Initialize a permutation oracle. Args: permutation (list): Permutation (starting from 0). Keyword Args: - synth: A RevKit synthesis command which creates a reversible - circuit based on a reversible truth table (e.g., - ``revkit.tbs`` or ``revkit.dbs``). Can also be a - nullary lambda that calls several RevKit commands. + synth: A RevKit synthesis command which creates a reversible circuit based on a reversible truth table + (e.g., ``revkit.tbs`` or ``revkit.dbs``). Can also be a nullary lambda that calls several RevKit + commands. **Default:** ``revkit.tbs`` """ self.permutation = permutation @@ -55,11 +54,10 @@ def __init__(self, permutation, **kwargs): def __or__(self, qubits): """ - Applies permutation to qubits (and synthesizes circuit). + Apply permutation to qubits (and synthesizes circuit). Args: - qubits (tuple): Qubits to which the permutation is being - applied. + qubits (tuple): Qubits to which the permutation is being applied. """ try: import revkit # pylint: disable=import-outside-toplevel @@ -88,10 +86,8 @@ def __or__(self, qubits): _exec(revkit.to_projectq(mct=True), qs) def _check_permutation(self): - """ - Checks whether permutation is valid. - """ + """Check whether permutation is valid.""" # permutation must start from 0, has no duplicates and all elements are # consecutive - if sorted(list(set(self.permutation))) != list(range(len(self.permutation))): + if sorted(set(self.permutation)) != list(range(len(self.permutation))): raise AttributeError("Invalid permutation (does it start from 0?)") diff --git a/projectq/libs/revkit/_permutation_test.py b/projectq/libs/revkit/_permutation_test.py index 57c92721a..06c9e0d33 100644 --- a/projectq/libs/revkit/_permutation_test.py +++ b/projectq/libs/revkit/_permutation_test.py @@ -18,7 +18,6 @@ from projectq import MainEngine from projectq.cengines import DummyEngine - from projectq.libs.revkit import PermutationOracle # run this test only if RevKit Python module can be loaded diff --git a/projectq/libs/revkit/_phase.py b/projectq/libs/revkit/_phase.py index d50539d92..edfc0afef 100644 --- a/projectq/libs/revkit/_phase.py +++ b/projectq/libs/revkit/_phase.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""RevKit support for phase oracles""" +"""RevKit support for phase oracles.""" from projectq.ops import BasicGate @@ -22,23 +22,19 @@ class PhaseOracle: # pylint: disable=too-few-public-methods """ - Synthesizes phase circuit from an arbitrary Boolean function. + Synthesize phase circuit from an arbitrary Boolean function. - This creates a phase circuit from a Boolean function. It inverts the phase - of all amplitudes for which the function evaluates to 1. The Boolean - function is provided as integer representation of the function's truth - table in binary notation. For example, for the majority-of-three function, - which truth table 11101000, the value for function can be, e.g., - ``0b11101000``, ``0xe8``, or ``232``. + This creates a phase circuit from a Boolean function. It inverts the phase of all amplitudes for which the + function evaluates to 1. The Boolean function is provided as integer representation of the function's truth table + in binary notation. For example, for the majority-of-three function, which truth table 11101000, the value for + function can be, e.g., ``0b11101000``, ``0xe8``, or ``232``. - Note that a phase circuit can only accurately be found for a normal - function, i.e., a function that maps the input pattern 0, 0, ..., 0 to 0. - The circuits for a function and its inverse are the same. + Note that a phase circuit can only accurately be found for a normal function, i.e., a function that maps the input + pattern 0, 0, ..., 0 to 0. The circuits for a function and its inverse are the same. Example: - - This example creates a phase circuit based on the majority-of-three - function on qubits ``a``, ``b``, and ``c``. + This example creates a phase circuit based on the majority-of-three function on qubits ``a``, ``b``, and + ``c``. .. code-block:: python @@ -47,16 +43,15 @@ class PhaseOracle: # pylint: disable=too-few-public-methods def __init__(self, function, **kwargs): """ - Initializes a phase oracle. + Initialize a phase oracle. Args: function (int): Function truth table. Keyword Args: - synth: A RevKit synthesis command which creates a reversible - circuit based on a truth table and requires no additional - ancillae (e.g., ``revkit.esopps``). Can also be a nullary - lambda that calls several RevKit commands. + synth: A RevKit synthesis command which creates a reversible circuit based on a truth table and requires + no additional ancillae (e.g., ``revkit.esopps``). Can also be a nullary lambda that calls several + RevKit commands. **Default:** ``revkit.esopps`` """ if isinstance(function, int): @@ -78,11 +73,10 @@ def __init__(self, function, **kwargs): def __or__(self, qubits): """ - Applies phase circuit to qubits (and synthesizes circuit). + Apply phase circuit to qubits (and synthesizes circuit). Args: - qubits (tuple): Qubits to which the phase circuit is being - applied. + qubits (tuple): Qubits to which the phase circuit is being applied. """ try: import revkit # pylint: disable=import-outside-toplevel @@ -118,9 +112,7 @@ def __or__(self, qubits): _exec(revkit.to_projectq(mct=True), qs) def _check_function(self): - """ - Checks whether function is valid. - """ + """Check whether function is valid.""" # function must be positive. We check in __or__ whether function is # too large if self.function < 0: diff --git a/projectq/libs/revkit/_phase_test.py b/projectq/libs/revkit/_phase_test.py index aee988d11..5c06a9162 100644 --- a/projectq/libs/revkit/_phase_test.py +++ b/projectq/libs/revkit/_phase_test.py @@ -14,16 +14,14 @@ # limitations under the License. """Tests for libs.revkit._phase.""" +import numpy as np import pytest from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import DummyEngine -from projectq.ops import All, H, Measure - from projectq.libs.revkit import PhaseOracle - -import numpy as np +from projectq.ops import All, H, Measure # run this test only if RevKit Python module can be loaded revkit = pytest.importorskip('revkit') diff --git a/projectq/libs/revkit/_utils.py b/projectq/libs/revkit/_utils.py index 573451bf6..7fd44ca9a 100644 --- a/projectq/libs/revkit/_utils.py +++ b/projectq/libs/revkit/_utils.py @@ -28,6 +28,6 @@ def _exec(code, qs): code (string): ProjectQ code. qubits (tuple): Qubits to which the permutation is being applied. """ - from projectq.ops import C, X, Z, All + from projectq.ops import All, C, X, Z exec(code) diff --git a/projectq/meta/__init__.py b/projectq/meta/__init__.py index 27c20b835..634b046c7 100755 --- a/projectq/meta/__init__.py +++ b/projectq/meta/__init__.py @@ -22,10 +22,15 @@ * Dagger (with Dagger(eng): ...) """ -from ._dirtyqubit import DirtyQubitTag -from ._loop import LoopTag, Loop -from ._compute import Compute, Uncompute, CustomUncompute, ComputeTag, UncomputeTag -from ._control import Control, get_control_count, has_negative_control, canonical_ctrl_state +from ._compute import Compute, ComputeTag, CustomUncompute, Uncompute, UncomputeTag +from ._control import ( + Control, + canonical_ctrl_state, + get_control_count, + has_negative_control, +) from ._dagger import Dagger -from ._util import insert_engine, drop_engine_after +from ._dirtyqubit import DirtyQubitTag from ._logicalqubit import LogicalQubitIDTag +from ._loop import Loop, LoopTag +from ._util import drop_engine_after, insert_engine diff --git a/projectq/meta/_compute.py b/projectq/meta/_compute.py index f09eb8514..dcf7932ab 100755 --- a/projectq/meta/_compute.py +++ b/projectq/meta/_compute.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Compute, Uncompute, CustomUncompute. +Definition of Compute, Uncompute and CustomUncompute. Contains Compute, Uncompute, and CustomUncompute classes which can be used to annotate Compute / Action / Uncompute sections, facilitating the conditioning of the entire operation on the value of a qubit / register (only Action needs @@ -25,42 +25,29 @@ from projectq.cengines import BasicEngine, CommandModifier from projectq.ops import Allocate, Deallocate -from ._util import insert_engine, drop_engine_after - - -class QubitManagementError(Exception): - """Exception raised when the lifetime of a qubit is problematic within a loop""" +from ._exceptions import QubitManagementError +from ._util import drop_engine_after, insert_engine class NoComputeSectionError(Exception): - """ - Exception raised if uncompute is called but no compute section found. - """ + """Exception raised if uncompute is called but no compute section found.""" -class ComputeTag: - """ - Compute meta tag. - """ +class ComputeTag: # pylint: disable=too-few-public-methods + """Compute meta tag.""" def __eq__(self, other): + """Equal operator.""" return isinstance(other, ComputeTag) - def __ne__(self, other): - return not self.__eq__(other) - -class UncomputeTag: - """ - Uncompute meta tag. - """ +class UncomputeTag: # pylint: disable=too-few-public-methods + """Uncompute meta tag.""" def __eq__(self, other): + """Equal operator.""" return isinstance(other, UncomputeTag) - def __ne__(self, other): - return not self.__eq__(other) - def _add_uncompute_tag(cmd): """ @@ -74,16 +61,11 @@ def _add_uncompute_tag(cmd): class ComputeEngine(BasicEngine): - """ - Adds Compute-tags to all commands and stores them (to later uncompute them - automatically) - """ + """Add Compute-tags to all commands and stores them (to later uncompute them automatically).""" def __init__(self): - """ - Initialize a ComputeEngine. - """ - BasicEngine.__init__(self) + """Initialize a ComputeEngine.""" + super().__init__() self._l = [] self._compute = True # Save all qubit ids from qubits which are created or destroyed. @@ -99,7 +81,6 @@ def run_uncompute(self): # pylint: disable=too-many-branches,too-many-statement during uncompute. If a qubit has been allocated and deallocated during compute, then a new qubit is allocated and deallocated during uncompute. """ - # No qubits allocated during Compute section -> do standard uncompute if len(self._allocated_qubit_ids) == 0: self.send([_add_uncompute_tag(cmd.get_inverse()) for cmd in reversed(self._l)]) @@ -133,7 +114,7 @@ def run_uncompute(self): # pylint: disable=too-many-branches,too-many-statement return # There was at least one qubit allocated and deallocated within # compute section. Handle uncompute in most general case - new_local_id = dict() + new_local_id = {} for cmd in reversed(self._l): if cmd.gate == Deallocate: if not cmd.qubits[0][0].id in ids_local_to_compute: # pragma: no cover @@ -217,6 +198,8 @@ def end_compute(self): def receive(self, command_list): """ + Receive a list of commands. + If in compute-mode, receive commands and store deepcopy of each cmd. Add ComputeTag to received cmd and send it on. Otherwise, send all received commands directly to next_engine. @@ -238,21 +221,19 @@ def receive(self, command_list): class UncomputeEngine(BasicEngine): - """ - Adds Uncompute-tags to all commands. - """ + """Adds Uncompute-tags to all commands.""" def __init__(self): - """ - Initialize a UncomputeEngine. - """ - BasicEngine.__init__(self) + """Initialize a UncomputeEngine.""" + super().__init__() # Save all qubit ids from qubits which are created or destroyed. self._allocated_qubit_ids = set() self._deallocated_qubit_ids = set() def receive(self, command_list): """ + Receive a list of commands. + Receive commands and add an UncomputeTag to their tags. Args: @@ -328,10 +309,12 @@ def __init__(self, engine): self._compute_eng = None def __enter__(self): + """Context manager enter function.""" self._compute_eng = ComputeEngine() insert_engine(self.engine, self._compute_eng) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" # notify ComputeEngine that the compute section is done self._compute_eng.end_compute() self._compute_eng = None @@ -369,6 +352,7 @@ def __init__(self, engine): self._uncompute_eng = None def __enter__(self): + """Context manager enter function.""" # first, remove the compute engine compute_eng = self.engine.next_engine if not isinstance(compute_eng, ComputeEngine): @@ -386,6 +370,7 @@ def __enter__(self): insert_engine(self.engine, self._uncompute_eng) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" # If an error happens in this context, qubits might not have been # deallocated because that code section was not yet executed, # so don't check and raise an additional error. diff --git a/projectq/meta/_compute_test.py b/projectq/meta/_compute_test.py index 66eec753d..1a87d413f 100755 --- a/projectq/meta/_compute_test.py +++ b/projectq/meta/_compute_test.py @@ -14,16 +14,15 @@ # limitations under the License. """Tests for projectq.meta._compute.py""" -import pytest import types import weakref -from projectq import MainEngine -from projectq.cengines import DummyEngine, CompareEngine -from projectq.ops import H, Rx, Ry, Deallocate, Allocate, CNOT, NOT, FlushGate -from projectq.meta import DirtyQubitTag +import pytest -from projectq.meta import _compute +from projectq import MainEngine +from projectq.cengines import CompareEngine, DummyEngine +from projectq.meta import DirtyQubitTag, _compute +from projectq.ops import CNOT, NOT, Allocate, Deallocate, FlushGate, H, Rx, Ry def test_compute_tag(): diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 97a25af90..9fa0803d8 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -28,12 +28,12 @@ from projectq.types import BasicQubit from ._compute import ComputeTag, UncomputeTag -from ._util import insert_engine, drop_engine_after +from ._util import drop_engine_after, insert_engine def canonical_ctrl_state(ctrl_state, num_qubits): """ - Return canonical form for control state + Return canonical form for control state. Args: ctrl_state (int,str,CtrlAll): Initial control state representation @@ -100,9 +100,7 @@ def _has_compute_uncompute_tag(cmd): class ControlEngine(BasicEngine): - """ - Adds control qubits to all commands that have no compute / uncompute tags. - """ + """Add control qubits to all commands that have no compute / uncompute tags.""" def __init__(self, qubits, ctrl_state=CtrlAll.One): """ @@ -112,7 +110,7 @@ def __init__(self, qubits, ctrl_state=CtrlAll.One): qubits (list of Qubit objects): qubits conditional on which the following operations are executed. """ - BasicEngine.__init__(self) + super().__init__() self._qubits = qubits self._state = ctrl_state @@ -122,7 +120,7 @@ def _handle_command(self, cmd): self.send([cmd]) def receive(self, command_list): - """Forward all commands to the next engine.""" + """Receive a list of commands.""" for cmd in command_list: self._handle_command(cmd) @@ -162,25 +160,23 @@ def __init__(self, engine, qubits, ctrl_state=CtrlAll.One): self._state = canonical_ctrl_state(ctrl_state, len(self._qubits)) def __enter__(self): + """Context manager enter function.""" if len(self._qubits) > 0: engine = ControlEngine(self._qubits, self._state) insert_engine(self.engine, engine) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" # remove control handler from engine list (i.e. skip it) if len(self._qubits) > 0: drop_engine_after(self.engine) def get_control_count(cmd): - """ - Return the number of control qubits of the command object cmd - """ + """Return the number of control qubits of the command object cmd.""" return len(cmd.control_qubits) def has_negative_control(cmd): - """ - Returns whether a command has negatively controlled qubits - """ + """Return whether a command has negatively controlled qubits.""" return get_control_count(cmd) > 0 and '0' in cmd.control_state diff --git a/projectq/meta/_control_test.py b/projectq/meta/_control_test.py index eadadad34..5e3618cfb 100755 --- a/projectq/meta/_control_test.py +++ b/projectq/meta/_control_test.py @@ -17,10 +17,15 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import Command, H, Rx, CtrlAll, X, IncompatibleControlState -from projectq.meta import DirtyQubitTag, ComputeTag, UncomputeTag, Compute, Uncompute - -from projectq.meta import _control +from projectq.meta import ( + Compute, + ComputeTag, + DirtyQubitTag, + Uncompute, + UncomputeTag, + _control, +) +from projectq.ops import Command, CtrlAll, H, IncompatibleControlState, Rx, X from projectq.types import WeakQubitRef diff --git a/projectq/meta/_dagger.py b/projectq/meta/_dagger.py index 86d28857c..36ead9f48 100755 --- a/projectq/meta/_dagger.py +++ b/projectq/meta/_dagger.py @@ -24,29 +24,23 @@ from projectq.cengines import BasicEngine from projectq.ops import Allocate, Deallocate -from ._util import insert_engine, drop_engine_after - -class QubitManagementError(Exception): - """Exception raised when the lifetime of a qubit is problematic within a loop""" +from ._exceptions import QubitManagementError +from ._util import drop_engine_after, insert_engine class DaggerEngine(BasicEngine): - """ - Stores all commands and, when done, inverts the circuit & runs it. - """ + """Store all commands and, when done, inverts the circuit & runs it.""" def __init__(self): - BasicEngine.__init__(self) + """Initialize a DaggerEngine object.""" + super().__init__() self._commands = [] self._allocated_qubit_ids = set() self._deallocated_qubit_ids = set() def run(self): - """ - Run the stored circuit in reverse and check that local qubits - have been deallocated. - """ + """Run the stored circuit in reverse and check that local qubits have been deallocated.""" if self._deallocated_qubit_ids != self._allocated_qubit_ids: raise QubitManagementError( "\n Error. Qubits have been allocated in 'with " @@ -129,10 +123,12 @@ def __init__(self, engine): self._dagger_eng = None def __enter__(self): + """Context manager enter function.""" self._dagger_eng = DaggerEngine() insert_engine(self.engine, self._dagger_eng) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" # If an error happens in this context, qubits might not have been # deallocated because that code section was not yet executed, # so don't check and raise an additional error. diff --git a/projectq/meta/_dagger_test.py b/projectq/meta/_dagger_test.py index a91e51cb4..a95beeda1 100755 --- a/projectq/meta/_dagger_test.py +++ b/projectq/meta/_dagger_test.py @@ -14,15 +14,14 @@ # limitations under the License. """Tests for projectq.meta._dagger.py""" -import pytest import types +import pytest + from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import CNOT, X, Rx, H, Allocate, Deallocate -from projectq.meta import DirtyQubitTag - -from projectq.meta import _dagger +from projectq.meta import DirtyQubitTag, _dagger +from projectq.ops import CNOT, Allocate, Deallocate, H, Rx, X def test_dagger_with_dirty_qubits(): diff --git a/projectq/meta/_dirtyqubit.py b/projectq/meta/_dirtyqubit.py index a26cc574a..3e9ba9833 100755 --- a/projectq/meta/_dirtyqubit.py +++ b/projectq/meta/_dirtyqubit.py @@ -12,18 +12,13 @@ # 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. -""" -Defines the DirtyQubitTag meta tag. -""" +"""Define the DirtyQubitTag meta tag.""" -class DirtyQubitTag: - """ - Dirty qubit meta tag - """ + +class DirtyQubitTag: # pylint: disable=too-few-public-methods + """Dirty qubit meta tag.""" def __eq__(self, other): + """Equal operator.""" return isinstance(other, DirtyQubitTag) - - def __ne__(self, other): - return not self.__eq__(other) diff --git a/projectq/meta/_dirtyqubit_test.py b/projectq/meta/_dirtyqubit_test.py index 3cfe437a5..74e4d94f3 100755 --- a/projectq/meta/_dirtyqubit_test.py +++ b/projectq/meta/_dirtyqubit_test.py @@ -14,9 +14,7 @@ # limitations under the License. """Tests for projectq.meta._dirtyqubit.py""" -from projectq.meta import ComputeTag - -from projectq.meta import _dirtyqubit +from projectq.meta import ComputeTag, _dirtyqubit def test_dirty_qubit_tag(): diff --git a/projectq/meta/_exceptions.py b/projectq/meta/_exceptions.py new file mode 100644 index 000000000..2f12ff361 --- /dev/null +++ b/projectq/meta/_exceptions.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +"""Exception classes for projectq.meta.""" + + +class QubitManagementError(Exception): + """ + Exception raised when the lifetime of a qubit is problematic within a context manager. + + This may occur within Loop, Dagger or Compute regions. + """ diff --git a/projectq/meta/_logicalqubit.py b/projectq/meta/_logicalqubit.py index 218456bae..2c6295235 100644 --- a/projectq/meta/_logicalqubit.py +++ b/projectq/meta/_logicalqubit.py @@ -12,12 +12,11 @@ # 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. -""" -Defines LogicalQubitIDTag to annotate a MeasureGate for mapped qubits. -""" +"""Definition of LogicalQubitIDTag to annotate a MeasureGate for mapped qubits.""" -class LogicalQubitIDTag: + +class LogicalQubitIDTag: # pylint: disable=too-few-public-methods """ LogicalQubitIDTag for a mapped qubit to annotate a MeasureGate. @@ -26,10 +25,9 @@ class LogicalQubitIDTag: """ def __init__(self, logical_qubit_id): + """Initialize a LogicalQubitIDTag object.""" self.logical_qubit_id = logical_qubit_id def __eq__(self, other): + """Equal operator.""" return isinstance(other, LogicalQubitIDTag) and self.logical_qubit_id == other.logical_qubit_id - - def __ne__(self, other): - return not self.__eq__(other) diff --git a/projectq/meta/_loop.py b/projectq/meta/_loop.py index f563bb2f0..cc64034ca 100755 --- a/projectq/meta/_loop.py +++ b/projectq/meta/_loop.py @@ -27,37 +27,33 @@ from projectq.cengines import BasicEngine from projectq.ops import Allocate, Deallocate -from ._util import insert_engine, drop_engine_after +from ._exceptions import QubitManagementError +from ._util import drop_engine_after, insert_engine -class QubitManagementError(Exception): - """Exception raised when the lifetime of a qubit is problematic within a loop""" - -class LoopTag: - """ - Loop meta tag - """ +class LoopTag: # pylint: disable=too-few-public-methods + """Loop meta tag.""" def __init__(self, num): + """Initialize a LoopTag object.""" self.num = num self.id = LoopTag.loop_tag_id LoopTag.loop_tag_id += 1 def __eq__(self, other): + """Equal operator.""" return isinstance(other, LoopTag) and self.id == other.id and self.num == other.num - def __ne__(self, other): - return not self.__eq__(other) - loop_tag_id = 0 class LoopEngine(BasicEngine): """ - Stores all commands and, when done, executes them num times if no loop tag - handler engine is available. - If there is one, it adds a loop_tag to the commands and sends them on. + A compiler engine to represent executing part of the code multiple times. + + Stores all commands and, when done, executes them num times if no loop tag handler engine is available. If there + is one, it adds a loop_tag to the commands and sends them on. """ def __init__(self, num): @@ -67,7 +63,7 @@ def __init__(self, num): Args: num (int): Number of loop iterations. """ - BasicEngine.__init__(self) + super().__init__() self._tag = LoopTag(num) self._cmd_list = [] self._allocated_qubit_ids = set() @@ -76,7 +72,7 @@ def __init__(self, num): # and deallocated within the loop body. # value: list contain reference to each weakref qubit with this qubit # id either within control_qubits or qubits. - self._refs_to_local_qb = dict() + self._refs_to_local_qb = {} self._next_engines_support_loop_tag = False def run(self): @@ -245,11 +241,13 @@ def __init__(self, engine, num): self._loop_eng = None def __enter__(self): + """Context manager enter function.""" if self.num != 1: self._loop_eng = LoopEngine(self.num) insert_engine(self.engine, self._loop_eng) def __exit__(self, exc_type, exc_value, exc_traceback): + """Context manager exit function.""" if self.num != 1: # remove loop handler from engine list (i.e. skip it) self._loop_eng.run() diff --git a/projectq/meta/_loop_test.py b/projectq/meta/_loop_test.py index 3785f5891..618c5f16a 100755 --- a/projectq/meta/_loop_test.py +++ b/projectq/meta/_loop_test.py @@ -14,16 +14,15 @@ # limitations under the License. """Tests for projectq.meta._loop.py""" -import pytest import types - from copy import deepcopy + +import pytest + from projectq import MainEngine -from projectq.meta import ComputeTag from projectq.cengines import DummyEngine -from projectq.ops import H, CNOT, X, FlushGate, Allocate, Deallocate - -from projectq.meta import _loop +from projectq.meta import ComputeTag, _loop +from projectq.ops import CNOT, Allocate, Deallocate, FlushGate, H, X def test_loop_tag(): diff --git a/projectq/meta/_util.py b/projectq/meta/_util.py index 4a5300eb2..414930f50 100755 --- a/projectq/meta/_util.py +++ b/projectq/meta/_util.py @@ -13,22 +13,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -Tools to add/remove compiler engines to the MainEngine list -""" +"""Tools to add/remove compiler engines to the MainEngine list.""" def insert_engine(prev_engine, engine_to_insert): """ - Inserts an engine into the singly-linked list of engines. + Insert an engine into the singly-linked list of engines. It also sets the correct main_engine for engine_to_insert. Args: - prev_engine (projectq.cengines.BasicEngine): - The engine just before the insertion point. - engine_to_insert (projectq.cengines.BasicEngine): - The engine to insert at the insertion point. + prev_engine (projectq.cengines.BasicEngine): The engine just before the insertion point. + engine_to_insert (projectq.cengines.BasicEngine): The engine to insert at the insertion point. """ if prev_engine.main_engine is not None: prev_engine.main_engine.n_engines += 1 @@ -43,11 +39,11 @@ def insert_engine(prev_engine, engine_to_insert): def drop_engine_after(prev_engine): """ - Removes an engine from the singly-linked list of engines. + Remove an engine from the singly-linked list of engines. Args: - prev_engine (projectq.cengines.BasicEngine): - The engine just before the engine to drop. + prev_engine (projectq.cengines.BasicEngine): The engine just before the engine to drop. + Returns: Engine: The dropped engine. """ diff --git a/projectq/meta/_util_test.py b/projectq/meta/_util_test.py index a010d1b81..90b781b53 100755 --- a/projectq/meta/_util_test.py +++ b/projectq/meta/_util_test.py @@ -18,7 +18,6 @@ from projectq import MainEngine from projectq.cengines import DummyEngine - from . import _util diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 81b3313ac..67b570263 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -16,25 +16,33 @@ """ProjectQ module containing all basic gates (operations)""" from ._basics import ( - NotMergeable, - NotInvertible, BasicGate, - MatrixGate, - SelfInverseGate, + BasicMathGate, + BasicPhaseGate, BasicRotationGate, ClassicalInstructionGate, FastForwardingGate, - BasicMathGate, - BasicPhaseGate, + MatrixGate, + NotInvertible, + NotMergeable, + SelfInverseGate, ) -from ._command import apply_command, Command, CtrlAll, IncompatibleControlState -from ._metagates import DaggeredGate, get_inverse, is_identity, ControlledGate, C, Tensor, All +from ._command import Command, CtrlAll, IncompatibleControlState, apply_command from ._gates import * +from ._metagates import ( + All, + C, + ControlledGate, + DaggeredGate, + Tensor, + get_inverse, + is_identity, +) +from ._qaagate import QAA from ._qftgate import QFT, QFTGate +from ._qpegate import QPE from ._qubit_operator import QubitOperator from ._shortcuts import * +from ._state_prep import StatePreparation from ._time_evolution import TimeEvolution from ._uniformly_controlled_rotation import UniformlyControlledRy, UniformlyControlledRz -from ._state_prep import StatePreparation -from ._qpegate import QPE -from ._qaagate import QAA diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 6c9943c78..6cff8c18d 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -13,9 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Defines the BasicGate class, the base class of all gates, the -BasicRotationGate class, the SelfInverseGate, the FastForwardingGate, the -ClassicalInstruction gate, and the BasicMathGate class. +Definitions of some of the most basic quantum gates. + +Defines the BasicGate class, the base class of all gates, the BasicRotationGate class, the SelfInverseGate, the +FastForwardingGate, the ClassicalInstruction gate, and the BasicMathGate class. Gates overload the | operator to allow the following syntax: @@ -27,19 +28,19 @@ Gate | qubit Gate | (qubit,) -This means that for more than one quantum argument (right side of | ), a tuple -needs to be made explicitely, while for one argument it is optional. +This means that for more than one quantum argument (right side of | ), a tuple needs to be made explicitely, while for +one argument it is optional. """ -from copy import deepcopy import math import unicodedata +from copy import deepcopy import numpy as np from projectq.types import BasicQubit -from ._command import Command, apply_command +from ._command import Command, apply_command ANGLE_PRECISION = 12 ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION @@ -49,21 +50,22 @@ class NotMergeable(Exception): """ - Exception thrown when trying to merge two gates which are not mergeable (or where it is not implemented (yet)). + Exception thrown when trying to merge two gates which are not mergeable. + + This exception is also thrown if the merging is not implemented (yet)). """ class NotInvertible(Exception): """ - Exception thrown when trying to invert a gate which is not invertable (or where the inverse is not implemented - (yet)). + Exception thrown when trying to invert a gate which is not invertable. + + This exception is also thrown if the inverse is not implemented (yet). """ class BasicGate: - """ - Base class of all gates. (Don't use it directly but derive from it) - """ + """Base class of all gates. (Don't use it directly but derive from it).""" def __init__(self): """ @@ -164,8 +166,9 @@ def make_tuple_of_qureg(qubits): def generate_command(self, qubits): """ - Helper function to generate a command consisting of the gate and - the qubits being acted upon. + Generate a command. + + The command object created consists of the gate and the qubits being acted upon. Args: qubits: see BasicGate.make_tuple_of_qureg(qubits) @@ -201,7 +204,7 @@ def __or__(self, qubits): def __eq__(self, other): """ - Equality comparision + Equal operator. Return True if instance of the same class, unless other is an instance of :class:MatrixGate, in which case equality is to be checked by testing for existence and (approximate) equality of matrix representations. @@ -212,40 +215,36 @@ def __eq__(self, other): return NotImplemented return False - def __ne__(self, other): - return not self.__eq__(other) - def __str__(self): + """Return a string representation of the object.""" raise NotImplementedError('This gate does not implement __str__.') def to_string(self, symbols): # pylint: disable=unused-argument """ - String representation + Return a string representation of the object. Achieve same function as str() but can be extended for configurable representation """ return str(self) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) def is_identity(self): # pylint: disable=no-self-use - """ - Return True if the gate is an identity gate. In this base class, always returns False. - """ + """Return True if the gate is an identity gate. In this base class, always returns False.""" return False class MatrixGate(BasicGate): """ - Defines a gate class whose instances are defined by a matrix. + A gate class whose instances are defined by a matrix. Note: Use this gate class only for gates acting on a small numbers of qubits. In general, consider instead using one of the provided ProjectQ gates or define a new class as this allows the compiler to work symbolically. Example: - .. code-block:: python gate = MatrixGate([[0, 1], [1, 0]]) @@ -254,31 +253,27 @@ class MatrixGate(BasicGate): def __init__(self, matrix=None): """ - Initialize MatrixGate + Initialize a MatrixGate object. Args: matrix(numpy.matrix): matrix which defines the gate. Default: None """ - BasicGate.__init__(self) + super().__init__() self._matrix = np.matrix(matrix) if matrix is not None else None @property def matrix(self): - """ - Access to the matrix property of this gate. - """ + """Access to the matrix property of this gate.""" return self._matrix @matrix.setter def matrix(self, matrix): - """ - Set the matrix property of this gate. - """ + """Set the matrix property of this gate.""" self._matrix = np.matrix(matrix) def __eq__(self, other): """ - Equality comparision + Equal operator. Return True only if both gates have a matrix respresentation and the matrices are (approximately) equal. Otherwise return False. @@ -294,12 +289,15 @@ def __eq__(self, other): return False def __str__(self): + """Return a string representation of the object.""" return "MatrixGate(" + str(self.matrix.tolist()) + ")" def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) def get_inverse(self): + """Return the inverse of this gate.""" return MatrixGate(np.linalg.inv(self.matrix)) @@ -307,7 +305,7 @@ class SelfInverseGate(BasicGate): # pylint: disable=abstract-method """ Self-inverse basic gate class. - Automatic implementation of the get_inverse-member function for self- inverse gates. + Automatic implementation of the get_inverse-member function for self-inverse gates. Example: .. code-block:: python @@ -317,12 +315,13 @@ class SelfInverseGate(BasicGate): # pylint: disable=abstract-method """ def get_inverse(self): + """Return the inverse of this gate.""" return deepcopy(self) class BasicRotationGate(BasicGate): """ - Defines a base class of a rotation gate. + Base class of for all rotation gates. A rotation gate has a continuous parameter (the angle), labeled 'angle' / self.angle. Its inverse is the same gate with the negated argument. Rotation gates of the same class can be merged by adding the angles. The continuous @@ -336,7 +335,7 @@ def __init__(self, angle): Args: angle (float): Angle of rotation (saved modulo 4 * pi) """ - BasicGate.__init__(self) + super().__init__() rounded_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) if rounded_angle > 4 * math.pi - ANGLE_TOLERANCE: rounded_angle = 0.0 @@ -381,10 +380,7 @@ def tex_str(self): return str(self.__class__.__name__) + "$_{" + str(round(self.angle / math.pi, 3)) + "\\pi}$" def get_inverse(self): - """ - Return the inverse of this rotation gate (negate the angle, return new - object). - """ + """Return the inverse of this rotation gate (negate the angle, return new object).""" if self.angle == 0: return self.__class__(0) return self.__class__(-self.angle + 4 * math.pi) @@ -414,22 +410,18 @@ def __eq__(self, other): return self.angle == other.angle return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) def is_identity(self): - """ - Return True if the gate is equivalent to an Identity gate - """ + """Return True if the gate is equivalent to an Identity gate.""" return self.angle == 0.0 or self.angle == 4 * math.pi class BasicPhaseGate(BasicGate): """ - Defines a base class of a phase gate. + Base class for all phase gates. A phase gate has a continuous parameter (the angle), labeled 'angle' / self.angle. Its inverse is the same gate with the negated argument. Phase gates of the same class can be merged by adding the angles. The continuous @@ -443,7 +435,7 @@ def __init__(self, angle): Args: angle (float): Angle of rotation (saved modulo 2 * pi) """ - BasicGate.__init__(self) + super().__init__() rounded_angle = round(float(angle) % (2.0 * math.pi), ANGLE_PRECISION) if rounded_angle > 2 * math.pi - ANGLE_TOLERANCE: rounded_angle = 0.0 @@ -474,9 +466,7 @@ def tex_str(self): return str(self.__class__.__name__) + "$_{" + str(self.angle) + "}$" def get_inverse(self): - """ - Return the inverse of this rotation gate (negate the angle, return new object). - """ + """Return the inverse of this rotation gate (negate the angle, return new object).""" if self.angle == 0: return self.__class__(0) return self.__class__(-self.angle + 2 * math.pi) @@ -507,10 +497,8 @@ def __eq__(self, other): return self.angle == other.angle return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) @@ -526,6 +514,8 @@ class ClassicalInstructionGate(BasicGate): # pylint: disable=abstract-method class FastForwardingGate(ClassicalInstructionGate): # pylint: disable=abstract-method """ + Base class for fast-forward gates. + Base class for classical instruction gates which require a fast-forward through compiler engines that cache / buffer gates. Examples include Measure and Deallocate, which both should be executed asap, such that Measurement results are available and resources are freed, respectively. @@ -581,7 +571,7 @@ def __init__(self, math_fun): def add(a,b): return (a,a+b) - BasicMathGate.__init__(self, add) + super().__init__(add) If the gate acts on, e.g., fixed point numbers, the number of bits per register is also required in order to describe the action of such a mathematical gate. For this reason, there is @@ -603,7 +593,7 @@ def math_fun(a): return math_fun """ - BasicGate.__init__(self) + super().__init__() def math_function(arg): return list(math_fun(*arg)) @@ -611,10 +601,13 @@ def math_function(arg): self._math_function = math_function def __str__(self): + """Return a string representation of the object.""" return "MATH" def get_math_function(self, qubits): # pylint: disable=unused-argument """ + Get the math function associated with a BasicMathGate. + Return the math function which corresponds to the action of this math gate, given the input to the gate (a tuple of quantum registers). diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 7fa40643b..af3ab34b2 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -19,13 +19,10 @@ import numpy as np import pytest -from projectq.types import Qubit, Qureg -from projectq.ops import Command, X from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.types import WeakQubitRef - -from projectq.ops import _basics +from projectq.ops import Command, X, _basics +from projectq.types import Qubit, Qureg, WeakQubitRef @pytest.fixture @@ -314,7 +311,7 @@ def my_math_function(a, b, c): class MyMultiplyGate(_basics.BasicMathGate): def __init__(self): - _basics.BasicMathGate.__init__(self, my_math_function) + super().__init__(my_math_function) gate = MyMultiplyGate() assert str(gate) == 'MATH' diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 621bee320..c67de0683 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This file defines the apply_command function and the Command class. +The apply_command function and the Command class. When a gate is applied to qubits, e.g., @@ -37,22 +37,20 @@ apply wrapper (apply_command). """ +import itertools from copy import deepcopy from enum import IntEnum -import itertools import projectq -from projectq.types import WeakQubitRef, Qureg +from projectq.types import Qureg, WeakQubitRef class IncompatibleControlState(Exception): - """ - Exception thrown when trying to set two incompatible states for a control qubit. - """ + """Exception thrown when trying to set two incompatible states for a control qubit.""" class CtrlAll(IntEnum): - """Enum type to initialise the control state of qubits""" + """Enum type to initialise the control state of qubits.""" Zero = 0 One = 1 @@ -73,9 +71,11 @@ def apply_command(cmd): class Command: # pylint: disable=too-many-instance-attributes """ - Class used as a container to store commands. If a gate is applied to qubits, then the gate and qubits are saved in - a command object. Qubits are copied into WeakQubitRefs in order to allow early deallocation (would be kept alive - otherwise). WeakQubitRef qubits don't send deallocate gate when destructed. + Class used as a container to store commands. + + If a gate is applied to qubits, then the gate and qubits are saved in a command object. Qubits are copied into + WeakQubitRefs in order to allow early deallocation (would be kept alive otherwise). WeakQubitRef qubits don't send + deallocate gate when destructed. Attributes: gate: The gate to execute @@ -83,10 +83,10 @@ class Command: # pylint: disable=too-many-instance-attributes control_qubits: The Qureg of control qubits in a unique order engine: The engine (usually: MainEngine) tags: The list of tag objects associated with this command (e.g., ComputeTag, UncomputeTag, LoopTag, ...). tag - objects need to support ==, != (__eq__ and __ne__) for comparison as used in e.g. TagRemover. New tags - should always be added to the end of the list. This means that if there are e.g. two LoopTags in a command, - tag[0] is from the inner scope while tag[1] is from the other scope as the other scope receives the command - after the inner scope LoopEngine and hence adds its LoopTag to the end. + objects need to support ==, != (__eq__ and __ne__) for comparison as used in e.g. TagRemover. New tags + should always be added to the end of the list. This means that if there are e.g. two LoopTags in a + command, tag[0] is from the inner scope while tag[1] is from the other scope as the other scope receives + the command after the inner scope LoopEngine and hence adds its LoopTag to the end. all_qubits: A tuple of control_qubits + qubits """ @@ -97,8 +97,8 @@ def __init__( Initialize a Command object. Note: - control qubits (Command.control_qubits) are stored as a list of qubits, and command tags (Command.tags) as - a list of tag- objects. All functions within this class also work if WeakQubitRefs are supplied instead of + control qubits (Command.control_qubits) are stored as a list of qubits, and command tags (Command.tags) as a + list of tag-objects. All functions within this class also work if WeakQubitRefs are supplied instead of normal Qubit objects (see WeakQubitRef). Args: @@ -109,7 +109,6 @@ def __init__( tags (list[object]): Tags associated with the command. control_state(int,str,projectq.meta.CtrlAll) Control state for any control qubits """ - qubits = tuple([WeakQubitRef(qubit.engine, qubit.id) for qubit in qreg] for qreg in qubits) self.gate = gate @@ -121,12 +120,12 @@ def __init__( @property def qubits(self): - """Qubits stored in a Command object""" + """Qubits stored in a Command object.""" return self._qubits @qubits.setter def qubits(self, qubits): - """Set the qubits stored in a Command object""" + """Set the qubits stored in a Command object.""" self._qubits = self._order_qubits(qubits) def __deepcopy__(self, memo): @@ -167,15 +166,13 @@ def is_identity(self): def get_merged(self, other): """ - Merge this command with another one and return the merged command - object. + Merge this command with another one and return the merged command object. Args: other: Other command to merge with this one (self) Raises: - NotMergeable: if the gates don't supply a get_merged()-function - or can't be merged for other reasons. + NotMergeable: if the gates don't supply a get_merged()-function or can't be merged for other reasons. """ if self.tags == other.tags and self.all_qubits == other.all_qubits and self.engine == other.engine: return Command( @@ -189,8 +186,7 @@ def get_merged(self, other): def _order_qubits(self, qubits): """ - Order the given qubits according to their IDs (for unique comparison of - commands). + Order the given qubits according to their IDs (for unique comparison of commands). Args: qubits: Tuple of quantum registers (i.e., tuple of lists of qubits) @@ -212,25 +208,23 @@ def interchangeable_qubit_indices(self): """ Return nested list of qubit indices which are interchangeable. - Certain qubits can be interchanged (e.g., the qubit order for a Swap - gate). To ensure that only those are sorted when determining the - ordering (see _order_qubits), self.interchangeable_qubit_indices is - used. + Certain qubits can be interchanged (e.g., the qubit order for a Swap gate). To ensure that only those are sorted + when determining the ordering (see _order_qubits), self.interchangeable_qubit_indices is used. + Example: - If we can interchange qubits 0,1 and qubits 3,4,5, - then this function returns [[0,1],[3,4,5]] + If we can interchange qubits 0,1 and qubits 3,4,5, then this function returns [[0,1],[3,4,5]] """ return self.gate.interchangeable_qubit_indices @property def control_qubits(self): - """Returns Qureg of control qubits.""" + """Return a Qureg of control qubits.""" return self._control_qubits @control_qubits.setter def control_qubits(self, qubits): """ - Set control_qubits to qubits + Set control_qubits to qubits. Args: control_qubits (Qureg): quantum register @@ -240,19 +234,21 @@ def control_qubits(self, qubits): @property def control_state(self): - """Returns the state of the control qubits (ie. either positively- or negatively-controlled)""" + """Return the state of the control qubits (ie. either positively- or negatively-controlled).""" return self._control_state @control_state.setter def control_state(self, state): """ - Set control_state to state + Set control_state to state. Args: state (int,str,projectq.meta.CtrtAll): state of control qubit (ie. positive or negative) """ # NB: avoid circular imports - from projectq.meta import canonical_ctrl_state # pylint: disable=import-outside-toplevel + from projectq.meta import ( # pylint: disable=import-outside-toplevel + canonical_ctrl_state, + ) self._control_state = canonical_ctrl_state(state, len(self._control_qubits)) @@ -270,7 +266,9 @@ def add_control_qubits(self, qubits, state=CtrlAll.One): control qubits. """ # NB: avoid circular imports - from projectq.meta import canonical_ctrl_state # pylint: disable=import-outside-toplevel + from projectq.meta import ( # pylint: disable=import-outside-toplevel + canonical_ctrl_state, + ) if not isinstance(qubits, list): raise ValueError('Control qubits must be a list of qubits!') @@ -302,10 +300,7 @@ def all_qubits(self): @property def engine(self): - """ - Return engine to which the qubits belong / on which the gates are - executed. - """ + """Return engine to which the qubits belong / on which the gates are executed.""" return self._engine @engine.setter @@ -343,16 +338,12 @@ def __eq__(self, other): return True return False - def __ne__(self, other): - return not self.__eq__(other) - def __str__(self): + """Return a string representation of the object.""" return self.to_string() def to_string(self, symbols=False): - """ - Get string representation of this Command object. - """ + """Get string representation of this Command object.""" qubits = self.qubits ctrlqubits = self.control_qubits if len(ctrlqubits) > 0: diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 1228f135a..30b555c71 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -14,19 +14,18 @@ # limitations under the License. """Tests for projectq.ops._command.""" -from copy import deepcopy -import sys import math +import sys +from copy import deepcopy + import pytest from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.meta import ComputeTag, canonical_ctrl_state -from projectq.ops import BasicGate, Rx, NotMergeable, CtrlAll +from projectq.ops import BasicGate, CtrlAll, NotMergeable, Rx, _command from projectq.types import Qubit, Qureg, WeakQubitRef -from projectq.ops import _command - @pytest.fixture def main_engine(): diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index a041c18f9..97acd12bc 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ +Definition of the basic set of quantum gates. + Contains definitions of standard gates such as * Hadamard (H) * Pauli-X (X / NOT) @@ -41,32 +43,33 @@ * FlipBits """ -import math import cmath +import math import numpy as np from ._basics import ( BasicGate, - SelfInverseGate, - BasicRotationGate, BasicPhaseGate, + BasicRotationGate, ClassicalInstructionGate, FastForwardingGate, + SelfInverseGate, ) from ._command import apply_command from ._metagates import get_inverse class HGate(SelfInverseGate): - """Hadamard gate class""" + """Hadamard gate class.""" def __str__(self): + """Return a string representation of the object.""" return "H" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return 1.0 / cmath.sqrt(2.0) * np.matrix([[1, 1], [1, -1]]) @@ -75,14 +78,15 @@ def matrix(self): class XGate(SelfInverseGate): - """Pauli-X gate class""" + """Pauli-X gate class.""" def __str__(self): + """Return a string representation of the object.""" return "X" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[0, 1], [1, 0]]) @@ -91,14 +95,15 @@ def matrix(self): class YGate(SelfInverseGate): - """Pauli-Y gate class""" + """Pauli-Y gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Y" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[0, -1j], [1j, 0]]) @@ -107,14 +112,15 @@ def matrix(self): class ZGate(SelfInverseGate): - """Pauli-Z gate class""" + """Pauli-Z gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Z" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[1, 0], [0, -1]]) @@ -123,14 +129,15 @@ def matrix(self): class SGate(BasicGate): - """S gate class""" + """S gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[1, 0], [0, 1j]]) def __str__(self): + """Return a string representation of the object.""" return "S" @@ -141,14 +148,15 @@ def __str__(self): class TGate(BasicGate): - """T gate class""" + """T gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[1, 0], [0, cmath.exp(1j * cmath.pi / 4)]]) def __str__(self): + """Return a string representation of the object.""" return "T" @@ -159,20 +167,19 @@ def __str__(self): class SqrtXGate(BasicGate): - """Square-root X gate class""" + """Square-root X gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return 0.5 * np.matrix([[1 + 1j, 1 - 1j], [1 - 1j, 1 + 1j]]) def tex_str(self): # pylint: disable=no-self-use - """ - Return the Latex string representation of a SqrtXGate. - """ + """Return the Latex string representation of a SqrtXGate.""" return r'$\sqrt{X}$' def __str__(self): + """Return a string representation of the object.""" return "SqrtX" @@ -181,18 +188,20 @@ def __str__(self): class SwapGate(SelfInverseGate): - """Swap gate class (swaps 2 qubits)""" + """Swap gate class (swaps 2 qubits).""" def __init__(self): - SelfInverseGate.__init__(self) + """Initialize a Swap gate.""" + super().__init__() self.interchangeable_qubit_indices = [[0, 1]] def __str__(self): + """Return a string representation of the object.""" return "Swap" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" # fmt: off return np.matrix([[1, 0, 0, 0], [0, 0, 1, 0], @@ -206,18 +215,20 @@ def matrix(self): class SqrtSwapGate(BasicGate): - """Square-root Swap gate class""" + """Square-root Swap gate class.""" def __init__(self): - BasicGate.__init__(self) + """Initialize a SqrtSwap gate.""" + super().__init__() self.interchangeable_qubit_indices = [[0, 1]] def __str__(self): + """Return a string representation of the object.""" return "SqrtSwap" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [1, 0, 0, 0], @@ -234,11 +245,13 @@ def matrix(self): class EntangleGate(BasicGate): """ - Entangle gate (Hadamard on first qubit, followed by CNOTs applied to all - other qubits). + Entangle gate class. + + (Hadamard on first qubit, followed by CNOTs applied to all other qubits). """ def __str__(self): + """Return a string representation of the object.""" return "Entangle" @@ -247,20 +260,20 @@ def __str__(self): class Ph(BasicPhaseGate): - """Phase gate (global phase)""" + """Phase gate (global phase).""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[cmath.exp(1j * self.angle), 0], [0, cmath.exp(1j * self.angle)]]) class Rx(BasicRotationGate): - """RotationX gate class""" + """RotationX gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [math.cos(0.5 * self.angle), -1j * math.sin(0.5 * self.angle)], @@ -270,11 +283,11 @@ def matrix(self): class Ry(BasicRotationGate): - """RotationY gate class""" + """RotationY gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [math.cos(0.5 * self.angle), -math.sin(0.5 * self.angle)], @@ -284,11 +297,11 @@ def matrix(self): class Rz(BasicRotationGate): - """RotationZ gate class""" + """RotationZ gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [cmath.exp(-0.5 * 1j * self.angle), 0], @@ -298,11 +311,11 @@ def matrix(self): class Rxx(BasicRotationGate): - """RotationXX gate class""" + """RotationXX gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [cmath.cos(0.5 * self.angle), 0, 0, -1j * cmath.sin(0.5 * self.angle)], @@ -314,11 +327,11 @@ def matrix(self): class Ryy(BasicRotationGate): - """RotationYY gate class""" + """RotationYY gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [cmath.cos(0.5 * self.angle), 0, 0, 1j * cmath.sin(0.5 * self.angle)], @@ -330,11 +343,11 @@ def matrix(self): class Rzz(BasicRotationGate): - """RotationZZ gate class""" + """RotationZZ gate class.""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix( [ [cmath.exp(-0.5 * 1j * self.angle), 0, 0, 0], @@ -346,11 +359,11 @@ def matrix(self): class R(BasicPhaseGate): - """Phase-shift gate (equivalent to Rz up to a global phase)""" + """Phase-shift gate (equivalent to Rz up to a global phase).""" @property def matrix(self): - """Access to the matrix property of this gate""" + """Access to the matrix property of this gate.""" return np.matrix([[1, 0], [0, cmath.exp(1j * self.angle)]]) @@ -373,6 +386,7 @@ class FlushGate(FastForwardingGate): """ def __str__(self): + """Return a string representation of the object.""" return "" @@ -380,14 +394,18 @@ class MeasureGate(FastForwardingGate): """Measurement gate class (for single qubits).""" def __str__(self): + """Return a string representation of the object.""" return "Measure" def __or__(self, qubits): """ - Previously (ProjectQ <= v0.3.6) MeasureGate/Measure was allowed to be - applied to any number of quantum registers. Now the MeasureGate/Measure - is strictly a single qubit gate. In the coming releases the backward - compatibility will be removed! + Operator| overload which enables the syntax Gate | qubits. + + Previously (ProjectQ <= v0.3.6) MeasureGate/Measure was allowed to be applied to any number of quantum + registers. Now the MeasureGate/Measure is strictly a single qubit gate. + + Raises: + RuntimeError: Since ProjectQ v0.6.0 if the gate is applied to multiple qubits. """ num_qubits = 0 for qureg in self.make_tuple_of_qureg(qubits): @@ -404,12 +422,14 @@ def __or__(self, qubits): class AllocateQubitGate(ClassicalInstructionGate): - """Qubit allocation gate class""" + """Qubit allocation gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Allocate" def get_inverse(self): + """Return the inverse of this gate.""" return DeallocateQubitGate() @@ -418,12 +438,14 @@ def get_inverse(self): class DeallocateQubitGate(FastForwardingGate): - """Qubit deallocation gate class""" + """Qubit deallocation gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Deallocate" def get_inverse(self): + """Return the inverse of this gate.""" return Allocate @@ -432,12 +454,14 @@ def get_inverse(self): class AllocateDirtyQubitGate(ClassicalInstructionGate): - """Dirty qubit allocation gate class""" + """Dirty qubit allocation gate class.""" def __str__(self): + """Return a string representation of the object.""" return "AllocateDirty" def get_inverse(self): + """Return the inverse of this gate.""" return Deallocate @@ -446,12 +470,14 @@ def get_inverse(self): class BarrierGate(BasicGate): - """Barrier gate class""" + """Barrier gate class.""" def __str__(self): + """Return a string representation of the object.""" return "Barrier" def get_inverse(self): + """Return the inverse of this gate.""" return Barrier @@ -460,11 +486,11 @@ def get_inverse(self): class FlipBits(SelfInverseGate): - """Gate for flipping qubits by means of XGates""" + """Gate for flipping qubits by means of XGates.""" def __init__(self, bits_to_flip): """ - Initialize FlipBits gate. + Initialize a FlipBits gate. Example: .. code-block:: python @@ -473,15 +499,12 @@ def __init__(self, bits_to_flip): FlipBits([0, 1]) | qureg Args: - bits_to_flip(list[int]|list[bool]|str|int): int or array of 0/1, - True/False, or string of 0/1 identifying the qubits to flip. - In case of int, the bits to flip are determined from the - binary digits, with the least significant bit corresponding - to qureg[0]. If bits_to_flip is negative, exactly all qubits - which would not be flipped for the input -bits_to_flip-1 are - flipped, i.e., bits_to_flip=-1 flips all qubits. + bits_to_flip(list[int]|list[bool]|str|int): int or array of 0/1, True/False, or string of 0/1 identifying + the qubits to flip. In case of int, the bits to flip are determined from the binary digits, with the + least significant bit corresponding to qureg[0]. If bits_to_flip is negative, exactly all qubits which + would not be flipped for the input -bits_to_flip-1 are flipped, i.e., bits_to_flip=-1 flips all qubits. """ - SelfInverseGate.__init__(self) + super().__init__() if isinstance(bits_to_flip, int): self.bits_to_flip = bits_to_flip else: @@ -491,9 +514,11 @@ def __init__(self, bits_to_flip): self.bits_to_flip = (self.bits_to_flip << 1) | bit def __str__(self): + """Return a string representation of the object.""" return "FlipBits(" + str(self.bits_to_flip) + ")" def __or__(self, qubits): + """Operator| overload which enables the syntax Gate | qubits.""" quregs_tuple = self.make_tuple_of_qureg(qubits) if len(quregs_tuple) > 1: raise ValueError( @@ -507,9 +532,11 @@ def __or__(self, qubits): XGate() | qubit def __eq__(self, other): + """Equal operator.""" if isinstance(other, self.__class__): return self.bits_to_flip == other.bits_to_flip return False def __hash__(self): + """Compute the hash of the object.""" return hash(self.__str__()) diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index fb5977769..12b49f7a9 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -14,20 +14,14 @@ # limitations under the License. """Tests for projectq.ops._gates.""" -import math import cmath +import math + import numpy as np import pytest from projectq import MainEngine -from projectq.ops import ( - All, - FlipBits, - get_inverse, - Measure, -) - -from projectq.ops import _gates +from projectq.ops import All, FlipBits, Measure, _gates, get_inverse def test_h_gate(): diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index 59fafb7d3..f67e4927f 100644 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ +Definition of some `meta` gates. + Contains meta gates, i.e., * DaggeredGate (Represents the inverse of an arbitrary gate) * ControlledGate (Represents a controlled version of an arbitrary gate) @@ -31,9 +33,7 @@ class ControlQubitError(Exception): - """ - Exception thrown when wrong number of control qubits are supplied. - """ + """Exception thrown when wrong number of control qubits are supplied.""" class DaggeredGate(BasicGate): @@ -64,7 +64,7 @@ def __init__(self, gate): Args: gate: Any gate object of which to represent the inverse. """ - BasicGate.__init__(self) + super().__init__() self._gate = gate try: @@ -74,33 +74,25 @@ def __init__(self, gate): pass def __str__(self): - r""" - Return string representation (str(gate) + \"^\dagger\"). - """ + r"""Return string representation (str(gate) + \"^\dagger\").""" return str(self._gate) + r"^\dagger" def tex_str(self): - """ - Return the Latex string representation of a Daggered gate. - """ + """Return the Latex string representation of a Daggered gate.""" if hasattr(self._gate, 'tex_str'): return self._gate.tex_str() + r"${}^\dagger$" return str(self._gate) + r"${}^\dagger$" def get_inverse(self): - """ - Return the inverse gate (the inverse of the inverse of a gate is the gate itself). - """ + """Return the inverse gate (the inverse of the inverse of a gate is the gate itself).""" return self._gate def __eq__(self, other): - """ - Return True if self is equal to other, i.e., same type and - representing the inverse of the same gate. - """ + """Return True if self is equal to other, i.e., same type and representing the inverse of the same gate.""" return isinstance(other, self.__class__) and self._gate == other._gate def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) @@ -175,7 +167,7 @@ def __init__(self, gate, n=1): gate: Gate to wrap. n (int): Number of control qubits. """ - BasicGate.__init__(self) + super().__init__() if isinstance(gate, ControlledGate): self._gate = gate._gate self._n = gate._n + n @@ -184,14 +176,11 @@ def __init__(self, gate, n=1): self._n = n def __str__(self): - """Return string representation, i.e., CC...C(gate).""" + """Return a string representation of the object.""" return "C" * self._n + str(self._gate) def get_inverse(self): - """ - Return inverse of a controlled gate, which is the controlled inverse - gate. - """ + """Return inverse of a controlled gate, which is the controlled inverse gate.""" return ControlledGate(get_inverse(self._gate), self._n) def __or__(self, qubits): @@ -234,9 +223,6 @@ def __eq__(self, other): """Compare two ControlledGate objects (return True if equal).""" return isinstance(other, self.__class__) and self._gate == other._gate and self._n == other._n - def __ne__(self, other): - return not self.__eq__(other) - def C(gate, n_qubits=1): """ @@ -256,8 +242,9 @@ def C(gate, n_qubits=1): class Tensor(BasicGate): """ - Wrapper class allowing to apply a (single-qubit) gate to every qubit in a quantum register. Allowed syntax is to - supply either a qureg or a tuple which contains only one qureg. + Wrapper class allowing to apply a (single-qubit) gate to every qubit in a quantum register. + + Allowed syntax is to supply either a qureg or a tuple which contains only one qureg. Example: .. code-block:: python @@ -268,27 +255,23 @@ class Tensor(BasicGate): def __init__(self, gate): """Initialize a Tensor object for the gate.""" - BasicGate.__init__(self) + super().__init__() self._gate = gate def __str__(self): - """Return string representation.""" + """Return a string representation of the object.""" return "Tensor(" + str(self._gate) + ")" def get_inverse(self): - """ - Return the inverse of this tensored gate (which is the tensored inverse of the gate). - """ + """Return the inverse of this tensored gate (which is the tensored inverse of the gate).""" return Tensor(get_inverse(self._gate)) def __eq__(self, other): + """Equal operator.""" return isinstance(other, Tensor) and self._gate == other._gate - def __ne__(self, other): - return not self.__eq__(other) - def __or__(self, qubits): - """Applies the gate to every qubit in the quantum register qubits.""" + """Operator| overload which enables the syntax Gate | qubits.""" if isinstance(qubits, tuple): if len(qubits) != 1: raise ValueError('Tensor/All must be applied to a single quantum register!') diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index 3c99780fc..e00d4b617 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -16,27 +16,26 @@ import cmath import math + import numpy as np import pytest -from projectq.types import Qubit from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import ( - T, - Y, - NotInvertible, - Entangle, - Rx, - FastForwardingGate, - Command, + All, C, ClassicalInstructionGate, - All, + Command, + Entangle, + FastForwardingGate, + NotInvertible, + Rx, + T, + Y, + _metagates, ) -from projectq.types import WeakQubitRef - -from projectq.ops import _metagates +from projectq.types import Qubit, WeakQubitRef def test_tensored_gate_invalid(): diff --git a/projectq/ops/_qaagate.py b/projectq/ops/_qaagate.py index 9dea09bef..565d61498 100755 --- a/projectq/ops/_qaagate.py +++ b/projectq/ops/_qaagate.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the quantum amplitude amplification gate""" +"""Definition of the quantum amplitude amplification gate.""" from ._basics import BasicGate @@ -71,9 +71,11 @@ def func_oracle(eng,system_qubits,qaa_ancilla): """ def __init__(self, algorithm, oracle): - BasicGate.__init__(self) + """Initialize a QAA object.""" + super().__init__() self.algorithm = algorithm self.oracle = oracle def __str__(self): + """Return a string representation of the object.""" return 'QAA(Algorithm = {0}, Oracle = {1})'.format(str(self.algorithm.__name__), str(self.oracle.__name__)) diff --git a/projectq/ops/_qaagate_test.py b/projectq/ops/_qaagate_test.py index ccc224938..707fb2fd5 100755 --- a/projectq/ops/_qaagate_test.py +++ b/projectq/ops/_qaagate_test.py @@ -14,7 +14,7 @@ # limitations under the License. """Tests for projectq.ops._qaagate.""" -from projectq.ops import _qaagate, All, H, X +from projectq.ops import All, H, X, _qaagate def test_qaa_str(): diff --git a/projectq/ops/_qftgate.py b/projectq/ops/_qftgate.py index 45eaec9bf..0ea56c032 100755 --- a/projectq/ops/_qftgate.py +++ b/projectq/ops/_qftgate.py @@ -13,17 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the QFT gate""" +"""Definition of the QFT gate.""" from ._basics import BasicGate class QFTGate(BasicGate): - """ - Quantum Fourier Transform gate. - """ + """Quantum Fourier Transform gate.""" def __str__(self): + """Return a string representation of the object.""" return "QFT" diff --git a/projectq/ops/_qpegate.py b/projectq/ops/_qpegate.py index c806c61a0..96ed00a5d 100755 --- a/projectq/ops/_qpegate.py +++ b/projectq/ops/_qpegate.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the quantum phase estimation gate""" +"""Definition of the quantum phase estimation gate.""" from ._basics import BasicGate @@ -26,8 +26,10 @@ class QPE(BasicGate): """ def __init__(self, unitary): - BasicGate.__init__(self) + """Initialize a QPE gate.""" + super().__init__() self.unitary = unitary def __str__(self): + """Return a string representation of the object.""" return 'QPE({})'.format(str(self.unitary)) diff --git a/projectq/ops/_qpegate_test.py b/projectq/ops/_qpegate_test.py index 2b19cd4d0..557b980bd 100755 --- a/projectq/ops/_qpegate_test.py +++ b/projectq/ops/_qpegate_test.py @@ -14,7 +14,7 @@ # limitations under the License. """Tests for projectq.ops._qpegate.""" -from projectq.ops import _qpegate, X +from projectq.ops import X, _qpegate def test_qpe_str(): diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 19f1301e0..55323fab1 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -45,7 +45,7 @@ class QubitOperatorError(Exception): - """Exception raised when a QubitOperator is instantiated with some invalid data""" + """Exception raised when a QubitOperator is instantiated with some invalid data.""" class QubitOperator(BasicGate): @@ -97,7 +97,7 @@ class QubitOperator(BasicGate): def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-branches """ - Inits a QubitOperator. + Initialize a QubitOperator object. The init function only allows to initialize one term. Additional terms have to be added using += (which is fast) or using + of two QubitOperator objects: @@ -132,7 +132,7 @@ def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-bran Raises: QubitOperatorError: Invalid operators provided to QubitOperator. """ - BasicGate.__init__(self) + super().__init__() if not isinstance(coefficient, (int, float, complex)): raise ValueError('Coefficient must be a numeric type.') self.terms = {} @@ -178,8 +178,10 @@ def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-bran def compress(self, abs_tol=1e-12): """ - Eliminates all terms with coefficients close to zero and removes imaginary parts of coefficients that are - close to zero. + Compress the coefficient of a QubitOperator. + + Eliminate all terms with coefficients close to zero and removes imaginary parts of coefficients that are close + to zero. Args: abs_tol(float): Absolute tolerance, must be at least 0.0 @@ -195,7 +197,7 @@ def compress(self, abs_tol=1e-12): def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): """ - Returns True if other (QubitOperator) is close to self. + Return True if other (QubitOperator) is close to self. Comparison is done for each term individually. Return True if the difference between each term in self and other is less than the relative tolerance w.r.t. either other or self (symmetric test) or if the difference is @@ -224,7 +226,9 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): def __or__(self, qubits): # pylint: disable=too-many-locals """ - Operator| overload which enables the following syntax: + Operator| overload which enables the syntax Gate | qubits. + + In particular, enable the following syntax: .. code-block:: python @@ -314,8 +318,8 @@ def __or__(self, qubits): # pylint: disable=too-many-locals 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)) + new_index = {} + non_trivial_qubits = sorted(non_trivial_qubits) for i, qubit in enumerate(non_trivial_qubits): new_index[qubit] = i new_qubitoperator = QubitOperator() @@ -335,7 +339,6 @@ def get_inverse(self): 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: @@ -370,7 +373,7 @@ def __imul__(self, multiplier): # pylint: disable=too-many-locals,too-many-bran # Handle QubitOperator. if isinstance(multiplier, QubitOperator): # pylint: disable=too-many-nested-blocks - result_terms = dict() + result_terms = {} for left_term in self.terms: for right_term in multiplier.terms: new_coefficient = self.terms[left_term] * multiplier.terms[right_term] @@ -484,6 +487,7 @@ def __truediv__(self, divisor): return self * (1.0 / divisor) def __itruediv__(self, divisor): + """Perform self =/ divisor for a scalar.""" if not isinstance(divisor, (int, float, complex)): raise TypeError('Cannot divide QubitOperator by non-scalar type.') self *= 1.0 / divisor @@ -548,10 +552,15 @@ def __sub__(self, subtrahend): return minuend def __neg__(self): + """ + Opposite operator. + + Return -self for a QubitOperator. + """ return -1.0 * self def __str__(self): - """Return an easy-to-read string representation.""" + """Return a string representation of the object.""" if not self.terms: return '0' string_rep = '' @@ -572,7 +581,9 @@ def __str__(self): return string_rep[:-3] def __repr__(self): + """Repr method.""" return str(self) def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 8077a8583..ad86e0d64 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -22,11 +22,11 @@ from projectq import MainEngine from projectq.cengines import DummyEngine +from projectq.ops import _qubit_operator as qo + from ._basics import NotInvertible, NotMergeable from ._gates import Ph, T, X, Y, Z -from projectq.ops import _qubit_operator as qo - def test_pauli_operator_product_unchanged(): correct = { @@ -79,7 +79,7 @@ def test_init_str_identity(): def test_init_bad_term(): with pytest.raises(ValueError): - qo.QubitOperator(list()) + qo.QubitOperator([]) def test_init_bad_coefficient(): diff --git a/projectq/ops/_shortcuts.py b/projectq/ops/_shortcuts.py index 0defb4e22..afcc8bdf4 100755 --- a/projectq/ops/_shortcuts.py +++ b/projectq/ops/_shortcuts.py @@ -13,20 +13,20 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Defines a few shortcuts for certain gates such as +A few shortcuts for certain gates. + +These include: * CNOT = C(NOT) * CRz = C(Rz) * Toffoli = C(NOT,2) = C(CNOT) """ -from ._metagates import C from ._gates import NOT, Rz, Z +from ._metagates import C def CRz(angle): - """ - Shortcut for C(Rz(angle), n_qubits=1). - """ + """Shortcut for C(Rz(angle), n_qubits=1).""" return C(Rz(angle), n_qubits=1) diff --git a/projectq/ops/_shortcuts_test.py b/projectq/ops/_shortcuts_test.py index dd8a65d71..345f8396c 100755 --- a/projectq/ops/_shortcuts_test.py +++ b/projectq/ops/_shortcuts_test.py @@ -14,9 +14,7 @@ # limitations under the License. """Tests for projectq.ops._shortcuts.""" -from projectq.ops import ControlledGate, Rz - -from projectq.ops import _shortcuts +from projectq.ops import ControlledGate, Rz, _shortcuts def test_crz(): diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py index d86824bf8..74c26d97a 100644 --- a/projectq/ops/_state_prep.py +++ b/projectq/ops/_state_prep.py @@ -13,19 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the state preparation gate""" +"""Definition of the state preparation gate.""" from ._basics import BasicGate class StatePreparation(BasicGate): - """ - Gate for transforming qubits in state |0> to any desired quantum state. - """ + """Gate for transforming qubits in state |0> to any desired quantum state.""" def __init__(self, final_state): """ - Initialize StatePreparation gate. + Initialize a StatePreparation gate. Example: .. code-block:: python @@ -34,28 +32,26 @@ def __init__(self, final_state): StatePreparation([0.5, -0.5j, -0.5, 0.5]) | qureg Note: - final_state[k] is taken to be the amplitude of the computational - basis state whose string is equal to the binary representation - of k. + final_state[k] is taken to be the amplitude of the computational basis state whose string is equal to the + binary representation of k. Args: - final_state(list[complex]): wavefunction of the desired - quantum state. len(final_state) must - be 2**len(qureg). Must be normalized! + final_state(list[complex]): wavefunction of the desired quantum state. len(final_state) must be + 2**len(qureg). Must be normalized! """ - BasicGate.__init__(self) + super().__init__() self.final_state = list(final_state) def __str__(self): + """Return a string representation of the object.""" return "StatePreparation" def __eq__(self, other): + """Equal operator.""" 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): + """Compute the hash of the object.""" return hash("StatePreparation(" + str(self.final_state) + ")") diff --git a/projectq/ops/_state_prep_test.py b/projectq/ops/_state_prep_test.py index 5826d8e56..198ace500 100644 --- a/projectq/ops/_state_prep_test.py +++ b/projectq/ops/_state_prep_test.py @@ -14,7 +14,7 @@ # limitations under the License. """Tests for projectq.ops._state_prep.""" -from projectq.ops import _state_prep, X +from projectq.ops import X, _state_prep def test_equality_and_hash(): diff --git a/projectq/ops/_time_evolution.py b/projectq/ops/_time_evolution.py index 80e051f70..aa10944a9 100644 --- a/projectq/ops/_time_evolution.py +++ b/projectq/ops/_time_evolution.py @@ -13,11 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains the definition of the time evolution gate""" +"""Definition of the time evolution gate.""" import copy - from ._basics import BasicGate, NotMergeable from ._command import apply_command from ._gates import Ph @@ -25,7 +24,7 @@ class NotHermitianOperatorError(Exception): - """Error raised if an operator is non-hermitian""" + """Error raised if an operator is non-hermitian.""" class TimeEvolution(BasicGate): @@ -67,7 +66,7 @@ def __init__(self, time, hamiltonian): TypeError: If time is not a numeric type and hamiltonian is not a QubitOperator. NotHermitianOperatorError: If the input hamiltonian is not hermitian (only real coefficients). """ - BasicGate.__init__(self) + super().__init__() if not isinstance(time, (float, int)): raise TypeError("time needs to be a (real) numeric type.") if not isinstance(hamiltonian, QubitOperator): @@ -81,9 +80,7 @@ def __init__(self, time, hamiltonian): raise NotHermitianOperatorError("hamiltonian must be hermitian and hence only have real coefficients.") def get_inverse(self): - """ - Return the inverse gate. - """ + """Return the inverse gate.""" return TimeEvolution(self.time * -1.0, self.hamiltonian) def get_merged(self, other): @@ -130,7 +127,9 @@ def get_merged(self, other): def __or__(self, qubits): """ - Operator| overload which enables the following syntax: + Operator| overload which enables the syntax Gate | qubits. + + In particular, enable the following syntax: .. code-block:: python @@ -142,7 +141,6 @@ def __or__(self, qubits): Unlike other gates, this gate is only allowed to be applied to one quantum register or one qubit. Example: - .. code-block:: python wavefunction = eng.allocate_qureg(5) @@ -184,8 +182,8 @@ def __or__(self, qubits): # create new TimeEvolution gate with rescaled qubit indices in # self.hamiltonian which are ordered from # 0,...,len(non_trivial_qubits) - 1 - new_index = dict() - non_trivial_qubits = sorted(list(non_trivial_qubits)) + new_index = {} + non_trivial_qubits = sorted(non_trivial_qubits) for i, qubit in enumerate(non_trivial_qubits): new_index[qubit] = i new_hamiltonian = QubitOperator() @@ -202,9 +200,6 @@ def __eq__(self, other): """Not implemented as this object is a floating point type.""" return NotImplemented - def __ne__(self, other): - """Not implemented as this object is a floating point type.""" - return NotImplemented - def __str__(self): + """Return a string representation of the object.""" return "exp({0} * ({1}))".format(-1j * self.time, self.hamiltonian) diff --git a/projectq/ops/_time_evolution_test.py b/projectq/ops/_time_evolution_test.py index e4ee41a79..078c96f39 100644 --- a/projectq/ops/_time_evolution_test.py +++ b/projectq/ops/_time_evolution_test.py @@ -12,7 +12,9 @@ # 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._time_evolution.""" + import cmath import copy @@ -21,8 +23,7 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import QubitOperator, BasicGate, NotMergeable, Ph - +from projectq.ops import BasicGate, NotMergeable, Ph, QubitOperator from projectq.ops import _time_evolution as te diff --git a/projectq/ops/_uniformly_controlled_rotation.py b/projectq/ops/_uniformly_controlled_rotation.py index c5ae74229..393467406 100644 --- a/projectq/ops/_uniformly_controlled_rotation.py +++ b/projectq/ops/_uniformly_controlled_rotation.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Contains uniformly controlled rotation gates""" +"""Definition of uniformly controlled Ry- and Rz-rotation gates.""" import math @@ -46,7 +46,8 @@ class UniformlyControlledRy(BasicGate): """ def __init__(self, angles): - BasicGate.__init__(self) + """Construct a UniformlyControlledRy gate.""" + super().__init__() rounded_angles = [] for angle in angles: new_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) @@ -56,15 +57,18 @@ def __init__(self, angles): self.angles = rounded_angles def get_inverse(self): + """Return the inverse of this rotation gate (negate the angles, return new object).""" return self.__class__([-1 * angle for angle in self.angles]) def get_merged(self, other): + """Return self merged with another gate.""" 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 a string representation of the object.""" return "UniformlyControlledRy(" + str(self.angles) + ")" def __eq__(self, other): @@ -73,10 +77,8 @@ def __eq__(self, other): return self.angles == other.angles return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) @@ -101,12 +103,12 @@ class UniformlyControlledRz(BasicGate): 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. + angles(list[float]): Rotation angles. Rz(angles[k]) is applied conditioned on the control qubits being in + state k. """ def __init__(self, angles): + """Construct a UniformlyControlledRz gate.""" super().__init__() rounded_angles = [] for angle in angles: @@ -117,15 +119,18 @@ def __init__(self, angles): self.angles = rounded_angles def get_inverse(self): + """Return the inverse of this rotation gate (negate the angles, return new object).""" return self.__class__([-1 * angle for angle in self.angles]) def get_merged(self, other): + """Return self merged with another gate.""" 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 a string representation of the object.""" return "UniformlyControlledRz(" + str(self.angles) + ")" def __eq__(self, other): @@ -134,8 +139,6 @@ def __eq__(self, other): return self.angles == other.angles return False - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): + """Compute the hash of the object.""" return hash(str(self)) diff --git a/projectq/ops/_uniformly_controlled_rotation_test.py b/projectq/ops/_uniformly_controlled_rotation_test.py index 14014da04..362932483 100644 --- a/projectq/ops/_uniformly_controlled_rotation_test.py +++ b/projectq/ops/_uniformly_controlled_rotation_test.py @@ -18,10 +18,10 @@ import pytest from projectq.ops import Rx -from ._basics import NotMergeable - from projectq.ops import _uniformly_controlled_rotation as ucr +from ._basics import NotMergeable + @pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, ucr.UniformlyControlledRz]) def test_init_rounding(gate_class): diff --git a/projectq/setups/_utils.py b/projectq/setups/_utils.py index a50110ec1..6f933aac6 100644 --- a/projectq/setups/_utils.py +++ b/projectq/setups/_utils.py @@ -12,11 +12,13 @@ # 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. -""" -Some utility functions common to some setups -""" + +"""Some utility functions common to some setups.""" + import inspect +import projectq.libs.math +import projectq.setups.decompositions from projectq.cengines import ( AutoReplacer, DecompositionRuleSet, @@ -24,15 +26,19 @@ LocalOptimizer, TagRemover, ) -from projectq.ops import ClassicalInstructionGate, CNOT, ControlledGate, Swap, QFT, get_inverse, BasicMathGate -import projectq.libs.math -import projectq.setups.decompositions +from projectq.ops import ( + CNOT, + QFT, + BasicMathGate, + ClassicalInstructionGate, + ControlledGate, + Swap, + get_inverse, +) def one_and_two_qubit_gates(eng, cmd): # pylint: disable=unused-argument - """ - Filter out 1- and 2-qubit gates. - """ + """Filter out 1- and 2-qubit gates.""" all_qubits = [qb for qureg in cmd.all_qubits for qb in qureg] if isinstance(cmd.gate, ClassicalInstructionGate): # This is required to allow Measure, Allocate, Deallocate, Flush @@ -45,9 +51,7 @@ def one_and_two_qubit_gates(eng, cmd): # pylint: disable=unused-argument def high_level_gates(eng, cmd): # pylint: disable=unused-argument - """ - Remove any MathGates. - """ + """Remove any MathGates.""" gate = cmd.gate if eng.next_engine.is_available(cmd): return True @@ -60,7 +64,7 @@ def high_level_gates(eng, cmd): # pylint: disable=unused-argument def get_engine_list_linear_grid_base(mapper, one_qubit_gates="any", two_qubit_gates=(CNOT, Swap)): """ - Returns an engine list to compile to a 2-D grid of qubits. + Return an engine list to compile to a 2-D grid of qubits. Note: If you choose a new gate set for which the compiler does not yet have standard rules, it raises an diff --git a/projectq/setups/aqt.py b/projectq/setups/aqt.py index bd4ff862e..02ac24b19 100644 --- a/projectq/setups/aqt.py +++ b/projectq/setups/aqt.py @@ -12,7 +12,10 @@ # 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. + """ +A setup for AQT trapped ion devices. + Defines a setup allowing to compile code for the AQT trapped ion devices: ->The 4 qubits device ->The 11 qubits simulator @@ -23,17 +26,15 @@ translated in the backend in the Rx/Ry/MS gate set. """ -from projectq.setups import restrictedgateset -from projectq.ops import Rx, Ry, Rxx, Barrier -from projectq.cengines import BasicMapperEngine - from projectq.backends._aqt._aqt_http_client import show_devices +from projectq.backends._exceptions import DeviceNotHandledError, DeviceOfflineError +from projectq.cengines import BasicMapperEngine +from projectq.ops import Barrier, Rx, Rxx, Ry +from projectq.setups import restrictedgateset def get_engine_list(token=None, device=None): - """ - Return the default list of compiler engine for the AQT plaftorm - """ + """Return the default list of compiler engine for the AQT plaftorm.""" # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. @@ -49,7 +50,7 @@ def get_engine_list(token=None, device=None): # Note: Manual Mapper doesn't work, because its map is updated only if # gates are applied if gates in the register are not used, then it # will lead to state errors - res = dict() + res = {} for i in range(devices[device]['nq']): res[i] = i mapper.current_mapping = res @@ -65,11 +66,3 @@ def get_engine_list(token=None, device=None): setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry), two_qubit_gates=(Rxx,), other_gates=(Barrier,)) setup.extend(aqt_setup) return setup - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" - - -class DeviceNotHandledError(Exception): - """Exception raised if a selected device is cannot handle the circuit""" diff --git a/projectq/setups/aqt_test.py b/projectq/setups/aqt_test.py index d95ce2f2e..e36146e0b 100644 --- a/projectq/setups/aqt_test.py +++ b/projectq/setups/aqt_test.py @@ -21,21 +21,19 @@ def test_aqt_mapper_in_cengines(monkeypatch): import projectq.setups.aqt def mock_show_devices(*args, **kwargs): - connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return {'aqt_simulator': {'coupling_map': connections, 'version': '0.0.0', 'nq': 32}} monkeypatch.setattr(projectq.setups.aqt, "show_devices", mock_show_devices) @@ -47,21 +45,19 @@ def test_aqt_errors(monkeypatch): import projectq.setups.aqt def mock_show_devices(*args, **kwargs): - connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return {'aqt_imaginary': {'coupling_map': connections, 'version': '0.0.0', 'nq': 6}} monkeypatch.setattr(projectq.setups.aqt, "show_devices", mock_show_devices) diff --git a/projectq/setups/awsbraket.py b/projectq/setups/awsbraket.py index e9558a1d1..0268b834f 100644 --- a/projectq/setups/awsbraket.py +++ b/projectq/setups/awsbraket.py @@ -12,7 +12,10 @@ # 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. + """ +A setup for AWS Braket devices. + Defines a setup allowing to compile code for the AWS Braket devices: ->The 11 qubits IonQ device ->The 32 qubits Rigetti device @@ -23,31 +26,30 @@ that will be used in the backend. """ -from projectq.setups import restrictedgateset +from projectq.backends._awsbraket._awsbraket_boto3_client import show_devices +from projectq.backends._exceptions import DeviceNotHandledError, DeviceOfflineError from projectq.ops import ( - R, - Swap, + Barrier, H, + R, Rx, Ry, Rz, S, Sdag, + SqrtX, + Swap, T, Tdag, X, Y, Z, - SqrtX, - Barrier, ) -from projectq.backends._awsbraket._awsbraket_boto3_client import show_devices +from projectq.setups import restrictedgateset def get_engine_list(credentials=None, device=None): - """ - Return the default list of compiler engine for the AWS Braket platform. - """ + """Return the default list of compiler engine for the AWS Braket platform.""" # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. @@ -84,8 +86,4 @@ def get_engine_list(credentials=None, device=None): other_gates=(Barrier,), ) return setup - raise RuntimeError('Unsupported device type: {}!'.format(device)) # pragma: no cover - - -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" + raise DeviceNotHandledError('Unsupported device type: {}!'.format(device)) # pragma: no cover diff --git a/projectq/setups/awsbraket_test.py b/projectq/setups/awsbraket_test.py index 2f78d8bc2..6f8ad41b7 100644 --- a/projectq/setups/awsbraket_test.py +++ b/projectq/setups/awsbraket_test.py @@ -14,9 +14,10 @@ # limitations under the License. """Tests for projectq.setup.awsbraket.""" -import pytest -from unittest.mock import patch import json +from unittest.mock import patch + +import pytest # ============================================================================== diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index cca7d08c9..908b04cc9 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -14,20 +14,22 @@ # limitations under the License. from . import ( + amplitudeamplification, arb1qubit2rzandry, barrier, carb1qubit2cnotrzandry, - crz2cxandrz, - cnot2rxx, cnot2cz, + cnot2rxx, cnu2toffoliandcu, controlstate, + crz2cxandrz, entangle, globalphase, h2rx, ph2r, - qubitop2onequbit, + phaseestimation, qft2crandhadamard, + qubitop2onequbit, r2rzandph, rx2rz, ry2rz, @@ -35,11 +37,9 @@ sqrtswap2cnot, stateprep2cnot, swap2cnot, - toffoli2cnotandtgate, time_evolution, + toffoli2cnotandtgate, uniformlycontrolledr2cnot, - phaseestimation, - amplitudeamplification, ) all_defined_decomposition_rules = [ diff --git a/projectq/setups/decompositions/_gates_test.py b/projectq/setups/decompositions/_gates_test.py index 3d03d2bc7..5ea730ff0 100755 --- a/projectq/setups/decompositions/_gates_test.py +++ b/projectq/setups/decompositions/_gates_test.py @@ -18,14 +18,15 @@ import pytest +from projectq.backends import Simulator from projectq.cengines import ( - MainEngine, - InstructionFilter, AutoReplacer, - DummyEngine, DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, ) -from projectq.backends import Simulator +from projectq.meta import Control from projectq.ops import ( All, ClassicalInstructionGate, @@ -41,7 +42,6 @@ Toffoli, X, ) -from projectq.meta import Control from projectq.setups.decompositions import ( crz2cxandrz, entangle, diff --git a/projectq/setups/decompositions/amplitudeamplification.py b/projectq/setups/decompositions/amplitudeamplification.py index 7e2a171b0..cbf2b0af7 100644 --- a/projectq/setups/decompositions/amplitudeamplification.py +++ b/projectq/setups/decompositions/amplitudeamplification.py @@ -71,10 +71,8 @@ def func_oracle(eng,system_qubits,qaa_ancilla): import math from projectq.cengines import DecompositionRule -from projectq.meta import Control, Compute, CustomUncompute, Dagger -from projectq.ops import X, Z, Ph, All - -from projectq.ops import QAA +from projectq.meta import Compute, Control, CustomUncompute, Dagger +from projectq.ops import QAA, All, Ph, X, Z def _decompose_QAA(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/amplitudeamplification_test.py b/projectq/setups/decompositions/amplitudeamplification_test.py index 9599c3982..adab7cd92 100644 --- a/projectq/setups/decompositions/amplitudeamplification_test.py +++ b/projectq/setups/decompositions/amplitudeamplification_test.py @@ -16,15 +16,13 @@ "Tests for projectq.setups.decompositions.amplitudeamplification.py." import math + import pytest from projectq.backends import Simulator from projectq.cengines import AutoReplacer, DecompositionRuleSet, MainEngine - -from projectq.ops import X, H, Ry, All, Measure -from projectq.meta import Loop, Control, Compute, Uncompute - -from projectq.ops import QAA +from projectq.meta import Compute, Control, Loop, Uncompute +from projectq.ops import QAA, All, H, Measure, Ry, X from projectq.setups.decompositions import amplitudeamplification as aa diff --git a/projectq/setups/decompositions/arb1qubit2rzandry.py b/projectq/setups/decompositions/arb1qubit2rzandry.py index fadc006d2..699b1fcfd 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Registers the Z-Y decomposition for an arbitrary one qubit gate. +Register the Z-Y decomposition for an arbitrary one qubit gate. See paper "Elementary gates for quantum computing" by Adriano Barenco et al., arXiv:quant-ph/9503016v1. (Note: They use different gate definitions!) @@ -55,8 +55,7 @@ def _recognize_arb1qubit(cmd): def _test_parameters(matrix, a, b_half, c_half, d_half): # pylint: disable=invalid-name """ - It builds matrix U with parameters (a, b/2, c/2, d/2) and compares against - matrix. + Build matrix U with parameters (a, b/2, c/2, d/2) and compares against matrix. U = [[exp(j*(a-b/2-d/2))*cos(c/2), -exp(j*(a-b/2+d/2))*sin(c/2)], [exp(j*(a+b/2-d/2))*sin(c/2), exp(j*(a+b/2+d/2))*cos(c/2)]] @@ -86,14 +85,14 @@ def _test_parameters(matrix, a, b_half, c_half, d_half): # pylint: disable=inva def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-statements """ - Given a 2x2 unitary matrix, find the parameters - a, b/2, c/2, and d/2 such that + Find decomposition parameters. + + Given a 2x2 unitary matrix, find the parameters a, b/2, c/2, and d/2 such that matrix == [[exp(j*(a-b/2-d/2))*cos(c/2), -exp(j*(a-b/2+d/2))*sin(c/2)], [exp(j*(a+b/2-d/2))*sin(c/2), exp(j*(a+b/2+d/2))*cos(c/2)]] Note: - If the matrix is element of SU(2) (determinant == 1), then - we can choose a = 0. + If the matrix is element of SU(2) (determinant == 1), then we can choose a = 0. Args: matrix(list): 2x2 unitary matrix diff --git a/projectq/setups/decompositions/arb1qubit2rzandry_test.py b/projectq/setups/decompositions/arb1qubit2rzandry_test.py index 6d2daa52a..90a72057a 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry_test.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry_test.py @@ -15,8 +15,8 @@ "Tests for projectq.setups.decompositions.arb1qubit2rzandry.py." -from cmath import exp import math +from cmath import exp import numpy as np import pytest @@ -29,6 +29,7 @@ InstructionFilter, MainEngine, ) +from projectq.meta import Control from projectq.ops import ( BasicGate, ClassicalInstructionGate, @@ -41,7 +42,6 @@ Rz, X, ) -from projectq.meta import Control from . import arb1qubit2rzandry as arb1q diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py index b78d20556..b2f8b701a 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Registers the decomposition of an controlled arbitary single qubit gate. +Register the decomposition of an controlled arbitary single qubit gate. See paper "Elementary gates for quantum computing" by Adriano Barenco et al., arXiv:quant-ph/9503016v1. (Note: They use different gate definitions!) or @@ -27,7 +27,7 @@ import numpy from projectq.cengines import DecompositionRule -from projectq.meta import get_control_count, Control +from projectq.meta import Control, get_control_count from projectq.ops import BasicGate, Ph, Ry, Rz, X from projectq.setups.decompositions import arb1qubit2rzandry as arb1q @@ -46,8 +46,7 @@ def _recognize_carb1qubit(cmd): def _test_parameters(matrix, a, b, c_half): # pylint: disable=invalid-name """ - It builds matrix V with parameters (a, b, c/2) and compares against - matrix. + Build matrix V with parameters (a, b, c/2) and compares against matrix. V = [[-sin(c/2) * exp(j*a), exp(j*(a-b)) * cos(c/2)], [exp(j*(a+b)) * cos(c/2), exp(j*a) * sin(c/2)]] @@ -76,7 +75,9 @@ def _test_parameters(matrix, a, b, c_half): # pylint: disable=invalid-name def _recognize_v(matrix): # pylint: disable=too-many-branches """ - Recognizes a matrix which can be written in the following form: + Test whether a matrix has the correct form. + + Recognize a matrix which can be written in the following form: V = [[-sin(c/2) * exp(j*a), exp(j*(a-b)) * cos(c/2)], [exp(j*(a+b)) * cos(c/2), exp(j*a) * sin(c/2)]] diff --git a/projectq/setups/decompositions/cnot2cz.py b/projectq/setups/decompositions/cnot2cz.py index 3525e83b8..ea60ce2f3 100644 --- a/projectq/setups/decompositions/cnot2cz.py +++ b/projectq/setups/decompositions/cnot2cz.py @@ -12,12 +12,11 @@ # 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 for a CNOT gate in terms of CZ and Hadamard. -""" + +"""Registers a decomposition to for a CNOT gate in terms of CZ and Hadamard.""" from projectq.cengines import DecompositionRule -from projectq.meta import Compute, get_control_count, Uncompute +from projectq.meta import Compute, Uncompute, get_control_count from projectq.ops import CZ, H, X diff --git a/projectq/setups/decompositions/cnot2cz_test.py b/projectq/setups/decompositions/cnot2cz_test.py index 9f50959ab..ca9b3af2f 100644 --- a/projectq/setups/decompositions/cnot2cz_test.py +++ b/projectq/setups/decompositions/cnot2cz_test.py @@ -26,8 +26,7 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import All, CNOT, CZ, Measure, X, Z - +from projectq.ops import CNOT, CZ, All, Measure, X, Z from projectq.setups.decompositions import cnot2cz diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 1a37ff045..7565ef062 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -16,15 +16,14 @@ # Module uses ideas from "Basic circuit compilation techniques # for an ion-trap quantum machine" by Dmitri Maslov (2017) at # https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 -""" -Registers a decomposition to for a CNOT gate in terms of Rxx, Rx and Ry gates. -""" + +"""Register a decomposition to for a CNOT gate in terms of Rxx, Rx and Ry gates.""" import math from projectq.cengines import DecompositionRule from projectq.meta import get_control_count -from projectq.ops import Ph, Rxx, Ry, Rx, X +from projectq.ops import Ph, Rx, Rxx, Ry, X def _decompose_cnot2rxx_M(cmd): # pylint: disable=invalid-name @@ -52,7 +51,7 @@ def _decompose_cnot2rxx_P(cmd): # pylint: disable=invalid-name def _recognize_cnot2(cmd): - """Identify that the command is a CNOT gate (control - X gate)""" + """Identify that the command is a CNOT gate (control - X gate).""" return get_control_count(cmd) == 1 diff --git a/projectq/setups/decompositions/cnot2rxx_test.py b/projectq/setups/decompositions/cnot2rxx_test.py index 31fcbdd42..9cb72fc3e 100644 --- a/projectq/setups/decompositions/cnot2rxx_test.py +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -26,7 +26,7 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import All, CNOT, CZ, Measure, X, Z +from projectq.ops import CNOT, CZ, All, Measure, X, Z from . import cnot2rxx diff --git a/projectq/setups/decompositions/cnu2toffoliandcu.py b/projectq/setups/decompositions/cnu2toffoliandcu.py index fd1d0e2b5..cd4b79901 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu.py @@ -12,8 +12,9 @@ # 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 rule for multi-controlled gates. +Register a decomposition rule for multi-controlled gates. Implements the decomposition of Nielsen and Chuang (Fig. 4.10) which decomposes a C^n(U) gate into a sequence of 2 * (n-1) Toffoli gates and one @@ -21,15 +22,12 @@ """ from projectq.cengines import DecompositionRule -from projectq.meta import get_control_count, Compute, Control, Uncompute +from projectq.meta import Compute, Control, Uncompute, get_control_count from projectq.ops import BasicGate, Toffoli, XGate def _recognize_CnU(cmd): # pylint: disable=invalid-name - """ - Recognize an arbitrary gate which has n>=2 control qubits, except a - Toffoli gate. - """ + """Recognize an arbitrary gate which has n>=2 control qubits, except a Toffoli gate.""" if get_control_count(cmd) == 2: if not isinstance(cmd.gate, XGate): return True @@ -40,11 +38,10 @@ def _recognize_CnU(cmd): # pylint: disable=invalid-name def _decompose_CnU(cmd): # pylint: disable=invalid-name """ - Decompose a multi-controlled gate U with n control qubits 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 for general U - and (n-2) work qubits and 2n - 3 Toffoli gates if U is an X-gate. + 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 diff --git a/projectq/setups/decompositions/cnu2toffoliandcu_test.py b/projectq/setups/decompositions/cnu2toffoliandcu_test.py index e6798af7d..178813beb 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu_test.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu_test.py @@ -27,11 +27,11 @@ ) from projectq.meta import Control from projectq.ops import ( + QFT, All, ClassicalInstructionGate, Measure, Ph, - QFT, Rx, Ry, X, diff --git a/projectq/setups/decompositions/controlstate.py b/projectq/setups/decompositions/controlstate.py index 26a3e5ae7..f10a8add3 100755 --- a/projectq/setups/decompositions/controlstate.py +++ b/projectq/setups/decompositions/controlstate.py @@ -14,21 +14,20 @@ # limitations under the License. """ -Register a decomposition to replace turn negatively controlled qubits into positively controlled qubits by applying X -gates. +Register a decomposition to replace turn negatively controlled qubits into positively controlled qubits. + +This achived by applying X gates to selected qubits. """ from copy import deepcopy + from projectq.cengines import DecompositionRule from projectq.meta import Compute, Uncompute, has_negative_control from projectq.ops import BasicGate, X def _decompose_controlstate(cmd): - """ - Decompose commands with control qubits in negative state (ie. control - qubits with state '0' instead of '1') - """ + """Decompose commands with control qubits in negative state (ie. control qubits with state '0' instead of '1').""" with Compute(cmd.engine): for state, ctrl in zip(cmd.control_state, cmd.control_qubits): if state == '0': diff --git a/projectq/setups/decompositions/controlstate_test.py b/projectq/setups/decompositions/controlstate_test.py index a74538b58..f50a9b01a 100755 --- a/projectq/setups/decompositions/controlstate_test.py +++ b/projectq/setups/decompositions/controlstate_test.py @@ -18,10 +18,15 @@ """ from projectq import MainEngine -from projectq.cengines import DummyEngine, AutoReplacer, InstructionFilter, DecompositionRuleSet +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) from projectq.meta import Control, has_negative_control from projectq.ops import X -from projectq.setups.decompositions import controlstate, cnot2cz +from projectq.setups.decompositions import cnot2cz, controlstate def filter_func(eng, cmd): diff --git a/projectq/setups/decompositions/crz2cxandrz.py b/projectq/setups/decompositions/crz2cxandrz.py index 013fdb978..b36f356ba 100755 --- a/projectq/setups/decompositions/crz2cxandrz.py +++ b/projectq/setups/decompositions/crz2cxandrz.py @@ -20,7 +20,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import get_control_count -from projectq.ops import NOT, Rz, C +from projectq.ops import NOT, C, Rz def _decompose_CRz(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/entangle.py b/projectq/setups/decompositions/entangle.py index 918be886e..30d921a9d 100755 --- a/projectq/setups/decompositions/entangle.py +++ b/projectq/setups/decompositions/entangle.py @@ -21,7 +21,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import Control -from projectq.ops import X, H, Entangle, All +from projectq.ops import All, Entangle, H, X def _decompose_entangle(cmd): diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index c9f27092a..9a0eb4239 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -16,15 +16,14 @@ # Module uses ideas from "Basic circuit compilation techniques for an # ion-trap quantum machine" by Dmitri Maslov (2017) at # https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 -""" -Registers a decomposition for the H gate into an Ry and Rx gate. -""" + +"""Register a decomposition for the H gate into an Ry and Rx gate.""" import math from projectq.cengines import DecompositionRule from projectq.meta import get_control_count -from projectq.ops import Ph, Rx, Ry, H +from projectq.ops import H, Ph, Rx, Ry def _decompose_h2rx_M(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py index ff06b277e..551517e42 100644 --- a/projectq/setups/decompositions/h2rx_test.py +++ b/projectq/setups/decompositions/h2rx_test.py @@ -26,7 +26,7 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import Measure, H, HGate +from projectq.ops import H, HGate, Measure from . import h2rx diff --git a/projectq/setups/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py index 5059ad1ec..58dea6e35 100644 --- a/projectq/setups/decompositions/phaseestimation.py +++ b/projectq/setups/decompositions/phaseestimation.py @@ -86,9 +86,7 @@ def two_qubit_gate(system_q, time): from projectq.cengines import DecompositionRule from projectq.meta import Control, Loop -from projectq.ops import H, Tensor, get_inverse, QFT - -from projectq.ops import QPE +from projectq.ops import QFT, QPE, H, Tensor, get_inverse def _decompose_QPE(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/phaseestimation_test.py b/projectq/setups/decompositions/phaseestimation_test.py index 118641d7e..151b8643a 100644 --- a/projectq/setups/decompositions/phaseestimation_test.py +++ b/projectq/setups/decompositions/phaseestimation_test.py @@ -16,23 +16,18 @@ "Tests for projectq.setups.decompositions.phaseestimation.py." import cmath + import numpy as np -from flaky import flaky import pytest +from flaky import flaky +import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot +import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot from projectq.backends import Simulator -from projectq.cengines import ( - AutoReplacer, - DecompositionRuleSet, - MainEngine, -) - -from projectq.ops import X, H, All, Measure, Tensor, Ph, CNOT, StatePreparation, QPE - +from projectq.cengines import AutoReplacer, DecompositionRuleSet, MainEngine +from projectq.ops import CNOT, QPE, All, H, Measure, Ph, StatePreparation, Tensor, X from projectq.setups.decompositions import phaseestimation as pe from projectq.setups.decompositions import qft2crandhadamard as dqft -import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot -import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot @flaky(max_runs=5, min_passes=2) @@ -44,8 +39,9 @@ def test_simple_test_X_eigenvectors(): AutoReplacer(rule_set), ], ) + N = 150 results = np.array([]) - for i in range(150): + for i in range(N): autovector = eng.allocate_qureg(1) X | autovector H | autovector @@ -62,8 +58,8 @@ def test_simple_test_X_eigenvectors(): eng.flush() num_phase = (results == 0.5).sum() - assert num_phase / 100.0 >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / 100.0, + assert num_phase / N >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / N, 0.35, ) @@ -77,8 +73,9 @@ def test_Ph_eigenvectors(): AutoReplacer(rule_set), ], ) + N = 150 results = np.array([]) - for i in range(150): + for i in range(N): autovector = eng.allocate_qureg(1) theta = cmath.pi * 2.0 * 0.125 unit = Ph(theta) @@ -94,8 +91,8 @@ def test_Ph_eigenvectors(): eng.flush() num_phase = (results == 0.125).sum() - assert num_phase / 100.0 >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / 100.0, + assert num_phase / N >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / N, 0.35, ) @@ -115,8 +112,9 @@ def test_2qubitsPh_andfunction_eigenvectors(): AutoReplacer(rule_set), ], ) + N = 150 results = np.array([]) - for i in range(150): + for i in range(N): autovector = eng.allocate_qureg(2) X | autovector[0] ancillas = eng.allocate_qureg(3) @@ -131,8 +129,8 @@ def test_2qubitsPh_andfunction_eigenvectors(): eng.flush() num_phase = (results == 0.125).sum() - assert num_phase / 100.0 >= 0.34, "Statistics phase calculation are not correct (%f vs. %f)" % ( - num_phase / 100.0, + assert num_phase / N >= 0.34, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / N, 0.34, ) @@ -145,10 +143,11 @@ def test_X_no_eigenvectors(): AutoReplacer(rule_set), ], ) + N = 100 results = np.array([]) results_plus = np.array([]) results_minus = np.array([]) - for i in range(100): + for i in range(N): autovector = eng.allocate_qureg(1) amplitude0 = (np.sqrt(2) + np.sqrt(6)) / 4.0 amplitude1 = (np.sqrt(2) - np.sqrt(6)) / 4.0 @@ -176,8 +175,8 @@ def test_X_no_eigenvectors(): eng.flush() total = len(results_plus) + len(results_minus) - plus_probability = len(results_plus) / 100.0 - assert total == pytest.approx(100, abs=5) + plus_probability = len(results_plus) / N + assert total == pytest.approx(N, abs=5) assert plus_probability == pytest.approx( 1.0 / 4.0, abs=1e-1 ), "Statistics on |+> probability are not correct (%f vs. %f)" % ( diff --git a/projectq/setups/decompositions/qft2crandhadamard.py b/projectq/setups/decompositions/qft2crandhadamard.py index 157f8e98a..4d8f9ebb1 100755 --- a/projectq/setups/decompositions/qft2crandhadamard.py +++ b/projectq/setups/decompositions/qft2crandhadamard.py @@ -25,8 +25,8 @@ import math from projectq.cengines import DecompositionRule -from projectq.ops import H, R, QFT from projectq.meta import Control +from projectq.ops import QFT, H, R def _decompose_QFT(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py index c4cc7873b..4109759ed 100644 --- a/projectq/setups/decompositions/qubitop2onequbit.py +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -12,9 +12,8 @@ # 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 rule for a unitary QubitOperator to one qubit gates. -""" + +"""Register a decomposition rule for a unitary QubitOperator to one qubit gates.""" import cmath diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py index 91d95b4d3..44567fc9b 100644 --- a/projectq/setups/decompositions/qubitop2onequbit_test.py +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -17,6 +17,7 @@ import pytest +import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import ( @@ -26,11 +27,9 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import All, Measure, Ph, QubitOperator, X, Y, Z, Command +from projectq.ops import All, Command, Measure, Ph, QubitOperator, X, Y, Z from projectq.types import WeakQubitRef -import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit - def test_recognize(): saving_backend = DummyEngine(save_commands=True) diff --git a/projectq/setups/decompositions/r2rzandph.py b/projectq/setups/decompositions/r2rzandph.py index dbd204721..918038e59 100755 --- a/projectq/setups/decompositions/r2rzandph.py +++ b/projectq/setups/decompositions/r2rzandph.py @@ -21,7 +21,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import Control -from projectq.ops import Ph, Rz, R +from projectq.ops import Ph, R, Rz def _decompose_R(cmd): # pylint: disable=invalid-name diff --git a/projectq/setups/decompositions/rx2rz.py b/projectq/setups/decompositions/rx2rz.py index eb64f63bf..e7839461e 100644 --- a/projectq/setups/decompositions/rx2rz.py +++ b/projectq/setups/decompositions/rx2rz.py @@ -12,13 +12,12 @@ # 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 for the Rx gate into an Rz gate and Hadamard. -""" + +"""Register a decomposition for the Rx gate into an Rz gate and Hadamard.""" from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Control, get_control_count, Uncompute -from projectq.ops import Rx, Rz, H +from projectq.meta import Compute, Control, Uncompute, get_control_count +from projectq.ops import H, Rx, Rz def _decompose_rx(cmd): diff --git a/projectq/setups/decompositions/ry2rz.py b/projectq/setups/decompositions/ry2rz.py index 4dc3dca1c..040907b3f 100644 --- a/projectq/setups/decompositions/ry2rz.py +++ b/projectq/setups/decompositions/ry2rz.py @@ -12,14 +12,13 @@ # 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 for the Ry gate into an Rz and Rx(pi/2) gate. -""" + +"""Register a decomposition for the Ry gate into an Rz and Rx(pi/2) gate.""" import math from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Control, get_control_count, Uncompute +from projectq.meta import Compute, Control, Uncompute, get_control_count from projectq.ops import Rx, Ry, Rz diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index 380b14a19..f61c10a11 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -16,15 +16,13 @@ # Module uses ideas from "Basic circuit compilation techniques for an # ion-trap quantum machine" by Dmitri Maslov (2017) at # https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 -""" -Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) -gate -""" + +"""Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) gate.""" import math from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Control, get_control_count, Uncompute +from projectq.meta import Compute, Control, Uncompute, get_control_count from projectq.ops import Rx, Ry, Rz diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index f1ca4a826..63dbc1f60 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -16,6 +16,7 @@ "Tests for projectq.setups.decompositions.rz2rx.py" import math + import numpy as np import pytest diff --git a/projectq/setups/decompositions/sqrtswap2cnot.py b/projectq/setups/decompositions/sqrtswap2cnot.py index 4c9ce919a..8e5392a51 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot.py +++ b/projectq/setups/decompositions/sqrtswap2cnot.py @@ -12,9 +12,8 @@ # 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. -""" + +"""Register a decomposition to achieve a SqrtSwap gate.""" from projectq.cengines import DecompositionRule from projectq.meta import Compute, Control, Uncompute @@ -23,7 +22,6 @@ def _decompose_sqrtswap(cmd): """Decompose (controlled) swap gates.""" - if len(cmd.qubits) != 2: raise ValueError('SqrtSwap gate requires two quantum registers') if not (len(cmd.qubits[0]) == 1 and len(cmd.qubits[1]) == 1): diff --git a/projectq/setups/decompositions/sqrtswap2cnot_test.py b/projectq/setups/decompositions/sqrtswap2cnot_test.py index ace87a94e..0ec4706cd 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot_test.py +++ b/projectq/setups/decompositions/sqrtswap2cnot_test.py @@ -16,6 +16,7 @@ import pytest +import projectq.setups.decompositions.sqrtswap2cnot as sqrtswap2cnot from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import ( @@ -24,11 +25,9 @@ DummyEngine, InstructionFilter, ) -from projectq.ops import All, Measure, SqrtSwap, Command +from projectq.ops import All, Command, Measure, SqrtSwap from projectq.types import WeakQubitRef -import projectq.setups.decompositions.sqrtswap2cnot as sqrtswap2cnot - def _decomp_gates(eng, cmd): if isinstance(cmd.gate, SqrtSwap.__class__): diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index c82bd62b9..671bb0097 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -12,9 +12,8 @@ # 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. -""" + +"""Register decomposition for StatePreparation.""" import cmath import math @@ -22,17 +21,15 @@ from projectq.cengines import DecompositionRule from projectq.meta import Control, Dagger from projectq.ops import ( + Ph, StatePreparation, UniformlyControlledRy, UniformlyControlledRz, - Ph, ) def _decompose_state_preparation(cmd): # pylint: disable=too-many-locals - """ - Implements state preparation based on arXiv:quant-ph/0407010v1. - """ + """Implement state preparation based on arXiv:quant-ph/0407010v1.""" eng = cmd.engine if len(cmd.qubits) != 1: raise ValueError('StatePreparation does not support multiple quantum registers!') diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index ffa510ce1..7e129419f 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -15,19 +15,18 @@ """Tests for projectq.setups.decompositions.stateprep2cnot.""" import cmath -from copy import deepcopy import math +from copy import deepcopy import numpy as np import pytest import projectq -from projectq.ops import All, Command, Measure, Ry, Rz, StatePreparation, Ph +import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot +from projectq.ops import All, Command, Measure, Ph, Ry, Rz, StatePreparation from projectq.setups import restrictedgateset from projectq.types import WeakQubitRef -import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot - def test_invalid_arguments(): qb0 = WeakQubitRef(engine=None, idx=0) diff --git a/projectq/setups/decompositions/swap2cnot.py b/projectq/setups/decompositions/swap2cnot.py index c7c438824..df17b4e4c 100755 --- a/projectq/setups/decompositions/swap2cnot.py +++ b/projectq/setups/decompositions/swap2cnot.py @@ -20,8 +20,8 @@ """ from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Uncompute, Control -from projectq.ops import Swap, CNOT +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import CNOT, Swap def _decompose_swap(cmd): diff --git a/projectq/setups/decompositions/time_evolution.py b/projectq/setups/decompositions/time_evolution.py index 1bce70fd5..9453f767e 100644 --- a/projectq/setups/decompositions/time_evolution.py +++ b/projectq/setups/decompositions/time_evolution.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Registers decomposition for the TimeEvolution gates. +Register decomposition for the TimeEvolution gates. An exact straight forward decomposition of a TimeEvolution gate is possible if the hamiltonian has only one term or if all the terms commute with each @@ -22,14 +22,12 @@ import math from projectq.cengines import DecompositionRule -from projectq.meta import Control, Compute, Uncompute -from projectq.ops import TimeEvolution, QubitOperator, H, CNOT, Rz, Rx, Ry +from projectq.meta import Compute, Control, Uncompute +from projectq.ops import CNOT, H, QubitOperator, Rx, Ry, Rz, TimeEvolution def _recognize_time_evolution_commuting_terms(cmd): - """ - Recognize all TimeEvolution gates with >1 terms but which all commute. - """ + """Recognize all TimeEvolution gates with >1 terms but which all commute.""" hamiltonian = cmd.gate.hamiltonian if len(hamiltonian.terms) == 1: return False @@ -61,7 +59,7 @@ def _recognize_time_evolution_individual_terms(cmd): def _decompose_time_evolution_individual_terms(cmd): # pylint: disable=too-many-branches """ - Implements a TimeEvolution gate with a hamiltonian having only one term. + Implement a TimeEvolution gate with a hamiltonian having only one term. To implement exp(-i * t * hamiltonian), where the hamiltonian is only one term, e.g., hamiltonian = X0 x Y1 X Z2, we first perform local diff --git a/projectq/setups/decompositions/time_evolution_test.py b/projectq/setups/decompositions/time_evolution_test.py index 293aba089..1710d5791 100644 --- a/projectq/setups/decompositions/time_evolution_test.py +++ b/projectq/setups/decompositions/time_evolution_test.py @@ -19,19 +19,29 @@ import numpy import pytest import scipy -from scipy import sparse as sps import scipy.sparse.linalg +from scipy import sparse as sps from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import ( - DummyEngine, AutoReplacer, - InstructionFilter, DecompositionRuleSet, + DummyEngine, + InstructionFilter, ) from projectq.meta import Control -from projectq.ops import QubitOperator, TimeEvolution, ClassicalInstructionGate, Ph, Rx, Ry, All, Measure, Command +from projectq.ops import ( + All, + ClassicalInstructionGate, + Command, + Measure, + Ph, + QubitOperator, + Rx, + Ry, + TimeEvolution, +) from projectq.types import WeakQubitRef from . import time_evolution as te @@ -143,7 +153,7 @@ def test_decompose_individual_terms_invalid(): qb1 = WeakQubitRef(eng, idx=1) op1 = QubitOperator("X0 Y1", 0.5) op2 = op1 + QubitOperator("Y2 X4", -0.5) - op3 = QubitOperator(tuple(), 0.5) + op3 = QubitOperator((), 0.5) op4 = QubitOperator("X0 Y0", 0.5) with pytest.raises(ValueError): diff --git a/projectq/setups/decompositions/toffoli2cnotandtgate.py b/projectq/setups/decompositions/toffoli2cnotandtgate.py index c3e794d75..cdaaf5bd6 100755 --- a/projectq/setups/decompositions/toffoli2cnotandtgate.py +++ b/projectq/setups/decompositions/toffoli2cnotandtgate.py @@ -20,7 +20,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import get_control_count -from projectq.ops import NOT, CNOT, T, Tdag, H +from projectq.ops import CNOT, NOT, H, T, Tdag def _decompose_toffoli(cmd): diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py index a04263b18..62571e82e 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py @@ -12,9 +12,8 @@ # 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. -""" + +"""Register decomposition for UnformlyControlledRy and UnformlyControlledRz.""" from projectq.cengines import DecompositionRule from projectq.meta import Compute, Control, CustomUncompute diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py index 7361d4292..1623ce51c 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py @@ -16,6 +16,7 @@ import pytest +import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import ( @@ -24,7 +25,6 @@ DummyEngine, InstructionFilter, ) - from projectq.meta import Compute, Control, Uncompute from projectq.ops import ( All, @@ -36,8 +36,6 @@ X, ) -import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot - def slow_implementation(angles, control_qubits, target_qubit, eng, gate_class): """ diff --git a/projectq/setups/default.py b/projectq/setups/default.py index 942b66894..8ac280417 100755 --- a/projectq/setups/default.py +++ b/projectq/setups/default.py @@ -14,20 +14,23 @@ # limitations under the License. """ -Defines the default setup which provides an `engine_list` for the `MainEngine` +The default setup which provides an `engine_list` for the `MainEngine`. -It contains `LocalOptimizers` and an `AutoReplacer` which uses most of the -decompositions rules defined in projectq.setups.decompositions +It contains `LocalOptimizers` and an `AutoReplacer` which uses most of the decompositions rules defined in +projectq.setups.decompositions """ import projectq import projectq.setups.decompositions -from projectq.cengines import TagRemover, LocalOptimizer, AutoReplacer, DecompositionRuleSet +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + LocalOptimizer, + TagRemover, +) def get_engine_list(): - """ - Return the default list of compiler engine. - """ + """Return the default list of compiler engine.""" rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) return [TagRemover(), LocalOptimizer(10), AutoReplacer(rule_set), TagRemover(), LocalOptimizer(10)] diff --git a/projectq/setups/grid.py b/projectq/setups/grid.py index 49dd393fb..1261a72d4 100644 --- a/projectq/setups/grid.py +++ b/projectq/setups/grid.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Defines a setup to compile to qubits placed in 2-D grid. +A setup to compile to qubits placed in 2-D grid. It provides the `engine_list` for the `MainEngine`. This engine list contains an AutoReplacer with most of the gate decompositions of ProjectQ, which are used to decompose a circuit into only two qubit gates and arbitrary single qubit @@ -30,7 +30,7 @@ def get_engine_list(num_rows, num_columns, one_qubit_gates="any", two_qubit_gates=(CNOT, Swap)): """ - Returns an engine list to compile to a 2-D grid of qubits. + Return an engine list to compile to a 2-D grid of qubits. Note: If you choose a new gate set for which the compiler does not yet have standard rules, it raises an diff --git a/projectq/setups/grid_test.py b/projectq/setups/grid_test.py index 3794e33f0..20ed6a7f2 100644 --- a/projectq/setups/grid_test.py +++ b/projectq/setups/grid_test.py @@ -17,11 +17,10 @@ import pytest import projectq +import projectq.setups.grid as grid_setup from projectq.cengines import DummyEngine, GridMapper from projectq.libs.math import AddConstant -from projectq.ops import BasicGate, CNOT, H, Measure, Rx, Rz, Swap, X - -import projectq.setups.grid as grid_setup +from projectq.ops import CNOT, BasicGate, H, Measure, Rx, Rz, Swap, X def test_mapper_present_and_correct_params(): diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index 559f8efdc..bab8ee824 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -13,32 +13,34 @@ # See the License for the specific language governing permissions and # limitations under the License. """ +A setup for IBM quantum chips. + Defines a setup allowing to compile code for the IBM quantum chips: -->Any 5 qubit devices -->the ibmq online simulator -->the melbourne 15 qubit device +* Any 5 qubit devices +* the ibmq online simulator +* the melbourne 15 qubit device + +It provides the `engine_list` for the `MainEngine' based on the requested device. -It provides the `engine_list` for the `MainEngine' based on the requested -device. Decompose the circuit into a Rx/Ry/Rz/H/CNOT gate set that will be -translated in the backend in the U1/U2/U3/CX gate set. +Decompose the circuit into a Rx/Ry/Rz/H/CNOT gate set that will be translated in the backend in the U1/U2/U3/CX gate +set. """ -from projectq.setups import restrictedgateset -from projectq.ops import Rx, Ry, Rz, H, CNOT, Barrier +from projectq.backends._exceptions import DeviceNotHandledError, DeviceOfflineError +from projectq.backends._ibm._ibm_http_client import show_devices from projectq.cengines import ( - LocalOptimizer, - IBM5QubitMapper, - SwapAndCNOTFlipper, BasicMapperEngine, GridMapper, + IBM5QubitMapper, + LocalOptimizer, + SwapAndCNOTFlipper, ) -from projectq.backends._ibm._ibm_http_client import show_devices +from projectq.ops import CNOT, Barrier, H, Rx, Ry, Rz +from projectq.setups import restrictedgateset def get_engine_list(token=None, device=None): - """ - Return the default list of compiler engine for the IBM QE platform - """ + """Return the default list of compiler engine for the IBM QE platform.""" # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. @@ -61,7 +63,7 @@ def get_engine_list(token=None, device=None): # Note: Manual Mapper doesn't work, because its map is updated only if # gates are applied if gates in the register are not used, then it # will lead to state errors - res = dict() + res = {} for i in range(devices[device]['nq']): res[i] = i mapper.current_mapping = res @@ -112,18 +114,8 @@ def get_engine_list(token=None, device=None): return setup -class DeviceOfflineError(Exception): - """Exception raised if a selected device is currently offline""" - - -class DeviceNotHandledError(Exception): - """Exception raised if a selected device is cannot handle the circuit""" - - def list2set(coupling_list): - """ - Convert a list() to a set() - """ + """Convert a list() to a set().""" result = [] for element in coupling_list: result.append(tuple(element)) diff --git a/projectq/setups/ibm_test.py b/projectq/setups/ibm_test.py index 4b3bebc19..d7ca80710 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -21,21 +21,19 @@ def test_ibm_cnot_mapper_in_cengines(monkeypatch): import projectq.setups.ibm def mock_show_devices(*args, **kwargs): - connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return { 'ibmq_burlington': { 'coupling_map': connections, @@ -67,21 +65,19 @@ def test_ibm_errors(monkeypatch): import projectq.setups.ibm def mock_show_devices(*args, **kwargs): - connections = set( - [ - (0, 1), - (1, 0), - (1, 2), - (1, 3), - (1, 4), - (2, 1), - (2, 3), - (2, 4), - (3, 1), - (3, 4), - (4, 3), - ] - ) + connections = { + (0, 1), + (1, 0), + (1, 2), + (1, 3), + (1, 4), + (2, 1), + (2, 3), + (2, 4), + (3, 1), + (3, 4), + (4, 3), + } return {'ibmq_imaginary': {'coupling_map': connections, 'version': '0.0.0', 'nq': 6}} monkeypatch.setattr(projectq.setups.ibm, "show_devices", mock_show_devices) diff --git a/projectq/setups/ionq.py b/projectq/setups/ionq.py index 985faa19d..492c16b1b 100644 --- a/projectq/setups/ionq.py +++ b/projectq/setups/ionq.py @@ -14,11 +14,13 @@ # limitations under the License. """ +A setup for IonQ trapped ion devices. + Defines a setup allowing to compile code for IonQ trapped ion devices: ->The 11 qubit device ->The 29 qubits simulator """ -from projectq.backends._ionq._ionq_exc import DeviceOfflineError +from projectq.backends._exceptions import DeviceOfflineError from projectq.backends._ionq._ionq_http_client import show_devices from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper from projectq.ops import ( @@ -44,9 +46,7 @@ def get_engine_list(token=None, device=None): - """ - Return the default list of compiler engine for the IonQ platform - """ + """Return the default list of compiler engine for the IonQ platform.""" devices = show_devices(token) if not device or device not in devices: raise DeviceOfflineError("Error checking engine list: no '{}' devices available".format(device)) diff --git a/projectq/setups/ionq_test.py b/projectq/setups/ionq_test.py index f7ce5a9c4..d319bc95f 100644 --- a/projectq/setups/ionq_test.py +++ b/projectq/setups/ionq_test.py @@ -16,7 +16,7 @@ import pytest -from projectq.backends._ionq._ionq_exc import DeviceOfflineError +from projectq.backends._exceptions import DeviceOfflineError from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper diff --git a/projectq/setups/linear.py b/projectq/setups/linear.py index b0ff5a7e8..223ac2d26 100644 --- a/projectq/setups/linear.py +++ b/projectq/setups/linear.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Defines a setup to compile to qubits placed in a linear chain or a circle. +A setup to compile to qubits placed in a linear chain or a circle. It provides the `engine_list` for the `MainEngine`. This engine list contains an AutoReplacer with most of the gate decompositions of ProjectQ, which are used to decompose a circuit into only two qubit gates and arbitrary single qubit @@ -29,7 +29,7 @@ def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any", two_qubit_gates=(CNOT, Swap)): """ - Returns an engine list to compile to a linear chain of qubits. + Return an engine list to compile to a linear chain of qubits. Note: If you choose a new gate set for which the compiler does not yet have standard rules, it raises an diff --git a/projectq/setups/linear_test.py b/projectq/setups/linear_test.py index 45c480475..7e838f460 100644 --- a/projectq/setups/linear_test.py +++ b/projectq/setups/linear_test.py @@ -17,11 +17,10 @@ import pytest import projectq +import projectq.setups.linear as linear_setup from projectq.cengines import DummyEngine, LinearMapper from projectq.libs.math import AddConstant -from projectq.ops import BasicGate, CNOT, H, Measure, Rx, Rz, Swap, X - -import projectq.setups.linear as linear_setup +from projectq.ops import CNOT, BasicGate, H, Measure, Rx, Rz, Swap, X def test_mapper_present_and_correct_params(): diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index c6d7f034f..86811b682 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -15,10 +15,9 @@ """ Defines a setup to compile to a restricted gate set. -It provides the `engine_list` for the `MainEngine`. This engine list contains -an AutoReplacer with most of the gate decompositions of ProjectQ, which are -used to decompose a circuit into a restricted gate set (with some limitions -on the choice of gates). +It provides the `engine_list` for the `MainEngine`. This engine list contains an AutoReplacer with most of the gate +decompositions of ProjectQ, which are used to decompose a circuit into a restricted gate set (with some limitions on +the choice of gates). """ import inspect @@ -33,20 +32,13 @@ LocalOptimizer, TagRemover, ) -from projectq.ops import ( - BasicGate, - ClassicalInstructionGate, - CNOT, - ControlledGate, -) +from projectq.ops import CNOT, BasicGate, ClassicalInstructionGate, ControlledGate -from ._utils import one_and_two_qubit_gates, high_level_gates +from ._utils import high_level_gates, one_and_two_qubit_gates def default_chooser(cmd, decomposition_list): # pylint: disable=unused-argument - """ - Default chooser function for the AutoReplacer compiler engine. - """ + """Provide the default chooser function for the AutoReplacer compiler engine.""" return decomposition_list[0] @@ -57,21 +49,17 @@ def get_engine_list( # pylint: disable=too-many-branches,too-many-statements compiler_chooser=default_chooser, ): """ - Returns an engine list to compile to a restricted gate set. + Return an engine list to compile to a restricted gate set. Note: - If you choose a new gate set for which the compiler does not yet have - standard rules, it raises an `NoGateDecompositionError` or a - `RuntimeError: maximum recursion depth exceeded...`. Also note that - even the gate sets which work might not yet be optimized. So make sure - to double check and potentially extend the decomposition rules. - This implemention currently requires that the one qubit gates must - contain Rz and at least one of {Ry(best), Rx, H} and the two qubit - gate must contain CNOT (recommended) or CZ. + If you choose a new gate set for which the compiler does not yet have standard rules, it raises an + `NoGateDecompositionError` or a `RuntimeError: maximum recursion depth exceeded...`. Also note that even the + gate sets which work might not yet be optimized. So make sure to double check and potentially extend the + decomposition rules. This implemention currently requires that the one qubit gates must contain Rz and at + least one of {Ry(best), Rx, H} and the two qubit gate must contain CNOT (recommended) or CZ. Note: - Classical instructions gates such as e.g. Flush and Measure are - automatically allowed. + Classical instructions gates such as e.g. Flush and Measure are automatically allowed. Example: get_engine_list(one_qubit_gates=(Rz, Ry, Rx, H), @@ -79,28 +67,21 @@ def get_engine_list( # pylint: disable=too-many-branches,too-many-statements other_gates=(TimeEvolution,)) Args: - one_qubit_gates: "any" allows any one qubit gate, otherwise provide a - tuple of the allowed gates. If the gates are - instances of a class (e.g. X), it allows all gates - which are equal to it. If the gate is a class (Rz), - it allows all instances of this class. Default is - "any" - two_qubit_gates: "any" allows any two qubit gate, otherwise provide a - tuple of the allowed gates. If the gates are - instances of a class (e.g. CNOT), it allows all gates - which are equal to it. If the gate is a class, it - allows all instances of this class. + one_qubit_gates: "any" allows any one qubit gate, otherwise provide a tuple of the allowed gates. If the gates + are instances of a class (e.g. X), it allows all gates which are equal to it. If the gate is + a class (Rz), it allows all instances of this class. + Default is "any" + two_qubit_gates: "any" allows any two qubit gate, otherwise provide a tuple of the allowed gates. If the gates + are instances of a class (e.g. CNOT), it allows all gates which are equal to it. If the gate + is a class, it allows all instances of this class. Default is (CNOT,). - other_gates: A tuple of the allowed gates. If the gates are - instances of a class (e.g. QFT), it allows all gates - which are equal to it. If the gate is a class, it - allows all instances of this class. - compiler_chooser:function selecting the decomposition to use in the - Autoreplacer engine + other_gates: A tuple of the allowed gates. If the gates are instances of a class (e.g. QFT), it allows all + gates which are equal to it. If the gate is a class, it allows all instances of this class. + compiler_chooser:function selecting the decomposition to use in the Autoreplacer engine + Raises: - TypeError: If input is for the gates is not "any" or a tuple. Also if - element within tuple is not a class or instance of BasicGate - (e.g. CRz which is a shortcut function) + TypeError: If input is for the gates is not "any" or a tuple. Also if element within tuple is not a class or + instance of BasicGate (e.g. CRz which is a shortcut function) Returns: A list of suitable compiler engines. diff --git a/projectq/setups/restrictedgateset_test.py b/projectq/setups/restrictedgateset_test.py index bf2a7c8b4..163386902 100644 --- a/projectq/setups/restrictedgateset_test.py +++ b/projectq/setups/restrictedgateset_test.py @@ -17,15 +17,17 @@ import pytest import projectq +import projectq.setups.restrictedgateset as restrictedgateset from projectq.cengines import DummyEngine from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN +from projectq.meta import Control from projectq.ops import ( - BasicGate, CNOT, + QFT, + BasicGate, CRz, H, Measure, - QFT, QubitOperator, Rx, Rz, @@ -34,9 +36,6 @@ Toffoli, X, ) -from projectq.meta import Control - -import projectq.setups.restrictedgateset as restrictedgateset def test_parameter_any(): diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index 4472b7c65..0eda14f37 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -25,15 +25,14 @@ A decomposition chooser is implemented following the ideas in QUOTE for reducing the number of Ry gates in the new circuit. -NOTE: - -Because the decomposition chooser is only called when a gate has to be decomposed, this reduction will work better -when the entire circuit has to be decomposed. Otherwise, If the circuit has both superconding gates and native ion -trapped gates the decomposed circuit will not be optimal. +Note: + Because the decomposition chooser is only called when a gate has to be decomposed, this reduction will work better + when the entire circuit has to be decomposed. Otherwise, If the circuit has both superconding gates and native ion + trapped gates the decomposed circuit will not be optimal. """ +from projectq.ops import Rx, Rxx, Ry from projectq.setups import restrictedgateset -from projectq.ops import Rxx, Rx, Ry # ------------------chooser_Ry_reducer-------------------# # If the qubit is not in the prev_Ry_sign dictionary, then no decomposition @@ -43,7 +42,7 @@ # 1 then the last gate applied (during a decomposition!) was Ry(+math.pi/2) # 0 then the last gate applied (during a decomposition!) was a Rx -prev_Ry_sign = dict() # Keeps track of most recent Ry sign, i.e. +prev_Ry_sign = {} # Keeps track of most recent Ry sign, i.e. # whether we had Ry(-pi/2) or Ry(pi/2) # prev_Ry_sign[qubit_index] should hold -1 or # +1 @@ -51,8 +50,10 @@ def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name, too-many-return-statements """ - Choose the decomposition so as to maximise Ry cancellations, based on the - previous decomposition used for the given qubit. + Choose the decomposition to maximise Ry cancellations. + + Choose the decomposition so as to maximise Ry cancellations, based on the previous decomposition used for the + given qubit. Note: Classical instructions gates e.g. Flush and Measure are automatically @@ -61,7 +62,7 @@ def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name Returns: A decomposition object from the decomposition_list. """ - decomp_rule = dict() + decomp_rule = {} name = 'default' for decomp in decomposition_list: @@ -77,7 +78,7 @@ def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name except IndexError: pass - local_prev_Ry_sign = prev_Ry_sign.setdefault(cmd.engine, dict()) # pylint: disable=invalid-name + local_prev_Ry_sign = prev_Ry_sign.setdefault(cmd.engine, {}) # pylint: disable=invalid-name if name == 'cnot2rxx': ctrl_id = cmd.control_qubits[0].id @@ -124,16 +125,12 @@ def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name def get_engine_list(): """ - Returns an engine list compiling code into a trapped ion based compiled - circuit code. + Return an engine list compiling code into a trapped ion based compiled circuit code. Note: - - - Classical instructions gates such as e.g. Flush and Measure are - automatically allowed. - - The restricted gate set engine does not work with Rxx gates, as - ProjectQ will by default bounce back and forth between Cz gates and Cx - gates. An appropriate decomposition chooser needs to be used! + - Classical instructions gates such as e.g. Flush and Measure are automatically allowed. + - The restricted gate set engine does not work with Rxx gates, as ProjectQ will by default bounce back and + forth between Cz gates and Cx gates. An appropriate decomposition chooser needs to be used! Returns: A list of suitable compiler engines. diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py index 7aaea0ff3..ba5d1518d 100644 --- a/projectq/setups/trapped_ion_decomposer_test.py +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -16,17 +16,17 @@ "Tests for projectq.setups.trapped_ion_decomposer.py." import projectq -from projectq.ops import Rx, Ry, Rz, H, X, CNOT, Measure, Rxx, ClassicalInstructionGate from projectq.cengines import ( - MainEngine, - DummyEngine, AutoReplacer, - TagRemover, - InstructionFilter, - DecompositionRuleSet, DecompositionRule, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, + TagRemover, ) from projectq.meta import get_control_count +from projectq.ops import CNOT, ClassicalInstructionGate, H, Measure, Rx, Rxx, Ry, Rz, X from . import restrictedgateset from .trapped_ion_decomposer import chooser_Ry_reducer, get_engine_list diff --git a/projectq/tests/_factoring_test.py b/projectq/tests/_factoring_test.py index 88ee0db4a..a25a344c9 100755 --- a/projectq/tests/_factoring_test.py +++ b/projectq/tests/_factoring_test.py @@ -19,16 +19,16 @@ import projectq.setups.decompositions from projectq.backends._sim._simulator_test import sim from projectq.cengines import ( - MainEngine, AutoReplacer, DecompositionRuleSet, InstructionFilter, LocalOptimizer, + MainEngine, TagRemover, ) from projectq.libs.math import MultiplyByConstantModN from projectq.meta import Control -from projectq.ops import All, BasicMathGate, get_inverse, H, Measure, QFT, Swap, X +from projectq.ops import QFT, All, BasicMathGate, H, Measure, Swap, X, get_inverse rule_set = DecompositionRuleSet(modules=(projectq.libs.math, projectq.setups.decompositions)) diff --git a/projectq/types/_qubit.py b/projectq/types/_qubit.py index 207efd08d..74daedcac 100755 --- a/projectq/types/_qubit.py +++ b/projectq/types/_qubit.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -This file defines BasicQubit, Qubit, WeakQubit and Qureg. +Definition of BasicQubit, Qubit, WeakQubit and Qureg classes. A Qureg represents a list of Qubit or WeakQubit objects. A Qubit represents a (logical-level) qubit with a unique index provided by the MainEngine. Qubit objects are @@ -51,21 +51,15 @@ def __init__(self, engine, idx): self.engine = engine def __str__(self): - """ - Return string representation of this qubit. - """ + """Return string representation of this qubit.""" return str(self.id) def __bool__(self): - """ - Access the result of a previous measurement and return False / True (0 / 1) - """ + """Access the result of a previous measurement and return False / True (0 / 1).""" return self.engine.main_engine.get_measurement_result(self) def __int__(self): - """ - Access the result of a previous measurement and return as integer (0 / 1). - """ + """Access the result of a previous measurement and return as integer (0 / 1).""" return int(bool(self)) def __eq__(self, other): @@ -79,9 +73,6 @@ def __eq__(self, other): return self is other return isinstance(other, BasicQubit) and self.id == other.id and self.engine == other.engine - def __ne__(self, other): - return not self.__eq__(other) - def __hash__(self): """ Return the hash of this qubit. @@ -104,9 +95,7 @@ class Qubit(BasicQubit): """ def __del__(self): - """ - Destroy the qubit and deallocate it (automatically). - """ + """Destroy the qubit and deallocate it (automatically).""" if self.id == -1: return # If a user directly calls this function, then the qubit gets id == -1 but stays in active_qubits as it is not @@ -185,9 +174,7 @@ def __int__(self): ) def __str__(self): - """ - Get string representation of a quantum register. - """ + """Get string representation of a quantum register.""" if len(self) == 0: return "Qureg[]" @@ -210,15 +197,11 @@ def __str__(self): @property def engine(self): - """ - Return owning engine. - """ + """Return owning engine.""" return self[0].engine @engine.setter def engine(self, eng): - """ - Set owning engine. - """ + """Set owning engine.""" for qb in self: qb.engine = eng diff --git a/projectq/types/_qubit_test.py b/projectq/types/_qubit_test.py index 54287749c..35fc961e6 100755 --- a/projectq/types/_qubit_test.py +++ b/projectq/types/_qubit_test.py @@ -73,7 +73,7 @@ def test_basic_qubit_hash(): assert a == c and hash(a) == hash(c) # For performance reasons, low ids should not collide. - assert len(set(hash(_qubit.BasicQubit(fake_engine, e)) for e in range(100))) == 100 + assert len({hash(_qubit.BasicQubit(fake_engine, e)) for e in range(100)}) == 100 # Important that weakref.WeakSet in projectq.cengines._main.py works. # When id is -1, expect reference equality. diff --git a/pyproject.toml b/pyproject.toml index 9f58329ba..b7e82e9ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,8 +57,6 @@ build-backend = "setuptools.build_meta" '.*_test.py', '.*_fixtures.py', '.*flycheck.*.py', - 'docs/.*', - 'examples/.*', ] extension-pkg-whitelist = [ @@ -102,6 +100,10 @@ testpaths = ['projectq'] ignore-glob = ['*flycheck*.py'] mock_use_standalone_module = true +[tool.isort] + +profile = "black" + [tool.setuptools_scm] write_to = 'VERSION.txt' diff --git a/setup.cfg b/setup.cfg index f01474cb7..15407bf54 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,10 +66,10 @@ max-line-length = 120 exclude = .git, __pycache__, - docs/conf.py, build, dist, __init__.py docstring-quotes = """ +eradicate-whitelist = # yapf: disable# yapf: enable # ============================================================================== diff --git a/setup.py b/setup.py index acb57eb05..bb9402ed1 100755 --- a/setup.py +++ b/setup.py @@ -36,26 +36,26 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -"""Setup.py file""" +"""Setup.py file.""" import distutils.log +import os +import platform +import subprocess +import sys +import tempfile from distutils.cmd import Command -from distutils.spawn import find_executable, spawn from distutils.errors import ( - CompileError, - LinkError, CCompilerError, + CompileError, DistutilsExecError, DistutilsPlatformError, + LinkError, ) -import os -import platform -import subprocess -import sys -import tempfile +from distutils.spawn import find_executable, spawn -from setuptools import setup, Extension from setuptools import Distribution as _Distribution +from setuptools import Extension, setup from setuptools.command.build_ext import build_ext # ============================================================================== @@ -64,23 +64,25 @@ class Pybind11Include: # pylint: disable=too-few-public-methods """ - Helper class to determine the pybind11 include path The purpose of this class is to postpone importing pybind11 - until it is actually installed, so that the ``get_include()`` method can be invoked. + Helper class to determine the pybind11 include path. + + The purpose of this class is to postpone importing pybind11 until it is actually installed, so that the + ``get_include()`` method can be invoked. """ def __init__(self, user=False): + """Initialize a Pybind11Include object.""" self.user = user def __str__(self): + """Conversion to string.""" import pybind11 # pylint: disable=import-outside-toplevel return pybind11.get_include(self.user) def important_msgs(*msgs): - """ - Print an important message. - """ + """Print an important message.""" print('*' * 75) for msg in msgs: print(msg) @@ -88,9 +90,7 @@ def important_msgs(*msgs): def status_msgs(*msgs): - """ - Print a status message. - """ + """Print a status message.""" print('-' * 75) for msg in msgs: print('# INFO: ', msg) @@ -100,11 +100,7 @@ def status_msgs(*msgs): def compiler_test( compiler, flagname=None, link=False, include='', body='', postargs=None ): # pylint: disable=too-many-arguments - """ - Return a boolean indicating whether a flag name is supported on the - specified compiler. - """ - + """Return a boolean indicating whether a flag name is supported on the specified compiler.""" fname = None with tempfile.NamedTemporaryFile('w', suffix='.cpp', delete=False) as temp: temp.write('{}\nint main (int argc, char **argv) {{ {} return 0; }}'.format(include, body)) @@ -167,9 +163,10 @@ def _fix_macosx_header_paths(*args): class BuildFailed(Exception): - """Extension raised if the build fails for any reason""" + """Extension raised if the build fails for any reason.""" def __init__(self): + """Initialize a BuildFailed exception object.""" super().__init__() self.cause = sys.exc_info()[1] # work around py 2/3 different syntax @@ -204,7 +201,7 @@ def __init__(self): class BuildExt(build_ext): - '''A custom build extension for adding compiler-specific options.''' + """A custom build extension for adding compiler-specific options.""" c_opts = { 'msvc': ['/EHsc'], @@ -222,21 +219,25 @@ class BuildExt(build_ext): boolean_options = build_ext.boolean_options + ['gen-compiledb'] def initialize_options(self): + """Initialize this command's options.""" build_ext.initialize_options(self) self.gen_compiledb = None def finalize_options(self): + """Finalize this command's options.""" build_ext.finalize_options(self) if self.gen_compiledb: self.dry_run = True # pylint: disable=attribute-defined-outside-init def run(self): + """Execute this command.""" try: build_ext.run(self) except DistutilsPlatformError as err: raise BuildFailed() from err def build_extensions(self): + """Build the individual C/C++ extensions.""" self._configure_compiler() for ext in self.extensions: @@ -492,7 +493,7 @@ def _cleanup_compiler_flags(self): class ClangTidy(Command): - """A custom command to run Clang-Tidy on all C/C++ source files""" + """A custom command to run Clang-Tidy on all C/C++ source files.""" description = 'run Clang-Tidy on all C/C++ source files' user_options = [('warning-as-errors', None, 'Warning as errors')] @@ -501,12 +502,14 @@ class ClangTidy(Command): sub_commands = [('build_ext', None)] def initialize_options(self): + """Initialize this command's options.""" self.warning_as_errors = None def finalize_options(self): - pass + """Finalize this command's options.""" def run(self): + """Execute this command.""" # Ideally we would use self.run_command(command) but we need to ensure # that --dry-run --gen-compiledb are passed to build_ext regardless of # other arguments @@ -534,7 +537,7 @@ def run(self): class GenerateRequirementFile(Command): - """A custom command to list the dependencies of the current""" + """A custom command to list the dependencies of the current.""" description = 'List the dependencies of the current package' user_options = [ @@ -545,11 +548,13 @@ class GenerateRequirementFile(Command): boolean_options = ['include-all-extras'] def initialize_options(self): + """Initialize this command's options.""" self.include_extras = None self.include_all_extras = None self.extra_pkgs = [] def finalize_options(self): + """Finalize this command's options.""" include_extras = self.include_extras.split(',') try: @@ -563,6 +568,7 @@ def finalize_options(self): self.extra_pkgs.extend(pkgs) def run(self): + """Execute this command.""" with open('requirements.txt', 'w') as req_file: try: for pkg in self.distribution.install_requires: @@ -579,10 +585,10 @@ def run(self): class Distribution(_Distribution): - """Distribution class""" + """Distribution class.""" def has_ext_modules(self): # pylint: disable=no-self-use - """Return whether this distribution has some external modules""" + """Return whether this distribution has some external modules.""" # We want to always claim that we have ext_modules. This will be fine # if we don't actually have them (such as on PyPy) because nothing # will get built, however we don't want to provide an overally broad @@ -596,7 +602,7 @@ def has_ext_modules(self): # pylint: disable=no-self-use def run_setup(with_cext): - """Run the setup() function""" + """Run the setup() function.""" kwargs = {} if with_cext: kwargs['ext_modules'] = ext_modules From a1c8d522721b79ef74a668813af82fccb16002ae Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Wed, 14 Jul 2021 17:24:44 +0000 Subject: [PATCH 5/6] Preparing release v0.7.0 --- CHANGELOG.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5adc9dbbf..83ebb573d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.0] - 2021-07-14 + ### Added -- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit. +- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit. ### Changed + ### Deprecated + ### Fixed - Prevent infinite recursion errors when too many compiler engines are added to the MainEngine @@ -34,7 +38,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix GitHub workflow for publishing a new release - ## [0.6.0] - 2021-06-23 ### Added @@ -66,17 +69,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - Use `setuptools-scm` for versioning + - Added `.editorconfig` file + - Added `pyproject.toml` and `setup.cfg` + - Added CHANGELOG.md + - Added support for GitHub Actions - Build and testing on various plaforms and compilers - Automatic draft of new release - Automatic publication of new release once ready - Automatic upload of releases artifacts to PyPi and GitHub + - Added pre-commit configuration file - Updated cibuildwheels action to v1.11.1 + - Updated thomaseizinger/create-pull-request action to v1.1.0 ## [0.5.1] - 2019-02-15 @@ -117,6 +126,8 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi Future releases might introduce changes that will require Python 3.5 (Python 3.4 and earlier have already been declared deprecated at the time of this writing) -[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.6.0...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.7.0...HEAD + +[0.7.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.6.1...0.7.0 [0.6.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.5.1...0.6.0 From 8ef487d10265e3a76b722fd2918a459de4f36cfb Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Thu, 15 Jul 2021 17:05:23 +0200 Subject: [PATCH 6/6] Update CHANGELOG --- CHANGELOG.md | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ebb573d..530441a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,20 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +### Changed +### Deprecated +### Fixed +### Removed +### Repository + ## [0.7.0] - 2021-07-14 ### Added -- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit. +- UnitarySimulator backend for computing the unitary transformation corresponding to a quantum circuit ### Changed +- Moved some exceptions classes into their own files to avoid code duplication + ### Deprecated ### Fixed - Prevent infinite recursion errors when too many compiler engines are added to the MainEngine - Error in testing the decomposition for the phase estimation gate +- Fixed small issue with matplotlib drawing backend - Make all docstrings PEP257 compliant ### Removed @@ -30,7 +40,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository - Added `isort` to the list of pre-commit hooks -- Added `flake8-docstrings` to the flake8 checks to ensure PEP257 compliance for docstrings +- Added some more flake8 plugins to the list used by `pre-commit`: + + flake8-breakpoint + + flake8-comprehensions + + flake8-docstrings + + flake8-eradicate + + flake8-mutable ## [0.6.1] - 2021-06-23 @@ -126,8 +141,12 @@ The ProjectQ v0.5.x release branch is the last one that is guaranteed to work wi Future releases might introduce changes that will require Python 3.5 (Python 3.4 and earlier have already been declared deprecated at the time of this writing) -[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.7.0...HEAD +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.7.0...HEAD + +[0.7.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.6.1...v0.7.0 + +[0.6.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.5.1...v0.6.0 -[0.7.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.6.1...0.7.0 +[0.5.1]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.5.0...v0.5.1 -[0.6.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.5.1...0.6.0 +[0.5.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/v0.4.2...v0.5.0