diff --git a/.pylintrc b/.pylintrc index 6af30e07e4..f15d30bf61 100644 --- a/.pylintrc +++ b/.pylintrc @@ -20,7 +20,8 @@ persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins=pylint.extensions.docparams # enable checking of docstring args +load-plugins=pylint.extensions.docparams, # enable checking of docstring args + pylint.extensions.docstyle # basic docstring stle checks # Use multiple processes to speed up Pylint. jobs=1 @@ -32,16 +33,7 @@ unsafe-load-any-extension=no # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code -extension-pkg-whitelist=numpy - -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. This option is deprecated -# and it will be removed in Pylint 2.0. -optimize-ast=no +extension-pkg-whitelist= [MESSAGES CONTROL] @@ -65,16 +57,20 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,import-star-module-level,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,long-suffix,old-ne-operator,old-octal-literal,suppressed-message,useless-suppression, - no-self-use, # disabled as it is too verbose +disable=no-self-use, # disabled as it is too verbose fixme, # disabled as TODOs would show up as warnings protected-access, # disabled as we don't follow the public vs private # convention strictly duplicate-code, # disabled as it is too verbose + redundant-returns-doc, # for @abstractmethod, it cannot interpret "pass" # disable the "too-many/few-..." refactoring hints too-many-lines, too-many-branches, too-many-locals, too-many-nested-blocks, too-many-statements, too-many-instance-attributes, too-many-arguments, - too-many-public-methods, too-few-public-methods, too-many-ancestors + too-many-public-methods, too-few-public-methods, too-many-ancestors, + unnecessary-pass, # allow for methods with just "pass", for clarity + no-else-return, # relax "elif" after a clause with a return + docstring-first-line-empty # relax docstring style + @@ -115,9 +111,12 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # v,w = typical vectors # x,y,z = typical axes # _ = placeholder name -# qr,cr,qc = quantum and classical registers, and quantum circuit +# q,r,qr,cr,qc = quantum and classical registers, and quantum circuit # pi = the PI constant -good-names=i,j,k,n,m,ex,v,w,x,y,z,Run,_,logger,qr,cr,qc,pi +# op = operation iterator +# b = basis iterator +good-names=i,j,k,n,m,ex,v,w,x,y,z,Run,_,logger,q,c,r,qr,cr,qc,nd,pi,op,b,ar,br, + __unittest # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,toto,tutu,tata @@ -158,7 +157,7 @@ function-rgx=[a-z_][a-z0-9_]{2,30}$ function-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct method names -method-rgx=(([a-z_][a-z0-9_]{2,49})|(assert[A-Z][a-zA-Z0-9]{2,43}))$ +method-rgx=(([a-z_][a-z0-9_]{2,49})|(assert[A-Z][a-zA-Z0-9]{2,43})|(test_[_a-zA-Z0-9]{2,}))$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,30}$ or camelCase `assert*` in tests. @@ -295,20 +294,17 @@ ignore-mixin-members=yes # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. -ignored-modules=matplotlib.cm +ignored-modules=matplotlib.cm,numpy.random # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local +ignored-classes=optparse.Values,thread._local,_thread._local,QuantumCircuit # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. -generated-members=self.circuit.*,qcs.h,qc1.h,qc2.cx,qc.h,self.u1,self.cx,self.ccx,trial_circuit -# self.circuit.*: false positives when self.circuit == QuantumCircuit, as it -# provides a "__getitem__" dynamic method and gate definition is dynamic. -# For qiskit.extensions.standard, self.foo is also needed. +generated-members= # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that @@ -335,7 +331,7 @@ callbacks=cb_,_cb # List of qualified module names which can have objects that can redefine # builtins. -redefining-builtins-modules=six.moves,future.builtins +redefining-builtins-modules=six.moves,future.builtins,tools.compiler [CLASSES] diff --git a/.travis.yml b/.travis.yml index 40934538ca..c2fc836653 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,7 +84,7 @@ stage_osx: &stage_osx # Define the order of the stages. stages: - # - lint + - lint - test # Define the job matrix explicitly, as matrix expansion causes issues when @@ -94,9 +94,13 @@ jobs: # "lint" stage ########################################################################### # C++ and Python linters - # TODO! - # - stage: lint - + # TODO: C++ clang-tidy! + - stage: lint + name: Python Style and Linter + <<: *stage_linux + script: + - pycodestyle --ignore=E402,W504 --max-line-length=100 qiskit/providers/aer + - pylint -j 2 -rn qiskit/providers/aer # "test" stage ########################################################################### diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..5c339e0667 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,164 @@ +Changelog +========= + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a +Changelog](http://keepachangelog.com/en/1.0.0/). + +> **Types of changes:** +> +> - **Added**: for new features. +> - **Changed**: for changes in existing functionality. +> - **Deprecated**: for soon-to-be removed features. +> - **Removed**: for now removed features. +> - **Fixed**: for any bug fixes. +> - **Security**: in case of vulnerabilities. + +[UNRELEASED](https://github.com/Qiskit/qiskit-aer/compare/0.2.1...HEAD) +======================================================================= + +Added +----- +- Added multi-controlled phase gate to `QubitVector` and changed + multi-controlled Z and multi-controlled u1 gates to use this method (\# 258) +- Added optimized anti-diagonal single-qubit gates to QubitVector (\# 258) + +Changed +------- +- Improve performance of matrix fusion circuit optimization and move fusion + code out of `QubitVector` class and into Fusion optimization class (\#255) + +Removed +------- +- Remove `matrix_sequence` Op type from `Op` class (\#255) + +Fixed +----- +- Change maximum parameter for depolarizing_error to allow for error channel + with no identity component. (\#243) +- Fixed 2-qubit depolarizing-only error parameter calculation in + basic_device_noise_model (\#243) +- Set maximum workers to ThreadPoolExecutor in AerJob to limit thread creation (\#259) + +[0.2.1](https://github.com/Qiskit/qiskit-aer/compare/0.2.0...0.2.1) - 2019-05-20 +================================================================================ + +Added +----- +- Added 2-qubit Pauli and reset approximation to noise transformation (\#236) +- Added `to_instruction` method to `ReadoutError` (\#257). + +Changed +------- + +- When loading qobj check if all instructions are conditional and raise an + exception if an unsupported instruction is conditional (\#271) +- Deprecate the use of \".as\_dict()\" in favor of \".to\_dict()\" + (\#228) +- Set simulator seed from \"seed\_simulator\" in qobj (\#210) + +Removed +------- + +Fixed +----- + +- Fix memory error handling for huge circuits (\#216) +- Fix equality expressions in Python code (\#208) + +[0.2.0](https://github.com/Qiskit/qiskit-aer/compare/0.1.1...0.2.0) - 2019-05-02 +================================================================================ + +Added +----- + +- Add multiplexer gate (\#192) +- Add [remap\_noise\_model]{.title-ref} function to noise.utils + (\#181) +- Add [\_\_eq\_\_]{.title-ref} method to NoiseModel, QuantumError, + ReadoutError (\#181) +- Add support for labelled gates in noise models (\#175). +- Add optimized mcx, mcy, mcz, mcu1, mcu2, mcu3, gates to QubitVector + (\#124) +- Add optimized controlled-swap gate to QubitVector (\#142) +- Add gate-fusion optimization for QasmContoroller, which is enabled + by setting fusion\_enable=true (\#136) +- Add better management of failed simulations (\#167) +- Add qubits truncate optimization for unused qubits (\#164) +- Add ability to disable depolarizing error on device noise model + (\#131) +- Add initialise simulator instruction to statevector\_state (\#117, + \#137) +- Add coupling maps to simulators (\#93) +- Add circuit optimization framework (\#83) +- Add benchmarking (\#71, \#177) +- Add wheels support for Debian-like distributions (\#69) +- Add autoconfiguration of threads for qasm simulator (\#61) +- Add Simulation method based on Stabilizer Rank Decompositions (\#51) + +Changed +------- + +- Add basis\_gates kwarg to NoiseModel init (\#175). +- Depreciated \"initial\_statevector\" backend option for + QasmSimulator and StatevectorSimulator (\#185) +- Rename \"chop\_threshold\" backend option to \"zero\_threshold\" and + change default value to 1e-10 (\#185). +- Add an optional parameter to [NoiseModel.as\_dict()]{.title-ref} for + returning dictionaries that can be serialized using the standard + [json]{.title-ref} library directly. (\#165) +- Refactor thread management (\#50) + +Removed +------- + +Fixed +----- + +- Improve noise transformations (\#162) +- Improve error reporting (\#160) +- Improve efficiency of parallelization with max\_memory\_mb a new + parameter of backend\_opts (\#61) +- Improve u1 performance in statevector (\#123) +- Fix OpenMP clashing problems on MacOS for the Terra Addon (\#46) + +[0.1.1](https://github.com/Qiskit/qiskit-aer/compare/0.1.0...0.1.1) - 2019-01-24 +================================================================================ + +Added +----- + +- Adds version information when using the standalone simulator (\#36) +- Adds a Clifford stabilizer simulation method to the QasmSimulator + (\#13) +- Improve Circuit and NoiseModel instructions checking (\#31) +- Add reset\_error function to Noise models (\#34) +- Improve error reporting at installation time (\#29) +- Validate n\_qubits before execution (\#24) +- Add qobj method to AerJob (\#19) + +Removed +------- + +- Reference model tests removed from the codebase (\#27) + +Fixed +----- + +- Fix Contributing guide (\#33) +- Fix an import in Terra integration tests (\#33) +- Fix non-OpenMP builds (\#19) + +[0.1.0](https://github.com/Qiskit/qiskit-aer/compare/0.0.0...0.1.0) - 2018-12-19 +================================================================================ + +Added +----- + +- QASM Simulator +- Statevector Simulator +- Unitary Simulator +- Noise models +- Terra integration +- Standalone Simulators support diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index da049c1442..0000000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,120 +0,0 @@ -Changelog -========= - -All notable changes to this project will be documented in this file. - -The format is based on `Keep a Changelog`_. - - **Types of changes:** - - - **Added**: for new features. - - **Changed**: for changes in existing functionality. - - **Deprecated**: for soon-to-be removed features. - - **Removed**: for now removed features. - - **Fixed**: for any bug fixes. - - **Security**: in case of vulnerabilities. - -`UNRELEASED`_ -============= - -Added ------ - -Changed -------- -- Set simulator seed from "seed_simulator" in qobj (#210) - -Removed -------- - -Fixed ------ - - -`0.2.0`_ - 2019-05-02 -===================== - -Added ------ -- Add multiplexer gate (#192) -- Add `remap_noise_model` function to noise.utils (#181) -- Add `__eq__` method to NoiseModel, QuantumError, ReadoutError (#181) -- Add support for labelled gates in noise models (#175). -- Add optimized mcx, mcy, mcz, mcu1, mcu2, mcu3, gates to QubitVector (#124) -- Add optimized controlled-swap gate to QubitVector (#142) -- Add gate-fusion optimization for QasmContoroller, which is enabled by setting fusion_enable=true (#136) -- Add better management of failed simulations (#167) -- Add qubits truncate optimization for unused qubits (#164) -- Add ability to disable depolarizing error on device noise model (#131) -- Add initialise simulator instruction to statevector_state (#117, #137) -- Add coupling maps to simulators (#93) -- Add circuit optimization framework (#83) -- Add benchmarking (#71, #177) -- Add wheels support for Debian-like distributions (#69) -- Add autoconfiguration of threads for qasm simulator (#61) -- Add Simulation method based on Stabilizer Rank Decompositions (#51) - -Changed -------- -- Add basis_gates kwarg to NoiseModel init (#175). -- Depreciated "initial_statevector" backend option for QasmSimulator and StatevectorSimulator (#185) -- Rename "chop_threshold" backend option to "zero_threshold" and change default value to 1e-10 (#185). -- Add an optional parameter to `NoiseModel.as_dict()` for returning dictionaries that can be - serialized using the standard `json` library directly. (#165) -- Refactor thread management (#50) - -Removed -------- - -Fixed ------ -- Improve noise transformations (#162) -- Improve error reporting (#160) -- Improve efficiency of parallelization with max_memory_mb a new parameter of backend_opts (#61) -- Improve u1 performance in statevector (#123) -- Fix OpenMP clashing problems on MacOS for the Terra Addon (#46) - -`0.1.1`_ - 2019-01-24 -===================== - -Added ------ -- Adds version information when using the standalone simulator (#36) -- Adds a Clifford stabilizer simulation method to the QasmSimulator (#13) -- Improve Circuit and NoiseModel instructions checking (#31) -- Add reset_error function to Noise models (#34) -- Improve error reporting at installation time (#29) -- Validate n_qubits before execution (#24) -- Add qobj method to AerJob (#19) - -Removed -------- -- Reference model tests removed from the codebase (#27) - -Fixed ------ -- Fix Contributing guide (#33) -- Fix an import in Terra integration tests (#33) -- Fix non-OpenMP builds (#19) - - - -`0.1.0`_ - 2018-12-19 -===================== - -Added ------ -- QASM Simulator -- Statevector Simulator -- Unitary Simulator -- Noise models -- Terra integration -- Standalone Simulators support - - -.. _UNRELEASED: https://github.com/Qiskit/qiskit-aer/compare/0.2.0...HEAD -.. _0.2.0: https://github.com/Qiskit/qiskit-aer/compare/0.1.1...0.2.0 -.. _0.1.1: https://github.com/Qiskit/qiskit-aer/compare/0.1.0...0.1.1 -.. _0.1.0: https://github.com/Qiskit/qiskit-aer/compare/0.0.0...0.1.0 - -.. _Keep a Changelog: http://keepachangelog.com/en/1.0.0/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 04d4f50fc2..53dbc5ad03 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,7 @@ endif() # # Looking for external libraries # +message(STATUS "Looking for OpenMP support...") find_package(OpenMP QUIET) # This is a hack for building with Apple's LLVM, which doesn't support OpenMP yet # so we need to link with an external library: libomp. @@ -136,12 +137,15 @@ if(OPENMP_FOUND) # the Terra Addon, see issue: https://github.com/Qiskit/qiskit-aer/issues/1 if(NOT SKBUILD) set(OPENMP_EXTERNAL_LIB "${OpenMP_${OpenMP_CXX_LIB_NAMES}_LIBRARY}") - message("Adding Clang: ${OPENMP_EXTERNAL_LIB}") + message(STATUS "Adding Clang: ${OPENMP_EXTERNAL_LIB}") endif() endif() - message("OpenMP found!") + message(STATUS "OpenMP found!") +else() + message(STATUS "WARNING: No OpenMP support found!") endif() +message(STATUS "Looking for NLOHMANN Json library...") set(NLOHMANN_JSON_PATH ${AER_SIMULATOR_CPP_EXTERNAL_LIBS}) find_package(nlohmann_json REQUIRED) find_package(Threads) @@ -160,7 +164,7 @@ else() endif() if(WIN32) - message("Uncompressing OpenBLAS static library...") + message(STATUS "Uncompressing OpenBLAS static library...") execute_process(COMMAND ${CMAKE_COMMAND} -E tar "xvfj" "${AER_SIMULATOR_CPP_SRC_DIR}/third-party/win64/lib/openblas.7z" WORKING_DIRECTORY "${AER_SIMULATOR_CPP_SRC_DIR}/third-party/win64/lib/") endif() @@ -172,7 +176,7 @@ if(NOT BLAS_FOUND) find_package(BLAS REQUIRED) endif() -message("BLAS: ${BLAS_LIBRARIES}") +message(STATUS "BLAS library found: ${BLAS_LIBRARIES}") # Set dependent libraries set(AER_LIBRARIES diff --git a/.github/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md similarity index 100% rename from .github/CODE_OF_CONDUCT.md rename to CODE_OF_CONDUCT.md diff --git a/.github/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 100% rename from .github/CONTRIBUTING.md rename to CONTRIBUTING.md diff --git a/README.md b/README.md index 0ff3df4dd6..85ea1756c5 100755 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ pip install qiskit PIP will handle all dependencies automatically for us and you will always install the latest (and well-tested) version. -To install from source, follow the instructions in the [contribution guidelines](.github/CONTRIBUTING.md). +To install from source, follow the instructions in the [contribution guidelines](https://github.com/Qiskit/qiskit-aer/blob/master/CONTRIBUTING.md). ## Simulating your first quantum program with Qiskit Aer Now that you have Qiskit Aer installed, you can start simulating quantum circuits with noise. Here is a basic example: @@ -68,7 +68,7 @@ print(sim_result.get_counts(qc)) ## Contribution Guidelines If you'd like to contribute to Qiskit, please take a look at our -[contribution guidelines](.github/CONTRIBUTING.md). This project adheres to Qiskit's [code of conduct](.github/CODE_OF_CONDUCT.md). By participating, you are expect to uphold to this code. +[contribution guidelines](https://github.com/Qiskit/qiskit-aer/blob/master/CONTRIBUTING.md). This project adheres to Qiskit's [code of conduct](https://github.com/Qiskit/qiskit-aer/blob/master/CODE_OF_CONDUCT.md). By participating, you are expect to uphold to this code. We use [GitHub issues](https://github.com/Qiskit/qiskit-aer/issues) for tracking requests and bugs. Please use our [slack](https://qiskit.slack.com) for discussion and simple questions. To join our Slack community use the [link](https://join.slack.com/t/qiskit/shared_invite/enQtNDc2NjUzMjE4Mzc0LTMwZmE0YTM4ZThiNGJmODkzN2Y2NTNlMDIwYWNjYzA2ZmM1YTRlZGQ3OGM0NjcwMjZkZGE0MTA4MGQ1ZTVmYzk). For questions that are more suited for a forum we use the Qiskit tag in the [Stack Exchange](https://quantumcomputing.stackexchange.com/questions/tagged/qiskit). diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index b11ce2c5a9..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,56 +0,0 @@ -image: Visual Studio 2017 -environment: - global: - # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the - # /E:ON and /V:ON options are not enabled in the batch script intepreter - # See: http://stackoverflow.com/a/13751649/163740 - CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd" - - matrix: - - PYTHON: "C:\\Miniconda37-x64" - PYTHON_VERSION: "3.7.0" - PYTHON_ARCH: "64" - - PYTHON: "C:\\Miniconda35-x64" - PYTHON_VERSION: "3.5.3" - PYTHON_ARCH: "64" - - PYTHON: "C:\\Miniconda36-x64" - PYTHON_VERSION: "3.6.1" - PYTHON_ARCH: "64" - - -install: - # If there is a newer build queued for the same PR, cancel this one. - # The AppVeyor 'rollout builds' option is supposed to serve the same - # purpose but it is problematic because it tends to cancel builds pushed - # directly to master instead of just PR builds (or the converse). - # credits: JuliaLang developers. - - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` - https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` - Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` - throw "There are newer queued builds for this pull request, failing early." } - # Prepend newly installed Python to the PATH of this build (this cannot be - # done from inside the powershell script as it would require to restart - # the parent CMD process). - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - # Check that we have the expected version and architecture for Python - - python --version - - # Because of issue #193, we have to keep pip in version 18. - - python -m pip install --disable-pip-version-check pip==18 - - pip install cython - - pip install https://github.com/Qiskit/qiskit-terra/archive/master.zip - # Due to https://github.com/ContinuumIO/anaconda-issues/issues/542 - # we need to pass --ignore-installed, otherwise Python3.5 will fail - # while upgrading setuptools. - - pip install --ignore-installed -r requirements-dev.txt - - python setup.py bdist_wheel -- -G "Visual Studio 15 2017 Win64" - - pip install --find-links=dist\ qiskit_aer - - -build: off - -test_script: - # Run the project tests - - python -m unittest discover -s test/terra -v - diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000..b004a74a58 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,45 @@ +# Python package +# Create and test a Python package on multiple Python versions. +# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/python + +trigger: +- master +- stable + +pool: + vmImage: 'vs2017-win2016' +strategy: + matrix: + Python35: + python.version: '3.5' + Python36: + python.version: '3.6' + Python37: + python.version: '3.7' + +steps: + - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" + displayName: Add conda to PATH + - script: conda create --yes --quiet --name qiskit-aer-$(Build.BuildNumber) + displayName: Create Anaconda environment + - script: | + call activate qiskit-aer-$(Build.BuildNumber) + conda install --yes --quiet --name qiskit-aer-$(Build.BuildNumber) python=%PYTHON_VERSION% numpy + displayName: Install Anaconda packages + - bash: | + set -e + source activate qiskit-aer-$(Build.BuildNumber) + git clean -fdX + python -m pip install --disable-pip-version-check pip==18 + pip install cython + pip install git+https://github.com/Qiskit/qiskit-terra.git + pip install --ignore-installed -r requirements-dev.txt + python setup.py bdist_wheel -- -G "Visual Studio 15 2017 Win64" + displayName: 'Install Dependencies and Build Aer' + - bash: | + set -e + source activate qiskit-aer-$(Build.BuildNumber) + pip install dist/qiskit_aer*.whl + python -m unittest discover -s test/terra -v + displayName: 'Install Aer and Run Tests' diff --git a/qiskit/providers/aer/VERSION.txt b/qiskit/providers/aer/VERSION.txt index 0c62199f16..ee1372d33a 100644 --- a/qiskit/providers/aer/VERSION.txt +++ b/qiskit/providers/aer/VERSION.txt @@ -1 +1 @@ -0.2.1 +0.2.2 diff --git a/qiskit/providers/aer/__init__.py b/qiskit/providers/aer/__init__.py index 178e77fa2a..8ffef2ac04 100644 --- a/qiskit/providers/aer/__init__.py +++ b/qiskit/providers/aer/__init__.py @@ -10,6 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +""" Main Terra addon public interface """ + # https://github.com/Qiskit/qiskit-aer/issues/1 # Because of this issue, we need to make sure that Numpy's OpenMP library is initialized # before loading our simulators, so we force it using this ugly trick @@ -19,8 +21,7 @@ np.dot(np.zeros(100), np.zeros(100)) # ... ¯\_(ツ)_/¯ -"""Aer Backends.""" - +# pylint: disable=wrong-import-position from .aerprovider import AerProvider from .aerjob import AerJob from .aererror import AerError diff --git a/qiskit/providers/aer/aerjob.py b/qiskit/providers/aer/aerjob.py index 0ea997bde4..cc43432505 100644 --- a/qiskit/providers/aer/aerjob.py +++ b/qiskit/providers/aer/aerjob.py @@ -16,7 +16,6 @@ from concurrent import futures import logging -import sys import functools from qiskit.providers import BaseJob, JobStatus, JobError @@ -51,7 +50,7 @@ class AerJob(BaseJob): _executor (futures.Executor): executor to handle asynchronous jobs """ - _executor = futures.ThreadPoolExecutor() + _executor = futures.ThreadPoolExecutor(max_workers=1) def __init__(self, backend, job_id, fn, qobj, *args): super().__init__(backend, job_id) diff --git a/qiskit/providers/aer/backends/__init__.py b/qiskit/providers/aer/backends/__init__.py index 1c483eda5e..e7db9d13e0 100644 --- a/qiskit/providers/aer/backends/__init__.py +++ b/qiskit/providers/aer/backends/__init__.py @@ -15,4 +15,3 @@ from .qasm_simulator import QasmSimulator from .statevector_simulator import StatevectorSimulator from .unitary_simulator import UnitarySimulator - diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index 62bc20e513..b676988025 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -50,8 +50,8 @@ def default(self, obj): return obj.tolist() if isinstance(obj, complex): return [obj.real, obj.imag] - if hasattr(obj, "as_dict"): - return obj.as_dict() + if hasattr(obj, "to_dict"): + return obj.to_dict() return super().default(obj) @@ -77,6 +77,7 @@ def __init__(self, controller, configuration, provider=None): super().__init__(configuration, provider=provider) self._controller = controller + # pylint: disable=arguments-differ def run(self, qobj, backend_options=None, noise_model=None, validate=True): """Run a qobj on the backend.""" # Submit job @@ -116,7 +117,7 @@ def _format_qobj_str(self, qobj, backend_options, noise_model): original_config = qobj.config # Convert to dictionary and add new parameters # from noise model and backend options - config = original_config.as_dict() + config = original_config.to_dict() if backend_options is not None: for key, val in backend_options.items(): config[key] = val diff --git a/qiskit/providers/aer/backends/qasm_simulator.py b/qiskit/providers/aer/backends/qasm_simulator.py index 5c18425577..6007f0fafd 100644 --- a/qiskit/providers/aer/backends/qasm_simulator.py +++ b/qiskit/providers/aer/backends/qasm_simulator.py @@ -19,6 +19,7 @@ from qiskit.util import local_hardware_info from qiskit.providers.models import BackendConfiguration from .aerbackend import AerBackend +# pylint: disable=import-error from .qasm_controller_wrapper import qasm_controller_execute from ..aererror import AerError from ..version import __version__ @@ -156,9 +157,9 @@ class QasmSimulator(AerBackend): 'description': 'A C++ simulator with realistic noise for qobj files', 'coupling_map': None, 'basis_gates': [ - 'u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', + 'u1', 'u2', 'u3', 'cx', 'cz', 'cu1', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap', 'multiplexer', 'snapshot', 'unitary', 'reset', - 'initialize', 'kraus' + 'initialize', 'kraus', 'roerror' ], 'gates': [{ 'name': 'TODO', @@ -184,9 +185,9 @@ def _validate(self, qobj, backend_options, noise_model): """ clifford_instructions = [ "id", "x", "y", "z", "h", "s", "sdg", "CX", "cx", "cz", "swap", - "barrier", "reset", "measure" + "barrier", "reset", "measure", 'roerror' ] - unsupported_ch_instructions = ["u2", "u3"] + unsupported_ch_instructions = ["u2", "u3", "cu1"] # Check if noise model is Clifford: method = "automatic" if backend_options and "method" in backend_options: @@ -196,7 +197,7 @@ def _validate(self, qobj, backend_options, noise_model): if clifford_noise: if method != "stabilizer" and noise_model: - for error in noise_model.as_dict()['errors']: + for error in noise_model.to_dict()['errors']: if error['type'] == 'qerror': for circ in error["instructions"]: for instr in circ: @@ -243,16 +244,16 @@ def _validate(self, qobj, backend_options, noise_model): self.name(), system_memory) if method != "automatic": raise AerError(err_string + '.') - else: - if n_qubits > 63: - raise AerError('{}, and has too many qubits to fall ' - 'back to the extended_stabilizer ' - 'method.'.format(err_string)) - if not ch_supported: - raise AerError('{}, and contains instructions ' - 'not supported by the extended_etabilizer ' - 'method.'.format(err_string)) - logger.info( - 'The QasmSimulator will automatically ' - 'switch to the Extended Stabilizer backend, based on ' - 'the memory requirements.') + + if n_qubits > 63: + raise AerError('{}, and has too many qubits to fall ' + 'back to the extended_stabilizer ' + 'method.'.format(err_string)) + if not ch_supported: + raise AerError('{}, and contains instructions ' + 'not supported by the extended_etabilizer ' + 'method.'.format(err_string)) + logger.info( + 'The QasmSimulator will automatically ' + 'switch to the Extended Stabilizer backend, based on ' + 'the memory requirements.') diff --git a/qiskit/providers/aer/backends/statevector_simulator.py b/qiskit/providers/aer/backends/statevector_simulator.py index aa877af726..5471e454fe 100644 --- a/qiskit/providers/aer/backends/statevector_simulator.py +++ b/qiskit/providers/aer/backends/statevector_simulator.py @@ -22,6 +22,7 @@ from qiskit.util import local_hardware_info from qiskit.providers.models import BackendConfiguration from .aerbackend import AerBackend +# pylint: disable=import-error from .statevector_controller_wrapper import statevector_controller_execute from ..aererror import AerError from ..version import __version__ @@ -82,7 +83,7 @@ class StatevectorSimulator(AerBackend): 'max_shots': 1, 'description': 'A C++ statevector simulator for qobj files', 'coupling_map': None, - 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', + 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'cz', 'cu1', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap', 'multiplexer', 'snapshot', 'unitary', 'reset', 'initialize'], 'gates': [ @@ -111,20 +112,19 @@ def _validate(self, qobj, backend_options, noise_model): """ name = self.name() if noise_model is not None: - logger.error("{} cannot be run with a noise.".format(name)) raise AerError("{} does not support noise.".format(name)) n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: - raise AerError('Number of qubits ({}) '.format(n_qubits) + - 'is greater than maximum ({}) '.format(max_qubits) + - 'for "{}" '.format(name) + - 'with {} GB system memory.'.format(int(local_hardware_info()['memory']))) + raise AerError( + 'Number of qubits ({}) is greater than max ({}) for "{}" with {} GB system memory.' + .format(n_qubits, max_qubits, name, int(local_hardware_info()['memory']))) + if qobj.config.shots != 1: - logger.info('"%s" only supports 1 shot. Setting shots=1.', - name) + logger.info('"%s" only supports 1 shot. Setting shots=1.', name) qobj.config.shots = 1 + for experiment in qobj.experiments: exp_name = experiment.header.name if getattr(experiment.config, 'shots', 1) != 1: diff --git a/qiskit/providers/aer/backends/unitary_simulator.py b/qiskit/providers/aer/backends/unitary_simulator.py index bebad03d9c..58811368e9 100644 --- a/qiskit/providers/aer/backends/unitary_simulator.py +++ b/qiskit/providers/aer/backends/unitary_simulator.py @@ -24,6 +24,7 @@ from .aerbackend import AerBackend from ..aererror import AerError +# pylint: disable=import-error from .unitary_controller_wrapper import unitary_controller_execute from ..version import __version__ @@ -84,10 +85,10 @@ class UnitarySimulator(AerBackend): 'open_pulse': False, 'memory': False, 'max_shots': 1, - 'description': 'A Python simulator for computing the unitary' + + 'description': 'A Python simulator for computing the unitary' 'matrix for experiments in qobj files', 'coupling_map': None, - 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', + 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'cz', 'cu1', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap', 'multiplexer', 'snapshot', 'unitary'], 'gates': [ @@ -116,16 +117,14 @@ def _validate(self, qobj, backend_options, noise_model): """ name = self.name() if noise_model is not None: - logger.error("{} cannot be run with a noise.".format(name)) raise AerError("{} does not support noise.".format(name)) n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: - raise AerError('Number of qubits ({}) '.format(n_qubits) + - 'is greater than maximum ({}) '.format(max_qubits) + - 'for "{}" '.format(name) + - 'with {} GB system memory.'.format(int(local_hardware_info()['memory']))) + raise AerError( + 'Number of qubits ({}) is greater than max ({}) for "{}" with {} GB system memory.' + .format(n_qubits, max_qubits, name, int(local_hardware_info()['memory']))) if qobj.config.shots != 1: logger.info('"%s" only supports 1 shot. Setting shots=1.', name) @@ -139,6 +138,6 @@ def _validate(self, qobj, backend_options, noise_model): experiment.config.shots = 1 for operation in experiment.instructions: if operation.name in ['measure', 'reset']: - raise AerError('Unsupported "%s" instruction "%s" ' + - 'in circuit "%s" ', name, - operation.name, exp_name) + raise AerError( + 'Unsupported {} instruction {} in circuit {}' + .format(name, operation.name, exp_name)) diff --git a/qiskit/providers/aer/noise/__init__.py b/qiskit/providers/aer/noise/__init__.py index 3af6e4f58f..f3d0b98b47 100644 --- a/qiskit/providers/aer/noise/__init__.py +++ b/qiskit/providers/aer/noise/__init__.py @@ -81,4 +81,4 @@ from .noise_model import NoiseModel from . import errors from . import device -from . import utils \ No newline at end of file +from . import utils diff --git a/qiskit/providers/aer/noise/device/models.py b/qiskit/providers/aer/noise/device/models.py index f86fa8ed47..6e6b2154f1 100644 --- a/qiskit/providers/aer/noise/device/models.py +++ b/qiskit/providers/aer/noise/device/models.py @@ -265,21 +265,28 @@ def _device_depolarizing_error(qubits, # with dim = 2 ** N for an N-qubit gate error. error = None + num_qubits = len(qubits) + dim = 2 ** num_qubits + if not thermal_relaxation: # Model gate error entirely as depolarizing error - p_depol = _depol_error_value_one_qubit(error_param) + if error_param is not None and error_param > 0: + dim = 2 ** num_qubits + depol_param = dim * error_param / (dim - 1) + else: + depol_param = 0 else: # Model gate error as thermal relaxation and depolarizing # error. # Get depolarizing probability - if len(qubits) == 1: + if num_qubits == 1: t1, t2, _ = relax_params[qubits[0]] - p_depol = _depol_error_value_one_qubit( + depol_param = _depol_error_value_one_qubit( error_param, gate_time, t1=t1, t2=t2) - elif len(qubits) == 2: + elif num_qubits == 2: q0_t1, q0_t2, _ = relax_params[qubits[0]] q1_t1, q1_t2, _ = relax_params[qubits[1]] - p_depol = _depol_error_value_two_qubit( + depol_param = _depol_error_value_two_qubit( error_param, gate_time, qubit0_t1=q0_t1, @@ -290,9 +297,15 @@ def _device_depolarizing_error(qubits, raise NoiseError("Device noise model only supports " "1 and 2-qubit gates when using " "thermal_relaxation=True.") - if p_depol > 0: + if depol_param > 0: + # If the device reports an error_param greater than the maximum + # allowed for a depolarzing error model we will get a non-physical + # depolarizing parameter. + # In this case we truncate it to 1 so that the error channel is a + # completely depolarizing channel E(rho) = id / d + depol_param = min(depol_param, 1.0) error = depolarizing_error( - p_depol, len(qubits), standard_gates=standard_gates) + depol_param, num_qubits, standard_gates=standard_gates) return error @@ -343,7 +356,7 @@ def _excited_population(freq, temperature): def _depol_error_value_one_qubit(error_param, gate_time=0, t1=inf, t2=inf): - """Return 2-qubit depolarizing channel probability for device model""" + """Return 2-qubit depolarizing channel parameter for device model""" # Check trivial case where there is no gate error if error_param is None: return None @@ -367,7 +380,7 @@ def _depol_error_value_one_qubit(error_param, gate_time=0, t1=inf, t2=inf): else: return 0 - # Otherwise we calculate the depolarizing error probability to account + # Otherwise we calculate the depolarizing error parameter to account # for the difference between the relaxation error and gate error if t1 == inf: par1 = 1 @@ -377,8 +390,8 @@ def _depol_error_value_one_qubit(error_param, gate_time=0, t1=inf, t2=inf): par2 = 1 else: par2 = exp(-gate_time / t2) - p_depol = 1 + 3 * (2 * error_param - 1) / (par1 + 2 * par2) - return p_depol + depol_param = 1 + 3 * (2 * error_param - 1) / (par1 + 2 * par2) + return depol_param def _depol_error_value_two_qubit(error_param, @@ -387,7 +400,7 @@ def _depol_error_value_two_qubit(error_param, qubit0_t2=inf, qubit1_t1=inf, qubit1_t2=inf): - """Return 2-qubit depolarizing channel probability for device model""" + """Return 2-qubit depolarizing channel parameter for device model""" # Check trivial case where there is no gate error if error_param is None: return None @@ -405,8 +418,10 @@ def _depol_error_value_two_qubit(error_param, if gate_time is None: gate_time = 0 - if gate_time == 0 or (qubit0_t1 == inf and qubit0_t2 == inf - and qubit1_t1 == inf and qubit1_t2 == inf): + if gate_time == 0 or (qubit0_t1 == inf and + qubit0_t2 == inf and + qubit1_t1 == inf and + qubit1_t2 == inf): if error_param is not None and error_param > 0: return 4 * error_param / 3 else: @@ -433,5 +448,5 @@ def _depol_error_value_two_qubit(error_param, denom = ( q0_par1 + q1_par1 + q0_par1 * q1_par1 + 4 * q0_par2 * q1_par2 + 2 * (q0_par2 + q1_par2) + 2 * (q1_par1 * q0_par2 + q0_par1 * q1_par2)) - p_depol = 1 + 5 * (4 * error_param - 3) / denom - return p_depol + depol_param = 1 + 5 * (4 * error_param - 3) / denom + return depol_param diff --git a/qiskit/providers/aer/noise/device/parameters.py b/qiskit/providers/aer/noise/device/parameters.py index bff0b8c899..160ee423d7 100644 --- a/qiskit/providers/aer/noise/device/parameters.py +++ b/qiskit/providers/aer/noise/device/parameters.py @@ -30,8 +30,8 @@ def gate_param_values(properties): Returns: list: A list of tuples (name, qubits, time, error). If gate - error or gate_time information is not available None will be - returned for value. + error or gate_time information is not available None + will be returned for value. """ values = [] for gate in properties.gates: @@ -63,8 +63,8 @@ def gate_error_values(properties): Returns: list: A list of tuples (name, qubits, value). If gate - error information is not available None will be returned for - value. + error information is not available None will be returned for + value. """ values = [] for gate in properties.gates: @@ -88,8 +88,8 @@ def gate_time_values(properties): Returns: list: A list of tuples (name, qubits, value). If gate - time information is not available None will be returned for - value. + time information is not available None will be returned for + value. """ values = [] for gate in properties.gates: @@ -114,10 +114,11 @@ def readout_error_values(properties): Returns: list: A list of readout error values for qubits. If readout - error information is not available None will be returned for value. + error information is not available None will be returned + for value. """ values = [] - for qubit, qubit_props in enumerate(properties.qubits): + for qubit_props in properties.qubits: value = None # default value params = _check_for_item(qubit_props, 'readout_error') if hasattr(params, 'value'): @@ -142,7 +143,8 @@ def thermal_relaxation_values(properties): Numpy.inf will be used. """ values = [] - for qubit, qubit_props in enumerate(properties.qubits): + for qubit_props in properties.qubits: + # pylint: disable=invalid-name # Default values t1, t2, freq = inf, inf, inf @@ -180,7 +182,7 @@ def thermal_relaxation_values(properties): def _check_for_item(lst, name): """Search list for item with given name.""" filtered = [item for item in lst if item.name == name] - if len(filtered) == 0: + if not filtered: return None else: return filtered[0] diff --git a/qiskit/providers/aer/noise/errors/__init__.py b/qiskit/providers/aer/noise/errors/__init__.py index 52e0759dc9..5ac342cde7 100644 --- a/qiskit/providers/aer/noise/errors/__init__.py +++ b/qiskit/providers/aer/noise/errors/__init__.py @@ -24,4 +24,3 @@ from .standard_errors import phase_amplitude_damping_error from .standard_errors import amplitude_damping_error from .standard_errors import phase_damping_error - diff --git a/qiskit/providers/aer/noise/errors/errorutils.py b/qiskit/providers/aer/noise/errors/errorutils.py index 8530f08c30..9245411e9b 100644 --- a/qiskit/providers/aer/noise/errors/errorutils.py +++ b/qiskit/providers/aer/noise/errors/errorutils.py @@ -41,6 +41,7 @@ def standard_gates_instructions(instructions): return output_instructions +# pylint: disable=too-many-return-statements def standard_gate_instruction(instruction, ignore_phase=True): """Convert a unitary matrix instruction into a standard gate instruction. @@ -241,7 +242,8 @@ def single_qubit_clifford_matrix(j): return mat -def single_qubit_clifford_instructions(j, qubit=0): +# pylint: disable=invalid-name +def single_qubit_clifford_instructions(index, qubit=0): """Return a list of qobj instructions for a single qubit cliffords. The instructions are returned in a basis set consisting of @@ -250,7 +252,7 @@ def single_qubit_clifford_instructions(j, qubit=0): decomposition. Args: - j (int): Clifford index 0, ..., 23. + index (int): Clifford index 0, ..., 23. qubit (int): the qubit to apply the Clifford to. Returns: @@ -260,73 +262,69 @@ def single_qubit_clifford_instructions(j, qubit=0): NoiseError: If index is out of range [0, 23] or qubit invalid. """ - if not isinstance(j, int) or j < 0 or j > 23: + if not isinstance(index, int) or index < 0 or index > 23: raise NoiseError( - "Index {} must be in the range [0, ..., 23]".format(j)) + "Index {} must be in the range [0, ..., 23]".format(index)) if not isinstance(qubit, int) or qubit < 0: raise NoiseError("qubit position must be positive integer.") instructions = [] - for gate in single_qubit_clifford_gates(j): + for gate in single_qubit_clifford_gates(index): instructions.append({"name": gate, "qubits": [qubit]}) return instructions def standard_gate_unitary(name): """Return the unitary matrix for a standard gate.""" - if name in ["id", "I"]: - return np.eye(2, dtype=complex) - if name in ["x", "X"]: - return np.array([[0, 1], [1, 0]], dtype=complex) - if name in ["y", "Y"]: - return np.array([[0, -1j], [1j, 0]], dtype=complex) - if name in ["z", "Z"]: - return np.array([[1, 0], [0, -1]], dtype=complex) - if name in ["h", "H"]: - return np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2) - if name in ["s", "S"]: - return np.array([[1, 0], [0, 1j]], dtype=complex) - if name in ["sdg", "Sdg"]: - return np.array([[1, 0], [0, -1j]], dtype=complex) - if name in ["t", "T"]: - return np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex) - if name in ["tdg", "Tdg"]: - return np.array([[1, 0], [0, np.exp(-1j * np.pi / 4)]], dtype=complex) - if name in ["cx", "CX", "cx_01"]: - return np.array( - [[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]], - dtype=complex) - if name == "cx_10": - return np.array( - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], - dtype=complex) - if name in ["cz", "CZ"]: - return np.array( - [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]], - dtype=complex) - if name in ["swap", "SWAP"]: - return np.array( - [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]], - dtype=complex) - if name in ["ccx", "CCX", "ccx_012", "ccx_102"]: - return np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 0, 0, 0, 0]], - dtype=complex) - if name in ["ccx_021", "ccx_201"]: - return np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 0, 0]], - dtype=complex) - if name in ["ccx_120", "ccx_210"]: - return np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0]], - dtype=complex) - return None + + unitary_matrices = { + ("id", "I"): + np.eye(2, dtype=complex), + ("x", "X"): + np.array([[0, 1], [1, 0]], dtype=complex), + ("y", "Y"): + np.array([[0, -1j], [1j, 0]], dtype=complex), + ("z", "Z"): + np.array([[1, 0], [0, -1]], dtype=complex), + ("h", "H"): + np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2), + ("s", "S"): + np.array([[1, 0], [0, 1j]], dtype=complex), + ("sdg", "Sdg"): + np.array([[1, 0], [0, -1j]], dtype=complex), + ("t", "T"): + np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex), + ("tdg", "Tdg"): + np.array([[1, 0], [0, np.exp(-1j * np.pi / 4)]], dtype=complex), + ("cx", "CX", "cx_01"): + np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]], dtype=complex), + ("cx_10",): + np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]], dtype=complex), + ("cz", "CZ"): + np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]], dtype=complex), + ("swap", "SWAP"): + np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]], dtype=complex), + ("ccx", "CCX", "ccx_012", "ccx_102"): + np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 0, 0, 0, 0]], + dtype=complex), + ("ccx_021", "ccx_201"): + np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1, 0, 0]], + dtype=complex), + ("ccx_120", "ccx_210"): + np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 0]], + dtype=complex) + } + + return next((value for key, value in unitary_matrices.items() if name in key), None) def reset_superop(num_qubits): @@ -454,8 +452,10 @@ def make_unitary_instruction(mat, qubits, standard_gates=True): """ if not is_unitary_matrix(mat): raise NoiseError("Input matrix is not unitary.") - elif isinstance(qubits, int): + + if isinstance(qubits, int): qubits = [qubits] + instruction = {"name": "unitary", "qubits": qubits, "params": [mat]} if standard_gates: return standard_gate_instruction(instruction) diff --git a/qiskit/providers/aer/noise/errors/quantum_error.py b/qiskit/providers/aer/noise/errors/quantum_error.py index 20d44db357..eb6d6bcea2 100644 --- a/qiskit/providers/aer/noise/errors/quantum_error.py +++ b/qiskit/providers/aer/noise/errors/quantum_error.py @@ -27,6 +27,7 @@ from .errorutils import circuit2superop from .errorutils import standard_instruction_channel from .errorutils import standard_instruction_operator +from ...utils.helpers import deprecation logger = logging.getLogger(__name__) @@ -41,6 +42,7 @@ class QuantumError: module. """ + # pylint: disable=invalid-name ATOL = ATOL_DEFAULT RTOL = RTOL_DEFAULT MAX_TOL = 1e-4 @@ -283,6 +285,16 @@ def error_term(self, position): position) + "of error outcomes {}".format(self.size)) def as_dict(self): + """ + DEPRECATED: Use to_dict() + Returns: + dict: The current error as a dictionary. + """ + deprecation("QuantumError::as_dict() method is deprecated and will be removed after 0.3." + "Use '.to_dict()' instead") + return self.to_dict() + + def to_dict(self): """Return the current error as a dictionary.""" error = { "type": "qerror", @@ -343,10 +355,10 @@ def compose(self, other, front=False): last_instr = combined_circuit[-1] last_name = last_instr['name'] name = instr['name'] - can_combine = (last_name in ['id', 'kraus', 'unitary'] - or name in ['id', 'kraus', 'unitary']) - if (can_combine and self._check_instr(last_name) - and self._check_instr(name)): + can_combine = (last_name in ['id', 'kraus', 'unitary'] or + name in ['id', 'kraus', 'unitary']) + if (can_combine and self._check_instr(last_name) and + self._check_instr(name)): combined_circuit[-1] = self._compose_instr( last_instr, instr, self.number_of_qubits) else: @@ -474,10 +486,10 @@ def _tensor_product(self, other, reverse=False): last_instr = combined_circuit[-1] last_name = last_instr['name'] name = instr['name'] - can_combine = (last_name in ['id', 'kraus', 'unitary'] - or name in ['id', 'kraus', 'unitary']) - if (can_combine and self._check_instr(last_name) - and self._check_instr(name)): + can_combine = (last_name in ['id', 'kraus', 'unitary'] or + name in ['id', 'kraus', 'unitary']) + if (can_combine and self._check_instr(last_name) and + self._check_instr(name)): combined_circuit[-1] = self._tensor_instr( last_instr, instr) else: @@ -517,12 +529,12 @@ def _combine_kraus(noise_ops): # remove from lists kraus_instr.pop(i) prob_i = kraus_probs.pop(i) - p = prob + prob_i - kraus = (prob / p) * Kraus( - instr['params']) + (prob_i / p) * Kraus(item['params']) + sum_prob = prob + prob_i + kraus = (prob / sum_prob) * Kraus( + instr['params']) + (prob_i / sum_prob) * Kraus(item['params']) # update instruction instr['param'] = kraus.data - prob = p + prob = sum_prob # append combined instruction to circuits new_circuits.append([instr]) new_probs.append(prob) diff --git a/qiskit/providers/aer/noise/errors/readout_error.py b/qiskit/providers/aer/noise/errors/readout_error.py index e5b05c21bb..e98ae17337 100644 --- a/qiskit/providers/aer/noise/errors/readout_error.py +++ b/qiskit/providers/aer/noise/errors/readout_error.py @@ -18,18 +18,19 @@ import numpy as np from numpy.linalg import norm -from qiskit.quantum_info.operators.predicates import is_identity_matrix +from qiskit.circuit import Instruction from qiskit.quantum_info.operators.predicates import ATOL_DEFAULT, RTOL_DEFAULT from ..noiseerror import NoiseError from .errorutils import qubits_from_mat +from ...utils.helpers import deprecation class ReadoutError: """ Readout error class for Qiskit Aer noise model. """ - + # pylint: disable=invalid-name ATOL = ATOL_DEFAULT RTOL = RTOL_DEFAULT MAX_TOL = 1e-4 @@ -141,7 +142,21 @@ def ideal(self): return True return False + def to_instruction(self): + """Convet the ReadoutError to a circuit Instruction.""" + return Instruction("roerror", 0, self.number_of_qubits, self._probabilities) + def as_dict(self): + """ + DEPRECATED: Use to_dict() + Returns: + dict: The current error as a dictionary. + """ + deprecation("ReadoutError::as_dict() method is deprecated and will be removed after 0.3." + "Use '.to_dict()' instead") + return self.to_dict() + + def to_dict(self): """Return the current error as a dictionary.""" error = { "type": "roerror", @@ -226,7 +241,9 @@ def expand(self, other): @staticmethod def _check_probabilities(probabilities, threshold): """Check probabilities are valid.""" - if len(probabilities) == 0: + # probabilities parameter can be a list or a numpy.ndarray + if (isinstance(probabilities, list) and not probabilities) or \ + (isinstance(probabilities, np.ndarray) and probabilities.size == 0): raise NoiseError("Input probabilities: empty.") num_outcomes = len(probabilities[0]) num_qubits = int(np.log2(num_outcomes)) @@ -243,7 +260,7 @@ def _check_probabilities(probabilities, threshold): if abs(sum(arr) - 1) > threshold: raise NoiseError("Invalid probabilities: sum({})= {} " "is not 1.".format(vec, sum(arr))) - if len(arr[arr < 0]) > 0: + if arr[arr < 0].size > 0: raise NoiseError( "Invalid probabilities: {} " "contains a negative probability.".format(vec)) diff --git a/qiskit/providers/aer/noise/errors/standard_errors.py b/qiskit/providers/aer/noise/errors/standard_errors.py index 69b1385383..58ac60f8e1 100644 --- a/qiskit/providers/aer/noise/errors/standard_errors.py +++ b/qiskit/providers/aer/noise/errors/standard_errors.py @@ -191,7 +191,7 @@ def single_pauli(pauli): # Pauli strings go from qubit-0 on left to qubit-N on right # but pauli ops are tensor product of qubit-N on left to qubit-0 on right # We also drop identity operators to reduce dimension of matrix multiplication - mat = 1 + mat = np.identity(1) qubits = [] if isinstance(pauli, Pauli): pauli_str = pauli.to_label() @@ -203,7 +203,7 @@ def single_pauli(pauli): qubits.append(qubit) elif pstr != 'I': raise NoiseError("Invalid Pauli string.") - if mat is 1: + if mat.size == 1: prob_identity += prob else: circ = make_unitary_instruction( @@ -231,6 +231,7 @@ def single_pauli(pauli): return {'name': 'y'} if pauli == 'Z': return {'name': 'z'} + raise NoiseError("Invalid Pauli string.") prob_identity = 0.0 pauli_circuits = [] @@ -266,12 +267,12 @@ def single_pauli(pauli): return error -def depolarizing_error(prob, num_qubits, standard_gates=True): +def depolarizing_error(param, num_qubits, standard_gates=True): """ Depolarizing quantum error channel. Args: - prob (double): completely depolarizing channel error probability. + param (double): depolarizing error parameter. num_qubits (int): the number of qubits for the error channel. standard_gates (bool): if True return the operators as standard qobj Pauli gate instructions. If false return as @@ -283,21 +284,32 @@ def depolarizing_error(prob, num_qubits, standard_gates=True): Raises: NoiseError: If noise parameters are invalid. - """ - if prob < 0 or prob > 1: - raise NoiseError( - "Depolarizing probability must be in between 0 and 1.") + Additional Information: + The depolarizing channel is defined as: + E(ρ) = (1 - λ) ρ + λ * (I / 2 ** n) + with 0 <= λ <= 4 ** n / (4 ** n - 1) + where λ is the depolarizing error param and n is the number of + qubits. + If λ = 0 this is the identity channel E(ρ) = ρ + If λ = 1 this is a completely depolarizing channel E(ρ) = I / 2 ** n + if λ = 4 ** n / (4 ** n - 1) this is a uniform Pauli error channel + E(ρ) = sum_j P_j ρ P_j / (4 ** n - 1) for all P_j != I. + """ if not isinstance(num_qubits, int) or num_qubits < 1: raise NoiseError("num_qubits must be a positive integer.") + # Check that the depolarizing parameter gives a valid CPTP + num_terms = 4**num_qubits + max_param = num_terms / (num_terms - 1) + if param < 0 or param > max_param: + raise NoiseError("Depolarizing parameter must be in between 0 " + "and {}.".format(max_param)) # Rescale completely depolarizing channel error probs # with the identity component removed - num_terms = 4**num_qubits # terms in completely depolarizing channel - prob_error = prob / num_terms - prob_iden = 1 - ( - num_terms - 1) * prob_error # subtract off non-identity terms - probs = [prob_iden] + (num_terms - 1) * [prob_error] + prob_iden = 1 - param / max_param + prob_pauli = param / num_terms + probs = [prob_iden] + (num_terms - 1) * [prob_pauli] # Generate pauli strings. The order doesn't matter as long # as the all identity string is first. paulis = [ @@ -344,6 +356,7 @@ def reset_error(prob0, prob1=0): return QuantumError(noise_ops) +# pylint: disable=invalid-name def thermal_relaxation_error(t1, t2, time, excited_state_population=0): """ Single-qubit thermal relaxation quantum error channel. diff --git a/qiskit/providers/aer/noise/noise_model.py b/qiskit/providers/aer/noise/noise_model.py index 9be017c579..170564b4b0 100644 --- a/qiskit/providers/aer/noise/noise_model.py +++ b/qiskit/providers/aer/noise/noise_model.py @@ -23,6 +23,7 @@ from .noiseerror import NoiseError from .errors.quantum_error import QuantumError from .errors.readout_error import ReadoutError +from ..utils.helpers import deprecation logger = logging.getLogger(__name__) @@ -173,10 +174,10 @@ def __eq__(self, other): # the same basis_gates # the same noise_qubits # the same noise_instructions - if (not isinstance(other, NoiseModel) - or self.basis_gates != other.basis_gates - or self.noise_qubits != other.noise_qubits - or self.noise_instructions != other.noise_instructions): + if (not isinstance(other, NoiseModel) or + self.basis_gates != other.basis_gates or + self.noise_qubits != other.noise_qubits or + self.noise_instructions != other.noise_instructions): return False # Check default readout errors is equal if not self._readout_errors_equal(other): @@ -423,7 +424,7 @@ def add_nonlocal_quantum_error(self, qs_str = self._qubits2str(qubits) nqs_str = self._qubits2str(noise_qubits) if qs_str in gate_qubit_dict: - noise_qubit_dict = gate_qubit_dict[nqs_str] + noise_qubit_dict = gate_qubit_dict[qs_str] if nqs_str in noise_qubit_dict: new_error = noise_qubit_dict[nqs_str].compose(error) noise_qubit_dict[nqs_str] = new_error @@ -550,6 +551,22 @@ def add_readout_error(self, error, qubits, warnings=True): self._noise_instructions.add("measure") def as_dict(self, serializable=False): + """ + DEPRECATED: Use to_dict() + Returns a dictionary for noise model. + + Args: + serializable (bool): if `True`, return a dict containing only types + that can be serializable by the stdlib `json` module. + + Returns: + dict: a dictionary for a noise model. + """ + deprecation("NoiseModel::as_dict() method is deprecated and will be removed after 0.3." + "Use '.to_dict()' instead") + self.to_dict(serializable) + + def to_dict(self, serializable=False): """ Return dictionary for noise model. @@ -564,14 +581,14 @@ def as_dict(self, serializable=False): # Add default quantum errors for name, error in self._default_quantum_errors.items(): - error_dict = error.as_dict() + error_dict = error.to_dict() error_dict["operations"] = [name] error_list.append(error_dict) # Add specific qubit errors for name, qubit_dict in self._local_quantum_errors.items(): for qubits_str, error in qubit_dict.items(): - error_dict = error.as_dict() + error_dict = error.to_dict() error_dict["operations"] = [name] error_dict["gate_qubits"] = [self._str2qubits(qubits_str)] error_list.append(error_dict) @@ -580,7 +597,7 @@ def as_dict(self, serializable=False): for name, qubit_dict in self._nonlocal_quantum_errors.items(): for qubits_str, noise_qubit_dict in qubit_dict.items(): for noise_qubits_str, error in noise_qubit_dict.items(): - error_dict = error.as_dict() + error_dict = error.to_dict() error_dict["operations"] = [name] error_dict["gate_qubits"] = [self._str2qubits(qubits_str)] error_dict["noise_qubits"] = [ @@ -590,12 +607,12 @@ def as_dict(self, serializable=False): # Add default readout error if self._default_readout_error is not None: - error_dict = self._default_readout_error.as_dict() + error_dict = self._default_readout_error.to_dict() error_list.append(error_dict) # Add local readout error for qubits_str, error in self._local_readout_errors.items(): - error_dict = error.as_dict() + error_dict = error.to_dict() error_dict["gate_qubits"] = [self._str2qubits(qubits_str)] error_list.append(error_dict) diff --git a/qiskit/providers/aer/noise/utils/noise_remapper.py b/qiskit/providers/aer/noise/utils/noise_remapper.py index 948d987b62..0bdbca87c8 100644 --- a/qiskit/providers/aer/noise/utils/noise_remapper.py +++ b/qiskit/providers/aer/noise/utils/noise_remapper.py @@ -84,7 +84,7 @@ def remap_noise_model(noise_model, remapping, discard_qubits=False, warnings=Tru raise NoiseError('Duplicate qubits in remapping: {}'.format(inv_map)) # Convert noise model to dict - nm_dict = noise_model.as_dict() + nm_dict = noise_model.to_dict() # Indexes of errors to keep new_errors = [] diff --git a/qiskit/providers/aer/noise/utils/noise_transformation.py b/qiskit/providers/aer/noise/utils/noise_transformation.py index 2d9f6b2c17..c37da91241 100644 --- a/qiskit/providers/aer/noise/utils/noise_transformation.py +++ b/qiskit/providers/aer/noise/utils/noise_transformation.py @@ -59,7 +59,8 @@ def approximate_quantum_error(error, *, Raises: NoiseError: if number of qubits is not supported or approximation - failsed. + failsed. + RuntimeError: If there's no information about the noise type Additional Information ---------------------- @@ -71,18 +72,22 @@ def approximate_quantum_error(error, *, if not isinstance(error, QuantumError): error = QuantumError(error) - if error.number_of_qubits > 1: - raise NoiseError("Only 1-qubit noises can be converted, {}-qubit " + if error.number_of_qubits > 2: + raise NoiseError("Only 1-qubit and 2-qubit noises can be converted, {}-qubit " "noise found in model".format(error.number_of_qubits)) error_kraus_operators = Kraus(error.to_quantumchannel()).data transformer = NoiseTransformer() if operator_string is not None: + no_info_error = "No information about noise type {}".format(operator_string) operator_string = operator_string.lower() if operator_string not in transformer.named_operators.keys(): + raise RuntimeError(no_info_error) + operator_lists = transformer.named_operators[operator_string] + if len(operator_lists) < error.number_of_qubits: raise RuntimeError( - "No information about noise type {}".format(operator_string)) - operator_dict = transformer.named_operators[operator_string] + no_info_error + " for {} qubits".format(error.number_of_qubits)) + operator_dict = operator_lists[error.number_of_qubits - 1] if operator_dict is not None: names, operator_list = zip(*operator_dict.items()) if operator_list is not None: @@ -94,8 +99,7 @@ def approximate_quantum_error(error, *, identity_prob = 1 - sum(probabilities) if identity_prob < 0 or identity_prob > 1: raise RuntimeError( - "Approximated channel operators probabilities sum to {}". - format(1 - identity_prob)) + "Channel probabilities sum to {}".format(1 - identity_prob)) quantum_error_spec = [([{'name': 'id', 'qubits': [0]}], identity_prob)] op_circuit_list = [ transformer.operator_circuit(operator) @@ -140,8 +144,8 @@ def approximate_noise_model(model, *, For further information see `NoiseTransformer.named_operators`. """ - #We need to iterate over all the errors in the noise model. - #No nice interface for this now, easiest way is to mimic as_dict + # We need to iterate over all the errors in the noise model. + # No nice interface for this now, easiest way is to mimic as_dict error_list = [] # Add default quantum errors @@ -151,7 +155,7 @@ def approximate_noise_model(model, *, operator_string=operator_string, operator_dict=operator_dict, operator_list=operator_list) - error_dict = error.as_dict() + error_dict = error.to_dict() error_dict["operations"] = [operation] error_list.append(error_dict) @@ -163,7 +167,7 @@ def approximate_noise_model(model, *, operator_string=operator_string, operator_dict=operator_dict, operator_list=operator_list) - error_dict = error.as_dict() + error_dict = error.to_dict() error_dict["operations"] = [operation] error_dict["gate_qubits"] = [model._str2qubits(qubits_str)] error_list.append(error_dict) @@ -177,7 +181,7 @@ def approximate_noise_model(model, *, operator_string=operator_string, operator_dict=operator_dict, operator_list=operator_list) - error_dict = error.as_dict() + error_dict = error.to_dict() error_dict["operations"] = [operation] error_dict["gate_qubits"] = [model._str2qubits(qubits_str)] error_dict["noise_qubits"] = [model._str2qubits(noise_str)] @@ -190,7 +194,7 @@ def approximate_noise_model(model, *, operator_string=operator_string, operator_dict=operator_dict, operator_list=operator_list) - error_dict = error.as_dict() + error_dict = error.to_dict() error_list.append(error_dict) # Add local readout error @@ -200,7 +204,7 @@ def approximate_noise_model(model, *, operator_string=operator_string, operator_dict=operator_dict, operator_list=operator_list) - error_dict = error.as_dict() + error_dict = error.to_dict() error_dict["gate_qubits"] = [model._str2qubits(qubits_str)] error_list.append(error_dict) @@ -213,23 +217,76 @@ def approximate_noise_model(model, *, return approx_noise_model +def pauli_operators(): + """ + Returns: + list: a list of Pauli operators for 1 and 2 qubits + + """ + pauli_1_qubit = { + 'X': [{'name': 'x', 'qubits': [0]}], + 'Y': [{'name': 'y', 'qubits': [0]}], + 'Z': [{'name': 'z', 'qubits': [0]}] + } + pauli_2_qubit = { + 'XI': [{'name': 'x', 'qubits': [1]}, {'name': 'id', 'qubits': [0]}], + 'YI': [{'name': 'y', 'qubits': [1]}, {'name': 'id', 'qubits': [0]}], + 'ZI': [{'name': 'z', 'qubits': [1]}, {'name': 'id', 'qubits': [0]}], + 'IX': [{'name': 'id', 'qubits': [1]}, {'name': 'x', 'qubits': [0]}], + 'IY': [{'name': 'id', 'qubits': [1]}, {'name': 'y', 'qubits': [0]}], + 'IZ': [{'name': 'id', 'qubits': [1]}, {'name': 'z', 'qubits': [0]}], + 'XX': [{'name': 'x', 'qubits': [1]}, {'name': 'x', 'qubits': [0]}], + 'YY': [{'name': 'y', 'qubits': [1]}, {'name': 'y', 'qubits': [0]}], + 'ZZ': [{'name': 'z', 'qubits': [1]}, {'name': 'z', 'qubits': [0]}], + 'XY': [{'name': 'x', 'qubits': [1]}, {'name': 'y', 'qubits': [0]}], + 'XZ': [{'name': 'x', 'qubits': [1]}, {'name': 'z', 'qubits': [0]}], + 'YX': [{'name': 'y', 'qubits': [1]}, {'name': 'x', 'qubits': [0]}], + 'YZ': [{'name': 'y', 'qubits': [1]}, {'name': 'z', 'qubits': [0]}], + 'ZX': [{'name': 'z', 'qubits': [1]}, {'name': 'x', 'qubits': [0]}], + 'ZY': [{'name': 'z', 'qubits': [1]}, {'name': 'y', 'qubits': [0]}], + } + return [pauli_1_qubit, pauli_2_qubit] + + +def reset_operators(): + """ + Returns: + list: a list of reset operators for 1 and 2 qubits + + """ + reset_0_to_0 = [{'name': 'reset', 'qubits': [0]}] + reset_0_to_1 = [{'name': 'reset', 'qubits': [0]}, {'name': 'x', 'qubits': [0]}] + reset_1_to_0 = [{'name': 'reset', 'qubits': [1]}] + reset_1_to_1 = [{'name': 'reset', 'qubits': [1]}, {'name': 'x', 'qubits': [1]}] + id_0 = [{'name': 'id', 'qubits': [0]}] + id_1 = [{'name': 'id', 'qubits': [1]}] + + reset_1_qubit = { + 'p': reset_0_to_0, + 'q': reset_0_to_1 + } + + reset_2_qubit = { + 'p0': reset_0_to_0 + id_1, + 'q0': reset_0_to_1 + id_1, + 'p1': reset_1_to_0 + id_0, + 'q1': reset_1_to_1 + id_0, + 'p0_p1': reset_0_to_0 + reset_1_to_0, + 'p0_q1': reset_0_to_0 + reset_1_to_1, + 'q0_p1': reset_0_to_1 + reset_1_to_0, + 'q0_q1': reset_0_to_1 + reset_1_to_1, + } + return [reset_1_qubit, reset_2_qubit] + + class NoiseTransformer: """Transforms one quantum channel to another based on a specified criteria.""" def __init__(self): self.named_operators = { - 'pauli': { - 'X': [{'name': 'x', 'qubits': [0]}], - 'Y': [{'name': 'y', 'qubits': [0]}], - 'Z': [{'name': 'z', 'qubits': [0]}] - }, - 'reset': { - 'p': [{'name': 'reset', 'qubits': [0]}], # reset to |0> - 'q': [{'name': 'reset', 'qubits': [0]}, - {'name': 'x', 'qubits': [0]}] # reset to |1> - }, - 'clifford': dict([(j, single_qubit_clifford_instructions(j)) - for j in range(1, 24)]) + 'pauli': pauli_operators(), + 'reset': reset_operators(), + 'clifford': [{j: single_qubit_clifford_instructions(j) for j in range(24)}] } self.fidelity_data = None self.use_honesty_constraint = True @@ -257,7 +314,7 @@ def operator_circuit(self, operator): Args: operator (operator): operator representation. Can be a noise circuit or a matrix or a list of matrices. - Output: + Returns: List: The operator, converted to noise circuit representation. """ if isinstance(operator, numpy.ndarray): @@ -277,32 +334,34 @@ def transform_by_operator_list(self, transform_channel_operators, noise_kraus_operators): """ Args: - noise_kraus_operators: a list of matrices (Kraus operators) for the input channel - transform_channel_operators: a list of matrices or tuples of matrices - representing Kraus operators that can construct the output channel - e.g. [X,Y,Z] represent the Pauli channel - and [(|0><0|, |0><1|), |1><0|, |1><1|)] represents the relaxation channel - - Output: - A list of amplitudes that define the output channel. - In the case the input is a list [A1, A2, ..., An] of transform matrices - and [E0, E1, ..., Em] of noise kraus operators, the output is - a list [p1, p2, ..., pn] of probabilities such that: - 1) p_i >= 0 - 2) p1 + ... + pn <= 1 - 3) [sqrt(p1)A1, sqrt(p2)A2, ..., sqrt(pn)An, sqrt(1-(p1 + ... + pn))I] is - a list of kraus operators that define the output channel - (which is "close" to the input chanel given by [E0, ..., Em]) - - This channel can be thought of as choosing the operator Ai in probability pi and applying - this operator to the quantum state. - - More generally, if the input is a list of tuples (not neccesarily of the same size): - [(A1, B1, ...), (A2, B2, ...), ... (An, Bn, ...)] then the output is - still a list [p1, p2, ..., pn] and now the output channel is defined by the operators - [sqrt(p1)A1, sqrt(p1)B1, ..., sqrt(pn)An, sqrt(pn)Bn, ..., sqrt(1-(p1 + ... + pn))I] + noise_kraus_operators (List): a list of matrices (Kraus operators) for the input channel + transform_channel_operators (List): a list of matrices or tuples of matrices + representing Kraus operators that can construct the output channel + e.g. [X,Y,Z] represent the Pauli channel + and [(|0><0|, |0><1|), |1><0|, |1><1|)] represents the relaxation channel + + Returns: + List: A list of amplitudes that define the output channel. + In the case the input is a list [A1, A2, ..., An] of transform matrices + and [E0, E1, ..., Em] of noise kraus operators, the output is + a list [p1, p2, ..., pn] of probabilities such that: + 1) p_i >= 0 + 2) p1 + ... + pn <= 1 + 3) [sqrt(p1)A1, sqrt(p2)A2, ..., sqrt(pn)An, sqrt(1-(p1 + ... + pn))I] is + a list of kraus operators that define the output channel + (which is "close" to the input chanel given by [E0, ..., Em]) + + This channel can be thought of as choosing the operator Ai in probability pi and + applying this operator to the quantum state. + + More generally, if the input is a list of tuples (not neccesarily of the same size): + [(A1, B1, ...), (A2, B2, ...), ... (An, Bn, ...)] then the output is + still a list [p1, p2, ..., pn] and now the output channel is defined by theo + perators: + [sqrt(p1)A1, sqrt(p1)B1, ..., sqrt(pn)An, sqrt(pn)Bn, ..., sqrt(1-(p1 + ... + pn))I] """ self.noise_kraus_operators = noise_kraus_operators + # pylint: disable=invalid-name self.transform_channel_operators = transform_channel_operators full_transform_channel_operators = self.prepare_channel_operator_list( self.transform_channel_operators) @@ -315,18 +374,37 @@ def transform_by_operator_list(self, transform_channel_operators, @staticmethod def prepare_channel_operator_list(ops_list): + """ + Prepares a list of channel operators + Args: + ops_list (List): The list of operators to prepare + + Returns: + List: The channel operator list + """ # convert to sympy matrices and verify that each singleton is # in a tuple; also add identity matrix - result = [[sympy.eye(2)]] + result = [] for ops in ops_list: if not isinstance(ops, tuple) and not isinstance(ops, list): ops = [ops] result.append([sympy.Matrix(op) for op in ops]) + n = result[0][0].shape[0] # grab the dimensions from the first element + result = [[sympy.eye(n)]] + result return result + # pylint: disable=invalid-name def prepare_honesty_constraint(self, transform_channel_operators_list): + """ + Prepares the honesty constraint + + Args: + transform_channel_operators_list (list): A list of tuples of matrices which represent + Kraus operators. + """ if not self.use_honesty_constraint: return + goal = self.fidelity(self.noise_kraus_operators) coefficients = [ self.fidelity(ops) for ops in transform_channel_operators_list @@ -334,26 +412,30 @@ def prepare_honesty_constraint(self, transform_channel_operators_list): self.fidelity_data = { 'goal': goal, 'coefficients': - coefficients[1:] # coefficients[0] corresponds to I + coefficients[1:] # coefficients[0] corresponds to I } # methods relevant to the transformation to quadratic programming instance @staticmethod def fidelity(channel): - return sum([numpy.abs(numpy.trace(E))**2 for E in channel]) + """ Calculates channel fidelity """ + return sum([numpy.abs(numpy.trace(E)) ** 2 for E in channel]) + # pylint: disable=invalid-name def generate_channel_matrices(self, transform_channel_operators_list): """ - Generates a list of 4x4 symbolic matrices describing the channel defined from the given operators + Generates a list of 4x4 symbolic matrices describing the channel defined from the given + operators Args: - transform_channel_operators_list: a list of tuples of matrices which represent Kraus operators + transform_channel_operators_list (list): A list of tuples of matrices which represent + Kraus operators. The identity matrix is assumed to be the first element in the list [(I, ), (A1, B1, ...), (A2, B2, ...), ..., (An, Bn, ...)] - e.g. for a Pauli channel, the matrices are + e.g. for a Pauli channel, the matrices are: [(I,), (X,), (Y,), (Z,)] - for relaxation they are + for relaxation they are: [(I, ), (|0><0|, |0><1|), |1><0|, |1><1|)] We consider this input to symbolically represent a channel in the following manner: @@ -364,10 +446,12 @@ def generate_channel_matrices(self, transform_channel_operators_list): This is the channel C symbolically represented by the operators - Output: - A list of 4x4 complex matrices ([D1, D2, ..., Dn], E) such that: - The matrix x1*D1 + ... + xn*Dn + E represents the operation of the channel C on the density operator - we find it easier to work with this representation of C when performing the combinatorial optimization + Returns: + list: A list of 4x4 complex matrices ([D1, D2, ..., Dn], E) such that: + The matrix x1*D1 + ... + xn*Dn + E represents the operation of the channel C + on the density operator. + we find it easier to work with this representation of C when performing the + combinatorial optimization. """ symbols_string = " ".join([ @@ -386,7 +470,7 @@ def generate_channel_matrices(self, transform_channel_operators_list): symbolic_operators = [ op for ops in symbolic_operators_list for op in ops ] - # channel_matrix_representation() peforms the required linear + # channel_matrix_representation() performs the required linear # algebra to find the representing matrices. operators_channel = self.channel_matrix_representation( symbolic_operators).subs(symbols[0], 1 - exp) @@ -395,26 +479,54 @@ def generate_channel_matrices(self, transform_channel_operators_list): @staticmethod def compute_channel_operation(rho, operators): - # Given a quantum state's density function rho, the effect of the - # channel on this state is - # rho -> \sum_{i=1}^n E_i * rho * E_i^\dagger + """ + Given a quantum state's density function rho, the effect of the + channel on this state is: + rho -> sum_{i=1}^n E_i * rho * E_i^dagger + + Args: + rho (number): Density function + operators (list): List of operators + + Returns: + number: The result of applying the list of operators + """ return sum([E * rho * E.H for E in operators], sympy.zeros(operators[0].rows)) @staticmethod def flatten_matrix(m): + """ + Args: + m (Matrix): The matrix to flatten + + Returns: + list: A row vector repesenting the flattened matrix + """ + return [element for element in m] def channel_matrix_representation(self, operators): - # We convert the operators to a matrix by applying the channel to - # the four basis elements of the 2x2 matrix space representing - # density operators; this is standard linear algebra - standard_base = [ - sympy.Matrix([[1, 0], [0, 0]]), - sympy.Matrix([[0, 1], [0, 0]]), - sympy.Matrix([[0, 0], [1, 0]]), - sympy.Matrix([[0, 0], [0, 1]]) - ] + """ + We convert the operators to a matrix by applying the channel to + the four basis elements of the 2x2 matrix space representing + density operators; this is standard linear algebra + + Args: + operators (list): The list of operators to transform into a Matrix + + Returns: + sympy.Matrix: The matrx representation of the operators + """ + + shape = operators[0].shape + standard_base = [] + for i in range(shape[0]): + for j in range(shape[1]): + basis_element_ij = sympy.zeros(*shape) + basis_element_ij[(i, j)] = 1 + standard_base.append(basis_element_ij) + return (sympy.Matrix([ self.flatten_matrix( self.compute_channel_operation(rho, operators)) @@ -425,26 +537,28 @@ def generate_channel_quadratic_programming_matrices( self, channel, symbols): """ Args: - channel: a 4x4 symbolic matrix - symbols: the symbols x1, ..., xn which may occur in the matrix + channel (Matrix): a 4x4 symbolic matrix + symbols (list): the symbols x1, ..., xn which may occur in the matrix - Output: - A list of 4x4 complex matrices ([D1, D2, ..., Dn], E) such that: + Returns: + list: A list of 4x4 complex matrices ([D1, D2, ..., Dn], E) such that: channel == x1*D1 + ... + xn*Dn + E """ - return ([ - self.get_matrix_from_channel(channel, symbol) for symbol in symbols - ], self.get_const_matrix_from_channel(channel, symbols)) + return ( + [self.get_matrix_from_channel(channel, symbol) for symbol in symbols], + self.get_const_matrix_from_channel(channel, symbols) + ) @staticmethod def get_matrix_from_channel(channel, symbol): - """Extract the numeric parameter matrix. + """ + Extract the numeric parameter matrix. Args: channel (matrix): a 4x4 symbolic matrix. symbol (list): a symbol xi - Returns + Returns: matrix: a 4x4 numeric matrix. Additional Information @@ -462,14 +576,15 @@ def get_matrix_from_channel(channel, symbol): @staticmethod def get_const_matrix_from_channel(channel, symbols): - """Extract the numeric constant matrix. + """ + Extract the numeric constant matrix. Args: channel (matrix): a 4x4 symbolic matrix. symbols (list): The full list [x1, ..., xn] of symbols used in the matrix. - Returns + Returns: matrix: a 4x4 numeric matrix. Additional Information @@ -487,10 +602,25 @@ def get_const_matrix_from_channel(channel, symbols): def transform_by_given_channel(self, channel_matrices, const_channel_matrix): - # This method creates the quadratic programming instance for - # minimizing the Hilbert-Schmidt norm of the matrix (A-B) obtained - # as the difference of the input noise channel and the output - # channel we wish to determine. + """ + This method creates objective function representing the + Hilbert-Schmidt norm of the matrix (A-B) obtained + as the difference of the input noise channel and the output + channel we wish to determine. + + This function is represented by a matrix P and a vector q, such that + f(x) = 1/2(x*P*x)+q*x + where x is the vector we wish to minimize, where x represents + probabilities for the noise operators that construct the output channel + + Args: + channel_matrices (list): A list of 4x4 symbolic matrices + const_channel_matrix (matrix): a 4x4 constant matrix + + Returns: + list: a list of the optimal probabilities for the channel matrices, + determined by the quadratic program solver + """ target_channel = SuperOp(Kraus(self.noise_kraus_operators)) target_channel_matrix = target_channel._data.T @@ -500,6 +630,16 @@ def transform_by_given_channel(self, channel_matrices, return self.solve_quadratic_program(P, q) def compute_P(self, As): + """ + This method creates the matrix P in the + f(x) = 1/2(x*P*x)+q*x + representation of the objective function + Args: + As (list): list of symbolic matrices repersenting the channel matrices + + Returns: + matrix: The matrix P for the description of the quadaric program + """ vs = [numpy.array(A).flatten() for A in As] n = len(vs) P = sympy.zeros(n, n) @@ -508,6 +648,17 @@ def compute_P(self, As): return P def compute_q(self, As, C): + """ + This method creates the vector q for the + f(x) = 1/2(x*P*x)+q*x + representation of the objective function + Args: + As (list): list of symbolic matrices repersenting the quadratic program + C (matrix): matrix representing the the constant channel matrix + + Returns: + list: The vector q for the description of the quadaric program + """ vs = [numpy.array(A).flatten() for A in As] vC = numpy.array(C).flatten() n = len(vs) @@ -516,9 +667,30 @@ def compute_q(self, As, C): q[i] = 2 * numpy.real(numpy.dot(numpy.conj(vC), vs[i])) return q - # the following method is the only place in the code where we rely on the cvxopt library - # should we consider another library, only this method needs to change def solve_quadratic_program(self, P, q): + """ + This function solved the quadratic program to minimize the objective function + f(x) = 1/2(x*P*x)+q*x + subject to the additional constraints + Gx <= h + + Where P, q are given and G,h are computed to ensure that x represents + a probability vector and subject to honesty constraints if required + Args: + P (matrix): A matrix representing the P component of the objective function + q (list): A vector representing the q component of the objective function + + Returns: + list: The solution of the quadratic program (represents probabilites) + + Raises: + ImportError: If cvxopt external module is not installed + + Additional information + ====================== + This method is the only place in the code where we rely on the cvxopt library + should we consider another library, only this method needs to change + """ try: import cvxopt except ImportError: @@ -540,4 +712,4 @@ def solve_quadratic_program(self, P, q): G = cvxopt.matrix(numpy.array(G_data).astype(float)) h = cvxopt.matrix(numpy.array(h_data).astype(float)) cvxopt.solvers.options['show_progress'] = False - return cvxopt.solvers.qp(P, q, G, h)['x'] \ No newline at end of file + return cvxopt.solvers.qp(P, q, G, h)['x'] diff --git a/qiskit/providers/aer/utils/__init__.py b/qiskit/providers/aer/utils/__init__.py index 4f3ab871f1..133948e85b 100644 --- a/qiskit/providers/aer/utils/__init__.py +++ b/qiskit/providers/aer/utils/__init__.py @@ -13,3 +13,4 @@ """Utilities""" from . import qobj_utils +from . import helpers diff --git a/qiskit/providers/aer/utils/helpers.py b/qiskit/providers/aer/utils/helpers.py new file mode 100644 index 0000000000..92b967aeec --- /dev/null +++ b/qiskit/providers/aer/utils/helpers.py @@ -0,0 +1,22 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +A bunch of usefull helper functions +""" + +from warnings import warn + + +def deprecation(message): + """ Shows a deprecation message to the user """ + warn(message, DeprecationWarning, stacklevel=2) diff --git a/qiskit/providers/aer/utils/qobj_utils.py b/qiskit/providers/aer/utils/qobj_utils.py index 0da347742b..d8aba62393 100644 --- a/qiskit/providers/aer/utils/qobj_utils.py +++ b/qiskit/providers/aer/utils/qobj_utils.py @@ -29,6 +29,9 @@ def append_instr(qobj, exp_index, instruction): qobj (Qobj): a Qobj object. exp_index (int): The index of the experiment in the qobj. instruction (QasmQobjInstruction): instruction to insert. + + Returns: + qobj(Qobj): The Qobj object """ qobj.experiments[exp_index].instructions.append(instruction) return qobj @@ -40,8 +43,11 @@ def insert_instr(qobj, exp_index, item, pos): Args: qobj (Qobj): a Qobj object exp_index (int): The index of the experiment in the qobj. - instruction(QasmQobjInstruction): instruction to insert. + item (QasmQobjInstruction): instruction to insert. pos (int): the position to insert the item. + + Returns: + qobj(Qobj): The Qobj object """ qobj.experiments[exp_index].instructions.insert(pos, item) return qobj @@ -116,7 +122,7 @@ def measure_instr(qubits, memory, registers=None): if len(qubits) != len(registers): raise ValueError("Number of qubits does not match number of registers") return QasmQobjInstruction(name='measure', qubits=qubits, memory=memory, - register=registers) + register=registers) def reset_instr(qubits): @@ -197,6 +203,12 @@ def insert_snapshots_after_barriers(qobj, snapshot): qobj (Qobj): a qobj to insert snapshots into snapshot (QasmQobjInstruction): a snapshot instruction. + Returns: + qobj(Qobj): The Qobj object + + Raises: + ValueError: if the name of the instruction is not an snapshot + Additional Information: """ if snapshot.name != "snapshot": diff --git a/qiskit/providers/aer/version.py b/qiskit/providers/aer/version.py index 046824628e..1d2b31494e 100644 --- a/qiskit/providers/aer/version.py +++ b/qiskit/providers/aer/version.py @@ -10,8 +10,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +""" +Helper tools for getting Terra addon version information +""" + import os -from pathlib import Path ROOT_DIR = os.path.dirname(__file__) with open(os.path.join(ROOT_DIR, "VERSION.txt"), "r") as version_file: diff --git a/requirements-dev.txt b/requirements-dev.txt index 59b22db41a..bd643411e2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,4 +3,6 @@ cmake scikit-build cython asv -cvxopt \ No newline at end of file +cvxopt +pylint +pycodestyle \ No newline at end of file diff --git a/src/base/controller.hpp b/src/base/controller.hpp index 78f7ed2f16..0c43bee878 100755 --- a/src/base/controller.hpp +++ b/src/base/controller.hpp @@ -26,7 +26,7 @@ #if defined(__linux__) || defined(__APPLE__) #include -#elif _WIN64 +#elif defined(_WIN64) // This is needed because windows.h redefine min()/max() so interferes with std::min/max #define NOMINMAX #include @@ -42,8 +42,8 @@ #ifdef _OPENMP #include -#include "misc/hacks.hpp" #endif +#include "misc/hacks.hpp" namespace AER { @@ -98,11 +98,11 @@ namespace Base { * be used across all levels of parallelization. Set to 0 for maximum * available. [Default : 0] * - "max_parallel_experiments" (int): Set number of circuits that may be - * executed in parallel. Set to 0 to use the number of max parallel - * threads [Default: 1] + * executed in parallel. Set to 0 to automatically select a number of + * parallel threads. [Default: 0] * - "max_parallel_shots" (int): Set number of shots that maybe be executed - * in parallel for each circuit. Set to 0 to use the number of max - * parallel threads [Default: 1]. + * in parallel for each circuit. Set to 0 to automatically select a + * number of parallel threads. [Default: 0]. * - "max_memory_mb" (int): Sets the maximum size of memory for a store. * If a state needs more, an error is thrown. If set to 0, the maximum * will be automatically set to the system memory size [Default: 0]. @@ -194,7 +194,7 @@ class Controller { // Generate an equivalent circuit with input_circ as output_circ. template - Circuit optimize_circuit(const Circuit &input_circ, + void optimize_circuit(Circuit &input_circ, state_t& state, OutputData &data) const; @@ -241,10 +241,14 @@ class Controller { int max_parallel_shots_; size_t max_memory_mb_; + // use explicit parallelization + bool explicit_parallelization_; + // Parameters for parallelization management for experiments int parallel_experiments_; int parallel_shots_; int parallel_state_update_; + }; @@ -265,15 +269,14 @@ void Controller::set_config(const json_t &config) { noise_model_ = Noise::NoiseModel(config["noise_model"]); // Load OpenMP maximum thread settings - JSON::get_value(max_parallel_threads_, "max_parallel_threads", config); - JSON::get_value(max_parallel_shots_, "max_parallel_shots", config); - JSON::get_value(max_parallel_experiments_, "max_parallel_experiments", config); - - // Prevent using both parallel circuits and parallel shots - // with preference given to parallel circuit execution - if (max_parallel_experiments_ > 1) - max_parallel_shots_ = 1; - + if (JSON::check_key("max_parallel_threads", config)) + JSON::get_value(max_parallel_threads_, "max_parallel_threads", config); + + // Load configurations for parallelization + if (JSON::check_key("max_parallel_experiments", config)) + JSON::get_value(max_parallel_experiments_, "max_parallel_experiments", config); + if (JSON::check_key("max_parallel_shots", config)) + JSON::get_value(max_parallel_shots_, "max_parallel_shots", config); if (JSON::check_key("max_memory_mb", config)) { JSON::get_value(max_memory_mb_, "max_memory_mb", config); } else { @@ -284,6 +287,30 @@ void Controller::set_config(const json_t &config) { for (std::shared_ptr opt: optimizations_) opt->set_config(config_); + // for debugging + if (JSON::check_key("_parallel_experiments", config)) { + JSON::get_value(parallel_experiments_, "_parallel_experiments", config); + explicit_parallelization_ = true; + } + + // for debugging + if (JSON::check_key("_parallel_shots", config)) { + JSON::get_value(parallel_shots_, "_parallel_shots", config); + explicit_parallelization_ = true; + } + + // for debugging + if (JSON::check_key("_parallel_state_update", config)) { + JSON::get_value(parallel_state_update_, "_parallel_state_update", config); + explicit_parallelization_ = true; + } + + if (explicit_parallelization_) { + parallel_experiments_ = std::max( { parallel_experiments_, 1 }); + parallel_shots_ = std::max( { parallel_shots_, 1 }); + parallel_state_update_ = std::max( { parallel_state_update_, 1 }); + } + std::string path; JSON::get_value(path, "library_dir", config); // Fix for MacOS and OpenMP library double initialization crash. @@ -299,12 +326,14 @@ void Controller::clear_config() { void Controller::clear_parallelization() { max_parallel_threads_ = 0; - max_parallel_experiments_ = 1; - max_parallel_shots_ = 1; + max_parallel_experiments_ = 0; + max_parallel_shots_ = 0; parallel_experiments_ = 1; parallel_shots_ = 1; parallel_state_update_ = 1; + + explicit_parallelization_ = false; } void Controller::set_parallelization_experiments(const std::vector& circuits) { @@ -334,6 +363,7 @@ void Controller::set_parallelization_experiments(const std::vector& cir max_parallel_experiments_, max_parallel_threads_, static_cast(circuits.size()) }); + max_parallel_shots_ = 1; } } @@ -347,22 +377,26 @@ void Controller::set_parallelization_circuit(const Circuit& circ) { if (max_memory_mb_ < circ_memory_mb) throw std::runtime_error("a circuit requires more memory than max_memory_mb."); - if (circ_memory_mb == 0) + if (circ_memory_mb == 0) { parallel_shots_ = max_parallel_threads_; - else + parallel_state_update_ = 1; + } else if (max_parallel_shots_ > 0) { + parallel_shots_ = std::min ({ static_cast(max_memory_mb_ / circ_memory_mb), + max_parallel_shots_, + static_cast(circ.shots) }); + parallel_state_update_ = max_parallel_threads_ / parallel_shots_; + } else { + // try to use all the threads for shot-level parallelization + // no nested parallelization if max_parallel_shots is not configured parallel_shots_ = std::min ({ static_cast(max_memory_mb_ / circ_memory_mb), max_parallel_threads_, static_cast(circ.shots) }); - - if (max_parallel_shots_ < 1) { // no nested parallelization if max_parallel_shots is not configured if (parallel_shots_ == max_parallel_threads_) { parallel_state_update_ = 1; } else { parallel_shots_ = 1; parallel_state_update_ = max_parallel_threads_; } - } else { - parallel_state_update_ = max_parallel_threads_ / parallel_shots_; } } @@ -373,7 +407,7 @@ size_t Controller::get_system_memory_mb(void){ auto pages = sysconf(_SC_PHYS_PAGES); auto page_size = sysconf(_SC_PAGE_SIZE); total_physical_memory = pages * page_size; -#elif _WIN64 +#elif defined(_WIN64) MEMORYSTATUSEX status; status.dwLength = sizeof(status); GlobalMemoryStatusEx(&status); @@ -443,20 +477,18 @@ bool Controller::validate_memory_requirements(state_t &state, // Circuit optimization //------------------------------------------------------------------------- template -Circuit Controller::optimize_circuit(const Circuit &input_circ, +void Controller::optimize_circuit(Circuit &input_circ, state_t& state, OutputData &data) const { - Circuit working_circ = input_circ; Operations::OpSet allowed_opset; allowed_opset.optypes = state.allowed_ops(); allowed_opset.gates = state.allowed_gates(); allowed_opset.snapshots = state.allowed_snapshots(); - for (std::shared_ptr opt: optimizations_) - opt->optimize_circuit(working_circ, allowed_opset, data); - - return working_circ; + for (std::shared_ptr opt: optimizations_) { + opt->optimize_circuit(input_circ, allowed_opset, data); + } } //------------------------------------------------------------------------- @@ -507,8 +539,10 @@ json_t Controller::execute(const json_t &qobj_js) { max_parallel_threads_ = 1; #endif - // set parallelization for experiments - set_parallelization_experiments(qobj.circuits); + if (!explicit_parallelization_) { + // set parallelization for experiments + set_parallelization_experiments(qobj.circuits); + } #ifdef _OPENMP result["metadata"]["omp_enabled"] = true; @@ -575,8 +609,9 @@ json_t Controller::execute_circuit(Circuit &circ) { // for individual circuit failures. try { // set parallelization for this circuit - if (parallel_experiments_ == 1) + if (!explicit_parallelization_ && parallel_experiments_ == 1) { set_parallelization_circuit(circ); + } // Single shot thread execution if (parallel_shots_ <= 1) { result["data"] = run_circuit(circ, circ.shots, circ.seed); diff --git a/src/framework/operations.hpp b/src/framework/operations.hpp index 2c34704a3d..e28f650155 100755 --- a/src/framework/operations.hpp +++ b/src/framework/operations.hpp @@ -36,7 +36,7 @@ enum class RegComparison {Equal, NotEqual, Less, LessEqual, Greater, GreaterEqua // Enum class for operation types enum class OpType { gate, measure, reset, bfunc, barrier, snapshot, - matrix, matrix_sequence, multiplexer, kraus, roerror, noise_switch, initialize + matrix, multiplexer, kraus, roerror, noise_switch, initialize }; inline std::ostream& operator<<(std::ostream& stream, const OpType& type) { @@ -62,9 +62,6 @@ inline std::ostream& operator<<(std::ostream& stream, const OpType& type) { case OpType::matrix: stream << "matrix"; break; - case OpType::matrix_sequence: - stream << "matrix_sequence"; - break; case OpType::multiplexer: stream << "multiplexer"; break; @@ -105,7 +102,7 @@ struct Op { uint_t conditional_reg; // (opt) the (single) register location to look up for conditional RegComparison bfunc; // (opt) boolean function relation - // DEPRECATED: old style conditionals (will be removed when Terra supports new style) + // DEPRECATED: Old style conditionals (remove in 0.3) bool old_conditional = false; // is gate old style conditional gate std::string old_conditional_mask; // hex string for conditional mask std::string old_conditional_val; // hex string for conditional value @@ -138,6 +135,20 @@ inline std::ostream& operator<<(std::ostream& s, const Op& op) { s << qubit; first = false; } + s << "],["; + first = true; + for (reg_t reg: op.regs) { + if (!first) s << ","; + s << "["; + bool first0 = true; + for (size_t qubit: reg) { + if (!first0) s << ","; + s << qubit; + first0 = false; + } + s << "]"; + first = false; + } s << "]"; return s; } @@ -390,14 +401,15 @@ inline Op make_unitary(const reg_t &qubits, const cmatrix_t &mat, std::string la return op; } -inline Op make_matrix_sequence(const std::vector ®s, const std::vector &mats, std::string label = "") { +inline Op make_fusion(const reg_t &qubits, const cmatrix_t &mat, const std::vector& fusioned_ops, std::string label = "") { Op op; - op.type = OpType::matrix_sequence; - op.name = "matrix_sequence"; - op.regs = regs; - op.mats = mats; + op.type = OpType::matrix; + op.name = "fusion"; + op.qubits = qubits; + op.mats = {mat}; if (label != "") op.string_params = {label}; + return op; } @@ -543,6 +555,9 @@ Op json_to_op_noise_switch(const json_t &js); // Classical bits Op json_to_op_roerror(const json_t &js); +// Optional instruction parameters +enum class Allowed {Yes, No}; +void add_condtional(const Allowed val, Op& op, const json_t &js); //------------------------------------------------------------------------------ @@ -611,6 +626,29 @@ json_t op_to_json(const Op &op) { // Implementation: Gates, measure, reset deserialization //------------------------------------------------------------------------------ + +void add_condtional(const Allowed allowed, Op& op, const json_t &js) { + // Check conditional + if (JSON::check_key("conditional", js)) { + // If instruction isn't allow to be conditional throw an exception + if (allowed == Allowed::No) { + throw std::invalid_argument("Invalid instruction: \"" + op.name + "\" cannot be conditional."); + } + // If instruction is allowed to be conditional add parameters + if (js["conditional"].is_number()) { + // New style conditional + op.conditional_reg = js["conditional"]; + op.conditional = true; + } else { + // DEPRECATED: old style conditional (remove in 0.3) + JSON::get_value(op.old_conditional_mask, "mask", js["conditional"]); + JSON::get_value(op.old_conditional_val, "val", js["conditional"]); + op.old_conditional = true; + } + } +} + + Op json_to_op_gate(const json_t &js) { Op op; op.type = OpType::gate; @@ -627,19 +665,8 @@ Op json_to_op_gate(const json_t &js) { else op.string_params = {op.name}; - // Check conditional - if (JSON::check_key("conditional", js)) { - if (js["conditional"].is_number()) { - // New style conditional - op.conditional_reg = js["conditional"]; - op.conditional = true; - } else { - // DEPRECIATED: old style conditional - JSON::get_value(op.old_conditional_mask, "mask", js["conditional"]); - JSON::get_value(op.old_conditional_val, "val", js["conditional"]); - op.old_conditional = true; - } - } + // Conditional + add_condtional(Allowed::Yes, op, js); // Validation check_empty_name(op); @@ -660,6 +687,8 @@ Op json_to_op_barrier(const json_t &js) { op.type = OpType::barrier; op.name = "barrier"; JSON::get_value(op.qubits, "qubits", js); + // Check conditional + add_condtional(Allowed::No, op, js); return op; } @@ -672,6 +701,9 @@ Op json_to_op_measure(const json_t &js) { JSON::get_value(op.memory, "memory", js); JSON::get_value(op.registers, "register", js); + // Conditional + add_condtional(Allowed::No, op, js); + // Validation check_empty_qubits(op); check_duplicate_qubits(op); @@ -691,6 +723,9 @@ Op json_to_op_reset(const json_t &js) { op.name = "reset"; JSON::get_value(op.qubits, "qubits", js); + // Conditional + add_condtional(Allowed::No, op, js); + // Validation check_empty_qubits(op); check_duplicate_qubits(op); @@ -704,6 +739,10 @@ Op json_to_op_initialize(const json_t &js) { op.name = "initialize"; JSON::get_value(op.qubits, "qubits", js); JSON::get_value(op.params, "params", js); + + // Conditional + add_condtional(Allowed::No, op, js); + // Validation check_empty_qubits(op); check_duplicate_qubits(op); @@ -756,11 +795,13 @@ Op json_to_op_bfunc(const json_t &js) { op.bfunc = it->second; } + // Conditional + add_condtional(Allowed::No, op, js); + // Validation if (op.registers.empty()) { throw std::invalid_argument("Invalid measure operation: \"register\" is empty."); } - return op; } @@ -771,7 +812,10 @@ Op json_to_op_roerror(const json_t &js) { op.name = "roerror"; JSON::get_value(op.memory, "memory", js); JSON::get_value(op.registers, "register", js); - JSON::get_value(op.probs, "probabilities", js); + JSON::get_value(op.probs, "probabilities", js); // DEPRECATED: Remove in 0.4 + JSON::get_value(op.probs, "params", js); + // Conditional + add_condtional(Allowed::No, op, js); return op; } @@ -800,11 +844,13 @@ Op json_to_op_unitary(const json_t &js) { std::string label; JSON::get_value(label, "label", js); op.string_params.push_back(label); + + // Conditional + add_condtional(Allowed::Yes, op, js); return op; } Op json_to_op_multiplexer(const json_t &js) { - // Parse parameters reg_t qubits; std::vector mats; @@ -813,7 +859,10 @@ Op json_to_op_multiplexer(const json_t &js) { JSON::get_value(mats, "params", js); JSON::get_value(label, "label", js); // Construct op - return make_multiplexer(qubits, mats, label); + auto op = make_multiplexer(qubits, mats, label); + // Conditional + add_condtional(Allowed::Yes, op, js); + return op; } Op json_to_op_kraus(const json_t &js) { @@ -826,6 +875,8 @@ Op json_to_op_kraus(const json_t &js) { // Validation check_empty_qubits(op); check_duplicate_qubits(op); + // Conditional + add_condtional(Allowed::No, op, js); return op; } @@ -835,6 +886,8 @@ Op json_to_op_noise_switch(const json_t &js) { op.type = OpType::noise_switch; op.name = "noise_switch"; JSON::get_value(op.params, "params", js); + // Conditional + add_condtional(Allowed::No, op, js); return op; } @@ -853,7 +906,10 @@ Op json_to_op_snapshot(const json_t &js) { snapshot_type == "expectation_value_matrix_with_variance") return json_to_op_snapshot_matrix(js); // Default snapshot: has "type", "label", "qubits" - return json_to_op_snapshot_default(js); + auto op = json_to_op_snapshot_default(js); + // Conditional + add_condtional(Allowed::No, op, js); + return op; } diff --git a/src/framework/utils.hpp b/src/framework/utils.hpp index 4a12b5c816..80a27cc7dc 100755 --- a/src/framework/utils.hpp +++ b/src/framework/utils.hpp @@ -88,20 +88,35 @@ class Matrix { // Matrix Functions //------------------------------------------------------------------------------ -// Vector conversion +// Construct a matrix from a vector of matrix-row vectors template matrix make_matrix(const std::vector> &mat); + +// Reshape a length column-major vectorized matrix into a square matrix template matrix devectorize_matrix(const std::vector &vec); + +// Vectorize a matrix by stacking matrix columns (column-major vectorization) template std::vector vectorize_matrix(const matrix &mat); -// Transformations +// Return the transpose a matrix template matrix transpose(const matrix &A); + +// Return the adjoing (Hermitian-conjugate) of a matrix template matrix> dagger(const matrix> &A); + +// Return the complex conjugate of a matrix template matrix> conjugate(const matrix> &A); + +// Given a list of matrices for a multiplexer stacks and packs them 0/1/2/... +// into a single 2^control x (2^target x 2^target) cmatrix_t) +// Equivalent to a 2^qubits x 2^target "flat" matrix template matrix stacked_matrix(const std::vector> &mmat); +// Return a vector containing the diagonal of a matrix +template std::vector matrix_diagonal(const matrix& mat); + // Tracing template T trace(const matrix &A); template matrix partial_trace_a(const matrix &rho, size_t dimA); @@ -496,7 +511,6 @@ matrix devectorize_matrix(const std::vector& vec) { return mat; } - template std::vector vectorize_matrix(const matrix& mat) { std::vector vec; @@ -510,7 +524,6 @@ std::vector vectorize_matrix(const matrix& mat) { return vec; } - template matrix make_matrix(const std::vector> & mat) { size_t nrows = mat.size(); @@ -565,8 +578,6 @@ matrix> conj(const matrix> &A) { return temp; } -// Given a list of matrices for a multiplexer, stacks and packs them 0/1/2/... into a single 2^control x (2^target x 2^target) cmatrix_t) -// Equivalent to a 2^qubits x 2^target "flat" matrix template matrix stacked_matrix(const std::vector> &mmat){ size_t size_of_controls = mmat[0].GetRows(); // or GetColumns, as these matrices are (should be) square @@ -596,6 +607,15 @@ matrix stacked_matrix(const std::vector> &mmat){ return stacked_matrix; } +template +std::vector matrix_diagonal(const matrix& mat) { + std::vector vec; + size_t size = std::min(mat.GetRows(), mat.GetColumns()); + vec.resize(size, 0.); + for (size_t i=0; i < size; i++) + vec[i] = mat(i, i); + return vec; +} template T trace(const matrix &A) { diff --git a/src/simulators/qasm/qasm_controller.hpp b/src/simulators/qasm/qasm_controller.hpp index 61d0e7f2fc..72f9fc155a 100755 --- a/src/simulators/qasm/qasm_controller.hpp +++ b/src/simulators/qasm/qasm_controller.hpp @@ -251,7 +251,7 @@ class QasmController : public Base::Controller { // Constructor //------------------------------------------------------------------------- QasmController::QasmController() { - add_circuit_optimization(Transpile::ReduceNop()); + add_circuit_optimization(Transpile::ReduceBarrier()); add_circuit_optimization(Transpile::Fusion()); add_circuit_optimization(Transpile::TruncateQubits()); } @@ -501,7 +501,7 @@ void QasmController::run_circuit_with_noise(const Circuit &circ, while(shots-- > 0) { Circuit noise_circ = noise_model_.sample_noise(circ, rng); if (noise_circ.num_qubits > circuit_opt_noise_threshold_) { - noise_circ = optimize_circuit(noise_circ, state, data); + optimize_circuit(noise_circ, state, data); } run_single_shot(noise_circ, state, initial_state, data, rng); } @@ -518,7 +518,7 @@ void QasmController::run_circuit_without_noise(const Circuit &circ, // Optimize circuit for state type Circuit opt_circ = circ; if (circ.num_qubits > circuit_opt_ideal_threshold_) { - opt_circ = optimize_circuit(opt_circ, state, data); + optimize_circuit(opt_circ, state, data); } // Check if measure sampler and optimization are valid @@ -574,7 +574,7 @@ QasmController::check_measure_sampling_opt(const Circuit &circ) const { auto start_meas = start; // Check all remaining operations are measurements while (start != circ.ops.end()) { - if (start->type != Operations::OpType::measure) { + if (start->type != Operations::OpType::measure && start->conditional) { return std::make_pair(false, 0); } ++start; diff --git a/src/simulators/statevector/qubitvector.hpp b/src/simulators/statevector/qubitvector.hpp index 15aea83068..66c518ef52 100755 --- a/src/simulators/statevector/qubitvector.hpp +++ b/src/simulators/statevector/qubitvector.hpp @@ -245,11 +245,6 @@ class QubitVector { // The matrix is input as vector of the column-major vectorized N-qubit matrix. void apply_matrix(const reg_t &qubits, const cvector_t &mat); - // Apply a N-qubit matrix constructed from composition of 1 and 2 qubit matrices. - // The sets of qubits and matrices are passed as vectors, where each individual matrix - // is input as a column-major vectorized matrix. - void apply_matrix_sequence(const std::vector ®s, const std::vector &mats); - // Apply a stacked set of 2^control_count target_count--qubit matrix to the state vector. // The matrix is input as vector of the column-major vectorized N-qubit matrix. void apply_multiplexer(const reg_t &control_qubits, const reg_t &target_qubits, const cvector_t &mat); @@ -281,15 +276,17 @@ class QubitVector { // If N=2 this implements an optimized CY gate // If N=3 this implements an optimized CCY gate void apply_mcy(const reg_t &qubits); - - // Apply a general multi-controlled Z-gate - // If N=1 this implements an optimized Z gate - // If N=2 this implements an optimized CZ gate - // If N=3 this implements an optimized CCZ gate - void apply_mcz(const reg_t &qubits); + // Apply a general multi-controlled single-qubit phase gate + // with diagonal [1, ..., 1, phase] + // If N=1 this implements an optimized single-qubit phase gate + // If N=2 this implements an optimized CPhase gate + // If N=3 this implements an optimized CCPhase gate + // if phase = -1 this is a Z, CZ, CCZ gate + void apply_mcphase(const reg_t &qubits, const complex_t phase); + // Apply a general multi-controlled single-qubit unitary gate - // If N=1 this implements an optimized single-qubit gate + // If N=1 this implements an optimized single-qubit U gate // If N=2 this implements an optimized CU gate // If N=3 this implements an optimized CCU gate void apply_mcu(const reg_t &qubits, const cvector_t &mat); @@ -528,21 +525,6 @@ class QubitVector { complex_t apply_reduction_lambda(Lambda&& func, const list_t &qubits, const param_t ¶ms) const; - - // Permute an N-qubit vectorized matrix to match a reordering of qubits - cvector_t sort_matrix(const reg_t &src, - const reg_t &sorted, - const cvector_t &mat) const; - - // Swap cols and rows of vectorized matrix - void swap_cols_and_rows(const uint_t idx1, const uint_t idx2, - cvector_t &mat, uint_t dim) const; - - // Extend a matrix for qubits (src_qubits) to a matrix for more qubits (dst_sorted_qubits) - // qubits in dst_sorted_qubits must contains all the qubits in src_qubits - cvector_t expand_matrix(const reg_t& src_qubits, - const reg_t& dst_sorted_qubits, - const cvector_t& vmat) const; }; /******************************************************************************* @@ -1090,7 +1072,7 @@ complex_t QubitVector::apply_reduction_lambda(Lambda&& func, template void QubitVector::apply_matrix(const reg_t &qubits, const cvector_t &mat) { - + const size_t N = qubits.size(); // Error checking #ifdef DEBUG @@ -1173,69 +1155,6 @@ void QubitVector::apply_matrix(const reg_t &qubits, } // end switch } -template -void QubitVector::apply_matrix_sequence(const std::vector ®s, - const std::vector &mats) { - if (mats.size() == 0) - return; - - -#ifdef DEBUG - if (regs.size() != mats.size()); - throw std::runtime_error("QubitVector::apply_matrix_sequence allows same size of qubitss and mats."); -#endif - - bool at_most_two = true; - // check 1 or 2 qubits - for (const reg_t& reg: regs) { - if (reg.size() > 2) { - at_most_two = false; - break; - } - } - - if (!at_most_two) { - for (size_t i = 0; i < regs.size(); ++i) - apply_matrix(regs[i], mats[i]); - return; - } - - - reg_t sorted_qubits; - for (const reg_t& reg: regs) - for (const uint_t qubit: reg) - if (std::find(sorted_qubits.begin(), sorted_qubits.end(), qubit) == sorted_qubits.end()) - sorted_qubits.push_back(qubit); - - std::sort(sorted_qubits.begin(), sorted_qubits.end()); - - std::vector sorted_mats; - - for (size_t i = 0; i < regs.size(); ++i) { - const reg_t& reg = regs[i]; - const cvector_t& mat = mats[i]; - sorted_mats.push_back(expand_matrix(reg, sorted_qubits, mat)); - } - - auto U = sorted_mats[0]; - const auto dim = BITS[sorted_qubits.size()]; - - for (size_t m = 1; m < sorted_mats.size(); m++) { - - cvector_t u_tmp(U.size(), 0.); - const cvector_t& u = sorted_mats[m]; - - for (size_t i = 0; i < dim; ++i) - for (size_t j = 0; j < dim; ++j) - for (size_t k = 0; k < dim; ++k) - u_tmp[i + j * dim] += u[i + k * dim] * U[k + j * dim]; - - U = u_tmp; - } - - apply_matrix(sorted_qubits, U); -} - template void QubitVector::apply_multiplexer(const reg_t &control_qubits, const reg_t &target_qubits, @@ -1273,61 +1192,29 @@ void QubitVector::apply_multiplexer(const reg_t &control_qubits, template void QubitVector::apply_diagonal_matrix(const reg_t &qubits, const cvector_t &diag) { - const size_t N = qubits.size(); + const int_t N = qubits.size(); // Error checking #ifdef DEBUG check_vector(diag, N); #endif - switch (N) { - case 1: - apply_diagonal_matrix(qubits[0], diag); - return; - case 2: { - // Lambda function for 2-qubit diagional matrix multiplication - auto lambda = [&](const areg_t<4> &inds, - const cvector_t &_mat)->void { - for (size_t i = 0; i < 4; i++) { - data_[inds[i]] *= _mat[i]; - } - }; - apply_lambda(lambda, areg_t<2>({{qubits[0], qubits[1]}}), diag); - return; - } - case 3: { - // Lambda function for 3-qubit diagional matrix multiplication - auto lambda = [&](const areg_t<8> &inds, - const cvector_t &_mat)->void { - for (size_t i = 0; i < 8; i++) { - data_[inds[i]] *= _mat[i]; - } - }; - apply_lambda(lambda, areg_t<3>({{qubits[0], qubits[1], qubits[2]}}), diag); - return; - } - case 4: { - // Lambda function for 4-qubit diagional matrix multiplication - auto lambda = [&](const areg_t<16> &inds, - const cvector_t &_mat)->void { - for (size_t i = 0; i < 16; i++) { - data_[inds[i]] *= _mat[i]; - } - }; - apply_lambda(lambda, areg_t<4>({{qubits[0], qubits[1], qubits[2], qubits[3]}}), diag); - return; - } - default: { - const uint_t DIM = BITS[N]; - // Lambda function for N-qubit diagional matrix multiplication - auto lambda = [&](const indexes_t &inds, - const cvector_t &_mat)->void { - for (size_t i = 0; i < DIM; i++) { - data_[inds[i]] *= _mat[i]; - } - }; - apply_lambda(lambda, qubits, diag); + if (N == 1) { + apply_diagonal_matrix(qubits[0], diag); + return; + } + + auto lambda = [&](const areg_t<2> &inds, const cvector_t &_diag)->void { + for (int_t i = 0; i < 2; ++i) { + const int_t k = inds[i]; + int_t iv = 0; + for (int_t j = 0; j < N; j++) + if ((k & (1ULL << qubits[j])) != 0) + iv += (1 << j); + if (diag[iv] != 1.0) + data_[k] *= diag[iv]; } - } // end switch + }; + apply_lambda(lambda, areg_t<1>({{qubits[0]}}), diag); } template @@ -1499,74 +1386,72 @@ void QubitVector::apply_mcy(const reg_t &qubits) { } template -void QubitVector::apply_mcz(const reg_t &qubits) { +void QubitVector::apply_mcswap(const reg_t &qubits) { + // Calculate the swap positions for the last two qubits. + // If N = 2 this is just a regular SWAP gate rather than a controlled-SWAP gate. const size_t N = qubits.size(); + const size_t pos0 = MASKS[N - 1]; + const size_t pos1 = pos0 + BITS[N - 2]; switch (N) { - case 1: { - // Lambda function for Z gate - auto lambda = [&](const areg_t<2> &inds)->void { - data_[inds[1]] *= -1.; - }; - apply_lambda(lambda, areg_t<1>({{qubits[0]}})); - return; - } case 2: { - // Lambda function for CZ gate + // Lambda function for SWAP gate auto lambda = [&](const areg_t<4> &inds)->void { - data_[inds[3]] *= -1.; + std::swap(data_[inds[pos0]], data_[inds[pos1]]); }; apply_lambda(lambda, areg_t<2>({{qubits[0], qubits[1]}})); return; } case 3: { - // Lambda function for CCZ gate + // Lambda function for C-SWAP gate auto lambda = [&](const areg_t<8> &inds)->void { - data_[inds[7]] *= -1.; + std::swap(data_[inds[pos0]], data_[inds[pos1]]); }; apply_lambda(lambda, areg_t<3>({{qubits[0], qubits[1], qubits[2]}})); return; } default: { - // Lambda function for general multi-controlled X gate + // Lambda function for general multi-controlled SWAP gate auto lambda = [&](const indexes_t &inds)->void { - data_[inds[MASKS[N]]] *= -1.; + std::swap(data_[inds[pos0]], data_[inds[pos1]]); }; apply_lambda(lambda, qubits); } } // end switch } - template -void QubitVector::apply_mcswap(const reg_t &qubits) { - // Calculate the swap positions for the last two qubits. - // If N = 2 this is just a regular SWAP gate rather than a controlled-SWAP gate. +void QubitVector::apply_mcphase(const reg_t &qubits, const complex_t phase) { const size_t N = qubits.size(); - const size_t pos0 = MASKS[N - 1]; - const size_t pos1 = pos0 + BITS[N - 2]; - switch (N) { + case 1: { + // Lambda function for arbitrary Phase gate with diagonal [1, phase] + auto lambda = [&](const areg_t<2> &inds)->void { + data_[inds[1]] *= phase; + }; + apply_lambda(lambda, areg_t<1>({{qubits[0]}})); + return; + } case 2: { - // Lambda function for SWAP gate + // Lambda function for CPhase gate with diagonal [1, 1, 1, phase] auto lambda = [&](const areg_t<4> &inds)->void { - std::swap(data_[inds[pos0]], data_[inds[pos1]]); + data_[inds[3]] *= phase; }; apply_lambda(lambda, areg_t<2>({{qubits[0], qubits[1]}})); return; } case 3: { - // Lambda function for C-SWAP gate auto lambda = [&](const areg_t<8> &inds)->void { - std::swap(data_[inds[pos0]], data_[inds[pos1]]); + data_[inds[7]] *= phase; }; apply_lambda(lambda, areg_t<3>({{qubits[0], qubits[1], qubits[2]}})); return; } default: { - // Lambda function for general multi-controlled SWAP gate + // Lambda function for general multi-controlled Phase gate + // with diagonal [1, ..., 1, phase] auto lambda = [&](const indexes_t &inds)->void { - std::swap(data_[inds[pos0]], data_[inds[pos1]]); + data_[inds[MASKS[N]]] *= phase; }; apply_lambda(lambda, qubits); } @@ -1585,6 +1470,12 @@ void QubitVector::apply_mcu(const reg_t &qubits, // diagonal matrix lambda function // TODO: this should be changed to not check doubles with == if (mat[1] == 0.0 && mat[2] == 0.0) { + // Check if actually a phase gate + if (mat[0] == 1.0) { + apply_mcphase(qubits, mat[3]); + return; + } + // Otherwise apply general diagonal gate const cvector_t diag = {{mat[0], mat[3]}}; // Diagonal version switch (N) { @@ -1597,8 +1488,8 @@ void QubitVector::apply_mcu(const reg_t &qubits, // Lambda function for CU gate auto lambda = [&](const areg_t<4> &inds, const cvector_t &_diag)->void { - data_[pos0] = _diag[0] * data_[pos0]; - data_[pos1] = _diag[1] * data_[pos1]; + data_[inds[pos0]] = _diag[0] * data_[inds[pos0]]; + data_[inds[pos1]] = _diag[1] * data_[inds[pos1]]; }; apply_lambda(lambda, areg_t<2>({{qubits[0], qubits[1]}}), diag); return; @@ -1607,8 +1498,8 @@ void QubitVector::apply_mcu(const reg_t &qubits, // Lambda function for CCU gate auto lambda = [&](const areg_t<8> &inds, const cvector_t &_diag)->void { - data_[pos0] = _diag[0] * data_[pos0]; - data_[pos1] = _diag[1] * data_[pos1]; + data_[inds[pos0]] = _diag[0] * data_[inds[pos0]]; + data_[inds[pos1]] = _diag[1] * data_[inds[pos1]]; }; apply_lambda(lambda, areg_t<3>({{qubits[0], qubits[1], qubits[2]}}), diag); return; @@ -1617,8 +1508,8 @@ void QubitVector::apply_mcu(const reg_t &qubits, // Lambda function for general multi-controlled U gate auto lambda = [&](const indexes_t &inds, const cvector_t &_diag)->void { - data_[pos0] = _diag[0] * data_[pos0]; - data_[pos1] = _diag[1] * data_[pos1]; + data_[inds[pos0]] = _diag[0] * data_[inds[pos0]]; + data_[inds[pos1]] = _diag[1] * data_[inds[pos1]]; }; apply_lambda(lambda, qubits, diag); return; @@ -1637,9 +1528,9 @@ void QubitVector::apply_mcu(const reg_t &qubits, // Lambda function for CU gate auto lambda = [&](const areg_t<4> &inds, const cvector_t &_mat)->void { - const auto cache = data_[pos0]; - data_[pos0] = _mat[0] * data_[pos0] + _mat[2] * data_[pos1]; - data_[pos1] = _mat[1] * cache + _mat[3] * data_[pos1]; + const auto cache = data_[inds[pos0]]; + data_[inds[pos0]] = _mat[0] * data_[inds[pos0]] + _mat[2] * data_[inds[pos1]]; + data_[inds[pos1]] = _mat[1] * cache + _mat[3] * data_[inds[pos1]]; }; apply_lambda(lambda, areg_t<2>({{qubits[0], qubits[1]}}), mat); return; @@ -1648,9 +1539,9 @@ void QubitVector::apply_mcu(const reg_t &qubits, // Lambda function for CCU gate auto lambda = [&](const areg_t<8> &inds, const cvector_t &_mat)->void { - const auto cache = data_[pos0]; - data_[pos0] = _mat[0] * data_[pos0] + _mat[2] * data_[pos1]; - data_[pos1] = _mat[1] * cache + _mat[3] * data_[pos1]; + const auto cache = data_[inds[pos0]]; + data_[inds[pos0]] = _mat[0] * data_[inds[pos0]] + _mat[2] * data_[inds[pos1]]; + data_[inds[pos1]] = _mat[1] * cache + _mat[3] * data_[inds[pos1]]; }; apply_lambda(lambda, areg_t<3>({{qubits[0], qubits[1], qubits[2]}}), mat); return; @@ -1659,9 +1550,9 @@ void QubitVector::apply_mcu(const reg_t &qubits, // Lambda function for general multi-controlled U gate auto lambda = [&](const indexes_t &inds, const cvector_t &_mat)->void { - const auto cache = data_[pos0]; - data_[pos0] = _mat[0] * data_[pos0] + _mat[2] * data_[pos1]; - data_[pos1] = _mat[1] * cache + _mat[3] * data_[pos1]; + const auto cache = data_[inds[pos0]]; + data_[inds[pos0]] = _mat[0] * data_[inds[pos0]] + _mat[2] * data_[inds[pos1]]; + data_[inds[pos1]] = _mat[1] * cache + _mat[3] * data_[inds[pos1]]; }; apply_lambda(lambda, qubits, mat); return; @@ -1676,25 +1567,62 @@ void QubitVector::apply_mcu(const reg_t &qubits, template void QubitVector::apply_matrix(const uint_t qubit, const cvector_t& mat) { - // Check if matrix is actually diagonal and if so use - // apply_diagonal_matrix - // TODO: this should be changed to not check doubles with == + // Check if matrix is diagonal and if so use optimized lambda if (mat[1] == 0.0 && mat[2] == 0.0) { const cvector_t diag = {{mat[0], mat[3]}}; apply_diagonal_matrix(qubit, diag); return; } + + // Convert qubit to array register for lambda functions + areg_t<1> qubits = {{qubit}}; - // Lambda function for single-qubit matrix multiplication - auto lambda = [&](const areg_t<2> &inds, - const cvector_t &_mat)->void { - const auto pos0 = inds[0]; - const auto pos1 = inds[1]; - const auto cache = data_[pos0]; - data_[pos0] = _mat[0] * data_[pos0] + _mat[2] * data_[pos1]; - data_[pos1] = _mat[1] * cache + _mat[3] * data_[pos1]; + // Check if anti-diagonal matrix and if so use optimized lambda + if(mat[0] == 0.0 && mat[3] == 0.0) { + if (mat[1] == 1.0 && mat[2] == 1.0) { + // X-matrix + auto lambda = [&](const areg_t<2> &inds)->void { + std::swap(data_[inds[0]], data_[inds[1]]); + }; + apply_lambda(lambda, qubits); + return; + } + if (mat[2] == 0.0) { + // Non-unitary projector + // possibly used in measure/reset/kraus update + auto lambda = [&](const areg_t<2> &inds)->void { + data_[inds[1]] = mat[1] * data_[inds[0]]; + data_[inds[0]] = 0.0; + }; + apply_lambda(lambda, qubits); + return; + } + if (mat[1] == 0.0) { + // Non-unitary projector + // possibly used in measure/reset/kraus update + auto lambda = [&](const areg_t<2> &inds)->void { + data_[inds[0]] = mat[2] * data_[inds[1]]; + data_[inds[1]] = 0.0; + }; + apply_lambda(lambda, qubits); + return; + } + // else we have a general anti-diagonal matrix + auto lambda = [&](const areg_t<2> &inds)->void { + const complex_t cache = data_[inds[0]]; + data_[inds[0]] = mat[2] * data_[inds[1]]; + data_[inds[1]] = mat[1] * cache; + }; + apply_lambda(lambda, qubits); + return; + } + // Otherwise general single-qubit matrix multiplication + auto lambda = [&](const areg_t<2> &inds)->void { + const auto cache = data_[inds[0]]; + data_[inds[0]] = mat[0] * cache + mat[2] * data_[inds[1]]; + data_[inds[1]] = mat[1] * cache + mat[3] * data_[inds[1]]; }; - apply_lambda(lambda, areg_t<1>({{qubit}}), mat); + apply_lambda(lambda, qubits); } template @@ -1801,151 +1729,6 @@ void QubitVector::apply_diagonal_matrix(const uint_t qubit, } } -//------------------------------------------------------------------------------ -// Gate-swap optimized helper functions -//------------------------------------------------------------------------------ -template -void QubitVector::swap_cols_and_rows(const uint_t idx1, - const uint_t idx2, - cvector_t &mat, - uint_t dim) const { - - uint_t mask1 = BITS[idx1]; - uint_t mask2 = BITS[idx2]; - - for (uint_t first = 0; first < dim; ++first) { - if ((first & mask1) && !(first & mask2)) { - uint_t second = (first ^ mask1) | mask2; - - for (uint_t i = 0; i < dim; ++i) { - complex_t cache = mat[first * dim + i]; - mat[first * dim + i] = mat[second * dim + i]; - mat[second * dim + i] = cache; - } - for (uint_t i = 0; i < dim; ++i) { - complex_t cache = mat[i * dim + first]; - mat[i * dim + first] = mat[i * dim + second]; - mat[i * dim + second] = cache; - } - } - } -} - -template -cvector_t QubitVector::sort_matrix(const reg_t& src, - const reg_t& sorted, - const cvector_t &mat) const { - - const uint_t N = src.size(); - const uint_t DIM = BITS[N]; - auto ret = mat; - auto current = src; - - while (current != sorted) { - uint_t from; - uint_t to; - for (from = 0; from < current.size(); ++from) - if (current[from] != sorted[from]) - break; - if (from == current.size()) - break; - for (to = from + 1; to < current.size(); ++to) - if (current[from] == sorted[to]) - break; - if (to == current.size()) { - throw std::runtime_error("QubitVector::sort_matrix we should not reach here"); - } - swap_cols_and_rows(from, to, ret, DIM); - std::swap(current[from], current[to]); - } - - return ret; -} - -template -cvector_t QubitVector::expand_matrix(const reg_t& src_qubits, const reg_t& dst_sorted_qubits, const cvector_t& vmat) const { - - const auto dst_dim = BITS[dst_sorted_qubits.size()]; - const auto dst_vmat_size = dst_dim * dst_dim; - const auto src_dim = BITS[src_qubits.size()]; - - // generate a matrix for op - cvector_t u(dst_vmat_size, .0); - std::vector filled(dst_dim, false); - - if (src_qubits.size() == 1) { //1-qubit operation - // 1. identify delta - const auto index = std::distance(dst_sorted_qubits.begin(), - std::find(dst_sorted_qubits.begin(), dst_sorted_qubits.end(), src_qubits[0])); - - const auto delta = BITS[index]; - - // 2. find vmat(0, 0) position in U - for (uint_t i = 0; i < dst_dim; ++i) { - - if (filled[i]) - continue; - - // 3. allocate op.u to u based on u(i, i) and delta - u[i + (i + 0) * dst_dim] = vmat[0 + 0 * src_dim]; - u[i + (i + delta) * dst_dim] = vmat[0 + 1 * src_dim]; - u[(i + delta) + (i + 0) * dst_dim] = vmat[1 + 0 * src_dim]; - u[(i + delta) + (i + delta) * dst_dim] = vmat[1 + 1 * src_dim]; - filled[i] = filled[i + delta] = true; - } - } else if (src_qubits.size() == 2) { //2-qubit operation - - reg_t sorted_src_qubits = src_qubits; - std::sort(sorted_src_qubits.begin(), sorted_src_qubits.end()); - const cvector_t sorted_vmat = sort_matrix(src_qubits, sorted_src_qubits, vmat); - - // 1. identify low and high delta - auto low = std::distance(dst_sorted_qubits.begin(), - std::find(dst_sorted_qubits.begin(), dst_sorted_qubits.end(), src_qubits[0])); - - auto high = std::distance(dst_sorted_qubits.begin(), - std::find(dst_sorted_qubits.begin(), dst_sorted_qubits.end(), src_qubits[1])); - - auto low_delta = BITS[low]; - auto high_delta = BITS[high]; - - // 2. find op.u(0, 0) position in U - for (uint_t i = 0; i < dst_dim; ++i) { - if (filled[i]) - continue; - - // 3. allocate vmat to u based on u(i, i) and delta - u[i + (i + 0) * dst_dim] = sorted_vmat[0 + 0 * src_dim]; - u[i + (i + low_delta) * dst_dim] = sorted_vmat[0 + 1 * src_dim]; - u[i + (i + high_delta) * dst_dim] = sorted_vmat[0 + 2 * src_dim]; - u[i + (i + low_delta + high_delta) * dst_dim] = sorted_vmat[0 + 3 * src_dim]; - u[(i + low_delta) + (i + 0) * dst_dim] = sorted_vmat[1 + 0 * src_dim]; - u[(i + low_delta) + (i + low_delta) * dst_dim] = sorted_vmat[1 + 1 * src_dim]; - u[(i + low_delta) + (i + high_delta) * dst_dim] = sorted_vmat[1 + 2 * src_dim]; - u[(i + low_delta) + (i + low_delta + high_delta) * dst_dim] = sorted_vmat[1 + 3 * src_dim]; - u[(i + high_delta) + (i + 0) * dst_dim] = sorted_vmat[2 + 0 * src_dim]; - u[(i + high_delta) + (i + low_delta) * dst_dim] = sorted_vmat[2 + 1 * src_dim]; - u[(i + high_delta) + (i + high_delta) * dst_dim] = sorted_vmat[2 + 2 * src_dim]; - u[(i + high_delta) + (i + low_delta + high_delta) * dst_dim] = sorted_vmat[2 + 3 * src_dim]; - u[(i + low_delta + high_delta) + (i + 0) * dst_dim] = sorted_vmat[3 + 0 * src_dim]; - u[(i + low_delta + high_delta) + (i + low_delta) * dst_dim] = sorted_vmat[3 + 1 * src_dim]; - u[(i + low_delta + high_delta) + (i + high_delta) * dst_dim] = sorted_vmat[3 + 2 * src_dim]; - u[(i + low_delta + high_delta) + (i + low_delta + high_delta) * dst_dim] = sorted_vmat[3 + 3 * src_dim]; - - filled[i] = true; - filled[i + low_delta] = true; - filled[i + high_delta] = true; - filled[i + low_delta + high_delta] = true; - } - //TODO: } else if (src_qubits.size() == 3) { - } else { - std::stringstream ss; - ss << "Fusion::illegal qubit number:" << src_qubits.size(); - throw std::runtime_error(ss.str()); - } - - return u; -} /******************************************************************************* * * NORMS diff --git a/src/simulators/statevector/statevector_state.hpp b/src/simulators/statevector/statevector_state.hpp index 67795682d6..2300fbcb8b 100755 --- a/src/simulators/statevector/statevector_state.hpp +++ b/src/simulators/statevector/statevector_state.hpp @@ -74,7 +74,6 @@ class State : public Base::State { Operations::OpType::bfunc, Operations::OpType::roerror, Operations::OpType::matrix, - Operations::OpType::matrix_sequence, Operations::OpType::multiplexer, Operations::OpType::kraus }); @@ -82,9 +81,9 @@ class State : public Base::State { // Return the set of qobj gate instruction names supported by the State virtual stringset_t allowed_gates() const override { - return {"u1", "u2", "u3", "cx", "cz", "cy", "swap", + return {"u1", "u2", "u3", "cx", "cz", "cy", "cu1", "swap", "id", "x", "y", "z", "h", "s", "sdg", "t", "tdg", "ccx", - "mcx", "mcz", "mcy", "mcu1", "mcu2", "mcu3", "mcswap"}; + "mcx", "mcz", "mcy", "mcz", "mcu1", "mcu2", "mcu3", "mcswap"}; } // Return the set of qobj snapshot types supported by the State @@ -169,7 +168,7 @@ class State : public Base::State { virtual void apply_snapshot(const Operations::Op &op, OutputData &data); // Apply a matrix to given qubits (identity on all other qubits) - void apply_matrix(const reg_t &qubits, const cmatrix_t & mat); + void apply_matrix(const Operations::Op &op); // Apply a vectorized matrix to given qubits (identity on all other qubits) void apply_matrix(const reg_t &qubits, const cvector_t & vmat); @@ -181,9 +180,6 @@ class State : public Base::State { void apply_multiplexer(const reg_t &control_qubits, const reg_t &target_qubits, const cmatrix_t &mat); - // Apply multiple gate operations - void apply_matrix_sequence(const std::vector ®s, const std::vector& mats); - // Apply a Kraus error operation void apply_kraus(const reg_t &qubits, const std::vector &krausops, @@ -289,23 +285,24 @@ template const stringmap_t State::gateset_({ // Single qubit gates {"id", Gates::id}, // Pauli-Identity gate - {"x", Gates::mcx}, // Pauli-X gate - {"y", Gates::mcy}, // Pauli-Y gate - {"z", Gates::mcz}, // Pauli-Z gate + {"x", Gates::mcx}, // Pauli-X gate + {"y", Gates::mcy}, // Pauli-Y gate + {"z", Gates::mcz}, // Pauli-Z gate {"s", Gates::s}, // Phase gate (aka sqrt(Z) gate) {"sdg", Gates::sdg}, // Conjugate-transpose of Phase gate {"h", Gates::h}, // Hadamard gate (X + Z / sqrt(2)) {"t", Gates::t}, // T-gate (sqrt(S)) {"tdg", Gates::tdg}, // Conjguate-transpose of T gate // Waltz Gates - {"u1", Gates::mcu1}, // zero-X90 pulse waltz gate - {"u2", Gates::mcu2}, // single-X90 pulse waltz gate - {"u3", Gates::mcu3}, // two X90 pulse waltz gate + {"u1", Gates::mcu1}, // zero-X90 pulse waltz gate + {"u2", Gates::mcu2}, // single-X90 pulse waltz gate + {"u3", Gates::mcu3}, // two X90 pulse waltz gate // Two-qubit gates - {"cx", Gates::mcx}, // Controlled-X gate (CNOT) - {"cy", Gates::mcy}, // Controlled-Y gate - {"cz", Gates::mcz}, // Controlled-Z gate - {"swap", Gates::mcswap}, // SWAP gate + {"cx", Gates::mcx}, // Controlled-X gate (CNOT) + {"cy", Gates::mcy}, // Controlled-Y gate + {"cz", Gates::mcz}, // Controlled-Z gate + {"cu1", Gates::mcu1}, // Controlled-u1 gate + {"swap", Gates::mcswap}, // SWAP gate {"mcswap", Gates::mcswap}, // Multi-controlled SWAP gate // Multi-qubit controlled gates {"ccx", Gates::mcx}, // Controlled-CX gate (Toffoli) @@ -419,6 +416,7 @@ template void State::apply_ops(const std::vector &ops, OutputData &data, RngEngine &rng) { + // Simple loop over vector of input operations for (const auto & op: ops) { switch (op.type) { @@ -447,11 +445,8 @@ void State::apply_ops(const std::vector &ops, apply_snapshot(op, data); break; case Operations::OpType::matrix: - apply_matrix(op.qubits, op.mats[0]); + apply_matrix(op); break; - case Operations::OpType::matrix_sequence: - apply_matrix_sequence(op.regs, op.mats); - break; case Operations::OpType::multiplexer: apply_multiplexer(op.regs[0], op.regs[1], op.mats); // control qubits ([0]) & target qubits([1]) break; @@ -567,7 +562,7 @@ void State::snapshot_pauli_expval(const Operations::Op &op, BaseState::qreg_.apply_mcy({op.qubits[pos]}); break; case 'Z': - BaseState::qreg_.apply_mcz({op.qubits[pos]}); + BaseState::qreg_.apply_mcphase({op.qubits[pos]}, -1); break; default: { std::stringstream msg; @@ -658,7 +653,7 @@ void State::apply_gate(const Operations::Op &op) { break; case Gates::mcz: // Includes Z, CZ, CCZ, etc - BaseState::qreg_.apply_mcz(op.qubits); + BaseState::qreg_.apply_mcphase(op.qubits, -1); break; case Gates::id: break; @@ -699,9 +694,8 @@ void State::apply_gate(const Operations::Op &op) { break; case Gates::mcu1: // Includes u1, cu1, etc - apply_gate_mcu3(op.qubits, 0., 0., std::real(op.params[0])); + BaseState::qreg_.apply_mcphase(op.qubits, std::exp(complex_t(0, 1) * op.params[0])); break; - default: // We shouldn't reach here unless there is a bug in gateset throw std::invalid_argument("QubitVector::State::invalid gate instruction \'" + @@ -710,13 +704,6 @@ void State::apply_gate(const Operations::Op &op) { } -template -void State::apply_matrix(const reg_t &qubits, const cmatrix_t &mat) { - if (qubits.empty() == false && mat.size() > 0) { - apply_matrix(qubits, Utils::vectorize_matrix(mat)); - } -} - template void State::apply_multiplexer(const reg_t &control_qubits, const reg_t &target_qubits, const cmatrix_t &mat) { if (control_qubits.empty() == false && target_qubits.empty() == false && mat.size() > 0) { @@ -725,6 +712,17 @@ void State::apply_multiplexer(const reg_t &control_qubits, const reg } } +template +void State::apply_matrix(const Operations::Op &op) { + if (op.qubits.empty() == false && op.mats[0].size() > 0) { + if (Utils::is_diagonal(op.mats[0], .0)) { + BaseState::qreg_.apply_diagonal_matrix(op.qubits, Utils::matrix_diagonal(op.mats[0])); + } else { + BaseState::qreg_.apply_matrix(op.qubits, Utils::vectorize_matrix(op.mats[0])); + } + } +} + template void State::apply_matrix(const reg_t &qubits, const cvector_t &vmat) { // Check if diagonal matrix @@ -736,21 +734,6 @@ void State::apply_matrix(const reg_t &qubits, const cvector_t &vmat) } - -template -void State::apply_matrix_sequence(const std::vector ®s, const std::vector& mats) { - - if (regs.empty()) - return; - - std::vector vmats; - for (const cmatrix_t& mat: mats) - vmats.push_back(Utils::vectorize_matrix(mat)); - - BaseState::qreg_.apply_matrix_sequence(regs, vmats); -} - - template void State::apply_gate_mcu3(const reg_t& qubits, double theta, diff --git a/src/simulators/unitary/unitary_state.hpp b/src/simulators/unitary/unitary_state.hpp index 1ceb75f22e..efc5172ead 100755 --- a/src/simulators/unitary/unitary_state.hpp +++ b/src/simulators/unitary/unitary_state.hpp @@ -67,7 +67,7 @@ class State : public Base::State> { // Return the set of qobj gate instruction names supported by the State virtual stringset_t allowed_gates() const override { - return {"u1", "u2", "u3", "cx", "cz", "cy", "swap", + return {"u1", "u2", "u3", "cx", "cz", "cy", "cu1", "swap", "id", "x", "y", "z", "h", "s", "sdg", "t", "tdg", "ccx", "mcx", "mcz", "mcy", "mcu1", "mcu2", "mcu3", "mcswap"}; } @@ -187,9 +187,10 @@ const stringmap_t State::gateset_({ {"u2", Gates::mcu2}, // single-X90 pulse waltz gate {"u3", Gates::mcu3}, // two X90 pulse waltz gate // Two-qubit gates - {"cx", Gates::mcx}, // Controlled-X gate (CNOT) - {"cy", Gates::mcy}, // Controlled-Z gate - {"cz", Gates::mcz}, // Controlled-Z gate + {"cx", Gates::mcx}, // Controlled-X gate (CNOT) + {"cy", Gates::mcy}, // Controlled-Z gate + {"cz", Gates::mcz}, // Controlled-Z gate + {"cu1", Gates::mcu1}, // Controlled-u1 gate {"swap", Gates::mcswap}, // SWAP gate // Three-qubit gates {"ccx", Gates::mcx}, // Controlled-CX gate (Toffoli) @@ -322,7 +323,7 @@ void State::apply_gate(const Operations::Op &op) { break; case Gates::mcz: // Includes Z, CZ, CCZ, etc - BaseState::qreg_.apply_mcz(op.qubits); + BaseState::qreg_.apply_mcphase(op.qubits, -1); break; case Gates::id: break; @@ -363,7 +364,7 @@ void State::apply_gate(const Operations::Op &op) { break; case Gates::mcu1: // Includes u1, cu1, etc - apply_gate_mcu3(op.qubits, 0., 0., std::real(op.params[0])); + BaseState::qreg_.apply_mcphase(op.qubits, std::exp(complex_t(0, 1) * op.params[0])); break; default: // We shouldn't reach here unless there is a bug in gateset diff --git a/src/transpile/basic_opts.hpp b/src/transpile/basic_opts.hpp index 7df6b28ce9..cb85e4b678 100644 --- a/src/transpile/basic_opts.hpp +++ b/src/transpile/basic_opts.hpp @@ -27,24 +27,28 @@ using oplist_t = std::vector; using opset_t = Operations::OpSet; using reg_t = std::vector; -class ReduceNop : public CircuitOptimization { +class ReduceBarrier : public CircuitOptimization { public: void optimize_circuit(Circuit& circ, const opset_t &opset, OutputData &data) const override; }; -void ReduceNop::optimize_circuit(Circuit& circ, +void ReduceBarrier::optimize_circuit(Circuit& circ, const opset_t &allowed_opset, OutputData &data) const { - oplist_t::iterator it = circ.ops.begin(); - while (it != circ.ops.end()) { - if (it->type == optype_t::barrier) - it = circ.ops.erase(it); - else - ++it; + size_t idx = 0; + for (size_t i = 0; i < circ.ops.size(); ++i) { + if (circ.ops[i].type != optype_t::barrier) { + if (idx != i) + circ.ops[idx] = circ.ops[i]; + ++idx; + } } + + if (idx != circ.ops.size()) + circ.ops.erase(circ.ops.begin() + idx, circ.ops.end()); } class Debug : public CircuitOptimization { diff --git a/src/transpile/fusion.hpp b/src/transpile/fusion.hpp index 0a05b567f6..5a45a3492d 100644 --- a/src/transpile/fusion.hpp +++ b/src/transpile/fusion.hpp @@ -29,19 +29,33 @@ using reg_t = std::vector; class Fusion : public CircuitOptimization { public: - Fusion(uint_t max_qubit = 5, uint_t threshold = 16, double cost_factor = 2.5); - + // constructor + Fusion(uint_t max_qubit = 5, uint_t threshold = 16, double cost_factor = 1.8); + + /* + * Fusion optimization uses following configuration options + * - fusion_verbose (bool): if true, output generated gates in metadata (default: false) + * - fusion_enable (bool): if true, activate fusion optimization (default: false) + * - fusion_max_qubit (int): maximum number of qubits for a operation (default: 5) + * - fusion_threshold (int): a threshold to activate fusion optimization when fusion_enable is true (default: 16) + * - fusion_cost_factor (double): a cost function to estimate an aggregate gate (default: 1.8) + */ void set_config(const json_t &config) override; void optimize_circuit(Circuit& circ, const opset_t &opset, OutputData &data) const override; +private: bool can_ignore(const op_t& op) const; bool can_apply_fusion(const op_t& op) const; - oplist_t aggregate(const oplist_t& buffer) const; + double get_cost(const op_t& op) const; + + bool aggregate_operations(oplist_t& ops, const int fusion_start, const int fusion_end) const; + + op_t generate_fusion_operation(const std::vector& fusioned_ops) const; void swap_cols_and_rows(const uint_t idx1, const uint_t idx2, @@ -52,6 +66,14 @@ class Fusion : public CircuitOptimization { const reg_t &sorted, const cmatrix_t &mat) const; + cmatrix_t expand_matrix(const reg_t& src_qubits, + const reg_t& dst_sorted_qubits, + const cmatrix_t& mat) const; + + bool only_u1(const oplist_t& ops, + const uint_t from, + const uint_t until) const; + double estimate_cost(const oplist_t& ops, const uint_t from, const uint_t until) const; @@ -69,7 +91,7 @@ class Fusion : public CircuitOptimization { private: uint_t max_qubit_; uint_t threshold_; - const double cost_factor_; + double cost_factor_; bool verbose_; bool active_; }; @@ -120,6 +142,8 @@ void Fusion::set_config(const json_t &config) { if (JSON::check_key("fusion_threshold", config_)) JSON::get_value(threshold_, "fusion_threshold", config_); + if (JSON::check_key("fusion_cost_factor", config_)) + JSON::get_value(cost_factor_, "fusion_cost_factor", config_); } @@ -149,50 +173,46 @@ void Fusion::optimize_circuit(Circuit& circ, OutputData &data) const { if (circ.num_qubits < threshold_ - || !active_ - || allowed_opset.optypes.find(optype_t::matrix_sequence) == allowed_opset.optypes.end()) + || !active_) return; - bool ret = false; - - oplist_t optimized_ops; - - optimized_ops.clear(); - oplist_t buffer; + bool applied = false; - for (const op_t op: circ.ops) { - if (can_ignore(op)) + uint_t fusion_start = 0; + for (uint_t op_idx = 0; op_idx < circ.ops.size(); ++op_idx) { + if (can_ignore(circ.ops[op_idx])) continue; - if (!can_apply_fusion(op)) { - if (!buffer.empty()) { - ret = true; - oplist_t optimized_buffer = aggregate(buffer); - optimized_ops.insert(optimized_ops.end(), optimized_buffer.begin(), optimized_buffer.end()); - buffer.clear(); - } - optimized_ops.push_back(op); - } else { - buffer.push_back(op); + if (!can_apply_fusion(circ.ops[op_idx])) { + applied |= fusion_start != op_idx && aggregate_operations(circ.ops, fusion_start, op_idx); + fusion_start = op_idx + 1; } } - if (!buffer.empty()) { - oplist_t optimized_buffer = aggregate(buffer); - optimized_ops.insert(optimized_ops.end(), optimized_buffer.begin(), optimized_buffer.end()); - ret = true; - } + if (fusion_start < circ.ops.size() + && aggregate_operations(circ.ops, fusion_start, circ.ops.size())) + applied = true; - if (ret) { - circ.ops = optimized_ops; - } + if (applied) { + + size_t idx = 0; + for (size_t i = 0; i < circ.ops.size(); ++i) { + if (circ.ops[i].name != "nop") { + if (i != idx) + circ.ops[idx] = circ.ops[i]; + ++idx; + } + } + + if (idx != circ.ops.size()) + circ.ops.erase(circ.ops.begin() + idx, circ.ops.end()); - if (verbose_) { - data.add_additional_data("metadata", - json_t::object({{"fusion_verbose", optimized_ops}})); + if (verbose_) + data.add_additional_data("metadata", + json_t::object({{"fusion_verbose", circ.ops}})); } #ifdef DEBUG - dump(optimized_ops); + dump(circ.ops); #endif } @@ -228,83 +248,202 @@ bool Fusion::can_apply_fusion(const op_t& op) const { } } -oplist_t Fusion::aggregate(const oplist_t& original) const { +double Fusion::get_cost(const op_t& op) const { + if (can_ignore(op)) + return .0; + else + return cost_factor_; +} + +bool Fusion::aggregate_operations(oplist_t& ops, const int fusion_start, const int fusion_end) const { // costs[i]: estimated cost to execute from 0-th to i-th in original.ops std::vector costs; // fusion_to[i]: best path to i-th in original.ops std::vector fusion_to; - int applied_total = 0; - // calculate the minimal path to each operation in the circuit - for (size_t i = 0; i < original.size(); ++i) { - bool applied = false; + // set costs and fusion_to of fusion_start + fusion_to.push_back(fusion_start); + costs.push_back(get_cost(ops[fusion_start])); - // first, fusion from i-th to i-th + bool applied = false; + // calculate the minimal path to each operation in the circuit + for (int i = fusion_start + 1; i < fusion_end; ++i) { + // init with fusion from i-th to i-th fusion_to.push_back(i); + costs.push_back(costs[i - fusion_start - 1] + get_cost(ops[i])); - // calculate the initial cost from i-th to i-th - if (i == 0) { - // if this is the first op, no fusion - costs.push_back(cost_factor_); - continue; - } - // otherwise, i-th cost is calculated from (i-1)-th cost - costs.push_back(costs[i - 1] + cost_factor_); - - for (uint_t num_fusion = 2; num_fusion <= max_qubit_; ++num_fusion) { + for (int num_fusion = 2; num_fusion <= static_cast (max_qubit_); ++num_fusion) { // calculate cost if {num_fusion}-qubit fusion is applied reg_t fusion_qubits; - add_fusion_qubits(fusion_qubits, original[i]); + add_fusion_qubits(fusion_qubits, ops[i]); - for (int j = i - 1; j >= 0; --j) { - add_fusion_qubits(fusion_qubits, original[j]); + for (int j = i - 1; j >= fusion_start; --j) { + add_fusion_qubits(fusion_qubits, ops[j]); - if (fusion_qubits.size() > num_fusion) // exceed the limit of fusion + if (static_cast (fusion_qubits.size()) > num_fusion) // exceed the limit of fusion break; // calculate a new cost of (i-th) by adding - double estimated_cost = estimate_cost(original, (uint_t) j, i) // fusion gate from j-th to i-th, and - + (j == 0 ? 0.0 : costs[j - 1]); // cost of (j-1)-th + double estimated_cost = estimate_cost(ops, (uint_t) j, i) // fusion gate from j-th to i-th, and + + (j == 0 ? 0.0 : costs[j - 1 - fusion_start]); // cost of (j-1)-th // update cost - if (estimated_cost < costs[i]) { - costs[i] = estimated_cost; - fusion_to[i] = j; + if (estimated_cost <= costs[i - fusion_start]) { + costs[i - fusion_start] = estimated_cost; + fusion_to[i - fusion_start] = j; applied = true; } } } - if (applied) - ++applied_total; } - if (applied_total / static_cast (original.size()) < 0.25) - return original; + if (!applied) + return false; // generate a new circuit with the minimal path to the last operation in the circuit - oplist_t optimized; + for (int i = fusion_end - 1; i >= fusion_start;) { - for (int i = original.size() - 1; i >= 0;) { - int to = fusion_to[i]; + int to = fusion_to[i - fusion_start]; - if (to == i) { - optimized.push_back(original[i]); - } else { - std::vector regs; - std::vector mats; + if (to != i) { + std::vector fusioned_ops; for (int j = to; j <= i; ++j) { - regs.push_back(original[j].qubits); - mats.push_back(matrix(original[j])); + fusioned_ops.push_back(ops[j]); + ops[j].name = "nop"; } - optimized.push_back(Operations::make_matrix_sequence(regs, mats)); + if (!fusioned_ops.empty()) + ops[i] = generate_fusion_operation(fusioned_ops); } i = to - 1; } - std::reverse(optimized.begin(), optimized.end()); + return true; +} + +op_t Fusion::generate_fusion_operation(const std::vector& fusioned_ops) const { + + std::vector regs; + std::vector mats; + + for (const op_t& fusioned_op: fusioned_ops) { + regs.push_back(fusioned_op.qubits); + mats.push_back(matrix(fusioned_op)); + } + + reg_t sorted_qubits; + for (const reg_t& reg: regs) + for (const uint_t qubit: reg) + if (std::find(sorted_qubits.begin(), sorted_qubits.end(), qubit) == sorted_qubits.end()) + sorted_qubits.push_back(qubit); + + std::sort(sorted_qubits.begin(), sorted_qubits.end()); + + std::vector sorted_mats; + + for (size_t i = 0; i < regs.size(); ++i) { + const reg_t& reg = regs[i]; + const cmatrix_t& mat = mats[i]; + sorted_mats.push_back(expand_matrix(reg, sorted_qubits, mat)); + } + + auto U = sorted_mats[0]; + const auto dim = 1ULL << sorted_qubits.size(); + + for (size_t m = 1; m < sorted_mats.size(); m++) { + + cmatrix_t u_tmp(U.GetRows(), U.GetColumns()); + const cmatrix_t& u = sorted_mats[m]; + + for (size_t i = 0; i < dim; ++i) + for (size_t j = 0; j < dim; ++j) + for (size_t k = 0; k < dim; ++k) + u_tmp(i, j) += u(i, k) * U(k, j); + + U = u_tmp; + } + + return Operations::make_fusion(sorted_qubits, U, fusioned_ops); +} + +cmatrix_t Fusion::expand_matrix(const reg_t& src_qubits, const reg_t& dst_sorted_qubits, const cmatrix_t& mat) const { + + const auto dst_dim = 1ULL << dst_sorted_qubits.size(); + + // generate a matrix for op + cmatrix_t u(dst_dim, dst_dim); + std::vector filled(dst_dim, false); + + if (src_qubits.size() == 1) { //1-qubit operation + // 1. identify delta + const auto index = std::distance(dst_sorted_qubits.begin(), + std::find(dst_sorted_qubits.begin(), dst_sorted_qubits.end(), src_qubits[0])); + + const auto delta = 1ULL << index; - return optimized; + // 2. find vmat(0, 0) position in U + for (uint_t i = 0; i < dst_dim; ++i) { + + if (filled[i]) + continue; + + // 3. allocate op.u to u based on u(i, i) and delta + u(i , (i + 0) ) = mat(0, 0); + u(i , (i + delta)) = mat(0, 1); + u((i + delta), (i + 0) ) = mat(1, 0); + u((i + delta), (i + delta)) = mat(1, 1); + filled[i] = filled[i + delta] = true; + } + } else if (src_qubits.size() == 2) { //2-qubit operation + + reg_t sorted_src_qubits = src_qubits; + std::sort(sorted_src_qubits.begin(), sorted_src_qubits.end()); + const cmatrix_t sorted_mat = sort_matrix(src_qubits, sorted_src_qubits, mat); + + // 1. identify low and high delta + auto low = std::distance(dst_sorted_qubits.begin(), + std::find(dst_sorted_qubits.begin(), dst_sorted_qubits.end(), sorted_src_qubits[0])); + + auto high = std::distance(dst_sorted_qubits.begin(), + std::find(dst_sorted_qubits.begin(), dst_sorted_qubits.end(), sorted_src_qubits[1])); + + auto low_delta = 1UL << low; + auto high_delta = 1UL << high; + + // 2. find op.u(0, 0) position in U + for (uint_t i = 0; i < dst_dim; ++i) { + if (filled[i]) + continue; + + // 3. allocate vmat to u based on u(i, i) and delta + u(i , (i + 0) ) = sorted_mat(0, 0); + u(i , (i + low_delta) ) = sorted_mat(0, 1); + u(i , (i + high_delta) ) = sorted_mat(0, 2); + u(i , (i + low_delta + high_delta)) = sorted_mat(0, 3); + u((i + low_delta) , (i + 0) ) = sorted_mat(1, 0); + u((i + low_delta) , (i + low_delta) ) = sorted_mat(1, 1); + u((i + low_delta) , (i + high_delta) ) = sorted_mat(1, 2); + u((i + low_delta) , (i + low_delta + high_delta)) = sorted_mat(1, 3); + u((i + high_delta) , (i + 0) ) = sorted_mat(2, 0); + u((i + high_delta) , (i + low_delta) ) = sorted_mat(2, 1); + u((i + high_delta) , (i + high_delta) ) = sorted_mat(2, 2); + u((i + high_delta) , (i + low_delta + high_delta)) = sorted_mat(2, 3); + u((i + low_delta + high_delta), (i + 0) ) = sorted_mat(3, 0); + u((i + low_delta + high_delta), (i + low_delta) ) = sorted_mat(3, 1); + u((i + low_delta + high_delta), (i + high_delta) ) = sorted_mat(3, 2); + u((i + low_delta + high_delta), (i + low_delta + high_delta)) = sorted_mat(3, 3); + + filled[i] = true; + filled[i + low_delta] = true; + filled[i + high_delta] = true; + filled[i + low_delta + high_delta] = true; + } + //TODO: } else if (src_qubits.size() == 3) { + } else { + throw std::runtime_error("Fusion::illegal qubit number: " + std::to_string(src_qubits.size())); + } + + return u; } //------------------------------------------------------------------------------ @@ -368,13 +507,41 @@ cmatrix_t Fusion::sort_matrix(const reg_t &src, return ret; } +bool Fusion::only_u1(const std::vector& ops, + const uint_t from, + const uint_t until) const { + + for (uint_t i = from; i <= until; ++i) { + if (ops[i].name == "u1") + continue; + if (from < i && (i + 2) <= until + && ops[i - 1].name == "u1" + && ops[i ].name == "cx" + && ops[i + 1].name == "u1" + && ops[i + 2].name == "cx" + && ops[i - 1].qubits[0] == ops[i ].qubits[1] + && ops[i ].qubits[1] == ops[i + 1].qubits[0] + && ops[i + 1].qubits[0] == ops[i + 2].qubits[1] + && ops[i ].qubits[0] == ops[i + 2].qubits[0] ) + { + i += 2; + continue; + } + return false; + } + return true; +} + double Fusion::estimate_cost(const std::vector& ops, const uint_t from, const uint_t until) const { + if (only_u1(ops, from, until)) + return cost_factor_; + reg_t fusion_qubits; for (uint_t i = from; i <= until; ++i) add_fusion_qubits(fusion_qubits, ops[i]); - return pow(cost_factor_, (double) fusion_qubits.size()); + return pow(cost_factor_, (double) std::max(fusion_qubits.size() - 1, size_t(1))); } void Fusion::add_fusion_qubits(reg_t& fusion_qubits, const op_t& op) const { @@ -432,7 +599,9 @@ cmatrix_t Fusion::matrix(const op_t& op) const { } else if (op.type == optype_t::matrix) { return op.mats[0]; } else { - throw std::runtime_error("Fusion: unexpected operation type"); + std::stringstream msg; + msg << "Fusion: unexpected operation type:" << op.type; + throw std::runtime_error(msg.str()); } } diff --git a/src/transpile/truncate_qubits.hpp b/src/transpile/truncate_qubits.hpp index 9ab4a73b8d..ed49b0572a 100644 --- a/src/transpile/truncate_qubits.hpp +++ b/src/transpile/truncate_qubits.hpp @@ -78,19 +78,18 @@ void TruncateQubits::optimize_circuit(Circuit& circ, if (new_mapping.size() == circ.num_qubits) return; - std::vector new_ops; - for (const Operations::Op& old_op: circ.ops) { - auto op = old_op; + for (Operations::Op& op: circ.ops) { // Remap qubits - op.qubits = remap_qubits(op.qubits, new_mapping); + reg_t new_qubits = remap_qubits(op.qubits, new_mapping); // Remap regs - for (reg_t ® : op.regs) { - reg = remap_qubits(reg, new_mapping); - } - new_ops.push_back(op); + std::vector new_regs; + for (reg_t ® : op.regs) + new_regs.push_back(remap_qubits(reg, new_mapping)); + + op.qubits = new_qubits; + op.regs = new_regs; } - circ.ops = new_ops; circ.num_qubits = new_mapping.size(); if (verbose_) { @@ -104,9 +103,13 @@ reg_t TruncateQubits::generate_mapping(const Circuit& circ) const { size_t not_used = circ.num_qubits + 1; reg_t mapping = reg_t(circ.num_qubits, not_used); - for (const Operations::Op& op: circ.ops) + for (const Operations::Op& op: circ.ops) { for (size_t qubit: op.qubits) mapping[qubit] = qubit; + for (const reg_t ®: op.regs) + for (size_t qubit: reg) + mapping[qubit] = qubit; + } mapping.erase(std::remove(mapping.begin(), mapping.end(), not_used), mapping.end()); diff --git a/test/benchmark/__init__.py b/test/benchmark/__init__.py index e10866e310..3b9d5acb9e 100644 --- a/test/benchmark/__init__.py +++ b/test/benchmark/__init__.py @@ -13,10 +13,12 @@ from qiskit.qobj import Qobj from qiskit.providers.aer.noise import NoiseModel + def qobj_repr_hook(self): """ This is needed for ASV to beauty-printing reports """ return "Num. qubits: {0}".format(self.config.n_qubits) + Qobj.__repr__ = qobj_repr_hook @@ -24,4 +26,5 @@ def noise_model_repr_hook(self): """ This is needed for ASV to beauty-printing reports """ return self.__class__.__name__.replace("_", " ").capitalize() + NoiseModel.__repr__ = noise_model_repr_hook diff --git a/test/benchmark/quantum_fourier_transform_benchmarks.py b/test/benchmark/quantum_fourier_transform_benchmarks.py new file mode 100644 index 0000000000..2fa82477e8 --- /dev/null +++ b/test/benchmark/quantum_fourier_transform_benchmarks.py @@ -0,0 +1,78 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Quantum Fourier Transform benchmark suite""" +# Write the benchmarking functions here. +# See "Writing benchmarks" in the asv docs for more information. + +from qiskit import QiskitError +from qiskit.compiler import transpile, assemble +from qiskit.providers.aer import QasmSimulator +from .tools import quantum_fourier_transform_circuit, mixed_unitary_noise_model, \ + reset_noise_model, kraus_noise_model, no_noise + + +class QuantumFourierTransformTimeSuite: + """ + Benchmarking times for Quantum Fourier Transform with various noise configurations + - ideal (no noise) + - mixed state + - reset + - kraus + + For each noise model, we want to test various configurations of number of + qubits + + The methods defined in this class will be executed by ASV framework as many + times as the combination of all parameters exist in `self.params`, for + exmaple: self.params = ([1,2,3],[4,5,6]), will run all methdos 9 times: + time_method(1,4) + time_method(1,5) + time_method(1,6) + time_method(2,4) + time_method(2,5) + time_method(2,6) + time_method(3,4) + time_method(3,5) + time_method(3,6) + """ + + def __init__(self): + self.timeout = 60 * 20 + self.qft_circuits = [] + self.backend = QasmSimulator() + for num_qubits in (5, 10, 15): + circ = quantum_fourier_transform_circuit(num_qubits) + circ = transpile(circ) + qobj = assemble(circ, self.backend, shots=1) + self.qft_circuits.append(qobj) + + self.param_names = ["Quantum Fourier Transform", "Noise Model"] + # This will run every benchmark for one of the combinations we have here: + # bench(qft_circuits, None) => bench(qft_circuits, mixed()) => + # bench(qft_circuits, reset) => bench(qft_circuits, kraus()) + self.params = (self.qft_circuits, [ + no_noise(), + mixed_unitary_noise_model(), + reset_noise_model(), + kraus_noise_model() + ]) + + + def setup(self, qobj, noise_model_wrapper): + pass + + + def time_quantum_fourier_transform(self, qobj, noise_model_wrapper): + result = self.backend.run(qobj, noise_model=noise_model_wrapper()).result() + if result.status != 'COMPLETED': + raise QiskitError("Simulation failed. Status: " + result.status) diff --git a/test/benchmark/quantum_volume_benchmarks.py b/test/benchmark/quantum_volume_benchmarks.py index a891023037..04a9f8021f 100644 --- a/test/benchmark/quantum_volume_benchmarks.py +++ b/test/benchmark/quantum_volume_benchmarks.py @@ -20,6 +20,7 @@ from .tools import quantum_volume_circuit, mixed_unitary_noise_model, \ reset_noise_model, kraus_noise_model, no_noise + class QuantumVolumeTimeSuite: """ Benchmarking times for Quantum Volume with various noise configurations diff --git a/test/benchmark/tools.py b/test/benchmark/tools.py index b076df1c97..ab73920c3a 100644 --- a/test/benchmark/tools.py +++ b/test/benchmark/tools.py @@ -24,6 +24,7 @@ from qiskit.providers.aer.noise.errors import amplitude_damping_error from qiskit.providers.aer.noise.errors import thermal_relaxation_error + class NoiseWithDescription: """ This is just a wrapper for adding a descriptive text to the noise model so ASV can print this text in its reports @@ -31,11 +32,14 @@ class NoiseWithDescription: def __init__(self, noise_model, description): self._noise_model = noise_model self._description = description + def __repr__(self): return self._description + def __call__(self): return self._noise_model + def _add_measurements(circuit, qr): cr = ClassicalRegister(qr.size) meas = QuantumCircuit(qr, cr) @@ -118,6 +122,29 @@ def quantum_volume_circuit(num_qubits, depth, measure=True, seed=None): # Transpile to force it into u1,u2,u3,cx basis gates return transpile(circuit, basis_gates=['u1', 'u2', 'u3', 'cx']) +def qft_circuit(num_qubits, measure=True): + """Create a qft circuit. + + Args: + num_qubits (int): number of qubits + measure (bool): include measurement in circuit. + + Returns: + QftCircuit: A qft circuit. + """ + # Create quantum/classical registers of size n + qr = QuantumRegister(num_qubits) + circuit = QuantumCircuit(qr) + + for i in range(num_qubits): + for j in range(i): + circuit.cu1(math.pi/float(2**(i-j)), qr[i], qr[j]) + circuit.h(qr[i]) + + if measure is True: + circuit = _add_measurements(circuit, qr) + # Transpile to force it into u1,u2,u3,cx basis gates + return transpile(circuit, basis_gates=['u1', 'u2', 'u3', 'cx']) def simple_u3_circuit(num_qubits, measure=True): """Creates a simple circuit composed by u3 gates, with measurements or not @@ -161,3 +188,28 @@ def simple_cnot_circuit(num_qubits, measure=True): if measure: circuit = _add_measurements(circuit, qr) return circuit + + +# pylint: disable=invalid-name +def quantum_fourier_transform_circuit(num_qubits): + """Create quantum fourier transform circuit. + + Args: + num_qubits (int): Number of qubits + + Returns: + QuantumCircuit: QFT circuit + """ + qreg = QuantumRegister(num_qubits) + creg = ClassicalRegister(num_qubits) + + circuit = QuantumCircuit(qreg, creg, name="qft") + + n = len(qreg) + + for i in range(n): + for j in range(i): + circuit.cu1(math.pi/float(2**(i-j)), qreg[i], qreg[j]) + circuit.h(qreg[i]) + circuit.measure(qreg, creg) + return circuit diff --git a/test/terra/backends/qasm_simulator/qasm_basics.py b/test/terra/backends/qasm_simulator/qasm_basics.py index ed5d1f99ed..7a1a427b66 100644 --- a/test/terra/backends/qasm_simulator/qasm_basics.py +++ b/test/terra/backends/qasm_simulator/qasm_basics.py @@ -32,7 +32,6 @@ def test_simulation_succeed(self): result = mocked_backend.run(qobj).result() self.is_completed(result) - def test_simulation_failed(self): """Test the we properly manage simulation failures.""" mocked_backend = FakeFailureQasmSimulator(time_alive=0) diff --git a/test/terra/backends/qasm_simulator/qasm_fusion.py b/test/terra/backends/qasm_simulator/qasm_fusion.py index ac6db34a9e..e54e46f0d1 100644 --- a/test/terra/backends/qasm_simulator/qasm_fusion.py +++ b/test/terra/backends/qasm_simulator/qasm_fusion.py @@ -13,17 +13,13 @@ """ QasmSimulator Integration Tests """ - -from test.benchmark.tools import quantum_volume_circuit +from test.terra.reference import ref_2q_clifford from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.compiler import assemble, transpile from qiskit.providers.aer import QasmSimulator from qiskit.providers.aer.noise import NoiseModel from qiskit.providers.aer.noise.errors import ReadoutError, depolarizing_error - -from test.terra.reference import ref_1q_clifford -from test.terra.reference import ref_2q_clifford - +from test.benchmark.tools import quantum_volume_circuit, qft_circuit class QasmFusionTests: """QasmSimulator fusion tests.""" @@ -31,6 +27,7 @@ class QasmFusionTests: SIMULATOR = QasmSimulator() def create_statevector_circuit(self): + """ Creates a simple circuit for running in the statevector """ qr = QuantumRegister(10) cr = ClassicalRegister(10) circuit = QuantumCircuit(qr, cr) @@ -48,6 +45,7 @@ def create_statevector_circuit(self): return circuit def noise_model(self): + """ Creates a new noise model for testing purposes """ readout_error = [0.01, 0.1] depolarizing = {'u3': (1, 0.001), 'cx': (2, 0.02)} noise = NoiseModel() @@ -272,17 +270,15 @@ def test_fusion_operations(self): circuit.h(qr[i]) circuit.barrier(qr) - circuit.x(qr[0]) + circuit.u3(0.1, 0.1, 0.1, qr[0]) circuit.barrier(qr) - circuit.x(qr[1]) + circuit.u3(0.1, 0.1, 0.1, qr[1]) circuit.barrier(qr) - circuit.x(qr[0]) + circuit.cx(qr[1], qr[0]) circuit.barrier(qr) - circuit.x(qr[1]) + circuit.u3(0.1, 0.1, 0.1, qr[0]) circuit.barrier(qr) - circuit.cx(qr[2], qr[3]) - circuit.barrier(qr) - circuit.u3(0.1, 0.1, 0.1, qr[3]) + circuit.u3(0.1, 0.1, 0.1, qr[1]) circuit.barrier(qr) circuit.u3(0.1, 0.1, 0.1, qr[3]) circuit.barrier(qr) @@ -350,3 +346,78 @@ def test_fusion_operations(self): result_nonfusion.get_counts(circuit), delta=0.0, msg="fusion x-x-x was failed") + + + def test_fusion_qv(self): + """Test Fusion with quantum volume""" + shots = 100 + + circuit = quantum_volume_circuit(10, 1, measure=True, seed=0) + qobj = assemble([circuit], self.SIMULATOR, shots=shots, seed_simulator=1) + + backend_options = self.BACKEND_OPTS.copy() + backend_options['fusion_enable'] = True + backend_options['fusion_verbose'] = True + backend_options['fusion_threshold'] = 1 + backend_options['optimize_ideal_threshold'] = 1 + backend_options['optimize_noise_threshold'] = 1 + + result_fusion = self.SIMULATOR.run( + qobj, + backend_options=backend_options).result() + self.is_completed(result_fusion) + + backend_options = self.BACKEND_OPTS.copy() + backend_options['fusion_enable'] = False + backend_options['fusion_verbose'] = True + backend_options['fusion_threshold'] = 1 + backend_options['optimize_ideal_threshold'] = 1 + backend_options['optimize_noise_threshold'] = 1 + + result_nonfusion = self.SIMULATOR.run( + qobj, + backend_options=backend_options).result() + self.is_completed(result_nonfusion) + + self.assertDictAlmostEqual( + result_fusion.get_counts(circuit), + result_nonfusion.get_counts(circuit), + delta=0.0, + msg="fusion for qv was failed") + + def test_fusion_qft(self): + """Test Fusion with qft""" + shots = 100 + + circuit = qft_circuit(10, measure=True) + qobj = assemble([circuit], self.SIMULATOR, shots=shots, seed_simulator=1) + + backend_options = self.BACKEND_OPTS.copy() + backend_options['fusion_enable'] = True + backend_options['fusion_verbose'] = True + backend_options['fusion_threshold'] = 1 + backend_options['optimize_ideal_threshold'] = 1 + backend_options['optimize_noise_threshold'] = 1 + + result_fusion = self.SIMULATOR.run( + qobj, + backend_options=backend_options).result() + self.is_completed(result_fusion) + + backend_options = self.BACKEND_OPTS.copy() + backend_options['fusion_enable'] = False + backend_options['fusion_verbose'] = True + backend_options['fusion_threshold'] = 1 + backend_options['optimize_ideal_threshold'] = 1 + backend_options['optimize_noise_threshold'] = 1 + + result_nonfusion = self.SIMULATOR.run( + qobj, + backend_options=backend_options).result() + self.is_completed(result_nonfusion) + + self.assertDictAlmostEqual( + result_fusion.get_counts(circuit), + result_nonfusion.get_counts(circuit), + delta=0.0, + msg="fusion for qft was failed") \ No newline at end of file diff --git a/test/terra/backends/qasm_simulator/qasm_thread_management.py b/test/terra/backends/qasm_simulator/qasm_thread_management.py index 0173b15793..aac796db87 100644 --- a/test/terra/backends/qasm_simulator/qasm_thread_management.py +++ b/test/terra/backends/qasm_simulator/qasm_thread_management.py @@ -311,3 +311,66 @@ def test_qasm_auto_disable_shot_parallelization_with_memory_shortage(self): multiprocessing.cpu_count(), msg="parallel_state_update should be " + str( multiprocessing.cpu_count())) + + + def test_qasm_auto_disable_shot_parallelization_with_max_parallel_shots(self): + """test disabling parallel shots because max_parallel_shots is 1""" + # Test circuit + shots = multiprocessing.cpu_count() + circuit = quantum_volume_circuit(16, 1, measure=True, seed=0) + + backend_opts = self.BACKEND_OPTS.copy() + backend_opts['max_parallel_shots'] = 1 + backend_opts['noise_model'] = self.dummy_noise_model() + + result = execute( + circuit, self.SIMULATOR, shots=shots, + backend_options=backend_opts).result() + if result.metadata['omp_enabled']: + self.assertEqual( + result.metadata['parallel_experiments'], + 1, + msg="parallel_experiments should be 1") + self.assertEqual( + result.to_dict()['results'][0]['metadata']['parallel_shots'], + 1, + msg="parallel_shots must be 1") + self.assertEqual( + result.to_dict()['results'][0]['metadata'] + ['parallel_state_update'], + multiprocessing.cpu_count(), + msg="parallel_state_update should be " + str( + multiprocessing.cpu_count())) + + + def _test_qasm_explicit_parallelization(self): + """test disabling parallel shots because max_parallel_shots is 1""" + # Test circuit + shots = multiprocessing.cpu_count() + circuit = quantum_volume_circuit(16, 1, measure=True, seed=0) + + backend_opts = self.BACKEND_OPTS.copy() + backend_opts['max_parallel_shots'] = 1 + backend_opts['max_parallel_experiments'] = 1 + backend_opts['noise_model'] = self.dummy_noise_model() + backend_opts['_parallel_experiments'] = 2 + backend_opts['_parallel_shots'] = 3 + backend_opts['_parallel_state_update'] = 4 + + result = execute( + circuit, self.SIMULATOR, shots=shots, + backend_options=backend_opts).result() + if result.metadata['omp_enabled']: + self.assertEqual( + result.metadata['parallel_experiments'], + 2, + msg="parallel_experiments should be 2") + self.assertEqual( + result.to_dict()['results'][0]['metadata']['parallel_shots'], + 3, + msg="parallel_shots must be 3") + self.assertEqual( + result.to_dict()['results'][0]['metadata'] + ['parallel_state_update'], + 4, + msg="parallel_state_update should be 4") diff --git a/test/terra/backends/test_statevector_simulator.py b/test/terra/backends/test_statevector_simulator.py index d84484a9dd..6588b12222 100644 --- a/test/terra/backends/test_statevector_simulator.py +++ b/test/terra/backends/test_statevector_simulator.py @@ -140,7 +140,8 @@ def test_h_gate_deterministic_waltz_basis_gates(self): """Test h-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.h_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.h_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -167,7 +168,8 @@ def test_h_gate_nondeterministic_waltz_basis_gates(self): """Test h-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.h_gate_circuits_nondeterministic(final_measure=False) targets = ref_1q_clifford.h_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -197,7 +199,8 @@ def test_x_gate_deterministic_waltz_basis_gates(self): """Test x-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.x_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.x_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -206,7 +209,8 @@ def test_x_gate_deterministic_minimal_basis_gates(self): """Test x-gate gate circuits compiling to u3,cx""" circuits = ref_1q_clifford.x_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.x_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -227,7 +231,8 @@ def test_z_gate_deterministic_waltz_basis_gates(self): """Test z-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.z_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.z_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -236,7 +241,8 @@ def test_z_gate_deterministic_minimal_basis_gates(self): """Test z-gate gate circuits compiling to u3,cx""" circuits = ref_1q_clifford.z_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.z_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -257,7 +263,8 @@ def test_y_gate_deterministic_waltz_basis_gates(self): """Test y-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.y_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.y_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -266,7 +273,8 @@ def test_y_gate_deterministic_minimal_basis_gates(self): """Test y-gate gate circuits compiling to u3, cx.""" circuits = ref_1q_clifford.y_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.y_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -287,7 +295,8 @@ def test_s_gate_deterministic_waltz_basis_gates(self): """Test s-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.s_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.s_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -296,7 +305,8 @@ def test_s_gate_deterministic_minimal_basis_gates(self): """Test s-gate gate circuits compiling to u3,cx""" circuits = ref_1q_clifford.s_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.s_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -314,7 +324,8 @@ def test_s_gate_nondeterministic_waltz_basis_gates(self): """Test s-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.s_gate_circuits_nondeterministic(final_measure=False) targets = ref_1q_clifford.s_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -323,7 +334,8 @@ def test_s_gate_nondeterministic_minimal_basis_gates(self): """Test s-gate gate circuits compiling to u3,cx""" circuits = ref_1q_clifford.s_gate_circuits_nondeterministic(final_measure=False) targets = ref_1q_clifford.s_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -344,7 +356,8 @@ def test_sdg_gate_deterministic_waltz_basis_gates(self): """Test sdg-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.sdg_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.sdg_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -353,7 +366,8 @@ def test_sdg_gate_deterministic_minimal_basis_gates(self): """Test sdg-gate gate circuits compiling to u3,cx""" circuits = ref_1q_clifford.sdg_gate_circuits_deterministic(final_measure=False) targets = ref_1q_clifford.sdg_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -371,7 +385,8 @@ def test_sdg_gate_nondeterministic_waltz_basis_gates(self): """Test sdg-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_1q_clifford.sdg_gate_circuits_nondeterministic(final_measure=False) targets = ref_1q_clifford.sdg_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -380,7 +395,8 @@ def test_sdg_gate_nondeterministic_minimal_basis_gates(self): """Test sdg-gate gate circuits compiling to u3,cx""" circuits = ref_1q_clifford.sdg_gate_circuits_nondeterministic(final_measure=False) targets = ref_1q_clifford.sdg_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -401,7 +417,8 @@ def test_cx_gate_deterministic_waltz_basis_gates(self): """Test cx-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_2q_clifford.cx_gate_circuits_deterministic(final_measure=False) targets = ref_2q_clifford.cx_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -410,7 +427,8 @@ def test_cx_gate_deterministic_minimal_basis_gates(self): """Test cx-gate gate circuits compiling to u3,cx""" circuits = ref_2q_clifford.cx_gate_circuits_deterministic(final_measure=False) targets = ref_2q_clifford.cx_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -428,7 +446,8 @@ def test_cx_gate_nondeterministic_waltz_basis_gates(self): """Test cx-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_2q_clifford.cx_gate_circuits_nondeterministic(final_measure=False) targets = ref_2q_clifford.cx_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -437,7 +456,8 @@ def test_cx_gate_nondeterministic_minimal_basis_gates(self): """Test cx-gate gate circuits compiling to u3,cx""" circuits = ref_2q_clifford.cx_gate_circuits_nondeterministic(final_measure=False) targets = ref_2q_clifford.cx_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -458,7 +478,8 @@ def test_cz_gate_deterministic_waltz_basis_gates(self): """Test cz-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_2q_clifford.cz_gate_circuits_deterministic(final_measure=False) targets = ref_2q_clifford.cz_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -467,7 +488,8 @@ def test_cz_gate_deterministic_minimal_basis_gates(self): """Test cz-gate gate circuits compiling to u3,cx""" circuits = ref_2q_clifford.cz_gate_circuits_deterministic(final_measure=False) targets = ref_2q_clifford.cz_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -485,7 +507,8 @@ def test_cz_gate_nondeterministic_waltz_basis_gates(self): """Test cz-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_2q_clifford.cz_gate_circuits_nondeterministic(final_measure=False) targets = ref_2q_clifford.cz_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -494,7 +517,8 @@ def test_cz_gate_nondeterministic_minimal_basis_gates(self): """Test cz-gate gate circuits compiling to u3,cx""" circuits = ref_2q_clifford.cz_gate_circuits_nondeterministic(final_measure=False) targets = ref_2q_clifford.cz_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -515,7 +539,8 @@ def test_swap_gate_deterministic_waltz_basis_gates(self): """Test swap-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_2q_clifford.swap_gate_circuits_deterministic(final_measure=False) targets = ref_2q_clifford.swap_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -524,7 +549,8 @@ def test_swap_gate_deterministic_minimal_basis_gates(self): """Test swap-gate gate circuits compiling to u3,cx""" circuits = ref_2q_clifford.swap_gate_circuits_deterministic(final_measure=False) targets = ref_2q_clifford.swap_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -542,7 +568,8 @@ def test_swap_gate_nondeterministic_waltz_basis_gates(self): """Test swap-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_2q_clifford.swap_gate_circuits_nondeterministic(final_measure=False) targets = ref_2q_clifford.swap_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -551,7 +578,8 @@ def test_swap_gate_nondeterministic_minimal_basis_gates(self): """Test swap-gate gate circuits compiling to u3,cx""" circuits = ref_2q_clifford.swap_gate_circuits_nondeterministic(final_measure=False) targets = ref_2q_clifford.swap_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -572,7 +600,8 @@ def test_t_gate_deterministic_waltz_basis_gates(self): """Test t-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_non_clifford.t_gate_circuits_deterministic(final_measure=False) targets = ref_non_clifford.t_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -581,7 +610,8 @@ def test_t_gate_deterministic_minimal_basis_gates(self): """Test t-gate gate circuits compiling to u3,cx""" circuits = ref_non_clifford.t_gate_circuits_deterministic(final_measure=False) targets = ref_non_clifford.t_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -599,7 +629,8 @@ def test_t_gate_nondeterministic_waltz_basis_gates(self): """Test t-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_non_clifford.t_gate_circuits_nondeterministic(final_measure=False) targets = ref_non_clifford.t_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -608,7 +639,8 @@ def test_t_gate_nondeterministic_minimal_basis_gates(self): """Test t-gate gate circuits compiling to u3,cx""" circuits = ref_non_clifford.t_gate_circuits_nondeterministic(final_measure=False) targets = ref_non_clifford.t_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -629,7 +661,8 @@ def test_tdg_gate_deterministic_waltz_basis_gates(self): """Test tdg-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_non_clifford.tdg_gate_circuits_deterministic(final_measure=False) targets = ref_non_clifford.tdg_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -638,7 +671,8 @@ def test_tdg_gate_deterministic_minimal_basis_gates(self): """Test tdg-gate gate circuits compiling to u3,cx""" circuits = ref_non_clifford.tdg_gate_circuits_deterministic(final_measure=False) targets = ref_non_clifford.tdg_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -656,7 +690,8 @@ def test_tdg_gate_nondeterministic_waltz_basis_gates(self): """Test tdg-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_non_clifford.tdg_gate_circuits_nondeterministic(final_measure=False) targets = ref_non_clifford.tdg_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -665,7 +700,8 @@ def test_tdg_gate_nondeterministic_minimal_basis_gates(self): """Test tdg-gate gate circuits compiling to u3,cx""" circuits = ref_non_clifford.tdg_gate_circuits_nondeterministic(final_measure=False) targets = ref_non_clifford.tdg_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -686,7 +722,8 @@ def test_ccx_gate_deterministic_waltz_basis_gates(self): """Test ccx-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_non_clifford.ccx_gate_circuits_deterministic(final_measure=False) targets = ref_non_clifford.ccx_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -695,7 +732,8 @@ def test_ccx_gate_deterministic_minimal_basis_gates(self): """Test ccx-gate gate circuits compiling to u3,cx""" circuits = ref_non_clifford.ccx_gate_circuits_deterministic(final_measure=False) targets = ref_non_clifford.ccx_gate_statevector_deterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -713,7 +751,8 @@ def test_ccx_gate_nondeterministic_waltz_basis_gates(self): """Test ccx-gate gate circuits compiling to u1,u2,u3,cx""" circuits = ref_non_clifford.ccx_gate_circuits_nondeterministic(final_measure=False) targets = ref_non_clifford.ccx_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u1', 'u2', 'u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u1', 'u2', 'u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) @@ -722,7 +761,8 @@ def test_ccx_gate_nondeterministic_minimal_basis_gates(self): """Test ccx-gate gate circuits compiling to u3,cx""" circuits = ref_non_clifford.ccx_gate_circuits_nondeterministic(final_measure=False) targets = ref_non_clifford.ccx_gate_statevector_nondeterministic() - job = execute(circuits, StatevectorSimulator(), shots=1, basis_gates=['u3', 'cx']) + job = execute(circuits, StatevectorSimulator(), shots=1, + basis_gates=['u3', 'cx']) result = job.result() self.is_completed(result) self.compare_statevector(result, circuits, targets) diff --git a/test/terra/noise/test_noise_transformation.py b/test/terra/noise/test_noise_transformation.py index 6906e002b2..8e7db93c80 100644 --- a/test/terra/noise/test_noise_transformation.py +++ b/test/terra/noise/test_noise_transformation.py @@ -23,6 +23,7 @@ from qiskit.providers.aer.noise.errors.standard_errors import amplitude_damping_error from qiskit.providers.aer.noise.errors.standard_errors import reset_error from qiskit.providers.aer.noise.errors.standard_errors import pauli_error +from qiskit.providers.aer.noise.errors.quantum_error import QuantumError try: import cvxopt @@ -234,6 +235,56 @@ def test_approx_names(self): results_2 = approximate_quantum_error(error, operator_string="Pauli") self.assertErrorsAlmostEqual(results_1, results_2) + def test_paulis_1_and_2_qubits(self): + probs = [0.5, 0.3, 0.2] + paulis_1q = ['X', 'Y', 'Z'] + paulis_2q = ['XI', 'YI', 'ZI'] + + error_1q = pauli_error(zip(paulis_1q, probs)) + error_2q = pauli_error(zip(paulis_2q, probs)) + + results_1q = approximate_quantum_error(error_1q, operator_string="pauli") + results_2q = approximate_quantum_error(error_2q, operator_string="pauli") + + self.assertErrorsAlmostEqual(error_1q, results_1q) + self.assertErrorsAlmostEqual(error_2q, results_2q, places = 2) + + paulis_2q = ['XY', 'ZZ', 'YI'] + error_2q = pauli_error(zip(paulis_2q, probs)) + results_2q = approximate_quantum_error(error_2q, operator_string="pauli") + self.assertErrorsAlmostEqual(error_2q, results_2q, places=2) + + def test_reset_2_qubit(self): + # approximating amplitude damping using relaxation operators + gamma = 0.23 + p = (gamma - numpy.sqrt(1 - gamma) + 1) / 2 + q = 0 + A0 = [[1, 0], [0, numpy.sqrt(1 - gamma)]] + A1 = [[0, numpy.sqrt(gamma)], [0, 0]] + error_1 = QuantumError([([{'name': 'kraus', 'qubits': [0], 'params': [A0, A1]}, + {'name': 'id', 'qubits': [1]} + ], 1)]) + error_2 = QuantumError([([{'name': 'kraus', 'qubits': [1], 'params': [A0, A1]}, + {'name': 'id', 'qubits': [0]} + ], 1)]) + + expected_results_1 = QuantumError([ + ([{'name': 'id', 'qubits': [0]}, {'name': 'id', 'qubits': [1]}], 1-p), + ([{'name': 'reset', 'qubits': [0]}, {'name': 'id', 'qubits': [1]}],p), + ]) + expected_results_2 = QuantumError([ + ([{'name': 'id', 'qubits': [1]}, {'name': 'id', 'qubits': [0]}], 1 - p), + ([{'name': 'reset', 'qubits': [1]}, {'name': 'id', 'qubits': [0]}], p), + ]) + + results_1 = approximate_quantum_error(error_1, operator_string="reset") + results_2 = approximate_quantum_error(error_2, operator_string="reset") + + self.assertErrorsAlmostEqual(results_1, expected_results_1) + self.assertErrorsAlmostEqual(results_2, expected_results_2) + + + def test_errors(self): gamma = 0.23 error = amplitude_damping_error(gamma) diff --git a/test/terra/noise/test_readout_error.py b/test/terra/noise/test_readout_error.py index 49443ef90d..a9e33171e3 100644 --- a/test/terra/noise/test_readout_error.py +++ b/test/terra/noise/test_readout_error.py @@ -176,6 +176,24 @@ def test_equal(self): error2 = ReadoutError(np.array([[0.9, 0.1], [0.5, 0.5]])) self.assertEqual(error1, error2) + def test_to_instruction(self): + """Test conversion of ReadoutError to Instruction.""" + # 1-qubit case + probs1 = [[0.8, 0.2], [0.5, 0.5]] + instr1 = ReadoutError(probs1).to_instruction() + self.assertEqual(instr1.name, "roerror") + self.assertEqual(instr1.num_clbits, 1) + self.assertEqual(instr1.num_qubits, 0) + self.assertTrue(np.allclose(instr1.params, probs1)) + + # 2-qubit case + probs2 = np.kron(probs1, probs1) + instr2 = ReadoutError(probs2).to_instruction() + self.assertEqual(instr2.name, "roerror") + self.assertEqual(instr2.num_clbits, 2) + self.assertEqual(instr2.num_qubits, 0) + self.assertTrue(np.allclose(instr2.params, probs2)) + if __name__ == '__main__': unittest.main() diff --git a/test/terra/reference/ref_measure.py b/test/terra/reference/ref_measure.py index 31255ca0dd..7fd7262bfb 100644 --- a/test/terra/reference/ref_measure.py +++ b/test/terra/reference/ref_measure.py @@ -119,7 +119,6 @@ def measure_statevector_deterministic(): targets.append(array([0, 0, 0, 1])) return targets - # ========================================================================== # Non-Deterministic output # ==========================================================================